ORIGINAL DRAFT
One of the new features in the Java 1.3 release is a field validating architecture. You can use an InputVerifier to test whether the value in a given text field is appropriate before allowing the user to change the focus. This is a powerful mechanism that lets you use a verifier with any JTextField by simply extending the InputVerifier class and associating it with the field by using the setInputVerifier method. Unfortunately, developing individual verifiers for each field can quickly become tedious. When I first used this paradigm, it occurred to me that a library of validators which could be associated with an InputVerifier subclass would be more flexible and considerably more reusable.
Figure 1 shows the relationships between classes we’ll be developing. Of particular note is the large number of classes that implement an interface called FieldValidator. This is a simple interface which exposes two methods. The isValid method takes a String argument that reflects the value of the field and returns a boolean response, indicating whether the value is valid. The getMessage method returns a String message that can be presented to the user in order to guide them toward correcting the problem. In each of these implementations, I’ve supported two constructors - one without arguments that provides a generic message and one in which you can specify the message more explicitly.
You’ll notice an AbstractFieldValidator that exists primarily for convenience. This is a class that implements reusable message management and a method called isNull that can be used to avoid null pointer exceptions. The SwingFieldVerifier extends the Swing InputVerifier class and lets you add and remove FieldValidator instances. Whenever the InputVerifier’s verify method is called, each of the FieldValidator instances are checked by calling their isValid method. If any negative responses are encountered, the associated message is presented to the user in a dialog box, as shown in Figure 2.
Listing 1 shows the code for the SwingFieldVerifier class. The verifier method takes care of the casting you’re expected to do when you implement an InputVerifier. In my opinion this is a dangerous interface but relatively safe in that only the JTextField calls the verify method in Swing. I can see why designers wanted the interface to be generic, but forcing implementations to cast JTextField instances is not exactly an elegant solution. In any case, our implementation does the actual verification by iterating through the list of FieldVerifier instances in our isValid method. The only other methods, addValidator and removeValidator, allow you to add and remove validators to and from an ArrayList.
The JValidatingField class in Listing 2 is a JTextField extension which automatically uses the SwingFieldVerifier class and exposes addValidator and removeValidator methods to make it easy to work with. Because our validators may need to reference values in other fields, we extend a simple interface called ValidatingField which has getName and getValue methods to retrieving the name of the field and it’s value, respectively. This is useful for fields in a change password dialog, for example, where a confirmation field is expected to match the initial password field. The MatchingFieldValidator can do this easily, so long as the other field implements the ValidatingField interface.
JValidatingField exposes the same constructors as any JTextField but adds a name argument which is used to describe the field in text messages presented to the user. This is a requirement of the ValidatingField interface we implement. The addValidator and removeValidator methods delegate their functionality to the SwingFieldValidator assigned to the field. Notice that, aside from the name argument, the constructors reflect the same options available in any JTextField, so replacing an existing JTextField with a JValidatingField is a simple matter of adding a name as the first argument in any constructor.
As always, the amount of space we have in print is limited, so I’ll give you a whirlwind tour of each of the validator implementations I’ve provided. You can download them all from www.java-pro.com and use them as you see fit. We’ll work through them in the order you see them in the class diagram.
The AlphaFieldValidator and AlphaNumFieldValidator classes expect to see valid alphabetical and alphanumeric values, respectively. The BlankFieldValidator is typically the first one used and watches for fields that have no value. Since the order in which you add validators is relevant to the order in which they are tested, you’ll want to keep this in mind when you add them to JValidatingField. The DecimalFieldValidator accepts any decimal value that qualifies as a Double. It does it’s validating by calling the parseDouble method and catching the NumberFormatException.
FormatFieldValidator checks to see that the field value matches a given Java Format implementation by calling its parseObject method and catching a ParseException. The constructor lets you pass in any class that implements the Format interface, such as the standard DateFormat, MessageFormat or NumberFormat.
The IntegerFieldValidator does the same thing as the DecimalFieldValidator, but uses the Integer parseInt method, catching the NumberFormatException. The MatchingFieldValidator expects a reference to another field that implements the ValidatingField interface and verifies that the value matches that of the other field. You can use this, as I mentioned earlier, with password confirmation fields. The RangeFieldValidator expects an integer value that falls within a specified range of values, which you can specify in the constructor.
As you can see, it’s easy enough to build a useful inventory of reusable FieldValidator instances. The SwingFieldVerifier reduces the number of classes you need to implement and JValidatingField takes care of your interface needs more effectively, making it easier to deliver robust user interfaces to your end users. By providing a uniform mechanism that lets you add or remove validators in any combination, JValidatingField has the potential to considerably reduce your field validation workload As always, have fun with it.
Listing 1
import java.awt.*;
import java.util.*;
import javax.swing.*;
public class SwingFieldVerifier
extends InputVerifier
{
protected String name;
protected Component parent;
protected ArrayList validators = new ArrayList();
public SwingFieldVerifier(Component parent, String name)
{
this.parent = parent;
this.name = name;
}
public void addValidator(FieldValidator validator)
{
validators.add(validator);
}
public void removeValidator(FieldValidator validator)
{
validators.remove(validator);
}
public boolean verify(JComponent component)
{
JTextField field = (JTextField)component;
return isValid(field.getText());
}
public boolean isValid(String value)
{
for (int i = 0; i < validators.size(); i++)
{
FieldValidator validator = (FieldValidator)validators.get(i);
if (!validator.isValid(value))
{
JOptionPane.showMessageDialog(parent,
validator.getMessage(),
"Input Error in '" + name + "' Field",
JOptionPane.ERROR_MESSAGE);
return false;
}
}
return true;
}
}
Listing 2
import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;
public class JValidatingField
extends JTextField
implements ValidatingField
{
protected String name;
protected SwingFieldVerifier verifier;
public JValidatingField(String name)
{
this(name, new PlainDocument(), "", 0);
}
public JValidatingField(String name, int col)
{
this(name, new PlainDocument(), "", col);
}
public JValidatingField(String name, String text)
{
this(name, new PlainDocument(), text, 0);
}
public JValidatingField(String name, String text, int col)
{
this(name, new PlainDocument(), text, col);
}
public JValidatingField(String name, Document doc, String text, int col)
{
super(doc, text, col);
this.name = name;
setInputVerifier(verifier = new SwingFieldVerifier(this, name));
}
public String getName()
{
return name;
}
public String getValue()
{
return getText();
}
public void addValidator(FieldValidator validator)
{
verifier.addValidator(validator);
}
public void removeValidator(FieldValidator validator)
{
verifier.removeValidator(validator);
}
}