Noch ein Patch from Heiko Behrens (Knut) BUG 247406 [Core] Reimplement derivation of metamodel from Xtext grammar in Java

This commit is contained in:
dhubner 2008-09-25 13:28:47 +00:00
parent 54055641bc
commit 9a0da686a7
9 changed files with 146 additions and 46 deletions

View file

@ -9,8 +9,11 @@
package org.eclipse.xtext;
import static org.eclipse.emf.ecore.util.EcoreUtil.*;
import static org.eclipse.xtext.EcoreUtil2.*;
import static org.eclipse.emf.ecore.util.EcoreUtil.getRootContainer;
import static org.eclipse.xtext.EcoreUtil2.eAllContentsAsList;
import static org.eclipse.xtext.EcoreUtil2.getAllContentsOfType;
import static org.eclipse.xtext.EcoreUtil2.getContainerOfType;
import static org.eclipse.xtext.EcoreUtil2.typeSelect;
import java.util.ArrayList;
import java.util.HashSet;

View file

@ -11,8 +11,6 @@ package org.eclipse.xtext.parsetree;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.xtext.parsetree.AbstractNode;
import org.eclipse.xtext.parsetree.CompositeNode;
public class NodeAdapter implements Adapter {

View file

@ -11,7 +11,6 @@ package org.eclipse.xtext.parsetree;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.notify.impl.AdapterFactoryImpl;
import org.eclipse.xtext.parsetree.AbstractNode;
public class NodeAdapterFactory extends AdapterFactoryImpl {

View file

@ -0,0 +1,27 @@
/*******************************************************************************
* 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 Heiko Behrens - Initial contribution and API
*
*/
public interface ErrorAcceptor {
public enum ErrorCode {
NoSuchTypeAvailable,
MoreThanOneTypeChangeInOneRule,
CannotLoadMetamodel,
MissingAliasForReferencedMetamodel,
CannotCreateTypeInSealedMetamodel
}
public void acceptError(ErrorCode errorCode, String message, EObject element);
}

View file

@ -9,6 +9,7 @@
package org.eclipse.xtext.resource.metamodel;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.resource.metamodel.ErrorAcceptor.ErrorCode;
/**
* @author Jan Köhnlein - Initial contribution and API
@ -20,10 +21,22 @@ public class TransformationException extends Exception {
private EObject erroneousElement;
public TransformationException(String message, EObject erroneousElement) {
private ErrorCode errorCode;
public TransformationException(ErrorCode errorCode, String message, EObject erroneousElement) {
super(message);
this.errorCode = errorCode;
this.erroneousElement = erroneousElement;
}
@Deprecated
public TransformationException(ErrorCode errorCode, String message) {
this(errorCode, message, null);
}
public ErrorCode getErrorCode() {
return errorCode;
}
public EObject getErroneousElement() {
return erroneousElement;
@ -32,5 +45,5 @@ public class TransformationException extends Exception {
public void setErroneousElement(EObject erroneousElement) {
this.erroneousElement = erroneousElement;
}
}

View file

@ -13,6 +13,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.AbstractElement;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.Alternatives;
@ -22,6 +23,7 @@ import org.eclipse.xtext.GrammarUtil;
import org.eclipse.xtext.Group;
import org.eclipse.xtext.Keyword;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.resource.metamodel.ErrorAcceptor.ErrorCode;
/**
* @author Heiko Behrens - Initial contribution and API
@ -51,9 +53,10 @@ public class Xtext2ECoreInterpretationContext {
}
public Xtext2ECoreInterpretationContext(EClassifierInfo newType, EClassifierInfos classifierInfos,
boolean isRuleCallAllowed2) {
boolean isRuleCallAllowed) {
this(classifierInfos);
this.currentTypes.add(newType);
this.isRuleCallAllowed = isRuleCallAllowed;
}
public void addFeature(Assignment assignment) throws TransformationException {
@ -121,7 +124,8 @@ public class Xtext2ECoreInterpretationContext {
throws TransformationException {
EClassifierInfo featureTypeInfo = eClassifierInfos.getInfo(typeName);
if (featureTypeInfo == null) {
throw new TransformationException("Cannot resolve type " + typeName, parserElement);
throw new TransformationException(ErrorCode.NoSuchTypeAvailable, "Cannot resolve type " + typeName,
parserElement);
}
return featureTypeInfo;
}
@ -132,13 +136,13 @@ public class Xtext2ECoreInterpretationContext {
return result;
}
public Xtext2ECoreInterpretationContext spawnContextWith(EClassifierInfo newType) {
public Xtext2ECoreInterpretationContext spawnContextWith(EClassifierInfo newType, EObject parserElement)
throws TransformationException {
if (!isRuleCallAllowed)
throw new IllegalStateException("Cannot change type twice within a rule");
throw new TransformationException(ErrorCode.MoreThanOneTypeChangeInOneRule,
"Cannot change type twice within a rule", parserElement);
Xtext2ECoreInterpretationContext result = new Xtext2ECoreInterpretationContext(newType, eClassifierInfos,
isRuleCallAllowed);
return result;
return new Xtext2ECoreInterpretationContext(newType, eClassifierInfos, false);
}
public Xtext2ECoreInterpretationContext mergeSpawnedContexts(List<Xtext2ECoreInterpretationContext> contexts) {

View file

@ -34,6 +34,7 @@ import org.eclipse.xtext.ParserRule;
import org.eclipse.xtext.ReferencedMetamodel;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.TypeRef;
import org.eclipse.xtext.resource.metamodel.ErrorAcceptor.ErrorCode;
import org.eclipse.xtext.util.Strings;
/**
@ -46,6 +47,21 @@ public class Xtext2EcoreTransformer {
private Map<String, EPackage> generatedEPackages;
private Grammar superGrammar;
private EClassifierInfos eClassifierInfos;
private ErrorAcceptor errorAcceptor = new NullErrorAcceptor();
public ErrorAcceptor getErrorAcceptor() {
return errorAcceptor;
}
public void setErrorAcceptor(ErrorAcceptor errorAcceptor) {
this.errorAcceptor = errorAcceptor;
}
public class NullErrorAcceptor implements ErrorAcceptor {
public void acceptError(ErrorCode errorCode, String arg0, EObject arg1) {
// do nothing
}
}
public Xtext2EcoreTransformer() {
}
@ -79,7 +95,7 @@ public class Xtext2EcoreTransformer {
}
}
catch (TransformationException e) {
reportError(e.getMessage(), e.getErroneousElement());
reportError(e.getErrorCode(), e.getMessage(), e.getErroneousElement());
}
}
@ -93,7 +109,7 @@ public class Xtext2EcoreTransformer {
}
}
catch (TransformationException e) {
reportError(e.getMessage(), e.getErroneousElement());
reportError(e.getErrorCode(), e.getMessage(), e.getErroneousElement());
}
}
@ -135,12 +151,7 @@ public class Xtext2EcoreTransformer {
else if (element instanceof RuleCall && !GrammarUtil.isOptionalCardinality(element)) {
RuleCall ruleCall = (RuleCall) element;
AbstractRule calledRule = GrammarUtil.findRuleForName(grammar, ruleCall.getName());
try {
return context.spawnContextWith(findOrCreateEClass(calledRule));
}
catch (IllegalStateException e) {
throw new TransformationException(e.getMessage(), ruleCall);
}
return context.spawnContextWith(findOrCreateEClass(calledRule), ruleCall);
}
return context;
@ -155,12 +166,15 @@ public class Xtext2EcoreTransformer {
}
private void deriveFeatures(ParserRule rule) throws TransformationException {
EClassifierInfo classInfo = findOrCreateEClass(rule);
EClassifierInfo classInfo = findEClass(rule);
if (classInfo == null)
throw new TransformationException(ErrorCode.NoSuchTypeAvailable, "No such type available"
+ GrammarUtil.getReturnTypeName(rule), rule);
Xtext2ECoreInterpretationContext context = new Xtext2ECoreInterpretationContext(eClassifierInfos, classInfo);
deriveFeatures(context, rule.getAlternatives());
}
// TODO : Try to get rid of
// TODO : Try to get rid of typref and use qualified name (String) instead
private TypeRef getOrFakeReturnType(AbstractRule rule) {
TypeRef result = rule.getType();
if (result == null) {
@ -174,10 +188,6 @@ public class Xtext2EcoreTransformer {
}
/**
* @param alternatives
* @throws TransformationException
*/
private void deriveTypesAndHierarchy(EClassifierInfo ruleReturnType, AbstractElement element)
throws TransformationException {
if (element instanceof RuleCall) {
@ -219,14 +229,23 @@ public class Xtext2EcoreTransformer {
if (metamodelDeclaration instanceof ReferencedMetamodel) {
// load imported metamodel
ReferencedMetamodel referencedMetamodel = (ReferencedMetamodel) metamodelDeclaration;
EPackage referencedEPackage = GrammarUtil.loadEPackage(referencedMetamodel);
EPackage referencedEPackage;
try {
referencedEPackage = GrammarUtil.loadEPackage(referencedMetamodel);
}
catch (RuntimeException e) {
referencedEPackage = null;
}
if (referencedEPackage == null) {
reportError("Cannot not load metamodel " + referencedMetamodel.getUri(), referencedMetamodel);
reportError(ErrorCode.CannotLoadMetamodel, "Cannot not load metamodel "
+ referencedMetamodel.getUri(), referencedMetamodel);
}
else {
String alias = referencedMetamodel.getAlias();
if (Strings.isEmpty(alias)) {
reportError("Referenced metamodels must have an alias", referencedMetamodel);
reportError(ErrorCode.MissingAliasForReferencedMetamodel,
"Referenced metamodels must have an alias", referencedMetamodel);
}
else {
collectClassInfosOf(referencedEPackage, alias);
@ -261,17 +280,8 @@ public class Xtext2EcoreTransformer {
}
}
/**
* @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 void reportError(ErrorCode errorCode, String message, EObject erroneousElement) {
errorAcceptor.acceptError(errorCode, message, erroneousElement);
}
private EClassifierInfo findOrCreateEClass(AbstractRule rule) throws TransformationException {
@ -279,6 +289,11 @@ public class Xtext2EcoreTransformer {
return findOrCreateEClass(typeRef);
}
private EClassifierInfo findEClass(AbstractRule rule) {
TypeRef typeRef = getOrFakeReturnType(rule);
return eClassifierInfos.getInfo(typeRef);
}
private EClassifierInfo findOrCreateEClass(TypeRef typeRef) throws TransformationException {
EClassifierInfo info = eClassifierInfos.getInfo(typeRef);
if (info == null) {
@ -297,7 +312,8 @@ public class Xtext2EcoreTransformer {
String typeRefName = typeRef.getName();
EPackage generatedEPackage = generatedEPackages.get(typeRefAlias);
if (generatedEPackage == null) {
raiseError("Cannot create type in alias " + typeRefAlias, typeRef);
throw new TransformationException(ErrorCode.CannotCreateTypeInSealedMetamodel,
"Cannot create type in alias " + typeRefAlias, typeRef);
}
EClass generatedEClass = EcoreFactory.eINSTANCE.createEClass();
generatedEClass.setName(typeRefName);

View file

@ -14,5 +14,6 @@ Require-Bundle: org.eclipse.xtext,
org.eclipse.xtext.log4j;bundle-version="1.2.15",
org.eclipse.xtend,
org.eclipse.xtend.typesystem.emf,
org.eclipse.xtend.util.stdlib
org.eclipse.xtend.util.stdlib,
org.easymock;bundle-version="2.3.0"
Export-Package: org.eclipse.xtext

View file

@ -7,16 +7,24 @@
*******************************************************************************/
package org.eclipse.xtext.resource.metamodel;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.same;
import static org.easymock.EasyMock.verify;
import java.util.List;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EAttribute;
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.EReference;
import org.eclipse.xtext.Grammar;
import org.eclipse.xtext.XtextStandaloneSetup;
import org.eclipse.xtext.resource.metamodel.ErrorAcceptor.ErrorCode;
import org.eclipse.xtext.tests.AbstractGeneratorTest;
/**
@ -25,17 +33,24 @@ import org.eclipse.xtext.tests.AbstractGeneratorTest;
* @see http://wiki.eclipse.org/Xtext/Documentation#Meta-Model_Inference
*/
public class Xtext2EcoreTransformerTests extends AbstractGeneratorTest {
private Xtext2EcoreTransformer xtext2EcoreTransformer;
private ErrorAcceptor errorAcceptorMock;
@Override
protected void setUp() throws Exception {
super.setUp();
xtext2EcoreTransformer = new Xtext2EcoreTransformer();
errorAcceptorMock = createMock(ErrorAcceptor.class);
with(XtextStandaloneSetup.class);
}
private EPackage getEPackageFromGrammar(String xtextGrammar) throws Exception {
Grammar grammar = (Grammar) getModel(xtextGrammar);
Xtext2EcoreTransformer xtext2EcoreTransformer = new Xtext2EcoreTransformer();
replay(errorAcceptorMock);
xtext2EcoreTransformer.setErrorAcceptor(errorAcceptorMock);
List<EPackage> metamodels = xtext2EcoreTransformer.transform(grammar);
verify(errorAcceptorMock);
assertNotNull(metamodels);
assertEquals(1, metamodels.size());
@ -355,4 +370,28 @@ public class Xtext2EcoreTransformerTests extends AbstractGeneratorTest {
assertReferenceConfiguration(ruleA, 3, "refA4", "TypeB", 0, 1);
}
public void testImportWithoutAlias() throws Exception {
final String grammar = "language test generate test 'http://test' import 'http://www.eclipse.org/emf/2002/Ecore' RuleA: feature=ID;";
errorAcceptorMock.acceptError(same(ErrorCode.MissingAliasForReferencedMetamodel), (String)anyObject(), (EObject)anyObject());
getEPackageFromGrammar(grammar);
}
public void testModifyingSealedModel() throws Exception {
final String grammar = "language test generate test 'http://test' import 'http://www.eclipse.org/emf/2002/Ecore' as ecore RuleA returns ecore::SomeNewTypeA: feature=ID;";
errorAcceptorMock.acceptError(same(ErrorCode.CannotCreateTypeInSealedMetamodel), (String)anyObject(), (EObject)anyObject());
errorAcceptorMock.acceptError(same(ErrorCode.NoSuchTypeAvailable), (String)anyObject(), (EObject)anyObject());
getEPackageFromGrammar(grammar);
}
public void testImportingUnknownModel() throws Exception {
final String grammar = "language test generate test 'http://test' import 'http://www.unknownModel' as unknownModel RuleA: feature=ID;";
errorAcceptorMock.acceptError(same(ErrorCode.CannotLoadMetamodel), (String)anyObject(), (EObject)anyObject());
getEPackageFromGrammar(grammar);
}
public void testMoreThanOneRuleCall() throws Exception {
final String grammar = "language test generate test 'http://test' RuleA: RuleB RuleC; RuleB: featureB=ID; RuleC: featureC=ID;";
errorAcceptorMock.acceptError(same(ErrorCode.MoreThanOneTypeChangeInOneRule), (String)anyObject(), (EObject)anyObject());
getEPackageFromGrammar(grammar);
}
}