Friday, 22 March 2013

Behavioural extensions of EMF Models

If not suppressed on the Genmodel level notifications are an integral feature of EMF model instances changes to instances of a model are described through Notification objects. Every EObject broadcast notifications. Receivers of notifications are called adapters. Adapters my act as simple observers (listeners) or as behavioural extensions of the adapted model element.

A visitor pattern for EMF model instances


When working with model instances the visitor pattern is a convenient way to separate operations from the object structure. Not only once did I implement my own visitor implementation, before I learned what EMF has already in store. Combining the generated model switch with EMF’s TreeIterator is a powerful tool to navigate through your model and perform operations upon it. Let’s look at a simple example:

CarModelSwitch<Object> carVisitor = new CarModelSwitch<Object>() { 
   public Object caseCar(Car car) {
     System.out.println("Visiting car: " + car.getBrand());
  }
   public Object caseBody(Body body) {
     repair(body);
     return Boolean.TRUE;
   }
   public Object caseEngine(Engine engine) {
     repair(engine);
     return Boolean.TRUE;
   }

   public Object caseWheel(Wheel wheel) {
     checkTirePreasure(wheel);
     return Boolean.TRUE;
   }
};

The visitor is implemented as an anonymous CarModelSwitch class. The generated model switch class provides case methods for each type defined in our car model. The base implementations of the case methods do nothing but to return a null value. Our carVisitor now selectivley overrides case methods for all car elements and performs element specific operations upon them. Note that all case methods return Boolean.TRUE to prevent the switch from steping up the inheritance hierachy of each element. Let’s assume our car elements would all extend CarElement the corresponding case method caseCarElement would only be called for Body, Engine and Car types if previously case invocations return null.

Now we could call carVisitor.doSwitch(carelementOrCar) to perform specific case operation. To navigate through the whole content of a car model a TreeIterator can be used. The following code snippet retrieves the all content of a car model through EcoreUtil’s method getAllContent and calls for each model element the doSwitch method of the above visitor implementation.
for(TreeIterator<EObject> iterator = EcoreUtil.getAllContents(carModel);  iterator.hasNext();) {
     EObject modelElement = iterator.next();
     carvisitor.doSwitch(modelElement);
}

Find internal and external references to EMF model elements



EcoreUtil provides utilities to retrieve and navigate through the content structure of a model instance. To generically access direct content of a model element EObject’s eAllContent method can be used. 


When it comes to non containment references eCrossReferences of a EObject is used. Here again EcoreUtil provides a set of utilities which help to manage cross references within a model instance. The nested class CrossReferencer is used to map all cross references within a tree of model elements. CrossReferencer is implemented as a Map which maps an EObject to a set of EStructuralFeature.Setting. Upon creation a CrossReferencer can be initialized with either a EObject, a Resource or a ResourceSet. CrossReferencer extends HashMap on lookup the a CrossRefernce maps

Slightly different implementations to lookup cross references are the classes ExternalCrossReferencerProxyCrossReferencer and UnresolvedProxyCrossReferencer.


TreeIterators - Navigate through your EMF model

When working with tree based model instances iterating through of model elements content and contained substructures is a common activity. EcoreUtil provides methods to access all objects directly contained within the specified element or indirectly contained within a subtree of model elements. EObject eAllContent method only returns an elements direct content.

<T> TreeIterator<T> getAllContents(EObject eObject, boolean resolve)
<T> TreeIterator<T> getAllContents(Resource resource, boolean resolve)
<T> TreeIterator<T> getAllContents(ResourceSet resourceSet, boolean resolve)
<T> TreeIterator<T> getAllContents(Collection<?> emfObjects, boolean resolve)


The returned TreeIterator recursivley iterates over all direct and indirect content starting from a specified root element. Since content can be contained within other Resource elements the iterator may be instructed to resolve any proxy elements found. A very similar approach are EcoreUtil’s getAllProperContent methods, which are used to retrieve content which is only contained within the same Resource element.

<T> TreeIterator<T> getAllProperContents(EObject eObject, boolean resolve)
<T> TreeIterator<T> getAllProperContents(Resource resource, boolean resolve)
<T> TreeIterator<T> getAllProperContents(ResourceSet resourceSet, boolean resolve)
<T>TreeIterator<T> getAllProperContents(Collection<?> emfObjects, boolean resolve)


Both options to retrieve content yield a TreeIterator implemented by ContentTreeIterator. ContentTreeIterator iterates through all child elements retrieved by either EObjects econtents feature, a Resource elements getContents method or a ResourceSets getResources implementation. ContentTreeIterator is a nested inner class of EcoreUtil and extends AbstractTreeIterator. These implementation details may be of no further interest to you as long as your satisfied with EMFs iterator implementations (and to my opinion you can go a long way with them). If not, make use of EMF’s base iterator implementations. For example try to extend your implementation from AbstractTreeIterator and provide your own implemenation of getChildren.

When using TreeIterators remind yourself of the iterators prune method which let you skip over all subnodes contained within the last model element retrieved.