Sep 1, 1998 1:00 AM PT

Java Tip 59: Applet parameterization -- initializing arrays

Java Tip 57 showed you how to make applets easily configurable from HTML docs -- but that was just the beginning. This month we complete the process, with a look at how to automate array initialization

Last month, in Java Tip 57, "Applet parameterization via class reflection," I presented an elegant method for parameterizing applets. Parameterization refers to passing parameters via PARAM tags embedded in the HTML file. This method comes in handy, as it does all parameter fetching for you, allowing you to go quite a while without typing getParameter. What I didn't cover in the last tip, however, was how to implement array initialization. This month's tip will teach you just that.

This article assumes you have a reasonably good knowledge of the class reflection mechanism, although you could probably figure it out by just looking at the code. The class reflection mechanism feature is located in the java.lang.reflect package. It lets you examine the structure of a class and provides runtime information on fields, methods, access modifiers, and the like.

Feeding applets with tabular data

Those of you who read the previous tip on applet parameterization probably noticed that one important category of objects was left uninitialized by our method. In this article, we'll see how one-dimensional and two-dimensional arrays can be initialized thanks to the class reflection mechanism. I know the mad scientist in you will only be satisfied with higher dimensions, but I'll leave those to you as an exercise.

As in my previous tip, only arrays of primitive types and strings will be processed. This shouldn't be too restrictive considering that -- ultimately -- all objects can be constructed from primitives and strings alone. But of course, it's easy to extend our technique and directly initialize other types as well.

Arrays are the ideal data structure for the storage of tabular data. Our technique makes it simple to pass table parameters to applets. Usually, tabular data is fed into applets by programs (like servlets or CGI scripts) that generate HTML documents on the fly. As an example, we could imagine a tournament scoreboard applet. The HTML generator would output the current scoreboard database on a PARAM tag, and the corresponding array, in turn, would be seamlessly initialized thanks to our parameter-fetching method.

Syntax for the tabular data entry

What we want to implement is a method that will fetch either a one-dimensional or two-dimensional array from a PARAM tag. The syntax for a one-dimensional array is:

    PARAM NAME="myArray" VALUE="element1 element2 ... elementN" 

The delimiter between elements is white space.

And the syntax for a two-dimensional array is:

    PARAM NAME="myMatrix" VALUE="element11 element12 element13 | 
                                 element21 element22 element23 | 
                                 element31 element32 element33"

The delimiter between rows is the | symbol. Here myMatrix is a (3 x 3) array.

Note that Java allows ragged arrays. These are arrays having rows of various lengths. For example, the HTML coder may input Pascal's triangle as follows:

    PARAM NAME="pascalTriangle" VALUE="  1             | 
                                       1   1           | 
                                     1   2   1         | 
                                   1   3   3   1       | 
                                 1   4   6   4   1     | 
                               1   5   10  10  5   1   | 
                             1   6   15  20  15  6   1"

After initialization, the content of field pascalTriangle would be:

    pascalTriangle[0] = {1} 
    pascalTriangle[1] = {1, 1} 
    pascalTriangle[2] = {1, 2, 1} 
    pascalTriangle[3] = {1, 3, 3, 1} 
    pascalTriangle[4] = {1, 4, 6, 4, 1} 
    pascalTriangle[5] = {1, 5, 10, 10, 5, 1} 
    pascalTriangle[5] = {1, 6, 15, 20, 15, 6, 1} 

Normally the programmer should only declare pascalTriangle without any memory allocation. Our fetching method takes care of allocation. But let's say that the fourth row has already been allocated as follows:

    pascalTriangle[3] = new int[2]; 

Our method will only fetch the first two elements. So the resulting initialization for the fourth row would be:

    pascalTriangle[3] = {1, 3} 

A refresher on arrays

As you will see in the code listing above, the implementation of our method is somewhat "esoteric." So, it's a good idea to remind ourselves of a few facts about arrays before taking a look at the source code.

We are all familiar with Java's type hierarchy: we have a set of predefined primitive types (int, float, ...) and we also have an inheritance tree of sublasses of the cosmic Object class, the ultimate ancestor of all classes. But there is also a lesser known parallel hierarchy that I would call the array hierarchy. Whenever you define a new type Foo in the type hierarchy, you also virtually define a new type Foo[] that automatically grafts itself to the array hierarchy. Every class in the array hierarchy (except arrays of primitive types) is a subclass of Object[]. What can be confusing is that both Object[] and the arrays of primitive types are subclasses of Object. Figure 1 should clarify all this.

