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

Issue:
Nodes which group other nodes must be wrapped in `CtVirtualElement`
#184 introduced the feature to parse all super interfaces and put it under `SUPER_INTERFACES` which is a fake node. Speaking more technically, it doesn't have a corresponding spoon node. In fact, it has a corresponding spoon collection of `CtTypeReferences` (`CtType.getSuperInterfaces()`) which is not acceptable by [operations because they can't be cast to an instance of `CtElement`](https://github.com/SpoonLabs/gumtree-spoon-ast-diff/blob/master/src/main/java/gumtree/spoon/diff/operations/AdditionOperation.java#L15). So in case of the following diff, `operation.getSrcNode()` fails because there is no metadata for spoon node.
```diff
- class Test
+ class Test implements A
```

We would need to wrap such _fake nodes_ in `CtVirtualElement` so that `getSrcNode()` can return a valid instance of `CtElement` and help `diffmin` apply patches. Another approach would be to follow [approach 1 as explained here](https://github.com/SpoonLabs/gumtree-spoon-ast-diff/issues/181#issue-992891542) which completely eliminates `SUPER_INTERFACES` fake node.

> Please note that this situation is quite analogous to what we already do for `ModifierKind`s.

Pull request description:
fix: ensure insertion of node metadata is incorporated in the edit script
Fixes #192 

Patch:
diff --git a/src/main/java/gumtree/spoon/builder/NodeCreator.java b/src/main/java/gumtree/spoon/builder/NodeCreator.java
index d9eaf20..e7a770e 100644
--- a/src/main/java/gumtree/spoon/builder/NodeCreator.java
+++ b/src/main/java/gumtree/spoon/builder/NodeCreator.java
@@ -1,6 +1,7 @@
 package gumtree.spoon.builder;
 
 import com.github.gumtreediff.tree.ITree;
+import spoon.reflect.declaration.CtElement;
 import spoon.reflect.declaration.CtMethod;
 import spoon.reflect.declaration.CtModifiable;
 import spoon.reflect.declaration.CtTypeInformation;
@@ -94,7 +95,13 @@ public class NodeCreator extends CtInheritanceScanner {
 			return;
 		}
 
-		ITree superInterfaceRoot = builder.createNode("SUPER_INTERFACES", "SuperInterfaces");
+		// create the root super interface node whose children will be *actual* spoon nodes of interfaces
+		String typeLabel = "SuperInterfaces";
+		ITree superInterfaceRoot = builder.createNode("SUPER_INTERFACES", typeLabel);
+		String virtualNodeDescription = typeLabel + "_" + typeReference.getQualifiedName();
+		superInterfaceRoot.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, new CtVirtualElement(virtualNodeDescription, (CtElement) typeReference, typeReference.getSuperInterfaces(), CtRole.INTERFACE));
+
+		// attach each super interface to the root created above
 		for (CtTypeReference<?> superInterface: typeReference.getSuperInterfaces()) {
 			ITree superInterfaceNode = builder.createNode("INTERFACE", superInterface.getQualifiedName());
 			superInterfaceNode.setMetadata(SpoonGumTreeBuilder.SPOON_OBJECT, superInterface);


Test patch:
diff --git a/src/test/java/gumtree/spoon/diff/DiffTest.java b/src/test/java/gumtree/spoon/diff/DiffTest.java
index a3f6162..0cf07cd 100644
--- a/src/test/java/gumtree/spoon/diff/DiffTest.java
+++ b/src/test/java/gumtree/spoon/diff/DiffTest.java
@@ -1,5 +1,6 @@
 package gumtree.spoon.diff;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -8,6 +9,7 @@ import java.util.List;
 
 import org.junit.Test;
 
+import gumtree.spoon.builder.CtVirtualElement;
 import gumtree.spoon.AstComparator;
 import gumtree.spoon.diff.operations.Operation;
 import gumtree.spoon.diff.operations.OperationKind;
@@ -283,4 +285,20 @@ public class DiffTest {
 		// initial list is nested inside the above inserted list
 		assertTrue(diff.containsOperation(OperationKind.Move, "TYPE_ARGUMENT", "java.util.List"));
 	}
+
+	@Test
+	public void test_diffOfSuperInterfaces_insertionOfRootNodeOfInterface() throws Exception {
+		File left = new File("src/test/resources/examples/superInterfaces/insertion/left.java");
+		File right = new File("src/test/resources/examples/superInterfaces/insertion/right.java");
+
+		Diff diff = new AstComparator().compare(left, right);
+
+		// assert that only the root of super interfaces is inserted
+		assertEquals(1, diff.getRootOperations().size());
+		assertTrue(diff.containsOperation(OperationKind.Insert, "SUPER_INTERFACES", "SuperInterfaces"));
+
+		// verify children of the inserted root node
+		CtVirtualElement superInterfaceRoot = (CtVirtualElement) diff.getRootOperations().get(0).getSrcNode();
+		assertArrayEquals(new String[]{"A", "B"}, superInterfaceRoot.getChildren().stream().map(Object::toString).toArray());
+	}
 }
diff --git a/src/test/resources/examples/superInterfaces/insertion/left.java b/src/test/resources/examples/superInterfaces/insertion/left.java
new file mode 100644
index 0000000..a8f3f01
--- /dev/null
+++ b/src/test/resources/examples/superInterfaces/insertion/left.java
@@ -0,0 +1,1 @@
+class InsertionOfInterface { }
diff --git a/src/test/resources/examples/superInterfaces/insertion/right.java b/src/test/resources/examples/superInterfaces/insertion/right.java
new file mode 100644
index 0000000..fb1dcf9
--- /dev/null
+++ b/src/test/resources/examples/superInterfaces/insertion/right.java
@@ -0,0 +1,1 @@
+class InsertionOfInterface implements A, B { }