Post processing vs Post initialization

Sometimes it is useful to be able to modify an EObject right after its creation, in runtime.
With “modify” we can mean several things: change the metamodel of the model (adding for example a feature or an operation) or change the values of the model (setting the value of a given feature) or both.
For clarification I shall call the second case “post initialization” to differentiate it with the post processing step.

Post processing

Let’s consider the Xtext’s example org.xtext.example.mydsl.
Imagine that we want to add a feature to each model element named comment of type EString, without any modification to the .xtext file, how can we do that?
Xtext lets you customize the post processing step, which is what we exactly need.
To activate this process, we must ensure that Xtext actually generate the metamodel implementation for us (check that fragment = ecore.EcoreGeneratorFragment in the Generator component of Generate[MyLanguage].mwe2 is present and not commented) and so that we don’t use an already existing ECore model.
As the documentation says, we use Xtend (version 1) for the customization, creating a [MyLanguage]PostProcessor.ext in the same folder of the grammar file:

Edit the file so it looks like this:

process(xtext::GeneratedMetamodel this):
   process(ePackage)
;

process(ecore::EPackage this):
   eClassifiers.process()
;

process(ecore::EClass this):
   injectStructuralFeature(this,
   	                       createAttribute("comment",
org::eclipse::emf::ecore::EcorePackage::eINSTANCE.getEClassifier("EString")
   	                       ))
;

/**
 * Add feature only if is not already existing.
 */
injectStructuralFeature(ecore::EClass this, ecore::EStructuralFeature struct):
   if(this.getEStructuralFeature(struct.name)) == null
      then
   this.eStructuralFeatures.add(struct)->
   this
;

createAttribute(String name, ecore::EClassifier type):
   let attr = new ecore::EAttribute :
      attr.setName(name)->
      attr.setEType(type)->
      attr.setUpperBound(1)->
      attr.setLowerBound(0)-> //The feature is not mandatory.
      attr
;

Run the MWE2 Workflow and you should see that in both GreetingImpl.java and in ModelImpl.java of the src-gen folder there is a feature with the desired name and type. Setter and getter methods are also created.
If you want to add the feature only to certain EObject, for example only to Greeting, you can use typeSelect() and switch/case/default:

...
process(ecore::EClass this):
   switch(name)
      {
      case "Greeting":
         injectStructuralFeature(this,
   	                             createAttribute("comment",
org::eclipse::emf::ecore::EcorePackage::eINSTANCE.getEClassifier("EString")
   	                             ))
   	  default:
   	     {
   	     //Do nothing.
   	     }
   	  }
;
...

Post initialization

Now that we have our feature, what about initializing it with some value?
Actually there are several ways to do this, one of them is to check where the runtime run through each EObject, as close as possible to their instantiation. Reading through the documentation, the linking phase seems a good candidate.
In the DefaultRuntimeModule we can see that the default linker is the LazyLinker. In that class the method that actually does the linking is doLinkModel() which calls installProxies() for every EObject. So installProxies() seems a good candidate for an override and, lucky us, is protected. :-D
So what we have to do is to extend a LazyLinker:
MyDslLinker.java

public class MyDslLinker extends LazyLinker
   {
   @Override
   protected void installProxies(EObject obj,
                                 IDiagnosticProducer producer,
                                 Multimap<EStructuralFeature.Setting, INode> settingsToLink)
      {
      //Remember to call the super implementation.
      super.installProxies(obj, producer, settingsToLink);
      if(obj.eClass().getEStructuralFeature("comment")!=null)
         {
         //Of course, you can use NodeModelUtils here.
         obj.eSet(obj.eClass().getEStructuralFeature("comment"), "DEFAULT COMMENT");
         }
      }
   }

And bind it in the runtime:

public class MyDslRuntimeModule extends org.xtext.example.mydsl.AbstractMyDslRuntimeModule
   {
   @Override
   public Class<? extends ILinker> bindILinker()
      {
      return MyDslLinker.class;
      }
   }

Now, launch the plugins, compile a file and open it with the Sample Ecore Model Editor. You should see the new feature, initialized with a value:

Two final notes

  1. To attach data leaving the metamodel alone, you can “wrap” an adapter around each EObject. You can read Knut’s post for more details.
  2. If you want to use Xtend2 instead of Xtend1, read Christian’s post.
About these ads

About Luong
http://www.linkedin.com/in/manhluong

One Response to Post processing vs Post initialization

  1. Dominik says:

    The “fragment = ecore.EcoreGeneratorFragment” is deprecated in the meantime (http://download.eclipse.org/modeling/tmf/xtext/javadoc/2.4/org/eclipse/xtext/generator/ecore/EcoreGeneratorFragment.html).

    Use fragment = ecore.EMFGeneratorFragment!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.