Figure 1: Two parallel hierarchies

Surprisingly, Java has no multi-dimensional arrays at all, only one-dimensional arrays. Multi-dimensional arrays are actually "arrays of arrays of arrays ... of one-dimensional arrays." As a consequence, we can create ragged arrays. In fact, we may even leave some rows uninitialized to the null value.

Implementation of the array fetching method

Now we're ready to take a look at the source code. As you can see, it is heavily commented. Normally it's bad practice to include this many comments, but here we are wrapping the already abstract Java arrays into a layer of metadata abstraction provided by the class reflection mechanism. As a result, most program statements are not self-evident, so in this case it is justifiable to comment nearly every line of code.

Whether we are initializing a one-dimensional or two-dimensional array, ultimately we need to initialize a one-dimensional array with a row that was input by the HTML coder. We design a method that will do just that:

/**
* Fills a one-dimensional array with the contents of a tokenizer.
* The tokens are converted to the array's content type.
*
* @param array array to be filled.
* @param elementTokens tokenizer containing the tokens
which the array will be filled with.
*/
private static void fillOneDimensionalArray(Object array, 
                                                StringTokenizer elementTokens) 
    throws IllegalAccessException {
    if (array != null && elementTokens != null && array.getClass().isArray()) {
// Double check.
       // What type of elements is the array supposed to hold?
       Class componentType = array.getClass().getComponentType();
       int numElements = elementTokens.countTokens();                        
       // Assign elements to the array.
       //
       // Note that we make sure the index won't go out of bounds. The array may have
       // been allocated insufficient space to hold all parsed elements.  
       for (int j = 0; j < Array.getLength(array) && j < numElements; j++) {
           // Convert the token to the type held by the array.
          // And then add it to the array.
          if (componentType.equals(boolean.class))
              Array.setBoolean(array, j, Boolean.valueOf(elementTokens.nextToken().trim()).booleanValue());
          else if (componentType.equals(byte.class))
              Array.setByte(array, j, Byte.valueOf(elementTokens.nextToken().trim()).byteValue());
          else if (componentType.equals(char.class))
              Array.setChar(array, j, elementTokens.nextToken().charAt(0));
          else if (componentType.equals(double.class))
              Array.setDouble(array, j, Double.valueOf(elementTokens.nextToken().trim()).doubleValue());
          else if (componentType.equals(float.class))
              Array.setFloat(array, j, Float.valueOf(elementTokens.nextToken().trim()).floatValue());
          else if (componentType.equals(int.class))
              Array.setInt(array, j, Integer.valueOf(elementTokens.nextToken().trim()).intValue());
          else if (componentType.equals(long.class))
              Array.setLong(array, j, Long.valueOf(elementTokens.nextToken().trim()).longValue());
          else if (componentType.equals(short.class))
              Array.setShort(array, j, Short.valueOf(elementTokens.nextToken().trim()).shortValue());
          else if (componentType.equals(String.class))
             Array.set(array, j, elementTokens.nextToken());
       }
   }
}

We use the Class.getComponentType() method to get the type of the elements held by the given array object. Once we have this information in hand, we know what type the row elements must be converted to. This is done inside the loop.

As you might have guessed, Array.setByte(Object obj, int i, byte datum) assigns byte variable datum to the i-th element of array obj. This is equivalent to: ((byte[])obj)[i] = datum.

Next we'll look at the core of our implementation. I extended method Util.initializeApplet(Applet, String) (implemented in Java Tip 57) by adding a conditional statement that catches array fields and initializes them.

