by Mark Johnson

BeanLint: A JavaBeans troubleshooting tool, Part 1

how-to
Dec 1, 199823 mins

Use this new tool to avoid JavaBeans loading and runtime problems

Every couple of months, I receive panicked or bewildered e-mail from a JavaBeans neophyte who is trying to create a JavaBean containing an Image and who can’t figure out why the BeanBox won’t load the bean. The problem is that java.awt.Image isn’t Serializable, therefore neither is anything that contains a java.awt.Image, at least without custom serialization.

I myself have spent countless hours putting println() statements into the BeanBox code then recompiling it, trying to figure out why my beans won’t load. Sometimes it’s due to some simple, stupid thing — like forgetting to define the zero-argument constructor, or even the class, as public. Other times, it turns out to be something more obscure.

The case of the missing bean

While the requirements to write a Java class as a JavaBean are simple and straightforward, there are some hidden implications that many bean builder tools don’t address. These little gotchas can easily eat up an afternoon, as you hunt through your code, searching for the reason your builder tool can’t find your bean. If you’re lucky, you’ll get a pop-up dialog box with a cryptic error message — something along the lines of “NoSuchMethodException caught in FoolTool Introspection.” If you’re unlucky, the JavaBean you’ve poured so much sweat into will refuse to appear in your builder tool, and you’ll spend the afternoon rehearsing the vocabulary your mother tried so hard to cure you of. The BeanBox has long been an egregious offender in this regard, and though it has been improving, it will still drop properties and even whole beans without providing the developer with a single clue as to why.

This month, I’ll be leading you out of the “land of the missing bean” by introducing a new tool called, oddly, BeanLint, which analyzes classes within jar files, looking for possible problems that would make the classes unusable as beans. While this tool doesn’t cover every possible bean problem, it does identify some of the major common problems that make beans unloadable.

In order to understand how BeanLint works its magic, this month and next we’ll delve into some of the lesser-known corners of the standard Java API:

  • We’ll create a custom class loader, which loads new Java classes from a jar file

  • We’ll use the reflection mechanism, which lets Java programs analyze Java classes, to identify what’s inside our class files

  • We’ll use the Introspector to produce a report of all of the class’s beanlike properties for any class in the jar file that passes all tests (and is, therefore, a potential bean)

By the time we’re done, you’ll have a useful tool for debugging your beans, you’ll better understand bean requirements, and you’ll learn about some of Java’s cool new features at the same time.

Bean basics

For a class file to be a JavaBean, there are two simple requirements:

  1. The class must have a public constructor with no arguments (a zero-arg constructor)

  2. The class must implement the empty tag interface java.io.Serializable

That’s it. Follow those two simple rules, and your class will be a JavaBean. The simplest JavaBean, then, looks something like this:

import java.io.*;
public class TinyBean implements Serializable {
    public TinyBean() {}
}

Of course, the above bean isn’t good for much, but then we didn’t put a whole lot of work into it. Just try writing a basic component like this in another component framework. (And no fair using “wizards” or other code generators to create wrapper classes or default implementations. That’s not a fair comparison of the elegance of JavaBeans versus another technology.)

The TinyBean class has no properties (except, maybe, “name”), no events, and no methods. Unfortunately, it’s still easy to accidentally create classes that seem to follow the rules, yet don’t operate properly in a JavaBeans container such as the BeanBox or your favorite IDE (integrated development environment).

For example, the BeanBox wouldn’t load our TinyBean above if we’d forgotten to include the keyword public to the class definition. javac would create a class file for the class, but the BeanBox would refuse to load it, and (until recently anyway) would give no indication as to why it would refuse. To give Sun’s Java people credit, the BeanBox now usually reports the reason a bean won’t load, or the reason a property doesn’t appear on a property sheet, and so on. Wouldn’t it be nice, though, if we had a tool to check as many things as possible about such classes — and warn us of those likely to cause problems when used in a JavaBeans environment? That’s the goal of BeanLint: to help you, as a JavaBeans programmer, analyze beans inside their jar files, looking for possible problems so that you can fix them before you run into them in the testing process or — even worse — in the field.

Potential bean problems

