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