Navigating Generated Artifacts

One of the ways in which Mint enhances model-driven software development experience is by linking various code generator model elements directly to the artifacts that they either cause to be generated or are in some way related to. For instance, Mint enhances the GenModel editor by adding context menus that allow the user to open the various interfaces, classes, and methods that may be generated from a particular GenClass. By default, only the core GenModel is supported; however, the enhancement mechanisms used by Mint are generic -- support for other code generator models, as well as custom extension adapters for GenModel itself may be added by third-party developers. To that end, Mint provides an API and an extensible framework for mapping EMF-based generator models to the artifacts they produce.

Design Overview

EMF-based code generators can produce a variety of artifacts, Java and non-Java alike, from arbitrary Ecore models. Each code generator typically uses an intermediary model, such as the GenModel, to capture code generation specific information. The generator then uses this model along with the underlying Ecore model to produce the artifacts.

Exactly which Java elements and/or other artifacts are produced from any particular generator model element is both generator and model specific. In fact, this mapping is driven by the values in the model and it changes with the values. Because of this dynamic nature, Mint uses the same "Item Provider" pattern as the EMF Edit framework to implement the mapping. Specifically, Mint utilizes IItemJavaElementDescriptors, which are essentially stateless adapters for generator model elements (the JavaElement part of the name is due to historical reasons). Each descriptor represents a particular model-to-artifact mapping and provides methods that expose various aspects of this mapping.

The list of descriptors applicable to a particular model element can be retrieved from an IItemJavaElementSource. The source is in turn obtained from the appropriate adapter factory, which must be registered as an Item Provider Adapter Factory using the org.eclipse.emf.edit.itemProviderAdapterFactories extension point.

Registering the Adapter Factory

As an example, let's examine Mint's own adapter factory registration for the GenModel:

   <extension
         point="org.eclipse.emf.edit.itemProviderAdapterFactories">
      <factory
            class="org.eclipse.emf.mint.internal.genmodel.GenModelItemProviderAdapterFactory"
            supportedTypes="org.eclipse.emf.mint.IItemJavaElementSource"
            uri="http://www.eclipse.org/emf/2002/GenModel">
      </factory>
   </extension>
   

The adapter factory implementation must support the IItemJavaElementSource adapter. I.e., isFactoryForType(IItemJavaElementSource.class) must return true. Furthermore, the adapter factory, as well as the individual adapter implementations may want to implement org.eclipse.emf.edit.provider.IChangeNotifier to allow clients to be notified of relevant changes.

Note that because adapter factories are registered against the model's URI, there may be at most one such adapter factory for any given model.

Obtaining IItemJavaElementSource

The easiest way to get the IItemJavaElementSource for a given generator model element is to use a ComposedAdapterFactory that uses the default adapter factory registry. E.g.,

   Object genModelElement = ...
   ComposedAdapterFactory adapterFactory = new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
   Object adapter = adapterFactory.adapt(genModelElement, IItemJavaElementSource.class);
   if (adapter instanceof IItemJavaElementSource) {
       IItemJavaElementSource source = (IItemJavaElementSource) adapter;
       ...
   }
   

Alternatively, specific adapter factory implementations can be used (if they can be publicly instantiated), or a model-specific instance can be retrieved from the global registry. E.g.,

   EPackage ePackage = EPackage.Registry.INSTANCE.getEPackage("http://www.eclipse.org/emf/2002/GenModel");
   ArrayList<Object> types = new ArrayList<Object>();
   types.add(ePackage);
   types.add(IItemJavaElementSource.class);
   ComposedAdapterFactory.Descriptor descriptor = ComposedAdapterFactory.Descriptor.Registry.INSTANCE.getDescriptor(types);
   AdapterFactory adapterFactory = descriptor.createAdapterFactory();
   

Working with Descriptors

Once you've obtained the IItemJavaElementSource for your genmodel object, you can retrieve the list of descriptors from it. A descriptor can be one of three kinds as indicated by its getKind(Object) method: 1). JAVA_ELEMENT, 2). JAVA_TYPE_REFERENCE, or 3). NON_JAVA_RESOURCE. Depending on the kind, different methods are applicable. For example:

   IItemJavaElementSource source = ...
   List<IItemJavaElementDescriptor> descriptors = source.getJavaElementDescriptors(genModelElement);
   for (IItemJavaElementDescriptor descriptor : descriptors) {
       switch (descriptor.getKind(genModelElement)) {
       case JAVA_ELEMENT:
           IJavaElement javaElement = descriptor.getJavaElement(genModelElement);
           // note the javaElement may or may not actually exist!
           ...
           break;
       case JAVA_TYPE_REFERENCE:
           IJavaTypeReference reference = descriptor.getJavaTypeReference(genModelElement);
           IJavaProject context = reference.getContext();
           String typeName = reference.getTypeName();
           try {
               // note there are other ways to find the type by name, some of which are long-running operations
               IJavaElement javaElement = context.findType(typeName);
               ...
           } catch (JavaModelException e) {
               ...
           }
           
           ...
           break;
       case NON_JAVA_RESOURCE:
           Object obj = descriptor.getNonJavaElement(genModelElement);
           if (!(obj instanceof IResource) && obj instanceof IAdaptable)
               obj = (IResource) ((IAdaptable) obj).getAdapter(IResource.class);
           
           // note the object may not be an IResource, and if it is, it may or may not exist
           ...
           break;          
       }
   }
   
   ...
   

Additional Extensibility

Some generators, such as the GenModel generator provided by EMF, are further extensible through adapters and/or templates. That is, they can be extended to generate additional artifacts. In such cases, it is necessary to also extend the IItemJavaElementSource to provide additional descriptors.

The mechanism used to support this kind of extensibility is generator-specific. Mint provides an extension point, org.eclipse.emf.mint.itemJavaElementSourceContributors, through which third-party providers can contribute additional IItemJavaElementDescriptors. However, the IItemJavaElementSource implementation must be developed to use this extension point and aggregate all such contributions. Mint's own adapter factory for EMF's GenModel generator uses this extension point and can serve as an example.

Examples

EMF provides an example of how to contribute a custom GenModel generator adapter to generate model validators. Mint provides a complementary example showing how to contribute additional IItemJavaElementDescriptors to cover artifacts generated by the example generator adapter.

An archive with the example can be obtained from Mint's downloads page.


Copyright (c) 2010 Ecliptical Software Inc. and others. All rights reserved.