Clazz Class

The best way to introduce the Clazz class is by comparing it to the java.lang.Class class. Though both serve the same function as runtime meta-data about a type, there are several important differences:

  • Class is a final class embedded in the JVM. Its behavior cannot be customized or enhanced. Clazz is an abstract class and allows many alternative implementations.
  • Class describes a Java type in terms of fields, methods and constructors. Clazz describes a type in terms of properties, operations and instance factories. In the case when a Clazz represents a Java class, those properties, operations and instance factories may or may not map directly to fields, methods and constructors. The mapping is determined by the implementation of the Clazz and can be quite non-trivial. For example, a Clazz may map pairs of Java methods like getFoo() and setFoo() to a single property called "foo". There may be many ways to describe the same Java class with a Clazz.
  • Class is restricted to describing Java types. Clazz does not have that limitation. A Clazz can be created dynamically out of piece parts.

Client Side of Clazz

The client sees Clazz as a set of interfaces. The following code illustrates a basic interaction between a client and the Clazz package:


public class MyExample1 {

   void listAllOperations(){
   
     // Step 1. Obtain the ClazzLoader associated with the default 
     //         clazz model. Provide a ClassLoader as a context 
     //         for reflection.
     ClazzLoader clazzLoader = 
         Clazz.getDefaultClazzLoader(MyExample1.class.getClassLoader());

     // Step 2. Find the Clazz
     Clazz clazz = clazzLoader.getClazzForName("my.example.Example");
     
     // Step 3. Obtain operation descriptions from the clazz
     List operations = clazz.getOperations();
        
     for (Iterator iter = operations.iterator(); iter.hasNext();) {
          ClazzOperation operation = (ClazzOperation) iter.next();
          System.out.println("Operation: " + operation.getSignature());
     }
   }
}

The getDefaultClazzLoader() static method invokes a ClazzLoaderFactory which allocates and caches ClazzLoaders.

The roles of the three main classes: Clazz, ClazzLoader and ClazzLoaderFactory are illustrated by the following diagram (green - interfaces, blue - relationships):

Runtime Clazzes

Clazzes can be created dynamically out of piece parts: properties, operations, instance factories etc. If the client sticks to the Clazz APIs, there is no difference between clazzes created dynamically and those based on the reflection of hard Java classes.

Creating a Clazz Dynamically

In the following example we are creating a Clazz with a string property called "foo".

We always use a clazz loader to instatiate a new Clazz. The ClazzLoader maintains the integrity of inter-clazz references and also serves as a cache for clazzes. The instantiation method, declareClass, takes three parameters: the name of the new clazz, the Java class of the new Clazz object and the Java type the instances of the Clazz will belong to.


public class MyExample2 {

   void createCustomClazz(){
                                                                       
     // Step 1. Obtain the ClazzLoader associated with the default 
     //         clazz model. We always provide a class loader as well,
     //         since all ClazzLoaders are associated with ClassLoaders.
     ClazzLoader clazzLoader = 
         Clazz.getDefaultClazzLoader(MyExample2.class.getClassLoader());

     // Step 2. Create a new Clazz
     BeanClazz clazz = (BeanClazz) clazzLoader.defineClazz(
            "my.custom.DynamicBean",  // Name of the new clazz
            BeanClazz.class,          // Type of the new clazz
            Bean.class);              // Type of instances of the new clazz

     // Step 3. Build the new Clazz out of properties, operations etc
     ClazzProperty prop =
            new BeanClazzProperty(clazz, "foo", "java.lang.String");
     clazz.addDeclaredProperty(prop);
     
     // Step 4. Now we can use the new clazz
     
     Object instance = clazz.newInstance();
     clazz.getProperty("foo").set(instance, "bar");
     
     String value = (String)clazz.getProperty("foo").get(instance);
     
     // This prints "Dynamic property value: bar"
     System.out.println ("Dynamic property value: " + value);
  }
}

Clazz Model

Depending on the application, there may be different ways to represent the same type as a clazz. A particular method of describing types is a clazz model. Technically a clazz model is managed by a ClazzLoaderFactory. A ClazzLoaderFactory creates ClazzLoaders, which in turn create Clazz's according to the requirements of that specific model.

