BeanLint: A JavaBeans troubleshooting tool, Part 1

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, many 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)
1 2 3 Page 1
Page 1 of 3