Post processing vs Post initialization
7 May 2012 2 Comments
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
. 😀
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
- To attach data leaving the metamodel alone, you can “wrap” an adapter around each
EObject
. You can read Knut’s post for more details. - If you want to use Xtend2 instead of Xtend1, read Christian’s post.
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!
Post processing is not supported anymore !
https://bugs.eclipse.org/bugs/show_bug.cgi?id=483209