To introduce a new clazz model, the corresponding ClazzLoaderFactory is registered with a unique key. Clients identify models by such keys.


public class MyExample3 {

   // Create and register a ClazzLoaderFactory for a custom model
   static {
      Clazz.setClazzLoaderFactory("myclazzes", new MyClazzLoaderFactory());
   }
   
   
   void listAllProperties(){
   
     // Step 1. Obtain a ClazzLoader for the custom clazz model.
     ClazzLoader clazzLoader = 
         Clazz.getClazzLoader("myclazzes", MyExample.class.getClassLoader());

     // Step 2. Find the Clazz
     Clazz clazz = clazzLoader.getClazzForName("my.example.Example");
     
     // Step 3. Obtain property descriptions from the clazz
     List properties = clazz.getProperties();
     ...
     for (Iterator iter = properties.iterator(); iter.hasNext();) {
          ClazzProperty property = (ClazzProperty) iter.next();
          System.out.println("Property: " + property.getName());
     }
   }
}

<TBD> - if more models are added, mention them here.

The Clazz package comes with two clazz model out of the box: "Standard" and "Extended".

The Standard model implements class introspection according to the JavaBean specification.

The Extended model implement a more "modern" definition of beans, the one that recognizes "add", "remove", etc methods, supports List and Mapped properties, allows the use of plural form of the property name etc.

Clazz Loader

A ClazzLoader is responsible for creating and caching of clazzes. Typically a ClazzLoader returned by a ClazzLoaderFactory is a mere front for a whole family of ClazzLoaders, each responsible for its own "kind" of Clazzes. For instance, a ClazzLoader performing reflection will typically not be the same ClazzLoader as the one managing types not based on reflection. To facilitate the creation of such families of ClazzLoaders, the package provides such aggregating ClazzLoader implementations as GroupClazzLoader and CachingGroupClazzLoader.

Extended Property Types

Clazz introduces two new kinds of properties: List and Mapped. A List property is similar to the JavaBeans indexed property, except that to access a List property you can use java.util.List interface. Similarly, a Mapped property is accessed via the java.util.Map interface.

List Property

The List property behaves differently in different models.

In the Standard model, Clazz recognizes methods like getBook(), returning an array as well as indexed accessors: getBook(int index) and setBook(int index, value).

In the Extended model, the range of recognized accessors is much wider. For a property called "book", Clazz will recognize and use the following accessors:

  • Whole collection read method as a method returning an array or a list and named: getBook(), getBooks(), getBookList(), getBookArray() or getBookVector().
  • Whole collection set method as a method taking an array or a list as its only argument and named: setBook(array or list), setBooks(...), setBookList(...), setBookArray(...) or setBookVector(...).
  • Indexed get method: getBook(int index)
  • Indexed set method: setBook(int index, book)
  • Add method: addBook(book)
  • Indexed add method: addBook(int index, book)
  • Remove method: removeBook(book)
  • Indexed remove method: removeBook(int index)
  • Count method: int getBookCount() or int getBookSize() etc.

These accessors are not required. One get method, indexed or not is sufficient for Clazz to recognize a List property.

When you acquire the value of this property using the Clazz API, you get an object implementing the complete java.util.List interface. The implementations of List methods invoke relevant accessor methods on the bean. The implementation is smart enough to get by with whatever accessors are available. However, complete lack of accessors will render the List inoperable. For example, if the only accessors available are getBook(int) and setBook(int,book), you will be able to call list.get(i) and list.set(int,book), but you won't be able to iterate over the list or add anything to it. In order enable those operations, you need either int getBookCount() or getBooks().

Whole collection accessors are used only in the absense of more specific accessors.

Example:


public class Album {

   String getTrackTitle(int trackNumber) {...}
   int getTrackTitleCount() {...}
}
                                                                       
public class MyExample4 {