As I’ve developed JavaBeans for this column, I’ve probably made most of the mistakes one can make when writing a JavaBean. In a way, the BeanBox’s taciturn nature has forced me to learn more about beans — and about Java — than I would have otherwise. Most JavaBeans developers, though, would prefer simply to produce working JavaBeans that operate correctly, and save the “growth experiences” for their personal lives. I’ve collected a list of possible problems with a class file that can wreak havoc with a JavaBean. These problems occur during the process of loading the bean into a container, or in using the bean in an application. It’s easy to miss details in serialization, so we pay special attention to serializability requirements.

Here are some common problems that don’t cause compile-time errors but may cause a class file either to not be a JavaBean, or to not operate correctly once it’s loaded into a container:

  • The class has no zero-argument constructor. This is simply a violation of the first requirement listed above, and is an error not often encountered by non-beginners.

  • The class doesn’t implement Serializable. This is a violation of the second requirement listed above and is easy to spot. A class may claim to implement Serializable, and yet not follow through on the contract. In some cases we can detect automatically when this has occurred.

  • The class itself is not declared public.

  • The class fails to load for some reason. Classes sometimes throw exceptions as they’re being loaded. Often, this is because other classes upon which they depend aren’t available from the ClassLoader object used to load the class. We’ll be writing a custom class loader in this article (see below).

  • The class is abstract. While a component class, in theory, could be abstract, an actual running instance of a JavaBean is always an instance of some concrete (that is, non-abstract) class. Abstract classes cannot be instantiated, by definition, and so we won’t consider abstract classes as candidates to be beans.

  • The class implements Serializable, yet it or one of its base classes contains nonserializable fields. The default Java serialization mechanism design allows a class to be defined as implements Serializable, but permits it to fail when serialization is actually attempted. Our BeanLint class ensures that all appropriate fields of a Serializable class actually are Serializable.

A class that fails any of the problems above can be fairly certain not to operate correctly as a JavaBean, even if the two basic bean requirements, stated at the outset, are met. For each of these problems, then, we’ll define a test that detects the particular problem and reports it. In the BeanLint class, any class file in the jar file being analyzed that does pass all of these tests is then introspected (analyzed using the class java.beans.Introspector) to produce a report of the bean’s attributes (properties, event sets, customizer, and so on). java.beans.Introspector is a class in the package java.beans that uses the Java 1.1 reflection mechanism to find (or create) a java.beans.BeanInfo object for a JavaBean. We’ll cover reflection and introspection next month.

Now let’s take a look at the source code for BeanLint to see how to analyze potential bean classes.

Introducing BeanLint

In the “good old days” (which usually means, “back when I still thought I knew everything”), C programmers on the Unix operating system would use a program called lint to look for potential runtime trouble spots in their C programs. In honor of this venerable and useful tool, I have called my humble bean-analysis class BeanLint.

Instead of presenting the entire source code in one huge, indigestible chunk, we’re going to look at it one piece at a time, and I will explain along the way various idioms concerning how Java deals with class files. By the time we’re through, we’ll have written a class loader, used a respectable number of classes in java.lang.reflect, and have acquired a nodding acquaintance with the class java.beans.Introspector. First, let’s have a look at BeanLint in action to see what it does, and then we’ll delve into the details of its implementation.

Bad beans

In this section you’ll see some class files with various problems, with the problem indicated below the code. We’re going to create a jar file containing these classes, and see what BeanLint does with them.


import java.io.*;

public class w implements Serializable { w() { } }

Problem:

 Zero-argument constructor not

public


public class x {
    public x() { }
}

Problem:

 Not

Serializable.


import java.io.*;

public class y implements Serializable { public y(String y_) { } }

Problem:

 No zero-argument constructor.


import java.io.*;

class z implements Serializable { public z() { } }

Problem:

 Class not public.


import java.io.*; import java.awt.*;

class u0 implements Serializable { private Image i; public u0() { } }

public class u extends u0 implements Serializable { public u() { } }

Problem:

 Contains a nonserializable object or reference.


import java.io.*;

public class v extends java.awt.Button implements Serializable { public v() { } public v(String s) { super(s); } }

Problem:

 Nothing — should work fine!


Each of these aspiring beans, except the last one, has potential problems. The last one not only is a bean, but operates as one. After compiling all of these classes, we create a jar file like this:

