Several projects combine textual DSLs with Xtext with UML models and there have been some postings (by Sven, Holger and others) on how to achieve this with earlier versions of Xtext. This is a short sketch on how to do it with the latest version. If you do not now what this is about, have a look at the screenshot on the left: The Xtext grammar references a UML model with an Activity and two classes. This is fully integrated you see that content assist suggest the elements from the UML model.
The biggest difference is the need for IResourceProviders for UML. Xtext comes with a IResourceProvider for EMF models, and this can be used for writing an UML version. I added three new files to the Xtext project: UmlResourceDescriptionManager.java and UMLResourceServiceProviderImpl.java UMLResourceDescription.java . The former two are very simple: UMLResourceServiceProviderImpl.java only binds the .uml extension.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
<pre lang="java5" line="1" escaped="true">/******************************************************************************* * Copyright (c) 2009 itemis AG (http://www.itemis.eu) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package de.itemis.graf; import org.eclipse.emf.common.util.URI; import org.eclipse.xtext.parser.IEncodingProvider; import org.eclipse.xtext.resource.IContainer; import org.eclipse.xtext.resource.IResourceServiceProvider; import org.eclipse.xtext.validation.IResourceValidator; import org.eclipse.xtext.ecore.EcoreResourceDescriptionManager;; /** * @author Andreas Graf - Initial contribution and API */ public class UmlResourceServiceProviderImpl implements IResourceServiceProvider { public IContainer.Manager getContainerManager() { return null; } private UmlResourceDescriptionManager umlResourceDescriptionManager = new UmlResourceDescriptionManager(); public org.eclipse.xtext.resource.IResourceDescription.Manager getResourceDescriptionManager() { return umlResourceDescriptionManager; } public IResourceValidator getResourceValidator() { return IResourceValidator.NULL; } public boolean canHandle(URI uri) { return "uml".equals(uri.fileExtension()); } // private IEncodingProvider encodingProvider = new XMLEncodingProvider(); public IEncodingProvider getEncodingProvider() { // return encodingProvider; return null; } } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
/******************************************************************************* * Copyright (c) 2009 itemis AG (http://www.itemis.eu) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package de.itemis.graf; import java.util.Collection; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.resource.IResourceDescription; import org.eclipse.xtext.resource.IResourceDescriptions; import org.eclipse.xtext.resource.IResourceDescription.Delta; import org.eclipse.xtext.resource.IResourceDescription.Manager; /** * @author Andreas Graf - Initial contribution and API */ public class UmlResourceDescriptionManager implements Manager { public IResourceDescription getResourceDescription(Resource resource) { return new UmlResourceDescription(resource); } public boolean isAffected(Delta delta, IResourceDescription candidate) throws IllegalArgumentException { return false; } public boolean isAffected(Collection<Delta> deltas, IResourceDescription candidate, IResourceDescriptions descriptions) throws IllegalArgumentException { return false; } } |
This is a very basic implemenation of a UML Resource Description: It returns all "NamedElements" from the UML model. The functionality could be extended to different purposes, but this does the job right now.
|
1 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
/******************************************************************************* * Copyright (c) 2009 itemis AG (http://www.itemis.eu) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package de.itemis.graf; import java.util.Iterator; import java.util.List; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.ENamedElement; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EcoreSwitch; import org.eclipse.xtext.resource.EObjectDescription; import org.eclipse.xtext.resource.IEObjectDescription; import org.eclipse.xtext.resource.IReferenceDescription; import org.eclipse.xtext.resource.impl.AbstractResourceDescription; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import org.eclipse.uml2.uml.*; /** * @author Andreas Graf - Initial contribution and API */ public class UmlResourceDescription extends AbstractResourceDescription { /** * @author Andreas Graf - Initial contribution and API * * */ private static final class Switch extends EcoreSwitch<IEObjectDescription> { @Override public IEObjectDescription caseENamedElement(ENamedElement object) { return new EObjectDescription(object.getName(), object, null); } } private Resource resource; private URI uri; public UmlResourceDescription(Resource resource) { this.resource = resource; this.uri = getNormalizedURI(resource); } @Override protected List<IEObjectDescription> computeExportedObjects() { Iterator<EObject> contents = resource.getAllContents(); List<IEObjectDescription> result = Lists.newArrayList(); while (contents.hasNext()) { EObject eObject = contents.next(); IEObjectDescription desc = null; if(eObject instanceof org.eclipse.uml2.uml.NamedElement) { org.eclipse.uml2.uml.NamedElement ne = (org.eclipse.uml2.uml.NamedElement) eObject; // We have to check that the name is non-null, otherwise some code calling us will fail if(ne.getName()!=null) { desc = new EObjectDescription(ne.getName(), eObject, null); } } if (desc!=null) result.add(desc); } return result; } public Iterable<String> getImportedNames() { return Iterables.emptyIterable(); } public Iterable<IReferenceDescription> getReferenceDescriptions() { return Iterables.emptyIterable(); } public URI getURI() { return uri; } } |
The grammar of our languages is pretty ssimple. Nothing new compared to earlier releases:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
grammar de.itemis.graf.Xuml with org.eclipse.xtext.common.Terminals generate xuml "http://www.itemis.de/graf/Xuml" import 'http://www.eclipse.org/uml2/3.0.0/UML' as umlMM // name attribute is extremely important. //Elements without a name attribute will not be exported! // XModel : name=STRING (imports+=Import)* (rules+=Rule)* (names+=Name)*; Import : 'import' importURI=STRING; Rule: 'references' umlClass=[umlMM::Class]; Name: 'name' name=STRING; |
As in the other tutorials, you should copy the relevant-meta-models into your project. However, there's a lot of changes in the work flow. Have a look at this work flow to see how to adapt it to UML:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
module de.itemis.graf.Xuml import org.eclipse.emf.mwe.utils.* import org.eclipse.xtext.generator.* import org.eclipse.xtext.ui.generator.* var grammarURI = "classpath:/de/itemis/graf/Xuml.xtext" var file.extensions = "xuml" var projectName = "de.itemis.graf.xuml" var runtimeProject = "../${projectName}" Workflow { bean = StandaloneSetup { platformUri = "{runtimeProject}/.." // UML:important registerGeneratedEPackage = "org.eclipse.uml2.uml.UMLPackage" registerGeneratedEPackage = "org.eclipse.uml2.codegen.ecore.genmodel.GenModelPackage" uriMap = { from = "platform:/plugin/org.eclipse.emf.ecore/model/Ecore.ecore" to = "platform:/resource/de.itemis.graf.xuml/model/Ecore.ecore" } uriMap = { from = "platform:/plugin/org.eclipse.emf.ecore/model/Ecore.genmodel" to = "platform:/resource/de.itemis.graf.xuml/model/Ecore.genmodel" } } component = DirectoryCleaner { directory = "${runtimeProject}/src-gen" } component = DirectoryCleaner { directory = "${runtimeProject}.ui/src-gen" } component = Generator { pathRtProject = runtimeProject pathUiProject = "${runtimeProject}.ui" projectNameRt = projectName projectNameUi = "${projectName}.ui" language = { uri = grammarURI fileExtensions = file.extensions // Java API to access grammar elements (required by several other fragments) fragment = grammarAccess.GrammarAccessFragment {} // generates Java API for the generated EPackages fragment = ecore.EcoreGeneratorFragment { referencedGenModels = "platform:/resource/de.itemis.graf.xuml/model/UML.genmodel" // referencedGenModels = "uri to genmodel, uri to next genmodel" } // the serialization component fragment = parseTreeConstructor.ParseTreeConstructorFragment {} // a custom ResourceFactory for use with EMF fragment = resourceFactory.ResourceFactoryFragment { fileExtensions = file.extensions } // The antlr parser generator fragment. fragment = parser.antlr.XtextAntlrGeneratorFragment { // options = { // backtrack = true // } } // java-based API for validation fragment = validation.JavaValidatorFragment { // UML: Important // composedCheck = "org.eclipse.xtext.validation.ImportUriValidator" // composedCheck = "org.eclipse.xtext.validation.NamesAreUniqueValidator" registerForImportedPackages = true } // scoping and exporting API fragment = scoping.ImportURIScopingFragment {} fragment = exporting.SimpleNamesFragment {} // scoping and exporting API // fragment = scoping.ImportNamespacesScopingFragment {} // fragment = exporting.QualifiedNamesFragment {} // fragment = builder.BuilderIntegrationFragment {} // formatter API fragment = formatting.FormatterFragment {} // labeling API fragment = labeling.LabelProviderFragment {} // outline API fragment = outline.TransformerFragment {} fragment = outline.OutlineNodeAdapterFactoryFragment {} fragment = outline.QuickOutlineFragment {} // quickfix API fragment = quickfix.QuickfixProviderFragment {} // content assist API fragment = contentAssist.JavaBasedContentAssistFragment {} // generates a more lightweight Antlr parser and lexer tailored for content assist fragment = parser.antlr.XtextAntlrUiGeneratorFragment {} // project wizard (optional) // fragment = projectWizard.SimpleProjectWizardFragment { // generatorProjectName = "${projectName}.generator" // modelFileExtension = file.extensions // } } } } |
In the Manifest, the Resource Provider has to be made known through an extension. If you add org.eclipse.uml2.codegen.ecore, org.eclipse.uml2.uml and org.eclipse.xtext.ecore you are ready to go and create and run a grammar references to UML. However, if you want to also use code generation, this is a good time to modify the StandaloneSetup.java class:
package de.itemis.graf;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import com.google.inject.Injector; /** * Initialization support for running Xtext languages * without equinox extension registry */ public class XumlStandaloneSetup extends XumlStandaloneSetupGenerated{ public static void doSetup() { new XumlStandaloneSetup().createInjectorAndDoEMFRegistration(); } // Change AG @Override public void register(Injector injector) { super.register(injector); org.eclipse.xtext.resource.IResourceServiceProvider serviceProvider = injector.getInstance(de.itemis.graf.UmlResourceServiceProviderImpl.class); org.eclipse.xtext.resource.IResourceServiceProvider.Registry.INSTANCE.getExtensionToFactoryMap().put("uml", serviceProvider); } } |
At this point, you can run the editor, create a UML model and reference the model elements from within your Xtext model.
On top, if you want to process this with Xpand, here is the work flow:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
module workflow.XumlGenerator import org.eclipse.emf.mwe.utils.* import org.eclipse.xtend.typesystem.uml2.UML2MetaModel import org.eclipse.xtend.typesystem.emf.XmiReader import org.eclipse.xtend.typesystem.uml2.profile.ProfilingExtensions.XmiReader var targetDir = "src-gen" var fileEncoding = "Cp1252" var modelPath = "src/model" Workflow { bean = org.eclipse.emf.mwe.utils.StandaloneSetup { platformUri=".." } bean = org.eclipse.xtend.typesystem.uml2.Setup { standardUML2Setup = true } bean = org.eclipse.xtend.typesystem.uml2.UML2MetaModel : umlMM { } bean = org.eclipse.xtend.typesystem.emf.EmfRegistryMetaModel : emfMM { } component = org.eclipse.xtext.mwe.Reader { // lookup all resources on the classpath useJavaClassPath = false // or define search scope explicitly path = modelPath // this class will be generated by the xtext generator register = de.itemis.graf.XumlStandaloneSetup {} load = { slot = "greetings" type = "XModel" } } component = org.eclipse.emf.mwe.utils.Reader { uri = "platform:/resource/de.itemis.graf.xuml.generator/src/othermodel/My.uml" modelSlot = "umlmodel" } component = org.eclipse.xpand2.Generator { metaModel = umlMM metaModel = emfMM expand = "templates::Template::main FOREACH greetings" outlet = { path = targetDir } fileEncoding = fileEncoding } } |
And here an example of a template:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
«IMPORT de::itemis::graf::xuml» «EXTENSION templates::Extensions» «DEFINE main FOR xuml::XModel-» «FILE "dsl.txt"-» «FOREACH this.rules.umlClass AS class»!«class.name»«ENDFOREACH» This is an example of a generated file. The input element was "Hello!" All greetings in the same file: «ENDFILE-» «ENDDEFINE» «DEFINE main FOR uml::Class-» «FILE name+"uml.txt"-» This is an example of a generated file. The input element was "Hello!" All greetings in the same file: «ENDFILE-» «ENDDEFINE» |
Enjoy combining your DSLs with UML.
|
1 |
Hi Andreas,
thanks for your posting, helped a lot!
I’ve got one problem with usage of an external ecore model in the context of xpand file generation.
In standalone mode (running workflow) everything is fine. But within Eclipse the xpand template shows errors. It says:
Couldn’t find xuml::XModel
at the DEFINE statement.
Is maybe the additional ecore model not registered correctly in eclipse? Anything else to do here?
Thanks
Fabian
Sometimes it just helps writing down the problem…
I missed to activate the EMF metamodel lookup in the xtend/xpand project configuration. Now working.
Hi Andreas!
Just had the same problem, but was not aware of your article. I solved it nearly exactly the same, and just after finishing it I found this article. Damn, could have saved me some time, but at least now I really understood it
Thanks for your great post!
~Karsten
Thanks for your great post!
Without this information, I will switch to another tool
Hi!
Great article!
However I have a follow-up question:
Is it possible to use full qualified names for the imported UML-instances?
I am importing a series of packages containing classes with identical names, which obviously does not work.
I already tried changing the UMLResourceDescription to use getQualifiedName instead of getName – with the result, that no UML-instances where found anymore!
Thanks in advance
Thomas
Dear Thomas,
changing the UMLResourceDescription is the right track, but not complete. The grammar just takes an “ID” as a reference as it is declared now (umlClass=[umlMM::Class]). You could try adding something like:
QualifiedName:
ID (‘.’ ID)*; // you might use ‘:’ instead of ‘.’, depending on what getQualifiedName retursn.
to the grammar and then change umlClass=[umlMM::Class] to umlClass=[umlMM::Class|QualifiedName]
Good Luck!
Andreas
Hi Andreas
It’s really great post, because I’ve spent so much time searching the same !
I followed the same procedure you explained and at the beginning there were no problems at all. But when I started to use the editor I couldn’t get a list of suggestions of NamedElements. When I typed an element from the My.uml model (e.g. references foo), I got error like “Couldn’t resolve reference to Class ‘foo’ “.
Do you know what might be the problem ?
Thanks again for the post
Nikola
Hi Andreas
I solved the problem, actually I forgot to enable and disable some fragments of the workflow file. Now it’s working as intended by the post.
Thanks a lot ! This post saved my life
Best
Nikola