I've been able to sneak in a little bit of work on Tapestry 3.1 and HiveMind 1.1 over the last week or so. It's been fun stuff; On the Tapestry side, I've been re-working the enhancement subsystem.
Enhancement is the process of taking your classes (which are usually abstract) and the corresponding page or component specification, and building a subclass that fills in all the Tapestry details. It is the sub-class that gets instantiated. The class is abstract because Tapestry needs to fill in some critical details on each property, to make the resulting page or component work properly within Tapestry's page pool.
In fact, the whole process has gotten much, much smarter for Tapestry 3.1. There's the <inject> element, which allows HiveMind objects to be pulled out of the registry and plugged into a page (or component) as a read-only property. Also, any abstract property on a page (or component --- jeez that gets repetative; pages are components) will turn into a transient property automatically.
The <property> element is now only needed for persistent properties, for properties that want to set an initial value, or for properties that aren't referenced in Java code (but are simply used to move data beteween components).
The approach, using a configuration of worker services, each with a specific responsibility, works great. It will be easy to extend it in the future with new properties; for example, I want to be able to assign a property name to a component or asset, and have a property, of the correct type, show up.
Some things are getting more efficient as well. In Tapestty 3.0, each specified property must have an object implementing PageDetachListener, whose job is to reset the property back to its default value. This is needed even if there isn't an OGNL expression for the property's initial value. For the moment, that hasn't changed ... for specified properties. For unspecied abstract properties, a more sophisticated changed takes place: the class is made to implement PageDetachListener (if it did not already), and the pageDetached() method is created or overriden to take care of resetting the properties; in addition, the finishLoad() method is overriden to snapshot the initial values of the properties (each property gets a pair of instance variables).
So we've broken down a complex job into several much simpler jobs; each individual worker is fully tested (100% code coverage). They each work with an EnhancementOperation object that encapsulates most of the work of analyzing the existing class and constructing the enhanced class.
By plugging more workers into the configuration, even application-specific ones, new types of enhancements will be added. It will be possible, for example, to create a library that understands some form of JDK 1.5 annotation and have it plug into the configuration ... the Tapestry framework can continue to be compiled against JDK 1.3 while using JDK 1.5 features. Hows that for late binding?
This big payback for all of this is coming up: elimination of parameter direction. The definition of dread is when I get to that part of my Tapestry presentation. Parameter direction is a hint, given to Tapestry, that describes when OGNL expressions should be evaluated to move data out of a container (typically, the page) and into a contained component's properties. The problem is, you have to either understand a lot of what Tapestry is doing under the covers to decide what the right direction should be, or at least, do certain things by rote ("if I access that parameter property in a listener method, then it must be direction auto").
Yuck. It should just work ... and in Tapestry 3.1 it just will. The generated code for parameter properties will be more involved ... it will have to be aware of whether the component is currently rendering or not, whether a cached value for the property is available, and some tricky logic about converting types as needed.
That's going to take a little work to get right, which is why I flipped back to the HiveMind side. HiveMind has the ClassFactory service, which puts a relatively pretty face on the arcane aspects of Javassist. However, given how much runtime bytecode enhancement is going to occur, and how much of it will be distributed across many objects, ClassFactory and ClassFab have a weakness: they did not implement toString(). Now they do, I spent some time last night getting them to generate a reasonable string representation that looks kind of like Java psuedocode, so that you can see what's going to be generated by the time the class is created from the ClassFab. The end result for an enhanced class can look like:
public class $ExceptionDisplay_4 extends org.apache.tapestry.html.ExceptionDisplay
private org.apache.tapestry.util.exception.ExceptionDescription _$exceptions;
private int _$index;
private int _$index$defaultValue;
private int _$count;
private int _$count$defaultValue;
public org.apache.tapestry.util.exception.ExceptionDescription getExceptions()
public void setIndex(int $1)
_$index = $1;
public int getCount()
public void finishLoad(org.apache.tapestry.IRequestCycle $1, org.apache.tapestry.engine.IPageLoader $2, org.apache.tapestry.spec.IComponentSpecification $3)
_$index$defaultValue = _$index;
_$count$defaultValue = _$count;
public void setCount(int $1)
_$count = $1;
public void pageDetached(org.apache.tapestry.event.PageEvent $1)
_$index = _$index$defaultValue;
_$count = _$count$defaultValue;
public int getIndex()
public void setExceptions(org.apache.tapestry.util.exception.ExceptionDescription $1)
_$exceptions = $1;
It's not Java syntax, and its not even quite Javassist syntax ... but it certainly identifies what the new class is all about and that's what counts!