$ jar cvf BadBeans.jar *.class
adding: u.class (in=288) (out=218) (deflated 24%)
adding: u0.class (in=727) (out=392) (deflated 46%
adding: w.class (in=302) (out=229) (deflated 24%)
adding: x.class (in=274) (out=206) (deflated 24%)
adding: y.class (in=362) (out=257) (deflated 29%)
adding: z.class (in=302) (out=228) (deflated 24%)
adding: v.class (in=436) (out=285) (deflated 34%)

We aren’t going to include a manifest file (which is a file inside a jar file that describes the jar file’s contents — see “Opening the jar” below) in the jar file because BeanLint doesn’t deal with manifest files. Parsing the manifest file and comparing it to the contents of the jar would be an interesting exercise if you want to extend what BeanLint can do.

Let’s run BeanLint on the jar file and see what happens:

=== Analyzing class u0 === class u0 is not a JavaBean because: the class is not public

=== Analyzing class z === class z is not a JavaBean because: the class is not public

=== Analyzing class y === class y is not a JavaBean because: it has no zero-argument constructor

=== Analyzing class x === class x is not a JavaBean because: the class is not Serializable

=== Analyzing class w === class w is not a JavaBean because: its zero-argument constructor is not public

=== Analyzing class v === Note: java.awt.Button defines custom serialization Note: java.awt.Component defines custom serialization v passes all JavaBean tests

Introspection Report -------------------- Class: v Customizer class: none

Properties: boolean enabled {isEnabled, setEnabled} (... many more properties)

Event sets: java.awt.event.MouseListener mouse (... many more event sets)

Methods: public boolean java.awt.Component.isVisible() (... many, <i>many</i> more methods -- sheesh!)

=== End of class v ===

=== Analyzing class u === class u is not a JavaBean because: the following fields of the class are not Serializable: class java.awt.Image i (defined in u0) === End of class u ===

The output has been shortened somewhat because the listings of event sets and methods is very long doesn’t add much to our discussion here. You can see the entire output in the file output.html, if you want an idea of the amount of stuff BeanLint puts out.

Notice that BeanLint correctly identified the problems with the bad class files:

class u0 is not a JavaBean because: the class is not public
class z is not a JavaBean because:  the class is not public
class y is not a JavaBean because:  it has no zero-argument constructor
class x is not a JavaBean because:  the class is not Serializable
class w is not a JavaBean because:  its zero-argument constructor is not public
class u is not a JavaBean because:
    the following fields of the class are not Serializable:
        class java.awt.Image i (defined in u0)

There are some interesting additional details about class v, the only class that passed all of the tests. BeanLint uses the class java.beans.Introspector to get structured information about a JavaBean, and then uses the result to produce the Introspection Report. There is also an indication that java.awt.Button and java.awt.Component define custom serialization, meaning that those superclasses of v handle serialization themselves. This is important because there may be nonserializable fields in those superclasses, but since they define custom serialization, such fields will (we hope) not cause errors.

Finally, notice that class u failed the bean test because one of its superclasses (class u0) contains a nonserializable field, but doesn’t define custom serialization. If default serialization is used on an instance of this class, it will most likely fail, because java.awt.Image isn’t serializable (this is an example of the problem discussed at the very beginning of this article). We’ll go over this serialization issue in detail in next month’s column.

Now that we’ve seen what BeanLint can do, let’s go over how it works.

BeanLint inside (TM)

BeanLint‘s operation can be divided into three phases:

  1. It decompresses into memory all class files within the jar file being analyzed

  2. It defines all decompressed class files as java classes

  3. It performs tests on each newly defined class, and prints an Introspection Report on each class that passes all tests

These three phases correspond to three methods in the BeanLint class: loadAllClasses(), which handles decompressing the class files in the jar; defineAllClasses(), in which BeanLint acts as a class loader and defines new Java classes for each decompressed class file; and analyzeBeans(), which runs the tests on the class files.

We’re going to cover the first two phases this month, and in next month’s column, the second in this two-part series, we’ll go over bean analysis and introspection reporting.

Opening the jar A jar file is a zip file containing a manifest, a kind of machine-readable table of contents that programs use to find out what’s in the jar. Jar files typically contain Java class files but may also contain any other type of file. Their format is identical to zip files, and so the classes in package java.util.zip can be used to manipulate them. The method shown in Figure 1, loadAllClasses(), reads the contents of a jar file, decompresses each entry, and stores the class files in a hashtable called htCache_. (The line numbers correspond to the line numbers of the complete BeanLint.java file. See Resources below for a link to the actual java source file.)

Figure 1. The loadAllClasses() method

In lines 303 to 330, we first create a hashtable called sizes_, we then create a ZipFile object to access the jar file. ZipFile provides access to the directory information of a zip (or jar) file. We traverse all the entries of the jar file, recording each entry’s size in the size_ hashtable, indexed by name. (To see how a hashtable works, see the documentation for java.util.Hashtable in Resources.) This hashtable is used in the next section of code, which reads and decompresses the jar file entries.

Lines 326 to 329 set up a ZipInputStream, connected to a FileInputStream, which is opened to read the jar file. Streams are “stacked” on top of one another, and perform transformations on data as the data flows through. The configuration of streams created here reads data from the jar file, buffers the data (for efficiency), and then decompresses the result. Reading from a ZipInputStream is exactly like reading from the uncompressed file. The ZipInputStream has its own internal iterator, so instead of using an Enumeration as we did above, we use ZipInputStream.getNextEntry() to iterate through the entries in the file.

In lines 334 to 344 we discard any jar file entries that are not class files, and in 346 to 351, we replace all the filename separators (/ or ) with dots (.) to produce the complete class name.

Now that we have the name of the zip file entry, we want to read its bytes. The size of a zip file entry created by some other program may not be available in the entry. If this is true, getNextEntry() will return -1 since the stream doesn’t know the size of the entry. Luckily, we saved all of the sizes of the entries in the sizes_ hashtable, so that, in lines 353 to 358, if the zip entry we’re processing doesn’t know its own size, we can look it up by name in the hashtable. If you find line 357 a bit confusing, look at it more closely. A hashtable can only store objects, so we’ve stored objects of type Integer, which is a wrapper class for int values. The method Hashtable.get(), though, returns java.lang.Object instances, and so we must typecast its return value to Integer. Once that’s done (and it’s typesafe, because the does JVM check up on you), we call Integer.intValue() on the resulting Integer in order to get the int size value we’re after.

Lines 360 to 369 allocate a memory buffer for the decompressed class file and read precisely the number of bytes indicated by the size we calculated above. Finally, in line 370, we save the buffer in the hashtable htCache_, indexed by the name of the class we want to create. This new hashtable contains the actual class file bytecodes.

Now, note that we haven’t actually loaded any classes yet. All we’ve done is to create a hashtable full of blocks of bytes that contain the bytecodes for the classes we want to create.

In the next section, we actually create the classes, by using BeanLint as a class loader.

Getting loaded A Java class loader is a class that loads new Java classes into a Java virtual machine. Every JVM has an initial class loader, called the primordial class loader, which is hardwired into the particular JVM. Each class loader has its own security policy, which indicates what system resources a particular loader may access. This is how the applet sandbox works, for example: the applet class loader prevents the applet from writing files on the local disk, because its security policy refuses to allow this. The class loader for a Java application is much more permissive, allowing unfettered access to the filesystem (unless the security policy of the class loader is changed by the programmer). Java class loader security is an entirely separate topic worthy of book-length treatment, so we won’t discuss it greater detail here. What we’re interested in is how to load new classes, because we want to load the byte blocks in our htCache_ hashtable into the JVM, so we can analyze them as Java classes.

A programmer can create a custom class loader by extending the abstract base class java.lang.ClassLoader and defining its loadClass() method. This method receives the name of the class to load and a boolean flag resolve. (Read the documentation for java.lang.ClassLoader, linked to in Resources, if you want to know about the resolve flag.) The implementor of a new class loader receives the name of the class to load, and will eventually call the class loader’s defineClass method to define the new class. The defineClass() method takes a block of memory, a class name, and an integer size and offset, and defines a new class in the JVM. The block of memory contains the bytecodes for the new class. If defineClass succeeds (meaning it doesn’t throw a ClassFormatError exception), upon its return a new Java class is available in the current JVM.

So, we need to define a class loader that can load classes from the bytecodes in our hashtable, and then define a class for each entry in the htCache_ hashtable (by asking our new class loader to do it for us). Figure 2 below shows the source code in htCache_ that handles this class loading.

048 // Load the class passed in, resolving if requested. 049 public synchronized Class loadClass(String className, boolean resolve) { 050 Class c = null; 051 052 // Superclass maintains a cache, so check that first 053 if ((c = findLoadedClass(className)) != null) { 054 return c; 055 } 056 057 // Is this a system class? 058 try { 059 if ((c = findSystemClass(className)) != null) { 060 return c; 061 } 062 } catch (ClassNotFoundException exc) { 063 // If we don’t find it, then no problem… get it from 064 // the cache. 065 } 066 067 // Get the bytes for the class from the cache and 068 // define the class, since it doesn’t already exist. 069 byte[] buf = (byte[])htCache_.get(className); 070 c = defineClass(className, buf, 0, buf.length); 071 if (resolve) { 072 resolveClass(c); 073 } 074 return c; 075 }

373 374 // Call defineClass() on every class in the array. 375 protected void defineAllClasses() { 376 Enumeration e = htCache_.keys(); 377 Class c; 378 String className; 379 byte[] buf; 380 381 while (e.hasMoreElements()) { 382 className = (String)e.nextElement(); 383 buf = (byte[])(htCache_.get(className)); 384 if ((c = findLoadedClass(className)) == null) { 385 defineClass(className, buf, 0, buf.length); 386 } 387 } 388 }

Figure 2. loadClass() and defineAllClasses()

There are two parts to the class definition functionality of BeanLint. First, notice in line 011 that class BeanLint extends ClassLoader — that is, BeanLint — actually is a class loader. As such, it implements loadClass(), and in this method, we actually load classes from the hashtable (lines 048 to 075).

When loadClass() is called by the JVM, one of three possible conditions exist. Let’s go over the code that handles each one.

  • The requested class is already loaded:  In lines 052 to 055, we call findLoadedClass() to see if the JVM already knows about the class we’re trying to load. If it does, we simply return the already existing class object, and we’re done. (findLoadedClass() maintains a cache of already loaded classes.)

  • The JVM already knows how to load the requested class:  Lines 057 to 066 check to see if the class we’re trying to load is a system class. These lines determine if the class we’re trying to load is something the existing class loader can find on its own (by searching the CLASSPATH for example.) This is essentially a call to the primordial class loader of the JVM. If the class being requested is a system class, this method returns the appropriate Class object, and then so do we.

  • We have to define the class ourselves:  The final case, in lines 067 to 074, is that the class being requested is neither a system class, nor already loaded. In our implementation of loadClass(), we assume that the class loader is being asked for something from the hashtable, so we fetch the bytes for that class from htCache_, and then call defineClass() to define the new class. (The error checking should be better than it is here. Feel free to add code to handle the case where the hash entry isn’t found.)

Now that we have all the code necessary to create our new class, we simply need to load each class in the hashtable htCache_ by name. Lines 374 to 388 call findLoadedClass() (a member we inherited, you recall, from java.lang.ClassLoader) for each class name in the hashtable. The first time findLoadedClass() sees one of these class names, it realizes it doesn’t know about that class, and so it calls loadClass() to load it. loadClass then loads the requested class from the hashtable, as described above.

You may ask why we bothered with defining loadClass(), instead of just calling defineClass() for each class in the hashtable. The reason is that often class files within jars inherit from one another. If we try to define a class whose superclass doesn’t exist yet (because it hasn’t yet been defined, since it appears later in the hashtable), we’ll get an error. BeanLint gets around this problem by defining loadClass(). If a subclass needs its superclass (which occurs when resolveClass() is called), it can get it by way of a recursive call to loadClass(). Classes are loaded as they’re needed, which is precisely how class loaders are meant to work. You can try experimenting with this mechanism by putting println() statements in loadClass().

So, finally, we’ve managed to load all of the classes from the jar file into the JVM. What remains is to analyze the classes, and produce reports on the classes that pass all the JavaBeans tests.

Conclusion

This month, we’ve described the requirements for the BeanLint class, which finds potential problems with JavaBeans classes in jar files. In describing the implementation of the class, we’ve shown an example of how to use package java.util.zip and class java.lang.ClassLoader. Next month, we’ll complete this two-part series by discussing BeanLint‘s use of the package java.lang.reflection, and class java.beans.Introspector. We’ll also see how BeanLint can help JavaBeans developers avoid a potential danger zone in how Java performs serialization.

Mark Johnson has a BS in computer and electrical engineering from Purdue University (1986). He is a fanatical devotee of the design pattern approach in object-oriented architecture, of software components in theory, and of JavaBeans in practice. Over the past several years, he worked for Kodak, Booz-Allen and Hamilton, and EDS in Mexico City, developing Oracle and Informix database applications for the Mexican Federal Electoral Institute and for Mexican Customs. He currently works as a designer and developer for Object Products Inc. in Fort Collins, CO.