import java.applet.*;
import java.lang.reflect.*;
import java.util.*;
public abstract class Util {
/**
* Initializes an Applet's public non-final fields whose names start
* with a given filter prefix.  The initial values will be read from
* the HTML PARAM tags. 
*
* @param applet the applet to initialize.
* @param filterPrefix only fields whose name starts with this prefix
*        will be initialized.
*        If the prefix is null, all public non-final fields 
*        will be initialized
*/
public static void initializeApplet(Applet applet, String filterPrefix) {
   Class metaclass = applet.getClass();
   Field[] fields = metaclass.getFields();
   String param = null;
   for (int i = 0; i < fields.length; i++) {
       try {
           param = applet.getParameter(fields[i].getName());
           if (param == null ||
               Modifier.isFinal(fields[i].getModifiers()) ||                
               ((filterPrefix != null) && 
                !fields[i].getName().startsWith(filterPrefix))
              )                
              continue;            
           Class fieldType = fields[i].getType();
           if (fieldType.equals(boolean.class)) {
               fields[i].setBoolean(applet, Boolean.valueOf(param).booleanValue());
           }
           else if (fieldType.equals(byte.class)) {
               fields[i].setByte(applet, Byte.valueOf(param).byteValue());
           }
           /*********************************************
            * Details deleted. See the previous article.
            * Download the source to get the whole code.
            *********************************************/
           // Initialize array.
           else if (fieldType.isArray()) {
               // Here we know that the field we are dealing with is an array.
               // But what type of elements is the array supposed to hold?
               Class componentType = fieldType.getComponentType();
               // Initialize a 1-dimensional array.
               if (componentType.isPrimitive() || 
                  componentType.equals(String.class)) {
                   // Use a StringTokenizer to parse the array elements that
                   // were fed in by the HTML coder.                        
                   StringTokenizer elementTokens = new StringTokenizer(param);
                   int numElements = elementTokens.countTokens();          
                   // Note that fields[i] is only a meta-field representing an
array object. 
                   // We need a reference to this array so we can assign its
elements.
                   //
                   // So:
                   Object array = fields[i].get(applet);
                   // If for some reason the array has already been constructed,
leave it
                   // as it is.
                   //
                   // Otherwise: construct it with the appropriate type, and
allocate
                   // sufficient space to hold the number of elements that were
fed in by
                   // the HTML coder.
                   if (array == null) {
                       // Construct the array of type componentType and 
                       // allocate space for numElements.
                       fields[i].set(applet, Array.newInstance(componentType,
numElements));
                       // Get a reference to the array that we just constructed.
                       array = fields[i].get(applet);
                   }
                   // Fill the array with the elements contained in
elementTokens.
                   fillOneDimensionalArray(array, elementTokens);
               }
               // Initialize a 2-dimensional array.
               else if (componentType.isArray() &&
                        (componentType.getComponentType().isPrimitive() || 
                              componentType.getComponentType().equals(String.class))) {
                   // The subarrays (i.e., rows) are delimited by the "|"
symbol.
                   // Use this delimiter to partition the 2-dimensional table
into a set
                   // of tokens representing 1-dimensional arrays. We call them
subarray tokens.
                   StringTokenizer subarrayTokens = new StringTokenizer(param,
"|");
                   int numSubarrays = subarrayTokens.countTokens() 
                   // Note that "fields[i]" is only a metafield representing an
array object. 
                   // We need a reference to this array so we can assign its
elements.
                   //
                   // So:
                   Object array = fields[i].get(applet); 
                   // If for some reason the array has already been constructed,
leave it
                   // as it is.
                   //
                   // Otherwise: construct it with the appropriate type, and
allocate
                   // sufficient space to hold the number of rows that were fed
in by
                   // the HTML coder.
                   //
                   // Note that here variable "array" is necessarily an array of
arrays. Therefore
                   // the "appropriate type" is necessarily an array type.
                   if (array == null) {
                       // Construct the array of type componentType and 
                       // allocate space for numSubarrays rows.
                       fields[i].set(applet, Array.newInstance(componentType,
numSubarrays));
                       // Get a reference to the array of arrays that we just
constructed.
                       array = fields[i].get(applet);
                   }
                   // Initialize each subarray in turn.
                   //
                   // Note that we make sure the index won't go out of bounds.
The array may 
                   // have been allocated insufficient space to hold all rows
fed in by the 
                   // HTML coder. 
                   for (int j = 0; j < Array.getLength(array) && j >
numSubarrays; j++) {
                       // Use a StringTokenizer to parse the row elements that
                       // were fed in by the HTML coder.                        
                       StringTokenizer elementTokens = new
StringTokenizer(subarrayTokens.nextToken());
                       int numElements = elementTokens.countTokens();                        
                       // This new variable is introduced with the sole purpose
of making
                       // the code more human readable. But as we will see
shortly this
                       // causes some very subtle hassle.
                       Object subArray = ((Object[])array)[j];
                       // If for some reason the subarray has already been
constructed, leave it
                       // as it is.
                       //
                       // Otherwise: construct it with the appropriate type, and
allocate
                       // sufficient space to hold the number of elements that
were fed in by
                       // the HTML coder.
                       if (subArray == null) {
                           // "componentType" is "array"'s type, hence the array
of subarray's type
                           // "componentType.getComponentType()" is "subArray"'s
type.
                           subArray = Array.newInstance(componentType.getComponentType(), numElements);
                       }
                       // The following statement is necessary. In the previous
conditional
                       // statement, "subArray" may have been assigned to an
object reference
                       // other than "((Object[])array)[j]".
                       //
                       // This re-aliases the two variables. Hence, this ensures
that
                       // all mutations applied to the subarray will be
reflected in
                       // the array of subarrays (ie. in variable "array").
                       ((Object[])array)[j] = subArray; 
                       // Fill the subarray with the elements contained in
elementTokens.
                       fillOneDimensionalArray(subArray, elementTokens); 
                   }
               }                      
           }
       }
       catch (Exception e) {
                System.err.println(e + " while initializing " + fields[i]);
       }
     }
  }
}

