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 |
