#18 Model Optimization
This episode answers a Stackoverflow question, how to improve syntax-influenced meta-models using an example of a comma separated variable definition.
- Download:
- source code Project Files in Zip (113 KB)
- mp4 Full Size H.264 Video (34 MB)
- m4v Smaller H.264 Video (19.5 MB)
- webm Full Size VP8 Video (20.2 MB)
- ogv Full Size Theora Video (48.4 MB)
Links
Postprocessor: Modifing inferred ecore meta-model
- Using Xtend1: http://malubu.wordpress.com/2012/05/07/post-processing-vs-post-initialization/
- Using Xtend2: http://christiandietrich.wordpress.com/tag/postprocessor/
Collection of the eclipse icons
Original question on the stackoverflow.com
Grammar
MyDsl.xtext
grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals
generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"
Model:
methods += Method*;
Method:
"def" name = ID '{'
definitionBlocks += DefinitionBlock*
'}'
;
DefinitionBlock:
variableDefinitions += VariableDefinition
("," variableDefinitions += VariableDefinition)*
":"
type = ID
;
VariableDefinition:
name = ID
;
grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals generate myDsl "http://www.xtext.org/example/mydsl/MyDsl" Model: methods += Method*; Method: "def" name = ID '{' definitionBlocks += DefinitionBlock* '}' ; DefinitionBlock: variableDefinitions += VariableDefinition ("," variableDefinitions += VariableDefinition)* ":" type = ID ; VariableDefinition: name = ID ;
Method #1 - Extension methods
Generator
We use extension method type()
to simulate missing type
attribute on the VariableDefinition
model class:
MyDslGenerator.xtend
package org.xtext.example.mydsl.generator
import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.generator.IGenerator
import org.eclipse.xtext.generator.IFileSystemAccess
import org.xtext.example.mydsl.myDsl.Model
import org.xtext.example.mydsl.myDsl.VariableDefinition
import org.xtext.example.mydsl.myDsl.DefinitionBlock
class MyDslGenerator implements IGenerator {
override void doGenerate(Resource resource, IFileSystemAccess fsa) {
fsa.generateFile("variables.txt", generate(resource.contents.filter(typeof(Model))))
}
def generate(Iterable<Model> models) {'''
«FOR model : models»
«FOR method : model.methods»
Method: «method.name»
«FOR definitionBlock : method.definitionBlocks»
«FOR variableDefinition : definitionBlock.variableDefinitions»
- «variableDefinition.generate»
«ENDFOR»
«ENDFOR»
«ENDFOR»
«ENDFOR»
'''}
def generate(VariableDefinition variableDefinition) {'''
«variableDefinition.name» : «variableDefinition.type»
'''}
def type(VariableDefinition variableDefinition) {
(variableDefinition.eContainer as DefinitionBlock).type
}
}
package org.xtext.example.mydsl.generator import org.eclipse.emf.ecore.resource.Resource import org.eclipse.xtext.generator.IGenerator import org.eclipse.xtext.generator.IFileSystemAccess import org.xtext.example.mydsl.myDsl.Model import org.xtext.example.mydsl.myDsl.VariableDefinition import org.xtext.example.mydsl.myDsl.DefinitionBlock class MyDslGenerator implements IGenerator { override void doGenerate(Resource resource, IFileSystemAccess fsa) { fsa.generateFile("variables.txt", generate(resource.contents.filter(typeof(Model)))) } def generate(Iterable<Model> models) {''' «FOR model : models» «FOR method : model.methods» Method: «method.name» «FOR definitionBlock : method.definitionBlocks» «FOR variableDefinition : definitionBlock.variableDefinitions» - «variableDefinition.generate» «ENDFOR» «ENDFOR» «ENDFOR» «ENDFOR» '''} def generate(VariableDefinition variableDefinition) {''' «variableDefinition.name» : «variableDefinition.type» '''} def type(VariableDefinition variableDefinition) { (variableDefinition.eContainer as DefinitionBlock).type } }
Fixing Outline
MyDslLabelProvider.java
package org.xtext.example.mydsl.ui.labeling;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
import org.eclipse.xtext.ui.label.DefaultEObjectLabelProvider;
import org.xtext.example.mydsl.myDsl.DefinitionBlock;
import org.xtext.example.mydsl.myDsl.Method;
import org.xtext.example.mydsl.myDsl.VariableDefinition;
import org.xtext.example.mydsl.myDsl.DefinitionBlock;
import com.google.inject.Inject;
public class MyDslLabelProvider extends DefaultEObjectLabelProvider {
@Inject
public MyDslLabelProvider(AdapterFactoryLabelProvider delegate) {
super(delegate);
}
public String text(VariableDefinition variableDefinition) {
return variableDefinition.getName() + " : "
+ ((DefinitionBlock)variableDefinition.eContainer()).getType();
}
public String image(VariableDefinition variableDefinition) {
return "variable.gif";
}
public String text(Method method) {
return method.getName() + "{...}";
}
public String image(Method method) {
return "method.gif";
}
}
package org.xtext.example.mydsl.ui.labeling; import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider; import org.eclipse.xtext.ui.label.DefaultEObjectLabelProvider; import org.xtext.example.mydsl.myDsl.DefinitionBlock; import org.xtext.example.mydsl.myDsl.Method; import org.xtext.example.mydsl.myDsl.VariableDefinition; import org.xtext.example.mydsl.myDsl.DefinitionBlock; import com.google.inject.Inject; public class MyDslLabelProvider extends DefaultEObjectLabelProvider { @Inject public MyDslLabelProvider(AdapterFactoryLabelProvider delegate) { super(delegate); } public String text(VariableDefinition variableDefinition) { return variableDefinition.getName() + " : " + ((DefinitionBlock)variableDefinition.eContainer()).getType(); } public String image(VariableDefinition variableDefinition) { return "variable.gif"; } public String text(Method method) { return method.getName() + "{...}"; } public String image(Method method) { return "method.gif"; } }
MyDslOutlineTreeProvider.java
package org.xtext.example.mydsl.ui.outline;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.ui.editor.outline.IOutlineNode;
import org.eclipse.xtext.ui.editor.outline.impl.DefaultOutlineTreeProvider;
import org.eclipse.xtext.ui.editor.outline.impl.DocumentRootNode;
import org.xtext.example.mydsl.myDsl.DefinitionBlock;
import org.xtext.example.mydsl.myDsl.Method;
import org.xtext.example.mydsl.myDsl.Model;
import org.xtext.example.mydsl.myDsl.VariableDefinition;
public class MyDslOutlineTreeProvider extends DefaultOutlineTreeProvider {
protected void _createChildren(DocumentRootNode parentNode,
Model model) {
for (Method method : model.getMethods()) {
createNode(parentNode, method);
}
}
protected void _createChildren(IOutlineNode parentNode, Method method) {
for (DefinitionBlock definitionBlock : method.getDefinitionBlocks()) {
for (VariableDefinition variableDefinition : definitionBlock.getVariableDefinitions()) {
createNode(parentNode, variableDefinition);
}
}
}
}
package org.xtext.example.mydsl.ui.outline; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.ui.editor.outline.IOutlineNode; import org.eclipse.xtext.ui.editor.outline.impl.DefaultOutlineTreeProvider; import org.eclipse.xtext.ui.editor.outline.impl.DocumentRootNode; import org.xtext.example.mydsl.myDsl.DefinitionBlock; import org.xtext.example.mydsl.myDsl.Method; import org.xtext.example.mydsl.myDsl.Model; import org.xtext.example.mydsl.myDsl.VariableDefinition; public class MyDslOutlineTreeProvider extends DefaultOutlineTreeProvider { protected void _createChildren(DocumentRootNode parentNode, Model model) { for (Method method : model.getMethods()) { createNode(parentNode, method); } } protected void _createChildren(IOutlineNode parentNode, Method method) { for (DefinitionBlock definitionBlock : method.getDefinitionBlocks()) { for (VariableDefinition variableDefinition : definitionBlock.getVariableDefinitions()) { createNode(parentNode, variableDefinition); } } } }
Test .mydsl file
test.mydsl
def test1 {
a : String
b : String
}
def test2 {
a, b, c : long
}
def test1 { a : String b : String } def test2 { a, b, c : long }
Generator output
variables.txt
Method: test1
- a : String
- b : String
Method: test2
- a : long
- b : long
- c : long
Method: test1 - a : String - b : String Method: test2 - a : long - b : long - c : long
Method #2 - Improve meta-model & model on the fly
Post-process meta-model
We write Xtend post-process class to modify inferred meta-model and add a new type
attribute to the VariableDefinition
class.
See this blog for more details: http://christiandietrich.wordpress.com/tag/postprocessor/
MyXtext2EcorePostProcessor.xtend
package org.xtext.example.mydsl.postprocessor
import org.eclipse.xtext.xtext.ecoreInference.IXtext2EcorePostProcessor
import org.eclipse.xtext.GeneratedMetamodel
import org.eclipse.emf.ecore.EPackage
import org.eclipse.emf.ecore.EClass
import org.xtext.example.mydsl.myDsl.VariableDefinition
import org.eclipse.emf.ecore.EcoreFactory
import org.eclipse.emf.ecore.EcorePackage
class MyXtext2EcorePostProcessor implements IXtext2EcorePostProcessor {
override process(GeneratedMetamodel metamodel) {
metamodel.EPackage.process
}
def process(EPackage p) {
for (clazz : p.EClassifiers.filter(typeof(EClass))) {
if (clazz.name == typeof(VariableDefinition).simpleName) {
val typeAttribute = EcoreFactory::eINSTANCE.createEAttribute
typeAttribute.name = "type"
typeAttribute.EType = EcorePackage::eINSTANCE.EString
clazz.EStructuralFeatures += typeAttribute
}
}
}
}
package org.xtext.example.mydsl.postprocessor import org.eclipse.xtext.xtext.ecoreInference.IXtext2EcorePostProcessor import org.eclipse.xtext.GeneratedMetamodel import org.eclipse.emf.ecore.EPackage import org.eclipse.emf.ecore.EClass import org.xtext.example.mydsl.myDsl.VariableDefinition import org.eclipse.emf.ecore.EcoreFactory import org.eclipse.emf.ecore.EcorePackage class MyXtext2EcorePostProcessor implements IXtext2EcorePostProcessor { override process(GeneratedMetamodel metamodel) { metamodel.EPackage.process } def process(EPackage p) { for (clazz : p.EClassifiers.filter(typeof(EClass))) { if (clazz.name == typeof(VariableDefinition).simpleName) { val typeAttribute = EcoreFactory::eINSTANCE.createEAttribute typeAttribute.name = "type" typeAttribute.EType = EcorePackage::eINSTANCE.EString clazz.EStructuralFeatures += typeAttribute } } } }
Bind MyXtext2EcorePostProcessor
extending the Generator used by the Xtext mwe2-workflow.
ExtendedGenerator.java
package org.xtext.example.mydsl.postprocessor;
import org.eclipse.xtext.XtextRuntimeModule;
import org.eclipse.xtext.XtextStandaloneSetup;
import org.eclipse.xtext.generator.Generator;
import org.eclipse.xtext.xtext.ecoreInference.IXtext2EcorePostProcessor;
import com.google.inject.Guice;
import com.google.inject.Injector;
@SuppressWarnings("restriction")
public class ExtendedGenerator extends Generator {
public ExtendedGenerator() {
new XtextStandaloneSetup() {
@Override
public Injector createInjector() {
return Guice.createInjector(new XtextRuntimeModule() {
@Override
public Class<? extends IXtext2EcorePostProcessor> bindIXtext2EcorePostProcessor() {
return MyXtext2EcorePostProcessor.class;
}
});
}
}.createInjectorAndDoEMFRegistration();
}
}
package org.xtext.example.mydsl.postprocessor; import org.eclipse.xtext.XtextRuntimeModule; import org.eclipse.xtext.XtextStandaloneSetup; import org.eclipse.xtext.generator.Generator; import org.eclipse.xtext.xtext.ecoreInference.IXtext2EcorePostProcessor; import com.google.inject.Guice; import com.google.inject.Injector; @SuppressWarnings("restriction") public class ExtendedGenerator extends Generator { public ExtendedGenerator() { new XtextStandaloneSetup() { @Override public Injector createInjector() { return Guice.createInjector(new XtextRuntimeModule() { @Override public Class<? extends IXtext2EcorePostProcessor> bindIXtext2EcorePostProcessor() { return MyXtext2EcorePostProcessor.class; } }); } }.createInjectorAndDoEMFRegistration(); } }
Configure new ExtendedGenerator
class to be used by the Xtend mwe2-workflow.
GenerateMyDsl.mwe2
...
Workflow {
...
bean = StandaloneSetup {
...
component = postprocessor.ExtendedGenerator { // Set ExtendedGenerator!
...
}
...
}
...
}
...
... Workflow { ... bean = StandaloneSetup { ... component = postprocessor.ExtendedGenerator { // Set ExtendedGenerator! ... } ... } ... } ...
Fill new type
attribute with data
MyDerivedStateComputer.xtend
package org.xtext.example.mydsl.postprocessor
import org.eclipse.xtext.resource.IDerivedStateComputer
import org.eclipse.xtext.resource.DerivedStateAwareResource
import org.xtext.example.mydsl.myDsl.VariableDefinition
import org.xtext.example.mydsl.myDsl.DefinitionBlock
class MyDerivedStateComputer implements IDerivedStateComputer {
override discardDerivedState(DerivedStateAwareResource resource) {
resource.allContents.filter(typeof(VariableDefinition)).forEach [
type = null
]
}
override installDerivedState(DerivedStateAwareResource resource, boolean preLinkingPhase) {
resource.allContents.filter(typeof(VariableDefinition)).forEach [
type = (eContainer as DefinitionBlock).type
]
}
}
package org.xtext.example.mydsl.postprocessor import org.eclipse.xtext.resource.IDerivedStateComputer import org.eclipse.xtext.resource.DerivedStateAwareResource import org.xtext.example.mydsl.myDsl.VariableDefinition import org.xtext.example.mydsl.myDsl.DefinitionBlock class MyDerivedStateComputer implements IDerivedStateComputer { override discardDerivedState(DerivedStateAwareResource resource) { resource.allContents.filter(typeof(VariableDefinition)).forEach [ type = null ] } override installDerivedState(DerivedStateAwareResource resource, boolean preLinkingPhase) { resource.allContents.filter(typeof(VariableDefinition)).forEach [ type = (eContainer as DefinitionBlock).type ] } }
Bind MyDerivedStateComputer
. The second and third bind-methods are necessary for non-Xbase projects.
MyDslRuntimeModule.java
public class MyDslRuntimeModule extends AbstractMyDslRuntimeModule {
public Class<? extends IDerivedStateComputer> bindIDerivedStateComputer() {
return MyDerivedStateComputer.class;
}
// Not needed for Xbase-projects
@Override
public Class<? extends XtextResource> bindXtextResource() {
return DerivedStateAwareResource.class;
}
// Not needed for Xbase-projects
public Class<? extends IResourceDescription.Manager> bindIResourceDescriptionManager() {
return DerivedStateAwareResourceDescriptionManager.class;
}
}
public class MyDslRuntimeModule extends AbstractMyDslRuntimeModule { public Class<? extends IDerivedStateComputer> bindIDerivedStateComputer() { return MyDerivedStateComputer.class; } // Not needed for Xbase-projects @Override public Class<? extends XtextResource> bindXtextResource() { return DerivedStateAwareResource.class; } // Not needed for Xbase-projects public Class<? extends IResourceDescription.Manager> bindIResourceDescriptionManager() { return DerivedStateAwareResourceDescriptionManager.class; } }
Optimize generator
The generator doesn't need the type
extension method, since the type can be accessed directly.
MyDslGenerator.xtend
class MyDslGenerator implements IGenerator {
...
def generate(VariableDefinition variableDefinition) {'''
«variableDefinition.name» : «variableDefinition.type»
'''}
// No type(...) extension method here!
}
class MyDslGenerator implements IGenerator { ... def generate(VariableDefinition variableDefinition) {''' «variableDefinition.name» : «variableDefinition.type» '''} // No type(...) extension method here! }
Label provider can also be improved.
MyDslLabelProvider.java
public class MyDslLabelProvider extends DefaultEObjectLabelProvider {
...
public String text(VariableDefinition variableDefinition) {
return variableDefinition.getName() + " : " + variableDefinition.getType();
}
...
}
public class MyDslLabelProvider extends DefaultEObjectLabelProvider { ... public String text(VariableDefinition variableDefinition) { return variableDefinition.getName() + " : " + variableDefinition.getType(); } ... }