Repository: SpoonLabs/gumtree-spoon-ast-diff (java)

Issue:
Super interfaces are not parsed in the ITree model
The super interfaces are ignored for now because instances of `CtReference` in general are ignored. Consider the code,
```java
class Example implements A, B, C { }
```
Its tree only has two nodes - `root` and `Example`.
```
-1@@
	65190232@@Example
```

I propose two models via which super interfaces can be incorporated.

- ```
  -1@@
	65190232@@Example
              <int>@@A
              <int>@@B
              <int>@@C
              ...other children...
  ```
- ```
  -1@@
	65190232@@Example
              <int>@@SUPER_INTERFACE
                   <int>@@A
                   <int>@@B
                   <int>@@C
              ...other children...
  ```

I am more inclined towards the second approach because of semantic reasons. Moreover, modifiers are also clubbed into a single parent called a virtual element.

Pull request description:
feat: support super interfaces in AST edit scripts
Fixes #181 

I shall resume the work after #179 is merged because it has some changes on which this PR depends.

Patch:
diff --git a/src/main/java/gumtree/spoon/builder/LabelFinder.java b/src/main/java/gumtree/spoon/builder/LabelFinder.java
index ab76708..9214e38 100644
--- a/src/main/java/gumtree/spoon/builder/LabelFinder.java
+++ b/src/main/java/gumtree/spoon/builder/LabelFinder.java
@@ -155,8 +155,6 @@ class LabelFinder extends CtInheritanceScanner {
 
 	@Override
 	public <T> void visitCtTypeReference(CtTypeReference<T> e) {
-		if (e.getRoleInParent() == CtRole.SUPER_TYPE) {
 			label = e.getQualifiedName();
-		}
 	}
 }
diff --git a/src/main/java/gumtree/spoon/builder/NodeCreator.java b/src/main/java/gumtree/spoon/builder/NodeCreator.java
index 812308c..6870d80 100644
--- a/src/main/java/gumtree/spoon/builder/NodeCreator.java
+++ b/src/main/java/gumtree/spoon/builder/NodeCreator.java
@@ -3,6 +3,7 @@ package gumtree.spoon.builder;
 import com.github.gumtreediff.tree.ITree;
 import spoon.reflect.declaration.CtMethod;
 import spoon.reflect.declaration.CtModifiable;
+import spoon.reflect.declaration.CtTypeInformation;
 import spoon.reflect.declaration.CtVariable;
 import spoon.reflect.declaration.ModifierKind;
 import spoon.reflect.path.CtRole;
@@ -87,6 +88,23 @@ public class NodeCreator extends CtInheritanceScanner {
 		}
 	}
 