Method type.isPrimitive() returns "true" when type is one of {void.class, boolean.class, byte.class, short.class, int.class, long.class, float.class, double.class}.

The class reflection method of allocating arrays is as follows:

      bazField.set(fooObject, Array.newInstance(componentType, numElements)); 

This invokes the creation and allocation of an array and then assigns this array to the field represented by variable bazField and owned by variable fooObject. In other words, fooObject's class FooClass has an attribute represented by bazField, and we assign the array to the corresponding field found in the FooClass instance that variable fooObject represents.

Both the syntax and the semantic of the following statement may surprise you.

      Object subArray = ((Object[])array)[j]; 

The purpose of this statement is to extract the j-th row of the two-dimensional array called array. We store this row in variable subArray. This variable is of type Object because we don't know in advance what type of one-dimensional array we're going to deal with, and as you may recall from our discussion on arrays, the closest common superclass of all types of one-dimensional arrays is the Object class. The variable array is also of type Object but for a different reason. We don't know in advance whether we're going to deal with a one-dimensional array of primitive types or with any other type of array, and again, the closest common super class is Object. When we reach this statement, we already know for a fact that variable array represents a two-dimensional array, and as you will recall from our discussion on arrays, two-dimensional arrays really are one-dimensional arrays of one-dimensional arrays. So they actually are subclasses of Object[]. That's why we can downcast variable array to an Object[]. Then it is a simple matter to extract the j-th row as an Object instance and assign it to variable subArray.

Conclusion

This tip has shown you how the class reflection mechanism can be used to ease the programmer's task of developing configurable applets. This month, we supplemented our technique with an array fetching routine. Using a method that does all the parameter fetching for you saves you from the drudgery of having to type getParameter again and again.

Yvon Sauvageau received a degree in mathematics and computer science from McGill University in Montreal. He has seven years of programming experience, ranging from business server applications to GUI programming. Two years ago he became a Java addict (as he puts it!). He received his Sun Certified Java Programmer title last January, and more recently, he became a Sun Certified Java Developer. He is working as a consultant for MTLI-NSK Technologies in Paris. His current project is a human resource management system written entirely in Java and based on an ObjectStore OODBMS. Life is interesting in Europe, but as a Canadian he misses hockey very much!

Learn more about this topic

  • For the source code for this tip in zip format, see http://www.javaworld.com/javatips/javatip59/javatip59.zip
  • For the source code for this tip in jar format, see http://www.javaworld.com/javatips/javatip59/javatip59.jar
  • See my previous tip, "Java Tip 57Applet parameterization via class reflection" http://www.javaworld.com/javatips/jw-javatip57.html
  • For information on class reflection, see Chuck McManis's JavaWorld article, "Take an in-depth look at the Java Reflection API" http://www.javaworld.com/javaworld/jw-09-1997/jw-09-indepth.html