Thursday, November 3, 2011

Validation with pure Java - 4


That code skeleton is just a starting point, but it demonstrates what is available at the beginning of the validation process. Surprisingly, it has a lot of information. From PropertyChangeEvent you can learn about the constrained property's object source, the name of the property itself, and its existing and proposed values. With that information handy, you can use the power of introspection to inquire about the property's additional information. What information should you look for? Validation rules, of course! A class java.beans.Introspector was designed solely for the purpose of collecting extra information about a target class. The information is provided in the form of a java.beans.BeanInfo object, which in this example, you may request with a simple call like this:
BeanInfo info = Introspector.getBeanInfo(evt.getSource().getClass());

The Introspector may collect BeanInfo in two ways. First it tries to get explicit information about the target class by looking for a class with the same name except for its BeanInfo suffix. In this case, it will look for a class named BusinessObjectBeanInfo. If for any reason such a class is not available, then the Introspector tries its best to dynamically build a BeanInfo object by using low-level reflection.
Although a fascinating subject, the implicit introspection is obviously not what you are going to rely upon in your quest for validation rules. One great thing about the BeanInfo class is its relation to the target class, which can be defined as a "rock-solid loose coupling." The absence of references from the target class makes the coupling very loose. Should the validation rules change, you just need to update and recompile the BeanInfo class without possibly affecting the target class. Clearly defined naming rules that are documented in the core Java API make compiling rock solid. This is where the rules-externalization approach with BeanInfo beats its proprietary-implemented counterparts hands down.
Before you start building the explicit BeanInfo class, I have to mention that you can load it with a variety of descriptors concerning the class itself, or its methods and events, not just the properties. Thanks to the creators of Java, you don't have to provide all that information. The Introspector picks up what is available explicitly and then performs its implicit analysis magic. Therefore, you can concentrate on the property descriptors only.
To describe a property's data-validation rules, you use the java.beans.PropertyDescriptor class. Looking at the documentation, you'll discover that this class has the facilities to provide every imaginable piece of information about the property, including data-validation rules! You use the setValue method to define the rules as a set of named attributes. Here is how you can constrain the bounds for the numeric property numericValue:
PropertyDescriptor _numericValue = new PropertyDescriptor("numericValue", targetClass, "getNumericValue", "setNumericValue");
_numericValue.setValue("maxVal", new Integer(100));
_numericValue.setValue("minVal", new Integer(-100));

The only weak point here is that you cannot rely on the power of the Java compiler to check the spelling of the attribute's names. Using a predefined set of constants can easily fix that. The class Validator is a good candidate for hosting such constants:
public static final String MAX_VALUE = "maxValue";
public static final String MIN_VALUE = "minValue";

So now, you can rewrite the same rules more safely. With all this in mind, let's build a final revision of the BeanInfo class:
import java.beans.*;
public class BusinessObjectBeanInfo extends SimpleBeanInfo {
  Class targetClass = BusinessObject.class;
  public PropertyDescriptor[] getPropertyDescriptors() {
    try  {
      PropertyDescriptor numericValue = new PropertyDescriptor("numericValue", targetClass, "getNumericValue", "setNumericValue");
      numericValue.setValue(Validator.MAX_VALUE, new Integer(100));
      numericValue.setValue(Validator.MIN_VALUE, new Integer(-100));
      PropertyDescriptor[] pds = new PropertyDescriptor[] {numericValue};
      return pds;
    } catch(IntrospectionException ex) {
      ex.printStackTrace();
      return null;
    }
  }
}

No comments: