From 112636ad028f5c3d42f101e1a87f058af65de497 Mon Sep 17 00:00:00 2001 From: jkohnlein Date: Thu, 18 Sep 2008 15:09:56 +0000 Subject: [PATCH] First shot at xtext to ecore in Java --- .../src/org/eclipse/xtext/util/Strings.java | 98 +++---- plugins/org.eclipse.xtext/.classpath | 2 +- .../org.eclipse.xtext/META-INF/MANIFEST.MF | 1 + .../src/org/eclipse/xtext/GrammarUtil.java | 17 +- .../xtext/resource/metamodel/EClassInfo.java | 43 ++++ .../xtext/resource/metamodel/EClassInfos.java | 52 ++++ .../metamodel/TransformationException.java | 36 +++ .../metamodel/Xtext2EcoreTransformer.java | 239 ++++++++++++++++++ .../metamodel/XtextMetamodelResource.java | 31 +++ .../Xtext2EcoreTransformerTests.java | 45 ++++ 10 files changed, 512 insertions(+), 52 deletions(-) create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/EClassInfo.java create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/EClassInfos.java create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/TransformationException.java create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/Xtext2EcoreTransformer.java create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/XtextMetamodelResource.java create mode 100644 tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/resource/metamodel/Xtext2EcoreTransformerTests.java diff --git a/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/Strings.java b/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/Strings.java index 5d0c2c89e..1b2e29697 100644 --- a/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/Strings.java +++ b/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/Strings.java @@ -16,55 +16,63 @@ import java.util.List; */ public class Strings { - public static String notNull(Object o) { - if (o == null) { - return "null"; - } - String string = o.toString(); - if (string == null) { - return "null"; - } - return string; - } + public static String notNull(Object o) { + if (o == null) { + return "null"; + } + String string = o.toString(); + if (string == null) { + return "null"; + } + return string; + } - public static String concat(String separator, List list) { - return concat(separator, list, 0); - } + public static String emptyIfNull(String s) { + return (s == null) ? "" : s; + } - public static String concat(String separator, List list, int skip) { - StringBuffer buff = new StringBuffer(); - int lastIndex = list.size() - skip; - for (int i = 0; i < lastIndex; i++) { - buff.append(list.get(i)); - if (i + 1 < lastIndex) - buff.append(separator); - } - String string = buff.toString(); - return string.trim().length() == 0 ? null : string; - } + public static String concat(String separator, List list) { + return concat(separator, list, 0); + } - public static String skipLastToken(String value, String separator) { - int endIndex = value.lastIndexOf(separator); - if (endIndex > 0) - return value.substring(0, endIndex); - else - return value; - } + public static String concat(String separator, List list, int skip) { + StringBuffer buff = new StringBuffer(); + int lastIndex = list.size() - skip; + for (int i = 0; i < lastIndex; i++) { + buff.append(list.get(i)); + if (i + 1 < lastIndex) + buff.append(separator); + } + String string = buff.toString(); + return string.trim().length() == 0 ? null : string; + } - public static String lastToken(String value, String separator) { - int index = value.lastIndexOf(separator) + 1; - if (index < value.length()) - return value.substring(index, value.length()); - else - return ""; - } + public static String skipLastToken(String value, String separator) { + int endIndex = value.lastIndexOf(separator); + if (endIndex > 0) + return value.substring(0, endIndex); + else + return value; + } - public static String toFirstUpper(String s) { - if (s == null || s.length() == 0) - return s; - if (s.length() == 1) - return s.toUpperCase(); - return s.substring(0, 1).toUpperCase() + s.substring(1); - } + public static String lastToken(String value, String separator) { + int index = value.lastIndexOf(separator) + 1; + if (index < value.length()) + return value.substring(index, value.length()); + else + return ""; + } + + public static String toFirstUpper(String s) { + if (s == null || s.length() == 0) + return s; + if (s.length() == 1) + return s.toUpperCase(); + return s.substring(0, 1).toUpperCase() + s.substring(1); + } + + public static boolean isEmpty(String s) { + return s == null || s.equals(""); + } } diff --git a/plugins/org.eclipse.xtext/.classpath b/plugins/org.eclipse.xtext/.classpath index 9e52aed49..653be5d2f 100644 --- a/plugins/org.eclipse.xtext/.classpath +++ b/plugins/org.eclipse.xtext/.classpath @@ -1,8 +1,8 @@ - + diff --git a/plugins/org.eclipse.xtext/META-INF/MANIFEST.MF b/plugins/org.eclipse.xtext/META-INF/MANIFEST.MF index a8056a681..8b5bf999b 100644 --- a/plugins/org.eclipse.xtext/META-INF/MANIFEST.MF +++ b/plugins/org.eclipse.xtext/META-INF/MANIFEST.MF @@ -24,6 +24,7 @@ Export-Package: org.eclipse.xtext, org.eclipse.xtext.parsetree.reconstr.impl, org.eclipse.xtext.parsetree.util, org.eclipse.xtext.resource, + org.eclipse.xtext.resource.metamodel, org.eclipse.xtext.service, org.eclipse.xtext.services, org.eclipse.xtext.util, diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/GrammarUtil.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/GrammarUtil.java index ed26cf1be..79896cd22 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/GrammarUtil.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/GrammarUtil.java @@ -27,7 +27,6 @@ import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.xtext.builtin.IXtextBuiltin; -import org.eclipse.xtext.resource.XtextResourceSet; import org.eclipse.xtext.util.Strings; /** @@ -44,14 +43,20 @@ public class GrammarUtil { public static EPackage loadEPackage(ReferencedMetamodel ref) { if (ref == null) throw new NullPointerException("ReferencedMetamodel was null"); - if (EPackage.Registry.INSTANCE.containsKey(ref.getUri())) - return EPackage.Registry.INSTANCE.getEPackage(ref.getUri()); - URI uri = URI.createURI(ref.getUri()); + String uriAsString = ref.getUri(); + ResourceSet resourceSet = ref.eResource().getResourceSet(); + return loadEPackage(uriAsString, resourceSet); + } + + public static EPackage loadEPackage(String resourceOrNsURI, ResourceSet resourceSet) { + if (EPackage.Registry.INSTANCE.containsKey(resourceOrNsURI)) + return EPackage.Registry.INSTANCE.getEPackage(resourceOrNsURI); + URI uri = URI.createURI(resourceOrNsURI); if (uri.fragment() == null) { - Resource resource = ref.eResource().getResourceSet().getResource(uri, true); + Resource resource = resourceSet.getResource(uri, true); return (EPackage) resource.getContents().get(0); } else { - return (EPackage) ref.eResource().getResourceSet().getEObject(uri, true); + return (EPackage) resourceSet.getEObject(uri, true); } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/EClassInfo.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/EClassInfo.java new file mode 100644 index 000000000..c9de47c94 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/EClassInfo.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2008 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 org.eclipse.xtext.resource.metamodel; + +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.emf.ecore.EClass; + +/** + * @author Jan Köhnlein - Initial contribution and API + * + */ +public class EClassInfo { + + private EClass eClass; + private boolean isGenerated; + private Set superTypes = new HashSet(); + + public EClassInfo(EClass metaType, boolean isGenerated) { + super(); + this.isGenerated = isGenerated; + this.eClass = metaType; + } + + public EClass getEClass() { + return eClass; + } + + public boolean isGenerated() { + return isGenerated; + } + + public boolean addSupertype(EClassInfo superTypeInfo) { + return superTypes.add(superTypeInfo); + } +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/EClassInfos.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/EClassInfos.java new file mode 100644 index 000000000..ce2380f95 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/EClassInfos.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2008 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 org.eclipse.xtext.resource.metamodel; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.xtext.TypeRef; +import org.eclipse.xtext.util.Pair; +import org.eclipse.xtext.util.Strings; + +/** + * A possible extension would be to normalize the type hierarchy and remove + * redundant supertype references. We currently don't think thats necessary as + * EMF handles multiple inheritance gracefully. + * + * @author Jan Köhnlein - Initial contribution and API + * + */ +public class EClassInfos { + + class Key extends Pair { + public Key(String firstElement, String secondElement) { + super(firstElement, secondElement); + } + } + + private Map infoMap = new HashMap(); + + public boolean addInfo(TypeRef typeRef, EClassInfo metatypeInfo) { + return infoMap.put(key(typeRef), metatypeInfo) != metatypeInfo; + } + + public boolean addInfo(String alias, String name, EClassInfo metatypeInfo) { + return infoMap.put(new Key(alias, name), metatypeInfo) != metatypeInfo; + } + + public EClassInfo getInfo(TypeRef typeRef) { + return infoMap.get(key(typeRef)); + } + + private Key key(TypeRef typeRef) { + return new Key(Strings.emptyIfNull(typeRef.getAlias()), typeRef.getName()); + } + +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/TransformationException.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/TransformationException.java new file mode 100644 index 000000000..4f0f69aa6 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/TransformationException.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2008 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 org.eclipse.xtext.resource.metamodel; + +import org.eclipse.emf.ecore.EObject; + +/** + * @author Jan Köhnlein - Initial contribution and API + * + */ +public class TransformationException extends Exception { + + private static final long serialVersionUID = -6324965070089590590L; + + private EObject erroneousElement; + + public TransformationException(String message, EObject erroneousElement) { + super(message); + this.erroneousElement = erroneousElement; + } + + public EObject getErroneousElement() { + return erroneousElement; + } + + public void setErroneousElement(EObject erroneousElement) { + this.erroneousElement = erroneousElement; + } + +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/Xtext2EcoreTransformer.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/Xtext2EcoreTransformer.java new file mode 100644 index 000000000..246940d05 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/Xtext2EcoreTransformer.java @@ -0,0 +1,239 @@ +/******************************************************************************* + * Copyright (c) 2008 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 org.eclipse.xtext.resource.metamodel; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EcoreFactory; +import org.eclipse.xtext.AbstractElement; +import org.eclipse.xtext.AbstractMetamodelDeclaration; +import org.eclipse.xtext.AbstractRule; +import org.eclipse.xtext.Action; +import org.eclipse.xtext.Alternatives; +import org.eclipse.xtext.GeneratedMetamodel; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.Group; +import org.eclipse.xtext.ParserRule; +import org.eclipse.xtext.ReferencedMetamodel; +import org.eclipse.xtext.RuleCall; +import org.eclipse.xtext.TypeRef; +import org.eclipse.xtext.XtextFactory; +import org.eclipse.xtext.util.Strings; + +/** + * @author Jan Köhnlein - Initial contribution and API + * + */ +public class Xtext2EcoreTransformer { + + private Grammar grammar; + private Map generatedEPackages; + private Grammar superGrammar; + private EClassInfos eClassInfos; + + public Xtext2EcoreTransformer() { + } + + /* + * pre-conditions - ensure non-duplicate aliases - ensure all aliases have + * matching metamodel declarations + */ + + public List transform(Grammar grammar) { + this.grammar = grammar; + generatedEPackages = new HashMap(); + superGrammar = GrammarUtil.getSuperGrammar(grammar); + eClassInfos = new EClassInfos(); + collectEPackages(); + + // create types: + // iterate rules + // - typeref in actions + for (AbstractRule rule : grammar.getRules()) { + // - return types (lexer and parser rules) + TypeRef ruleReturnTypeRef = rule.getType(); + try { + if (ruleReturnTypeRef == null) { + ruleReturnTypeRef = XtextFactory.eINSTANCE.createTypeRef(); + ruleReturnTypeRef.setName(rule.getName()); + } + EClassInfo generatedEClass = findOrCreateEClass(ruleReturnTypeRef); + if (rule instanceof ParserRule) { + ParserRule parserRule = (ParserRule) rule; + deriveTypesAndHierarchy(generatedEClass, parserRule.getAlternatives()); + } + } + catch (TransformationException e) { + reportError(e.getMessage(), e.getErroneousElement()); + } + } + + // create features + // iterate rules + // - assignments + // - feature in actions + // multiplicity! + + // type hierarchy + // - rule calls (optionality) + // - actions + + // feature normalization + // - uplift of common feature to supertype + // - removal in subtype if already in supertype + // - don't combine features with different EDatatypes + fillGeneratedPackages(); + + return new ArrayList(generatedEPackages.values()); + } + + private void fillGeneratedPackages() { + + } + + /** + * @param alternatives + * @throws TransformationException + */ + private void deriveTypesAndHierarchy(EClassInfo ruleReturnType, AbstractElement element) + throws TransformationException { + if (element instanceof RuleCall) { + RuleCall ruleCall = (RuleCall) element; + AbstractRule calledRule = GrammarUtil.calledRule(ruleCall); + TypeRef calledRuleReturnTypeRef = calledRule.getType(); + addSuperType(calledRuleReturnTypeRef, ruleReturnType); + } + else if (element instanceof Action) { + Action action = (Action) element; + TypeRef actionTypeRef = action.getTypeName(); + addSuperType(actionTypeRef, ruleReturnType); + } + else if (element instanceof Group) { + Group group = (Group) element; + deriveTypesAndHierarchy(ruleReturnType, group.getAbstractTokens()); + } + else if (element instanceof Alternatives) { + Alternatives alternatives = (Alternatives) element; + deriveTypesAndHierarchy(ruleReturnType, alternatives.getGroups()); + } + } + + private void deriveTypesAndHierarchy(EClassInfo ruleReturnType, List elements) + throws TransformationException { + for (AbstractElement element : elements) { + deriveTypesAndHierarchy(ruleReturnType, element); + } + } + + private void addSuperType(TypeRef subTypeRef, EClassInfo superType) throws TransformationException { + EClassInfo calledRuleReturnType = findOrCreateEClass(subTypeRef); + calledRuleReturnType.addSupertype(superType); + } + + class InterpretationContext { + public InterpretationContext(EClass currentType, boolean isGeneratedType, boolean isRuleCallAllowed) { + super(); + this.currentType = currentType; + this.isGeneratedType = isGeneratedType; + this.isRuleCallAllowed = isRuleCallAllowed; + } + + public InterpretationContext clone() { + return new InterpretationContext(currentType, isGeneratedType, isRuleCallAllowed); + } + + EClass currentType; + boolean isGeneratedType; + boolean isRuleCallAllowed = true; + } + + private InterpretationContext interpretElement(AbstractElement element, EClass returnType) { + // TODO: implement + return null; + } + + private void collectEPackages() { + EList metamodelDeclarations = grammar.getMetamodelDeclarations(); + for (AbstractMetamodelDeclaration metamodelDeclaration : metamodelDeclarations) { + if (metamodelDeclaration instanceof ReferencedMetamodel) { + // load imported metamodel + ReferencedMetamodel referencedMetamodel = (ReferencedMetamodel) metamodelDeclaration; + EPackage referencedEPackage = GrammarUtil.loadEPackage(referencedMetamodel); + if (referencedEPackage == null) { + reportError("Cannot not load metamodel " + referencedMetamodel.getUri(), referencedMetamodel); + } + else { + String alias = referencedMetamodel.getAlias(); + if (Strings.isEmpty(alias)) { + reportError("Referenced metamodels must have an alias", referencedMetamodel); + } + else { + for (EClassifier eClassifier : referencedEPackage.getEClassifiers()) { + if (eClassifier instanceof EClass) { + EClassInfo info = new EClassInfo((EClass) eClassifier, false); + eClassInfos.addInfo(alias, eClassifier.getName(), info); + } + } + } + } + } + else if (metamodelDeclaration instanceof GeneratedMetamodel) { + // instantiate EPackages for generated metamodel + GeneratedMetamodel generatedMetamodel = (GeneratedMetamodel) metamodelDeclaration; + EPackage generatedEPackage = EcoreFactory.eINSTANCE.createEPackage(); + generatedEPackage.setName(generatedMetamodel.getName()); + generatedEPackage.setNsPrefix(generatedMetamodel.getName()); + generatedEPackage.setNsURI(generatedMetamodel.getNsURI()); + String alias = Strings.emptyIfNull(generatedMetamodel.getAlias()); + generatedEPackages.put(alias, generatedEPackage); + } + } + } + + /** + * @param string + * @param generatedMetamodel + */ + private void reportError(String message, EObject erroneousElement) { + // TODO Auto-generated method stub + + } + + private void raiseError(String message, EObject erroneousElement) throws TransformationException { + throw new TransformationException(message, erroneousElement); + } + + private EClassInfo findOrCreateEClass(TypeRef typeRef) throws TransformationException { + EClassInfo info = eClassInfos.getInfo(typeRef); + if (info == null) { + String typeRefAlias = Strings.emptyIfNull(typeRef.getAlias()); + String typeRefName = typeRef.getName(); + EPackage generatedEPackage = generatedEPackages.get(typeRefAlias); + if (generatedEPackage == null) { + raiseError("Cannot create type in alias " + typeRefAlias, typeRef); + } + EClass generatedEClass = EcoreFactory.eINSTANCE.createEClass(); + generatedEClass.setName(typeRefName); + generatedEPackage.getEClassifiers().add(generatedEClass); + info = new EClassInfo(generatedEClass, true); + eClassInfos.addInfo(typeRef, info); + } + return info; + } + +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/XtextMetamodelResource.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/XtextMetamodelResource.java new file mode 100644 index 000000000..94e02361c --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/metamodel/XtextMetamodelResource.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2008 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 org.eclipse.xtext.resource.metamodel; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import org.eclipse.emf.ecore.resource.impl.ResourceImpl; + +/** + * @author Jan Köhnlein - Initial contribution and API + * + */ +public class XtextMetamodelResource extends ResourceImpl { + + /* (non-Javadoc) + * @see org.eclipse.emf.ecore.resource.impl.ResourceImpl#doLoad(java.io.InputStream, java.util.Map) + */ + @Override + protected void doLoad(InputStream inputStream, Map options) throws IOException { + // TODO: implement + super.doLoad(inputStream, options); + } +} diff --git a/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/resource/metamodel/Xtext2EcoreTransformerTests.java b/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/resource/metamodel/Xtext2EcoreTransformerTests.java new file mode 100644 index 000000000..c42b1f9cc --- /dev/null +++ b/tests/org.eclipse.xtext.generator.tests/src/org/eclipse/xtext/resource/metamodel/Xtext2EcoreTransformerTests.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2008 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 org.eclipse.xtext.resource.metamodel; + +import java.util.List; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.xtext.AbstractRule; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.XtextStandaloneSetup; +import org.eclipse.xtext.tests.AbstractGeneratorTest; + +/** + * @author Jan Köhnlein - Initial contribution and API + */ +public class Xtext2EcoreTransformerTests extends AbstractGeneratorTest { + + @Override + protected void setUp() throws Exception { + super.setUp(); + with(XtextStandaloneSetup.class); + } + + public void testRuleWithoutExplicitReturnType() throws Exception { + Grammar grammar = (Grammar) getModel("language test generate test 'http://test' MyRule: myFeature=INT;"); + EList rules = grammar.getRules(); + AbstractRule firstRule = rules.get(0); + assertNull(firstRule.getType()); + Xtext2EcoreTransformer xtext2EcoreTransformer = new Xtext2EcoreTransformer(); + List metamodels = xtext2EcoreTransformer.transform(grammar); + assertTrue(metamodels != null && !metamodels.isEmpty()); + EPackage firstEPackage = metamodels.get(0); + EList classifiers = firstEPackage.getEClassifiers(); + assertEquals(1, classifiers.size()); + EClassifier implicitlyDefinedMetatype = classifiers.get(0); + assertEquals("MyRule", implicitlyDefinedMetatype.getName()); + } +}