+	@Override
+	public <T> void scanCtTypeInformation(CtTypeInformation typeReference) {
+		if (typeReference.getSuperInterfaces().isEmpty()) {
+			return;
+		}
+
+		ITree superInterfaceRoot = builder.createNode("SUPER_INTERFACES", "SuperInterfaces");
+		for (CtTypeReference<?> superInterface: typeReference.getSuperInterfaces()) {
+			ITree superInterfaceNode = builder.createNode("INTERFACE", superInterface.getQualifiedName());
+			superInterfaceNode.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, superInterface);
+			superInterface.putMetadata(SpoonGumTreeBuilder.GUMTREE_NODE, superInterfaceNode);
+			superInterfaceRoot.addChild(superInterfaceNode);
+			computeTreeOfTypeReferences(superInterface, superInterfaceNode);
+		}
+		builder.addSiblingNode(superInterfaceRoot);
+	}
+
 	/** Creates a tree of nested type references where each nested type reference is a child of its container. */
 	private void computeTreeOfTypeReferences(CtTypeReference<?> type, ITree parentType) {
 		for (CtTypeReference<?> ctTypeArgument: type.getActualTypeArguments()) {
diff --git a/src/main/java/gumtree/spoon/diff/operations/Operation.java b/src/main/java/gumtree/spoon/diff/operations/Operation.java
index 5def376..e165408 100644
--- a/src/main/java/gumtree/spoon/diff/operations/Operation.java
+++ b/src/main/java/gumtree/spoon/diff/operations/Operation.java
@@ -6,7 +6,6 @@ import com.github.gumtreediff.actions.model.Update;
 
 import gumtree.spoon.builder.SpoonGumTreeBuilder;
 import spoon.reflect.cu.position.NoSourcePosition;
-import spoon.reflect.declaration.CtClass;
 import spoon.reflect.declaration.CtElement;
 import spoon.reflect.declaration.CtPackage;
 import spoon.reflect.declaration.CtType;
@@ -69,11 +68,11 @@ public abstract class Operation<T extends Action> {
 		}
 		if (action instanceof Move) {
 			CtElement elementDest = (CtElement) action.getNode().getMetadata(SpoonGumTreeBuilder.SPOON_OBJECT_DEST);
-			position = " from " + element.getParent(CtClass.class).getQualifiedName();
+			position = " from " + element.getParent(CtType.class).getQualifiedName();
 			if (element.getPosition() != null && !(element.getPosition() instanceof NoSourcePosition)) {
 				position += ":" + element.getPosition().getLine();
 			}
-			position += " to " + elementDest.getParent(CtClass.class).getQualifiedName();
+			position += " to " + elementDest.getParent(CtType.class).getQualifiedName();
 			if (elementDest.getPosition() != null && !(elementDest.getPosition() instanceof NoSourcePosition)) {
 				position += ":" + elementDest.getPosition().getLine();
 			}


Test patch:
diff --git a/src/test/java/gumtree/spoon/TreeTest.java b/src/test/java/gumtree/spoon/TreeTest.java
index a3e5119..108eee8 100644
--- a/src/test/java/gumtree/spoon/TreeTest.java
+++ b/src/test/java/gumtree/spoon/TreeTest.java
@@ -559,4 +559,13 @@ public class TreeTest {
 		assertEquals("Not enough String type arguments", 1, stringLabelsCount);
 		assertEquals("There should only be list and string labels", childLabels.size(), listLabelsCount + stringLabelsCount);
 	}
+
+	@Test
+	public void test_superInterfacesShouldBeDescendantsOfClassNode() {
+		CtClass<?> spoonClass = Launcher.parseClass("class Example implements A, B, C { }");
+		ITree root = new SpoonGumTreeBuilder().getTree(spoonClass);
+		ITree superInterfaces = root.getChild(0).getChild(0);
+
+		assertEquals(3, superInterfaces.getDescendants().size());
+	}
 }
diff --git a/src/test/java/gumtree/spoon/diff/DiffTest.java b/src/test/java/gumtree/spoon/diff/DiffTest.java
index 9a85fb1..a3f6162 100644
--- a/src/test/java/gumtree/spoon/diff/DiffTest.java
+++ b/src/test/java/gumtree/spoon/diff/DiffTest.java
@@ -250,4 +250,37 @@ public class DiffTest {
 		assertTrue(diff.containsOperation(OperationKind.Move, "TYPE_ARGUMENT"));
 		assertTrue(diff.containsOperation(OperationKind.Insert, "TYPE_ARGUMENT"));
 	}
+
+	@Test
+	public void test_diffOfSuperInterfaces_class() throws Exception {
+		File left = new File("src/test/resources/examples/superInterfaces/class/left.java");
+		File right = new File("src/test/resources/examples/superInterfaces/class/right.java");
+
+		Diff diff = new AstComparator().compare(left, right);
+
+		assertTrue(diff.containsOperation(OperationKind.Insert, "INTERFACE", "A"));
+		assertTrue(diff.containsOperation(OperationKind.Insert, "INTERFACE", "D"));
+		assertTrue(diff.containsOperation(OperationKind.Insert, "TYPE_ARGUMENT", "T"));
+		// outer list is inserted
+		assertTrue(diff.containsOperation(OperationKind.Insert, "TYPE_ARGUMENT", "java.util.List"));
+		// initial list is nested inside the above inserted list
+		assertTrue(diff.containsOperation(OperationKind.Move, "TYPE_ARGUMENT", "java.util.List"));
+
+	}
+
+	@Test
+	public void test_diffOfSuperInterfaces_interface() throws Exception {
+		File left = new File("src/test/resources/examples/superInterfaces/interface/left.java");
+		File right = new File("src/test/resources/examples/superInterfaces/interface/right.java");
+
+		Diff diff = new AstComparator().compare(left, right);
+
+		assertTrue(diff.containsOperation(OperationKind.Delete, "INTERFACE", "A"));
+		assertTrue(diff.containsOperation(OperationKind.Insert, "INTERFACE", "D"));
+		assertTrue(diff.containsOperation(OperationKind.Insert, "TYPE_ARGUMENT", "T"));
+		// outer list is inserted
+		assertTrue(diff.containsOperation(OperationKind.Insert, "TYPE_ARGUMENT", "java.util.List"));
+		// initial list is nested inside the above inserted list
+		assertTrue(diff.containsOperation(OperationKind.Move, "TYPE_ARGUMENT", "java.util.List"));
+	}
 }
diff --git a/src/test/resources/examples/spoon.json b/src/test/resources/examples/spoon.json
index b55c359..a1c3899 100644
--- a/src/test/resources/examples/spoon.json
+++ b/src/test/resources/examples/spoon.json
@@ -6,6 +6,27 @@
       "label": "QuickNotepad",
       "type": "Class",
       "children": [
+        {
+          "label": "SuperInterfaces",
+          "type": "SUPER_INTERFACES",
+          "children": [
+            {
+              "label": "org.gjt.sp.jedit.EBComponent",
+              "type": "INTERFACE",
+              "children": []
+            },
+            {
+              "label": "QuickNotepadActions",
+              "type": "INTERFACE",
+              "children": []
+            },
+            {
+              "label": "org.gjt.sp.jedit.gui.DefaultFocusComponent",
+              "type": "INTERFACE",
+              "children": []
+            }
+          ]
+        },
         {
           "label": "",
           "type": "Modifiers_Class",
@@ -20,7 +41,19 @@
         {
           "label": "javax.swing.JPanel",
           "type": "SUPER_TYPE",
-          "children": []
+          "children": [
+            {
+              "label": "SuperInterfaces",
+              "type": "SUPER_INTERFACES",
+              "children": [
+                {
+                  "label": "javax.accessibility.Accessible",
+                  "type": "INTERFACE",
+                  "children": []
+                }
+              ]
+            }
+          ]
         },
         {
           "label": "serialVersionUID",
diff --git a/src/test/resources/examples/superInterfaces/class/left.java b/src/test/resources/examples/superInterfaces/class/left.java
new file mode 100644
index 0000000..4a899aa
--- /dev/null
+++ b/src/test/resources/examples/superInterfaces/class/left.java
@@ -0,0 +1,3 @@
+import java.util.List;
+
+class NestedTypes<T> implements B, C<List<T>> { }
diff --git a/src/test/resources/examples/superInterfaces/class/right.java b/src/test/resources/examples/superInterfaces/class/right.java
new file mode 100644
index 0000000..514e676
--- /dev/null
+++ b/src/test/resources/examples/superInterfaces/class/right.java
@@ -0,0 +1,3 @@
+import java.util.List;
+
+class NestedTypes<T> implements A, B<T>, C<List<List<T>>>, D { }
diff --git a/src/test/resources/examples/superInterfaces/interface/left.java b/src/test/resources/examples/superInterfaces/interface/left.java
new file mode 100644
index 0000000..a90abdc
--- /dev/null
+++ b/src/test/resources/examples/superInterfaces/interface/left.java
@@ -0,0 +1,3 @@
+import java.util.List;
+
+interface NestedTypes<T> extends A, B, C<List<T>> { }
diff --git a/src/test/resources/examples/superInterfaces/interface/right.java b/src/test/resources/examples/superInterfaces/interface/right.java
new file mode 100644
index 0000000..dc0486b
--- /dev/null
+++ b/src/test/resources/examples/superInterfaces/interface/right.java
@@ -0,0 +1,3 @@
+import java.util.List;
+
+interface NestedTypes<T> extends B<T>, C<List<List<T>>>, D { }