Tapestry Training -- From The Source

Let me help you get your team up to speed in Tapestry ... fast. Visit howardlewisship.com for details on training, mentoring and support!

Friday, November 05, 2004

HiveMind AdapterRegistryFactory

Just did a little burst of work on HiveMind and added the AdapterRegistryFactory. What's it all about?

It's an implementation of the adapter pattern. The idea is that you have some common operation that should apply to all sorts of different types; perhaps its code that is used to output an XML representation of different objects, or performs some other common operation. In Tapestry, an example of this is the way different objects are evaluated as conditions for the Conditional component: a java.lang.Boolean is obvious, but others require some work: java.lang.String if it contains non-whitespace character; any Number if the value is non-zero, any java.util.Collection if not empty, etc.

With an AdapterRegistry, you define an interface for your adapters. The first parameter of each service method will be used to select the adapter.

public interface ConditionEvaluator
{
  public boolean evaluate(Object value);
}
This idiom reflects that the adaptors are singletons into which the object to operate upon is provided as a method parameter. This differs somewhat from the Gang Of Four useage, where a specific adaptor instance is created for a specific object instance.
You define a configuration and contribute classes and adapters:
<contribution configuration-id="ConditionEvaluators">
  <adapter class="java.lang.Boolean" object="instance:BooleanAdapter"/>
  <adapter class="java.lang.Number" object="instance:NumberAdapter"/>
  . . .

Your adapter implementations are simple:

public class BooleanAdapter implements ConditionEvaluator
{
  public boolean evaluate(Object value)
  {
    Boolean b = (Boolean)value;
 
    return b.booleanValue();
  }
}

Lastly, you define your service point:

<service-point id="ConditionEvaluator" interface="ConditionEvaluator">
  <invoke-factory service-id="hivemind.lib.AdapterRegistryFactory">
    <construct configuration-id="ConditionEvaluators"/>
  </invoke-factory>
</service-point>

At this point, you can reference this service and invoke methods on it, passing different instances into it. Internally, the service implementation will locate the matching adapter (BooleanAdapter for java.lang.Boolean, NumberAdapter for java.lang.Integer and friends) and let the adapter do the work. What's powerful is that your code just sees the one service proxy ... HiveMind connects the dots to get the correct adapter implementation invoked when you invoke a method on the proxy. This is similar to how the threaded and pooled service models expose just the one proxy. There are no questions such as "is this service threaded?" or "is this service an adapter?" ... it all just works.

For the moment, you are not allowed to pass null (you'll get a runtime exception). Still pondering the right approach to handling nulls.

Much like the PipelineFactory, this is a quick way to assemble a pretty sophisticated apparatus ... that all hides behind a single service and a single interface. I often talk about HiveMind's power coming from the mix of services and configurations. In a way that is familiar to Lisp hackers and the like, code (in HiveMind terms, services) gets all mixed up with data (configurations) to allow elegant, powerful solutions to arise.

Updated: Changed the naming from "Adaptor" to "Adapter".

2 comments:

Colin Sampaleanu said...

Not trying to nitpick, but while 'adaptor' is legal, it seems like in the computer field everybody users 'adapter'. Even Design Patterns uses the latter.

James Carman said...

What would be even better is if you didn't require us to register multiple instances...

public class ConditionEvaluatorImpl implements ConditionEvaluator
{
public boolean evaluate(Object value)
{
throw new IllegalArgumentException( "Unhandled type " + value.getClass().getName();
}

public boolean evaluate( Boolean value )
{
return value.booleanValue();
}

public boolean evaluate( Number value )
{
return value.intValue() != 0;
}
}

I have code that does this, but it is JDK proxy-based (it's an InvocationHandler), but it works quite nicely. I call it DoubleDispatchInvocationHandler, since it essentially is doing double dispatch. It looks up the "best match" for the method based upon parameter type. Let me know if you want the code posted somewhere.