Adaptable Objects as a Core Pattern of Eclipse Core Runtime
The adaptable objects pattern is probably the most important one used by the Eclipse core runtime. Formalized by the org.eclipse.core.runtime.IAdaptable interface, an adaptable object can easily be queried by clients (in a type-safe manner) for additional functionality that is not included within its general contract.public interface IAdaptable {/**
* Returns an object which is an instance of the given class* associated with this object. Returns <code>null</code> if* no such object can be found.** @param adapter the adapter class to look up* @return a object castable to the given class,* or <code>null</code> if this object does not* have an adapter for the given class*/public Object getAdapter(Class adapter);}
From another viewpoint, if an adaptable object properly delegates its getAdapter(Class) implementation to an IAdapterManager (most commonly Platform.getAdapterManager()) or provides a respective proprietary mechanism on its own, it can easily be extended with new functionality (even at runtime), without any need for local changes, and adapter creation can flexibly be handled through a set of IAdapterFactory implementations.
Why org.eclipse.core.runtime.IAdaptable is not perfectly suited for GEF4
As it has proven its usefulness in quite a number of places, I considered the adaptable objects pattern to be quite a good candidate to deal with the configurability and flexibility demands of a graphical editing framework as well. I thus wanted to give it a major role within the next generation API of our model-view-controller framework (GEF4 MVC).GEF4 MVC is the intended replacement of GEF (MVC) 3.x as the core framework, from which to build up graphical editors and views. As it has a high need for flexibility and configurability it seemed to be the ideal playground for adaptable objects. However, the way the Eclipse core runtime interprets the adaptable objects pattern does not make it a perfect match to fulfill our requirements, because:
- Only a single adapter of a specific type can be registered at an adaptable. Registering two different Provider
- Querying (and registering) adapters for parameterized types is not possible in a type-safe manner. The class-based signature of getAdapter(Class) does for instance not allow to differentiate between a Provider<IGeometry>
and a Provider<IFXAnchor> . - IAdaptable only provides an API for retrieving adapters, not for registering them, so (re-) configuration of adapters at runtime is not easily possible.
- Direct support for 'binding' an adapter to an adaptable object, i.e. to establish a reference from the adapter to the adaptable object, is not offered (unless the adapter explicitly provides a proprietary mechanism to establish such a back-reference).
Adaptable Objects as Interpreted by GEF4 Common
I thus created my own interpretation of the adaptable objects pattern, formalized by org.eclipse.gef4.common.adapt.IAdaptable. It is provided by the GEF4 Common component and can thus be easily used standalone, even for applications that have no use for graphical editors or views (GEF4 Common only requires Google Guice and Google Guava to run).AdapterKey to combine Type with Role
Instead of a simple Class-based type-key, adapters may now be registered by means of an AdapterKey, which combines (a Class- or TypeToken-based) type key (to retrieve the adapter in a type-safe manner) with a String-based role.
The combination of a type key with a role allows to retrieve several adapters of the same type with different roles. Two different Provider implementations can for instance now easily be retrieved (to provide independent geometric information for selection feedback and selection handles) through:
getAdapter(AdapterKey.get(new TypeToken<Provider<IGeometry>>(){}, "selectionFeedbackGeometryProvider"))
getAdapter(AdapterKey.get(new TypeToken<Provider<IGeometry>> (){}, "selectionHandlesGeometryProvider"))
TypeToken instead of Class
The second significant difference is that a com.google.common.reflect.TypeToken (provided by Google Guava) is used as a more general concept instead of a Class, which enables parameterized adapters to be registered and retrieved in a type-safe manner as well. A geometry provider can for instance now be easily retrieved through getAdapter(new TypeToken<Provider<IGeometry>>(){}) , while an anchor provider can alternatively be retrieved through getAdapter(new TypeToken<Provider<IFXAnchor>>(){}) . For convenience, retrieving adapters by means of Class-based type keys is also supported (which will internally be converted to a TypeToken).
IAdaptable as a local adapter registry
In contrast to the Eclipse core runtime interpretation, an org.eclipse.gef4.common.adapt.IAdaptable has the obligation to provide means to not only retrieve adapters (getAdapter()) but also register or unregister them (setAdapter(), unsetAdapter()). This way, the 'configuration' of an adaptable can easily be changed at runtime, even without providing an adapter manager or factory.
Of course this comes at the cost that an org.eclipse.gef4.common.adapt.IAdaptable is itself responsible of maintaining the set of registered adapters. This (and the fact that the interface contains a lot of convenience functions) is balanced by the fact that a base implementation (org.eclipse.gef4.common.adapt.AdaptableSupport) can easily be used as a delegate to realize the IAdaptable interface.
IAdaptable.Bound for back-references
If adapters need to be 'aware' of the adaptable they are registered at, they may implement the IAdaptable.Bound interface, which is used to establish a back reference from the adapter to the adaptable. It is part of the IAdaptable-contract that an adapter implementing the IAdaptable.Bound will be provided with a back-reference during registration (if an adaptable uses org.eclipse.gef4.common.adapt.AdaptableSupport to internally realize the interface, this contract is of course guaranteed).
IAdaptables and Dependency Injection
While the possibility to re-configure the registered adapters at runtime is quite helpful, proper support to create an initial adapter configuration during instantiation of an adaptable is also of importance. To properly support this, I integrated the GEF4 Common adaptable objects mechanism with Google Guice.
That is, the adapters that are to be registered at an adaptable can be configured in a Guice module, using a specific AdapterMap binding (which is based on Guice's multi-bindings). To register an adapter of type VisualBoundsGeometryProvider at a FXGeometricShapePart adaptable can for instance be performed using the following Guice module configuration:
protected void configure() {// enable adapter map injection supportinstall(new AdapterInjectionSupport());// obtain map-binder to bind adapters for FXGeometricShapePart instancesMapBinder<AdapterKey<?>, Object> adapterMapBinder =AdapterMaps.getAdapterMapBinder(binder(), FXGeometricShapePart.class);// bind geometry provider for selection handles as adapter on FXGeometricShapePartadapterMapBinder.addBinding(AdapterKey.role("selectionHandlesGeometryProvider")).to(VisualBoundsGeometryProvider.class);...}
It will not only inject a VisualBoundsGeometryProvider instance as an adapter to all direct instances of FXGeometricShapePart but also to all instances of its sub-types, which may be seen as a sort of 'polymorphic multi-binding'.
Two prerequisites have to be fulfilled in order to make use of adapter injections:
- Support for adapter injections has to be enabled in your Guice module by installing an org.eclipse.gef4.common.inject.AdapterInjectionSupport module as outlined in the snippet above.
- The adaptable (here: FXGeometricShapePart.class) or any of its super-classes has to provide a method that is eligible for adapter injection:
@InjectAdapterspublic <T> void setAdapter(TypeToken<T> adapterType, T adapter, String role) {// TODO: implement (probably by delegating to an AdaptableSupport)}
GEF4 MVC makes use of this mechanism quite intensively for the configuration of adapters (and indeed, within the MVC framework, more or less everything is an adapter). However, similar to the support for adaptable objects itself, the related injection mechanism is easily usable in a standalone scenario. Feel free to do so!