   void listAllTracks(Album album){
   
     // Step 1. Find the Extended Clazz
     Clazz clazz = Clazz.getClazz(album, "Extended");

     // Step 2. Acquire the property value
     List trackTitles = (List)clazz.get("trackTitles");
          
     // Step 3. Work with the property value
     for (Iterator iter = trackTitles.iterator(); iter.hasNext();) {
          String title = (String) iter.next();
          System.out.println("Track: " + title);
     }
   }
}

Mapped Property

Mapped property is similar to List property, except values are identified by keys rather than integer indexes.

In the Standard model Mapped properties are not supported.

In the Extended model, the following accesors are recognized.

  • Whole collection read method as a method returning a Map and named: getBook(), getBooks() or getBookMap().
  • Whole collection set method as a method taking a Map as its only argument and named: setBook(...), setBooks(...) or setBookMap(...).
  • Keyed get method: getBook(key)
  • Keyed set method: setBook(key, book)
  • Remove method: removeBook(key)
  • Key-set method: getBookKeys() or getBookKeySet(). This method returns an array or Collection of keys.

These accessors are not required. One get method, keyed or not is sufficient for Clazz to recognize a Mapped property.

When you acquire the value of this property using the Clazz API, you get an object implementing the complete java.util.Map interface. The implementations of the Map methods invoke relevant accessor methods on the bean. Whole collection accessors are used only in the absense of more specific accessors.

Example:


public class Album {

   String getComposer(String trackTitle) {...}

}
                                                                       
public class MyExample5 {

   void listAllTracks(Album album){
   
     // Step 1. Find the Extended Clazz
     Clazz clazz = Clazz.getClazz(album, "Extended");

     // Step 2. Acquire the property value
     Map composerMap = (Map)clazz.get("composer");
          
     // Step 3. Work with the property value
     String composer = (String) composerMap.get("Love Me Two Times");
     System.out.println("Composer: " + composer);
   }
}

Customization

Customizability is the most important aspect of the Clazz package. There are many points of customization and choosing the right one for your task is very important.

Customizing an Individual Reflected Clazz

Let's say we have a class Bean1 that has the property "data", which is accessed with unusual methods: "storeData" and "retrieveData". The implementations of ReflectionClazz included in the Clazz package are only programmed to detect accessor methods with signatures like "setData" and "getData", therefore they will not recognize these non-standard accessors.

We will address the problem by creating a customized implementation of Clazz. We will subclass one of the regular implementations and modify its behavior.

We put the custom implementation of Clazz into the same package as the class itself and name it <Class><Model>Clazz. By naming the implementaion like this, we make it possible for the ClazzLoader to discover and load it.

In our particular scenario "data" will not be automatically recognized as a property at all, because it does not have any standard accessors. Therefore, we will have to create and register that property explicitly.


public class Bean1ExtendedClazz extends ExtendedReflectedClazz {

    public Bean1ExtendedClazz(ClazzLoader loader, Class instanceClass) {
        super(loader, instanceClass);
    }
    
    protected void introspectProperties() {
        super.introspectProperties();
        
        ReflectedScalarProperty property = 
                new ReflectedScalarProperty(this, "data");
    
        Class javaClass = Bean1.class;
        try { 
            property.setReadMethod(
                javaClass.getMethod("retrieveData", null));
            property.setWriteMethod(
                javaClass.getMethod("storeData", new Class[]{String.class}));
        }
        catch (NoSuchMethodException e){            
        }
        
        addProperty(property);   
    }

}

Now, let's say one of the accessors is the standard "getData", while the other one is the unusual "storeData". This case is different in that the standard implementation of ReflectedClazz will recognize the property "data", but will not associate it with the non-standard accessor. All we need to do in this case is find the property and link it with the accessor.


public class Bean2ExtendedClazz extends ExtendedReflectedClazz {

    public Bean2ExtendedClazz(ClazzLoader loader, Class instanceClass) {
        super(loader, instanceClass);
    }
    
    protected void introspectProperties() {
        super.introspectProperties();
        
        ReflectedScalarProperty property = getProperty("data");
    
        Class javaClass = Bean2.class;
        try { 
            property.setWriteMethod(
                javaClass.getMethod("storeData", new Class[]{String.class}));
        }
        catch (NoSuchMethodException e){            
        }
    }

}

