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

1 2 Page 2
Page 2 of 2
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
1 2 Page 2
Page 2 of 2