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.

1 2 Page 1
Page 1 of 2