Finally, consider class Bean3 that has a method "getDataAsStream()". This method has a typical accessor signature, therefore ReflectedClazz will recognize "dataAsStream" as a legitimate property. If we don't want it to be recognized as a property, we can filter it out like this:


public class Bean3ExtendedClazz extends ExtendedReflectedClazz {

    public Bean3ExtendedClazz(ClazzLoader loader, Class instanceClass) {
        super(loader, instanceClass);
    }
    
    protected void addProperty(ClazzProperty property) {
        if (property.getName().equals("dataAsStream")){
            return;
        }
        
        super.addProperty(property);
    }
}

Customizing a Clazz using BeanInfo

If you have a custom implementation of BeanInfo for a custom bean, that implementation will be used in the construction of the Clazz. This method is not as flexible as the one described in the previous section. However, you don't have to redo your customizations if you already have a BeanInfo implementaion or if you generate one automatically using XDoclet.

Customizing a Family of Reflected Clazzes

In the previous section we examined customization of an individual clazz. More commonly we need to customize a whole family of Clazzes in a similar fashion. Let's say in our application all classes implementing a "PersistentBean" interface follow the same naming convention for accessors: the read method is always called "retrieveFoo" and the write method is always "storeFoo". Instead of customizing each of the clazzes independently, we can introduce a generic customization for all of them.

ReflectedClazz uses introspectors to recognize properties, operations etc. An introspector, in turn, uses AccessorMethodParsers to recognize accessor methods. Each parser is responsible for a certain type of accessor: read, write, etc. So, we will need two custom parsers: one for the "retrieve..." methods and one for the "store..." methods. Since these parsers are very simple, we will implement them as anonymous inner classes.


public class CustomPropertyIntrospector
    extends ReflectedScalarPropertyIntrospector 
{
    private static final AccessorMethodParser READ_METHOD_PARSER = 
        new ReadAccessorMethodParser()
    {
        protected String requiredPrefix(){
            return "retrieve";
        }
    };

    protected AccessorMethodParser getReadAccessorMethodParser(){
        return READ_METHOD_PARSER;
    }

    private static final AccessorMethodParser WRITE_METHOD_PARSER = 
        new WriteAccessorMethodParser() 
    {
        protected String requiredPrefix(){
            return "store";
        }
    };
    
    protected AccessorMethodParser getWriteAccessorMethodParser(){
        return WRITE_METHOD_PARSER;
    }
}

The next step is to write a custom implementation of Clazz. This implementation will be the same as its superclass, StandardReflectedClazz, with the exception of the way it recognizes properties. The custom clazz will use the above custom property introspector.


public class CustomClazz extends StandardReflectedClazz {

    protected static final 
            ReflectedPropertyIntrospector[] PROPERTY_INTROSPECTORS =
        new ReflectedPropertyIntrospector[] {
            new CustomPropertyIntrospector() 
        };


    public CustomClazz(ClazzLoader loader, Class instanceClass) {
        super(loader, instanceClass);
    }

    protected ReflectedPropertyIntrospector[] getPropertyIntrospectors() {
        return PROPERTY_INTROSPECTORS;
    }
}

Now we need to create a custom Clazz Loader that will produce instances of CustomClazz for all classes implementing the PersistentBean interface.


public class CustomClazzLoader extends ReflectedClazzLoader {

    public CustomClazzLoader(
        ModelClazzLoader modelClazzLoader,
        ClassLoader classLoader) 
    {
        super(modelClazzLoader, classLoader);
    }
    
    public boolean isSupportedClass(Class javaClass) {
        return PersistentBean.class.isAssignableFrom(javaClass);
    }

    protected Clazz createClazz(Class javaClass) {
        return new CustomClazz(getModelClazzLoader(), javaClass);
    }
}

The final step is to statically register the custom clazz loader. A clazz loader belongs to some clazz model, perhaps the default one. In the registration process we associate the clazz loader we created with the corresponding model.


        ClazzLoaderFactory factory =
            Clazz.getClazzLoaderFactory(Clazz.EXTENDED_CLAZZ_MODEL);
        factory.addClazzLoaderClass(CustomClazzLoader.class);