From 914d904838da4272447211b6329f00294ce3e302 Mon Sep 17 00:00:00 2001 From: Johannes Mey <johannes.mey@tu-dresden.de> Date: Thu, 12 Sep 2019 08:53:50 +0200 Subject: [PATCH] add initial name resolution and creation, improve parser --- src/main/jastadd/JastAddAPIExtension.jadd | 138 ++++++++++- src/main/jastadd/XMI/XMIPathConstruction.jrag | 69 ++++++ src/main/jastadd/XMI/XMIPathResolution.jrag | 225 ++++++++++++++++++ src/main/jastadd/XMI/XMIWriter.jadd | 31 ++- src/main/jastadd/ecore.relast | 4 +- .../inf/st/e2j/parser/EcoreParser.java | 4 +- 6 files changed, 460 insertions(+), 11 deletions(-) create mode 100644 src/main/jastadd/XMI/XMIPathConstruction.jrag create mode 100644 src/main/jastadd/XMI/XMIPathResolution.jrag diff --git a/src/main/jastadd/JastAddAPIExtension.jadd b/src/main/jastadd/JastAddAPIExtension.jadd index a2a9760..8c4e3b8 100644 --- a/src/main/jastadd/JastAddAPIExtension.jadd +++ b/src/main/jastadd/JastAddAPIExtension.jadd @@ -20,7 +20,7 @@ aspect JastAddAPIExtension { throw new RuntimeException("unable to remove child, because it was not contained in its parent!"); } - public int ASTNode.containedChildren() { + public int ASTNode.numContainedChildren() { int result = 0; for (ASTNode child: this.astChildren()) { if (child instanceof JastAddList) { @@ -36,6 +36,18 @@ aspect JastAddAPIExtension { return result; } + public java.util.List<ASTNode> ASTNode.containedChildren() { + java.util.List<ASTNode> result = new java.util.ArrayList<>(); + for (ASTNode child: this.astChildren()) { + if (child instanceof JastAddList || child instanceof Opt) { + result.addAll(child.containedChildren()); + } else { + result.add(child); + } + } + return Collections.unmodifiableList(result); + } + public ASTNode ASTNode.root() { if (getParent() == null) { return this; @@ -45,3 +57,127 @@ aspect JastAddAPIExtension { } } + +aspect JavaListInterface { + + public class JastAddList { + private class JavaListDecorator extends java.util.AbstractList<T> { + + @Override + public T set(int index, T element) { + T oldElement = JastAddList.this.getChild(index); + JastAddList.this.setChild(element, index); + return oldElement; + } + + @Override + public boolean add(T element) { + JastAddList.this.addChild(element); + return true; + } + + @Override + public T remove(int index) { + T removedChild = JastAddList.this.getChild(index); + JastAddList.this.removeChild(index); + return removedChild; + } + + @Override + public T get(int i) { + return JastAddList.this.getChild(i); + } + + @Override + public int size() { + return JastAddList.this.getNumChild(); + } + + } + + public java.util.List asJavaList() { + return new JavaListDecorator(); + } + } + +// // unfortunately, it is not possible to implement the java.util.List or java.util.Collection +// // interfaces, because $List.add() returns the list and not a boolean +// JastAddList implements java.util.List<T>; + + public int JastAddList.size() { + return getNumChild(); + } + + public boolean JastAddList.isEmpty() { + return (getNumChild() == 0); + } + + public boolean JastAddList.contains(Object o) { + if (o == null) { + for (int i = 1; i < getNumChild(); i++) { + if (getChild(i) == null) { + return true; + } + } + } else { + for (int i = 0; i < getNumChild(); i++) { + if (getChild(i) == o) { + return true; + } + } + } + return false; + } + + public boolean JastAddList.addAll(java.util.Collection<? extends T> collection) { + for (T t: collection) { + addChild(t); + } + return !collection.isEmpty(); + } + + public Object[] JastAddList.toArray() { + Object[] array = new Object[getNumChild()]; + for (int i = 0; i < getNumChild(); i++) { + array[i] = getChild(i); + } + return array; + } + + public <T1> T1[] JastAddList.toArray(T1[] t1s) { + T1[] array = t1s.length >= getNumChild() ? t1s : (T1[])((Object[]) java.lang.reflect.Array.newInstance(t1s.getClass().getComponentType(), getNumChild())); + + for (int i = 0; i < getNumChild(); i++) { + array[i] = (T1) getChild(i); + } + return array; + } + + public boolean JastAddList.remove(Object o) { + if (o == null) { + for (int i = 1; i < getNumChild(); i++) { + if (getChild(i) == null) { + removeChild(i); + return true; + } + } + } else { + for (int i = 0; i < getNumChild(); i++) { + if (getChild(i) == o) { + removeChild(i); + return true; + } + } + } + return false; + } + + public boolean JastAddList.containsAll(java.util.Collection<?> collection) { + for (Object o: collection) { + if (!this.contains(o)) { + return false; + } + } + return true; + } +} diff --git a/src/main/jastadd/XMI/XMIPathConstruction.jrag b/src/main/jastadd/XMI/XMIPathConstruction.jrag new file mode 100644 index 0000000..b85806c --- /dev/null +++ b/src/main/jastadd/XMI/XMIPathConstruction.jrag @@ -0,0 +1,69 @@ +aspect XMIPathConstruction { + + + syn String EModelElement.xmiReference() = xmiPath() + localName(); + + eq EDataType.xmiReference() { + switch (getName()) { + case "EInt": + return "ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"; + case "EString": + return "ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"; + default: + throw new RuntimeException("Unable to create reference to EDataType " + getName()); + } + } + + inh String EModelElement.xmiPath(); + eq EModelElement.getChild(int i).xmiPath() { + if (getParent() != null) { + return xmiPath() + localName(); + } else { + return "#/"; + } + } + + + syn String EModelElement.localName() = localRelativeName(); + eq ENamedElement.localName() { + if (getName() == null || getName() == "") { + if (getParent() != null) { + return localRelativeName(); + } else { + throw new RuntimeException("Unable to create reference to root node (that is not a EDataType)!"); + } + } else { + return "/" + getName(); + } + } + + inh String EModelElement.localRelativeName(); + + eq EPackage.getEClassifier(int i).localRelativeName() = "/@eNamedElements." + i; + eq EPackage.getESubPackage(int i).localRelativeName() = "/@eSubPackages." + i; + + eq EModelElement.getEAnnotation(int i).localRelativeName() = "//@eAnnotations." + i; + eq EAnnotation.getDetail(int i).localRelativeName() = "/@details." + i; + + + eq EClassifier.getETypeParameter(int i).localRelativeName() = "/@eTypeParameters." + i; + + eq ETypedElement.getEGenericType().localRelativeName() = "/@eGenericType"; + + eq EClass.getEStructuralFeature(int i).localRelativeName() = "//@eStructuralFeatures." + i; + eq EClass.getEOperation(int i).localRelativeName() = "/@eOperations." + i; + eq EClass.getEGenericSuperType(int i).localRelativeName() = "//@eGenericSuperTypes." + i; + + eq EEnum.getELiteral(int i).localRelativeName() = "/@eLiterals." + i; + + eq EOperation.getGenericException(int i).localRelativeName() = "/@eGenericExceptions." + i; + eq EOperation.getETypeParameter(int i).localRelativeName() = "/@eTypeParameters." + i; + eq EOperation.getEParameter(int i).localRelativeName() = "/@eParameters." + i; + + eq ETypeParameter.getEBound(int i).localRelativeName() = "/@eBounds." + i; + + eq EGenericType.getEUpperBound().localRelativeName() = "/@eUpperBound"; + eq EGenericType.getELowerBound().localRelativeName() = "/@eLowerBound"; + eq EGenericType.getETypeArgument(int i).localRelativeName() = "/@eTypeArguments." + i; + +} diff --git a/src/main/jastadd/XMI/XMIPathResolution.jrag b/src/main/jastadd/XMI/XMIPathResolution.jrag new file mode 100644 index 0000000..47a4bb7 --- /dev/null +++ b/src/main/jastadd/XMI/XMIPathResolution.jrag @@ -0,0 +1,225 @@ +aspect XMIPathResolution { + + refine RefResolverStubs eq ETypedElement.resolveETypeByToken(String id) { + return (EClassifier) resolve(id); + } + + refine RefResolverStubs eq EReference.resolveEOppositeByToken(String id) { + return (EReference) resolve(id); + } + + refine RefResolverStubs eq EClass.resolveESuperTypesByToken(String id, int position) { + return (EClass) resolve(id); + } + + private static EDataType EDataType.eInt; + public static final EDataType EDataType.eInt() { + if (eInt == null) { + eInt = new EDataType(); + eInt.setInstanceClassName("int"); + eInt.setName("EInt"); + } + return eInt; + } + + private static EDataType EDataType.eString; + public static final EDataType EDataType.eString() { + if (eString == null) { + eString = new EDataType(); + eString.setInstanceClassName("String"); + eString.setName("EString"); + } + return eString; + } + + syn ASTNode ASTNode.resolve(String uriString) { + + // built-in data types + if (uriString.startsWith("ecore:EDataType ")) { + switch (uriString.substring(16)) { + case "http://www.eclipse.org/emf/2002/Ecore#//EInt": + return EDataType.eInt(); + case "http://www.eclipse.org/emf/2002/Ecore#//EString": + return EDataType.eString(); + default: + throw new RuntimeException("Unable to resolve built-in data type '" + uriString + "'"); + } + } + + // TODO EMF uses its own implementation of URI with some extension with regard to the "device" + // like "c:" and for files within archives + String[] uriParts = uriString.split("#", 2); + + String fragment = uriParts.length == 2 ? uriParts[1] : uriParts[0]; + + // TODO we ignore everything before the fragment + return resolvePath(fragment); + } + + syn ASTNode ASTNode.resolvePath(String path) { + int length = path.length(); + if (length > 0) { + if (path.charAt(0) == '/') { + path = path.substring(1); + ASTNode result = resolvePath(path.split("/")); + return result; + } else if (path.charAt(length - 1) == '?') { + int index = path.lastIndexOf('?', length - 2); + if (index > 0) { + path = path.substring(0, index); + } + } else { + throw new RuntimeException("[ASTNode.resolvePath(String path)][2] Unable to resolve path'" + path + "'"); + } + } else { + throw new RuntimeException("Unable to resolve empty path"); + } + + throw new RuntimeException("[ASTNode.resolvePath(String path)][1] Unable to resolve path'" + path + "'"); + // return getEObjectByID(uriFragment); + } + + syn ASTNode ASTNode.resolvePath(String[] path) { + ASTNode result = null; + boolean first = true; + if (path.length == 0) { + return resolvePathRootFragment(""); + } else { + for (String fragment: path) { + if (first) { + first = false; + result = resolvePathRootFragment(fragment); + } else { + result = result.resolvePathFragment(fragment); + } + } + return result; + } + } + + syn ASTNode ASTNode.resolvePathRootFragment(String fragment) { + // This method is only relevant if there are multiple roots, and in this case, in general, it is not possible to + // select the right one (because we cannot navigate to them) + return root(); + } + + syn ASTNode ASTNode.resolvePathFragment(String fragment) { + + int length = fragment.length(); + + if (fragment.length() == 0) { + throw new RuntimeException("Empty fragment cannot be resolved!"); + } else if (fragment.charAt(0) != '@') { + // resolve as name (with optional index) + if (fragment.charAt(0) == '%') { + // TODO this is ignored right now + throw new RuntimeException("Fragment cannot be resolved, because it starts with a '%': '" + fragment + "'."); + } + + int dotIndex = fragment.lastIndexOf('.'); + String name = dotIndex == -1 ? fragment : fragment.substring(0, dotIndex); + int count = 0; + if (dotIndex != -1) { + try { + count = Integer.parseInt(fragment.substring(dotIndex + 1)); + } catch (NumberFormatException exception) { + throw new RuntimeException("Fragment cannot be resolved, because after the last dot is no number: '" + fragment + "'."); + } + } + + if ("%".equals(name)) { + throw new RuntimeException("Fragment '%' cannot be resolved."); + } + + // TODO decode the name + + return getASTNodeByName(name, count); + } + + int lastIndex = fragment.length() - 1; + char lastChar = fragment.charAt(lastIndex); + if (lastChar == ']') { + throw new RuntimeException("predicate references are not supported: '" + fragment + "'."); + } else { + int dotIndex = -1; + if (Character.isDigit(lastChar)) { + dotIndex = fragment.lastIndexOf('.', lastIndex - 1); + } + + int index = -1; + if (dotIndex > 0) { + try { + index = Integer.parseInt(fragment.substring(dotIndex + 1)); + } catch (NumberFormatException exception) { + throw new RuntimeException("the uri fragment contains a '.', but the string behind it is not a nuber: '" + fragment + "'."); + } + } + + return resolveChild(fragment.substring(1, dotIndex), index); + } + } + + coll java.util.Collection<EObject> EPackage.containedEObjects() [new java.util.ArrayList()] with add root EObject; + EObject contributes this to EPackage.containedEObjects() for root(); + + syn java.util.Map<String, ASTNode> ASTNode.idMap() { + java.util.Map<String, ASTNode> idMap = new java.util.HashMap<>(); + + // iterate all contents of the current subtree + for (ASTNode n: containedChildren()) { + if (n.id() != null) { + idMap.put(n.id(), n); + } + } + return idMap; + } + + syn String ASTNode.id() = null; + syn String EObject.id() { + // TODO + return null; + } + + syn String ASTNode.name() = null; + eq ENamedElement.name() = getName(); + + // from eObjectForURIFragmentNameSegment + /** + * The name of an ASTNode is only defined for NamedElements. This makes this method ecore-metamodel-specific + */ + syn ASTNode ASTNode.getASTNodeByName(String name, int index) { + int count = index; + // Look for a matching named element. + for (ASTNode child : containedChildren()) { + if (child.name() != null) { + String otherName = child.name(); + if ((name == null ? otherName == null : name.equals(otherName)) && count-- == 0) { + return child; + } + } + } + + throw new RuntimeException("Unable to find number '" + index + "'child with name '" + name + "'"); + } + +// public static String getID(EObject eObject) { +// EClass eClass = eObject.eClass(); +// EAttribute eIDAttribute = eClass.getEIDAttribute(); +// return eIDAttribute == null || !eObject.eIsSet(eIDAttribute) ? null : convertToString(eIDAttribute.getEAttributeType(), eObject.eGet(eIDAttribute)); +// } + + /** + * from: getEObjectByID + */ + syn ASTNode ASTNode.getASTNodeByID(String id) { + + // in EMF, there is a map that caches all IDs + // Likewise, we create such an ID map and simply perform a lookup + return idMap().get(id); + } + + syn ASTNode ASTNode.resolveChild(String name, int index) { + throw new RuntimeException("No child with name '" + name + "' found."); + } + +} diff --git a/src/main/jastadd/XMI/XMIWriter.jadd b/src/main/jastadd/XMI/XMIWriter.jadd index cc0714b..189c425 100644 --- a/src/main/jastadd/XMI/XMIWriter.jadd +++ b/src/main/jastadd/XMI/XMIWriter.jadd @@ -38,7 +38,7 @@ aspect XMIWriter { // from EPackage b.append(" nsURI=\"").append(getNsURI()).append("\"") .append(" nsPrefix=\"").append(getNsPrefix()).append("\""); - if (containedChildren() == 0) { + if (numContainedChildren() == 0) { b.append("/>\n"); return; } @@ -82,7 +82,20 @@ aspect XMIWriter { if (getInterface() == true) { b.append(" interface=\"true\""); } - if (containedChildren() == 0) { + if (!getESuperTypesList().isEmpty()) { + b.append(" eSuperTypes=\""); + boolean first = true; + for (EClass eSuperType : getESuperTypesList()) { + if (first) { + first = false; + } else { + b.append(" "); + } + b.append(eSuperType.xmiReference()); + } + b.append("\""); + } + if (numContainedChildren() == 0) { b.append("/>\n"); return; } @@ -120,7 +133,7 @@ aspect XMIWriter { // attributes b.append(" name=\"").append(getName()).append("\""); - if (containedChildren() == 0) { + if (numContainedChildren() == 0) { b.append("/>\n"); return; } @@ -162,6 +175,7 @@ aspect XMIWriter { if (getUpperBound() != 1) { b.append(" upperBound=\"" + getUpperBound() + "\""); } + b.append(" eType=\"").append(getEType().xmiReference()).append("\""); // from EStructuralFEature if (getChangeable() == false) { b.append(" changeable=\"false\""); @@ -188,15 +202,18 @@ aspect XMIWriter { if (getResolveProxies() == false) { b.append(" resolveProxies=\"false\""); } + if (hasEOpposite()) { + b.append(" eOpposite=\"").append(getEOpposite().xmiReference()).append("\""); + } if (getNumChild() == 0) { b.append("/>\n"); return; } - if (containedChildren() == 0) { + if (numContainedChildren() == 0) { b.append("/>\n"); return; } - System.out.println(containedChildren()); + System.out.println(numContainedChildren()); b.append(">\n"); // child nodes // from EModelElement @@ -229,6 +246,7 @@ aspect XMIWriter { if (getUpperBound() != 1) { b.append(" upperBound=\"" + getUpperBound() + "\""); } + b.append(" eType=\"").append(getEType().xmiReference()).append("\""); // from EStructuralFEature if (getChangeable() == false) { b.append(" changeable=\"false\""); @@ -256,11 +274,10 @@ aspect XMIWriter { b.append("/>\n"); return; } - if (containedChildren() == 0) { + if (numContainedChildren() == 0) { b.append("/>\n"); return; } - System.out.println(containedChildren()); b.append(">\n"); // child nodes // from EModelElement diff --git a/src/main/jastadd/ecore.relast b/src/main/jastadd/ecore.relast index a3cc7bb..bc96c0f 100644 --- a/src/main/jastadd/ecore.relast +++ b/src/main/jastadd/ecore.relast @@ -26,8 +26,8 @@ EParameter : ETypedElement; EStringToStringMapEntry ::= <Key:String> <Value:String>; -ETypeParameter : ENamedElement ::= EBounds:EGenericType*; -EGenericType ::= [EUpperBound:EGenericType] [ELowerBound:EGenericType] [ETypeArguments:EGenericType]; +ETypeParameter : ENamedElement ::= EBound:EGenericType*; +EGenericType ::= [EUpperBound:EGenericType] [ELowerBound:EGenericType] ETypeArgument:EGenericType*; rel ETypedElement.EType -> EClassifier; rel EOperation.EExceptions* -> EClassifier; diff --git a/src/main/java/de/tudresden/inf/st/e2j/parser/EcoreParser.java b/src/main/java/de/tudresden/inf/st/e2j/parser/EcoreParser.java index d05d28c..16ff7ab 100644 --- a/src/main/java/de/tudresden/inf/st/e2j/parser/EcoreParser.java +++ b/src/main/java/de/tudresden/inf/st/e2j/parser/EcoreParser.java @@ -194,7 +194,9 @@ public class EcoreParser { eClass.setInterface(Boolean.valueOf(attribute.getValue())); break; case "eSuperTypes": - eClass.addESuperTypes(EClass.createRefDirection(attribute.getValue())); + for (String superType : attribute.getValue().split(" ")) { + eClass.addESuperTypes(EClass.createRefDirection(superType)); + } break; default: logger.warn("ignoring attribute {}:{}", attribute.getName().getPrefix(), attribute.getName().getLocalPart()); -- GitLab