First shot at xtext to ecore in Java

This commit is contained in:
jkohnlein 2008-09-18 15:09:56 +00:00
parent 607d5a4aa3
commit 112636ad02
10 changed files with 512 additions and 52 deletions

View file

@ -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<String> list) {
return concat(separator, list, 0);
}
public static String emptyIfNull(String s) {
return (s == null) ? "" : s;
}
public static String concat(String separator, List<String> 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<String> 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<String> 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("");
}
}

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="emf-gen"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="emf-gen"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="src-gen"/>
<classpathentry kind="src" path="model"/>

View file

@ -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,

View file

@ -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);
}
}

View file

@ -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<EClassInfo> superTypes = new HashSet<EClassInfo>();
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);
}
}

View file

@ -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<String, String> {
public Key(String firstElement, String secondElement) {
super(firstElement, secondElement);
}
}
private Map<Key, EClassInfo> infoMap = new HashMap<Key, EClassInfo>();
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());
}
}

View file

@ -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;
}
}

View file

@ -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<String, EPackage> generatedEPackages;
private Grammar superGrammar;
private EClassInfos eClassInfos;
public Xtext2EcoreTransformer() {
}
/*
* pre-conditions - ensure non-duplicate aliases - ensure all aliases have
* matching metamodel declarations
*/
public List<EPackage> transform(Grammar grammar) {
this.grammar = grammar;
generatedEPackages = new HashMap<String, EPackage>();
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<EPackage>(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<AbstractElement> 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<AbstractMetamodelDeclaration> 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;
}
}

View file

@ -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);
}
}

View file

@ -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<AbstractRule> rules = grammar.getRules();
AbstractRule firstRule = rules.get(0);
assertNull(firstRule.getType());
Xtext2EcoreTransformer xtext2EcoreTransformer = new Xtext2EcoreTransformer();
List<EPackage> metamodels = xtext2EcoreTransformer.transform(grammar);
assertTrue(metamodels != null && !metamodels.isEmpty());
EPackage firstEPackage = metamodels.get(0);
EList<EClassifier> classifiers = firstEPackage.getEClassifiers();
assertEquals(1, classifiers.size());
EClassifier implicitlyDefinedMetatype = classifiers.get(0);
assertEquals("MyRule", implicitlyDefinedMetatype.getName());
}
}