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:
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):
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.
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); } }
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.
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.
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.
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:
getBook()
,
getBooks()
, getBookList()
,
getBookArray()
or getBookVector()
.
setBook(array or list)
,
setBooks(...)
, setBookList(...)
,
setBookArray(...)
or setBookVector(...)
.
getBook(int index)
setBook(int index, book)
addBook(book)
addBook(int index, book)
removeBook(book)
removeBook(int index)
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 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.
getBook()
,
getBooks()
or getBookMap()
.
setBook(...)
,
setBooks(...)
or setBookMap(...)
.
getBook(key)
setBook(key, book)
removeBook(key)
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); } }
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.
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); } }
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.
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 AccessorMethodParser
s 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);