Model API – Advanced navigation

Modelio v3.6

The Model API provides advanced navigation techniques for multiple associations that can return an heterogeneous set of different objects. A typical example is the getOwnedElement() accessor which, when applied to a package, can potentially return a bunch of various objects, classes, sub-packages, components and so on.

This “diversity” is in most cases not expected, the programmer most likely willing to get only the classes of the package for example.

A simple and naive solution to the “diversity” issue, consists in looping on the returned elements and selecting only the relevant ones using the instanceof Java operator. This is shown in the code snippet below:

 1import org.modelio.metamodel.statik.Class;
 2
 3Package root = ...; //some code to get the model root package
 4
 5// List the root classes 
 6List<ModelTree> subElements = root.getOwnedElement();
 7List<Class>     subClasses  = new ArrayList<Class>();
 8
 9for (ModelTree mt : subElements) {
10    if (mt instanceof Class) {
11       subClasses.add((Class)mt);
12    }
13}
14
15// At this point subClasses contains only the sub classes of the root package
16

line 1: this import ensures that Class represents the Modelio metamodel Class definition and not java.lang.class
line 9: the value returned by getOwnedElement() is a list of ModelTree which may represent classes, packages components and so on, we have to loop on this list to filter only the classes
line 10: using the instanceof Java operator to distinguish between ModelTree objects

This instanceof based solution is perfectly viable but is not really elegant. The model API provides two main techniques to deal with this situation:

  • filtering at “accessor” level
  • visiting

Filtering at “accessor” level

The multiple cardinality getter accessors of model API have a variant that takes a metaclass as parameter. This variant will return only those elements that belongs to the navigated association AND that match the metaclass argument.

Let see an example:

 1Package root = ...; //some code to get the model root package
 2
 3// List root classes 
 4List<Class> subClasses = root.getOwnedElement(Class.class);
 5for (Class aClass : subClasses) {
 6    System.out.println("Class name = " + aClass.getName());
 7}
 8
 9// List root packages
10List<Package> subPackages = root.getOwnedElement(Package.class);
11for (Package aPackage : subPackages) {
12    System.out.println("Package name = " + aPackage.getName());
13}

By only adding an additional parameter to the “get” accessor (lines 4 and 10) that indicates which metaclass objects are expected, we could benefit a smart accessor able to carry out the filtering of the returned contents. Note that the parameter must be a Java class from the XXX hierarchy set.

Note: Filtered accessors returns unmodifiable copies of the real list.

Visiting

As explained above, List returned by association navigation methods very often contain UML elements of different types. One way to deal with this situation, where different objects are mixed together in the List, consists in testing the returned objects using the instanceof Java operator and specializing the processing of each kind of object. Another way consists in using filtered accessors, but if we need to process a number N of types we have to carry out N filtered accessor calls, one for each type.

Unfortunately, these methods quickly become an inefficient nightmare when a large variety of object kinds has to be managed.

Model API provides a more powerful mechanism to solve this problem, as the MObject class hierarchy comes with an implemented visitor pattern.

The MVisitor interface

The MVisitor interface defines visit methods for each supported metaclass. Each XXXXXX metaclass Java representation interface has an accept(MVisitor) method that can be called, passing to it an instance of MVisitor.

1public Object accept(MVisitor e)

The following code example shows the use of the visitor pattern to print root element classes and packages.

First define a MVisitor implementation.

 1import org.modelio.metamodel.visitors.AbstractModelVisitor;
 2import org.modelio.metamodel.uml.statik.Class;
 3import org.modelio.metamodel.uml.statik.Package;
 4
 5class PrintClassAndPackageVisitor extends AbstractModelVisitor {
 6    @Override
 7    public Object visitClass(Class theClass) {
 8        System.out.println("Class name = " + theClass.getName());
 9        return null;
10    }
11    
12    @Override
13    public Object visitPackage(Package thePackage) {
14        System.out.println("Package name = " + thePackage.getName());
15        return null;
16    }
17}

Note that instead of directly implementing the MVisitor interface, we prefer to extend the AbstractModelVisitor abstract class. The AbstractModelVisitor abstract class implements the IModelVisitor interface, providing an “empty” implementation of each visit method.

If we had chosen to directly implement the IModelVisitor, we would have had to implement many visit methods that are of no interest for us. Inheriting from the AbstractModelVisitor convenience relieves us of this burden and lets us concentrate on those methods which really provide a useful service. Thus, in the example, only visitClass() and visitPackage() are re-defined.

Now, apply the visitor on the owned elements of the model root package.

 1// The following loop applies our own visitor implementation 
 2// to each owned elements of the root package
 3Package root = ... // some code to get the model root package
 4List<ModelTree> elements = root.getOwnedElement();
 5
 6// Instantiate our visitor implementation
 7MVisitor myVisitor = new PrintClassAndPackageVisitor();
 8
 9// Loop on elements, 'applying' the visitor on each of them
10for (ModelTree e : elements) {
11    e.accept(myVisitor);
12}

Iterating associations and model modification

The list returned by accessors reflects the real content of the model, ie any modification to the list applies to the model.
Modifying such a list while iterating it, can lead to ConcurrentModificationException, in the best case…

It is then highly advised to make a copy of the returned list if you plan to carry out model modifications while iterating the list.

As a rule of thumb prefer this safe code:

 1Package root = ... // some code to get a package
 2
 3// get a copy of the ownedElements of root
 4List<ModelTree> elements = new ArrayList<>(root.getOwnedElement());
 5
 6// process the copied list
 7for (ModelTree e : elements) {
 8   ... 
 9   // do something that may modify root.getOwnedElement() content.
10   if ("delete me".equals(e.getName()))
11      // As a side effect, this 'delete' call removes 'e' from the owned elements of root. 
12      // It is safe here only as we are not iterating the getOwnedElements() list itself but a copy.
13      e.delete(); 
14}
15

to the following unsafe code:

 1Package root = ... // some code to get a package
 2
 3// process the getOwnedElement() list directly
 4for (ModelTree e : root.getOwnedElement()) {
 5   ... 
 6   // do something that may modify root.getOwnedElement() content.
 7   if ("delete me".equals(e.getName()))
 8      // As a side effect, this 'delete' call removes 'e' from the owned elements of root. 
 9      // It is NOT safe here as we are currently iterating the getOwnedElements() list itself while modifying it !
10  
11      e.delete();  // ConcurrentModificationException or worst on next iteration !!!
12}