diff --git a/src/main/java/se/kth/spork/base3dm/ChangeSet.java b/src/main/java/se/kth/spork/base3dm/ChangeSet.java index e3c28af0..5e653f6c 100644 --- a/src/main/java/se/kth/spork/base3dm/ChangeSet.java +++ b/src/main/java/se/kth/spork/base3dm/ChangeSet.java @@ -196,7 +196,7 @@ private void add(Set> tree, Function getContent) { addToLookupTable(classRepPred, classRepPcs, predecessors); addToLookupTable(classRepSucc, classRepPcs, successors); } - if (!pred.isListEdge()) { + if (!pred.isVirtual()) { Content c = new Content(pcs, getContent.apply(pred)); addToLookupTable(classRepPred, c, content); } @@ -204,10 +204,10 @@ private void add(Set> tree, Function getContent) { } private Pcs addToStar(Pcs pcs) { - T root = pcs.getRoot(); - T pred = pcs.getPredecessor(); - T succ = pcs.getSuccessor(); - Pcs classRepPcs = new Pcs(classRepMap.get(root), classRepMap.get(pred), classRepMap.get(succ), pcs.getRevision()); + T root = classRepMap.get(pcs.getRoot()); + T pred = classRepMap.get(pcs.getPredecessor()); + T succ = classRepMap.get(pcs.getSuccessor()); + Pcs classRepPcs = new Pcs(root, pred, succ, pcs.getRevision()); pcsSet.add(classRepPcs); return classRepPcs; } diff --git a/src/main/java/se/kth/spork/base3dm/ListNode.java b/src/main/java/se/kth/spork/base3dm/ListNode.java index 2ecfea28..16503259 100644 --- a/src/main/java/se/kth/spork/base3dm/ListNode.java +++ b/src/main/java/se/kth/spork/base3dm/ListNode.java @@ -27,4 +27,11 @@ default boolean isEndOfList() { default boolean isListEdge() { return isStartOfList() || isEndOfList(); } + + /** + * @return true iff this node is a virtual node. + */ + default boolean isVirtual() { + return isListEdge(); + } } diff --git a/src/main/java/se/kth/spork/base3dm/Pcs.java b/src/main/java/se/kth/spork/base3dm/Pcs.java index bc2e1428..615f8ca6 100644 --- a/src/main/java/se/kth/spork/base3dm/Pcs.java +++ b/src/main/java/se/kth/spork/base3dm/Pcs.java @@ -21,6 +21,10 @@ public class Pcs { * @param revision The revision this PCS is related to. */ public Pcs(T root, T predecessor, T successor, Revision revision) { + if (root == null || predecessor == null || successor == null) { + throw new IllegalArgumentException("nodes may not be null"); + } + this.root = root; this.predecessor = predecessor; this.successor = successor; diff --git a/src/main/java/se/kth/spork/spoon/PcsBuilder.java b/src/main/java/se/kth/spork/spoon/PcsBuilder.java index 8e9b669a..19002d3f 100644 --- a/src/main/java/se/kth/spork/spoon/PcsBuilder.java +++ b/src/main/java/se/kth/spork/spoon/PcsBuilder.java @@ -5,6 +5,7 @@ import se.kth.spork.spoon.wrappers.NodeFactory; import se.kth.spork.spoon.wrappers.SpoonNode; import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtMethod; import spoon.reflect.visitor.CtScanner; import java.util.*; @@ -15,7 +16,7 @@ * @author Simon Larsén */ class PcsBuilder extends CtScanner { - private Map rootTolastSibling = new HashMap<>(); + private Map parentToLastSibling = new HashMap<>(); private Set> pcses = new HashSet<>(); private SpoonNode root = null; private Revision revision; @@ -23,7 +24,7 @@ class PcsBuilder extends CtScanner { public PcsBuilder(Revision revision) { super(); this.revision = revision; - rootTolastSibling.put(NodeFactory.ROOT, NodeFactory.startOfChildList(NodeFactory.ROOT)); + parentToLastSibling.put(NodeFactory.ROOT, NodeFactory.startOfChildList(NodeFactory.ROOT)); } /** @@ -36,36 +37,58 @@ public PcsBuilder(Revision revision) { public static Set> fromSpoon(CtElement spoonClass, Revision revision) { PcsBuilder scanner = new PcsBuilder(revision); scanner.scan(spoonClass); + scanner.finishPcses(); return scanner.getPcses(); } @Override protected void enter(CtElement e) { SpoonNode wrapped = NodeFactory.wrap(e); + SpoonNode parent = wrapped.getParent(); + if (root == null) root = wrapped; - rootTolastSibling.put(wrapped, NodeFactory.startOfChildList(wrapped)); + parentToLastSibling.put(wrapped, NodeFactory.startOfChildList(wrapped)); - SpoonNode parent = wrapped.getParent(); - SpoonNode predecessor = rootTolastSibling.get(parent); + SpoonNode predecessor = parentToLastSibling.getOrDefault(parent, NodeFactory.startOfChildList(parent)); pcses.add(new Pcs<>(parent, predecessor, wrapped, revision)); - rootTolastSibling.put(parent, wrapped); + parentToLastSibling.put(parent, wrapped); } - @Override - protected void exit(CtElement e) { - SpoonNode current = NodeFactory.wrap(e); - SpoonNode predecessor = rootTolastSibling.get(current); - SpoonNode successor = NodeFactory.endOfChildList(current); - pcses.add(new Pcs<>(current, predecessor, successor, revision)); + private void finishPcses() { + for (Map.Entry nodePair : parentToLastSibling.entrySet()) { + SpoonNode parent = nodePair.getKey(); + SpoonNode lastSibling = nodePair.getValue(); + if (parent.isVirtual()) { + // this is either the virtual root, or a RoleNode + // we just need to close their child lists + pcses.add(new Pcs<>(parent, lastSibling, NodeFactory.endOfChildList(parent), revision)); + } else { + // this is a concrete node, we must add all of its virtual children to the PCS structure, except for + // the start of the child list as it has all ready been added + List virtualNodes = parent.getVirtualNodes(); + SpoonNode pred = lastSibling; + for (SpoonNode succ : virtualNodes.subList(1, virtualNodes.size())) { + pcses.add(new Pcs<>(parent, pred, succ, revision)); - if (current.getParent() == NodeFactory.ROOT) { - // need to finish the virtual root's child list artificially as it is not a real node - pcses.add(new Pcs<>(NodeFactory.ROOT, current, NodeFactory.endOfChildList(NodeFactory.ROOT), revision)); + // also need to create "leaf child lists" for any non-list-edge virtual node that does not have any + // children, or Spork will not discover removals that entirely empty the child list. + // The problem is detailed in https://github.com/KTH/spork/issues/116 + if (!pred.isListEdge() && !parentToLastSibling.containsKey(pred)) { + pcses.add(createLeafPcs(pred)); + } + + pred = succ; + } + } } } + private Pcs createLeafPcs(SpoonNode node) { + return new Pcs<>(node, NodeFactory.startOfChildList(node), NodeFactory.endOfChildList(node), revision); + } + public Set> getPcses() { return pcses; } diff --git a/src/main/java/se/kth/spork/spoon/matching/ClassRepresentatives.java b/src/main/java/se/kth/spork/spoon/matching/ClassRepresentatives.java index c5659176..1031bfd4 100644 --- a/src/main/java/se/kth/spork/spoon/matching/ClassRepresentatives.java +++ b/src/main/java/se/kth/spork/spoon/matching/ClassRepresentatives.java @@ -5,10 +5,12 @@ import se.kth.spork.spoon.wrappers.SpoonNode; import se.kth.spork.spoon.wrappers.NodeFactory; import spoon.reflect.declaration.CtElement; +import spoon.reflect.reference.CtReference; import spoon.reflect.visitor.CtScanner; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; /** @@ -126,13 +128,19 @@ private static void mapNodes(SpoonNode from, SpoonNode to, Map fromVirtualNodes = from.getVirtualNodes(); + List toVirtualNodes = to.getVirtualNodes(); - classRepMap.put(fromSOL, toSOL); - classRepMap.put(fromEOL, toEOL); + for (int i = 0; i < fromVirtualNodes.size(); i++) { + SpoonNode fromVirt = fromVirtualNodes.get(i); + SpoonNode toVirt = toVirtualNodes.get(i); + + if (fromVirt.isListEdge()) { + classRepMap.put(fromVirt, toVirt); + } else { + mapNodes(fromVirt, toVirt, classRepMap); + } + } } /** diff --git a/src/main/java/se/kth/spork/spoon/matching/SpoonMapping.java b/src/main/java/se/kth/spork/spoon/matching/SpoonMapping.java index 7947da5a..36244402 100644 --- a/src/main/java/se/kth/spork/spoon/matching/SpoonMapping.java +++ b/src/main/java/se/kth/spork/spoon/matching/SpoonMapping.java @@ -4,6 +4,7 @@ import com.github.gumtreediff.matchers.MappingStore; import com.github.gumtreediff.tree.ITree; import com.github.gumtreediff.utils.Pair; +import gumtree.spoon.builder.CtWrapper; import gumtree.spoon.builder.SpoonGumTreeBuilder; import se.kth.spork.spoon.wrappers.SpoonNode; import se.kth.spork.spoon.wrappers.NodeFactory; @@ -98,6 +99,9 @@ private static boolean ignoreMapping(CtElement src, CtElement dst) { return true; } else if (isPrimitiveType(src) != isPrimitiveType(dst)) { return true; + } else if (src instanceof CtWrapper || dst instanceof CtWrapper) { + // the CtWrapper elements do not represent real Spoon nodes, and so are just noise + return true; } return false; } diff --git a/src/main/java/se/kth/spork/spoon/pcsinterpreter/SpoonTreeBuilder.java b/src/main/java/se/kth/spork/spoon/pcsinterpreter/SpoonTreeBuilder.java index 1f307f87..c7490271 100644 --- a/src/main/java/se/kth/spork/spoon/pcsinterpreter/SpoonTreeBuilder.java +++ b/src/main/java/se/kth/spork/spoon/pcsinterpreter/SpoonTreeBuilder.java @@ -187,6 +187,9 @@ private CtElement visit(SporkTree sporkParent, SporkTree sporkChild) { } } + // NOTE: Super important that the parent of the merge tree is set no matter what, as wrapping a spoon CtElement + // in a SpoonNode requires access to its parent. + mergeTree.setParent(mergeParent); nodes.put(origTreeNode, NodeFactory.wrap(mergeTree)); return mergeTree; diff --git a/src/main/java/se/kth/spork/spoon/pcsinterpreter/SporkTreeBuilder.java b/src/main/java/se/kth/spork/spoon/pcsinterpreter/SporkTreeBuilder.java index 8b68b50a..0956a4d5 100644 --- a/src/main/java/se/kth/spork/spoon/pcsinterpreter/SporkTreeBuilder.java +++ b/src/main/java/se/kth/spork/spoon/pcsinterpreter/SporkTreeBuilder.java @@ -107,8 +107,6 @@ public SporkTree buildTree() { public SporkTree build(SpoonNode currentRoot) { Map> children = rootToChildren.get(currentRoot); - SpoonNode next = NodeFactory.startOfChildList(currentRoot); - Set> currentContent = contents.getOrDefault(currentRoot, Collections.emptySet()); SporkTree tree = new SporkTree(currentRoot, currentContent); @@ -116,28 +114,7 @@ public SporkTree build(SpoonNode currentRoot) { return tree; try { - while (true) { - Pcs nextPcs = children.get(next); - tree.addRevision(nextPcs.getRevision()); - - next = nextPcs.getSuccessor(); - if (next.isEndOfList()) { - break; - } - - Set> conflicts = structuralConflicts.get(nextPcs); - Optional> successorConflict = conflicts == null ? Optional.empty() : - conflicts.stream().filter(confPcs -> - StructuralConflict.isSuccessorConflict(nextPcs, confPcs)).findFirst(); - - // successor conflicts mark the start of a conflict, any other conflict is to be ignored - if (successorConflict.isPresent()) { - Pcs conflicting = successorConflict.get(); - next = traverseConflict(nextPcs, conflicting, children, tree); - } else { - addChild(tree, build(next)); - } - } + build(NodeFactory.startOfChildList(currentRoot), tree, children); for (Pcs inconsistent : remainingInconsistencies) { if (inconsistent.getRoot().equals(tree.getNode())) { @@ -158,6 +135,49 @@ public SporkTree build(SpoonNode currentRoot) { return tree; } + private void build(SpoonNode start, SporkTree tree, Map> children) { + if (children == null) // leaf node + return; + + + SpoonNode next = start; + while (true) { + Pcs nextPcs = children.get(next); + tree.addRevision(nextPcs.getRevision()); + + next = nextPcs.getSuccessor(); + + if (next.isListEdge()) { + // can still have a conflict at the end of the child list + getSuccessorConflict(nextPcs).map(conflicting -> traverseConflict(nextPcs, conflicting, children, tree)); + break; + } + + if (next.isVirtual() && !next.isListEdge()) { + build(NodeFactory.startOfChildList(next), tree, rootToChildren.get(next)); + } else { + Optional> successorConflict = getSuccessorConflict(nextPcs); + if (successorConflict.isPresent()) { + Pcs conflicting = successorConflict.get(); + next = traverseConflict(nextPcs, conflicting, children, tree); + } else { + addChild(tree, build(next)); + } + + if (next.isEndOfList()) { + break; + } + } + } + } + + private Optional> getSuccessorConflict(Pcs pcs) { + Set> conflicts = structuralConflicts.get(pcs); + return conflicts == null ? Optional.empty() : + conflicts.stream().filter(confPcs -> + StructuralConflict.isSuccessorConflict(pcs, confPcs)).findFirst(); + } + /** * When a conflict in the child list of a node is not possible to resolve, we approximate the conflict by finding * the node's matches in the left and right revisions and have them make up the conflict instead. This is a rough @@ -211,6 +231,9 @@ private SpoonNode traverseConflict( List rightNodes = extractConflictList(rightPcs, children); Optional> resolved = tryResolveConflict(leftNodes, rightNodes); + + // if nextPcs happens to be the final PCS of a child list, next may be a virtual node + next = leftNodes.isEmpty() ? rightNodes.get(rightNodes.size() - 1) : leftNodes.get(leftNodes.size() - 1); if (resolved.isPresent()) { resolved.get().forEach(child -> addChild(tree, build(child)) @@ -225,7 +248,7 @@ private SpoonNode traverseConflict( tree.addChild(new SporkTree(next, contents.get(next), conflict)); } // by convention, build left tree - return leftNodes.isEmpty() ? next : leftNodes.get(leftNodes.size() - 1); + return next; } private void addChild(SporkTree tree, SporkTree child) { diff --git a/src/main/java/se/kth/spork/spoon/wrappers/NodeFactory.java b/src/main/java/se/kth/spork/spoon/wrappers/NodeFactory.java index 752cfd53..52a83cdd 100644 --- a/src/main/java/se/kth/spork/spoon/wrappers/NodeFactory.java +++ b/src/main/java/se/kth/spork/spoon/wrappers/NodeFactory.java @@ -3,9 +3,27 @@ import se.kth.spork.base3dm.Revision; import se.kth.spork.base3dm.TdmMerge; import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtExecutable; +import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.CtTypeMember; +import spoon.reflect.declaration.CtTypeParameter; import spoon.reflect.factory.ModuleFactory; - +import spoon.reflect.meta.RoleHandler; +import spoon.reflect.meta.impl.RoleHandlerHelper; +import spoon.reflect.path.CtRole; +import spoon.reflect.reference.CtExecutableReference; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Wraps a CtElement and stores the wrapper in the CtElement's metadata. @@ -17,6 +35,41 @@ public class NodeFactory { private static long currentKey = 0; public static final SpoonNode ROOT = new Root(); + private static final Map, List> EXPLODED_TYPE_ROLES; + private static final List> EXPLODED_TYPES = Arrays.asList( + CtExecutableReference.class, CtExecutable.class, CtType.class + ); + + // These are roles that are present in the EXPLODED_TYPES types, but are either not structural + // or are always present as a single node (such as a method body) + private static final Set IGNORED_ROLES = Stream.of( + /* START NON-STRUCTURAL ROLES */ + CtRole.IS_IMPLICIT, + CtRole.IS_DEFAULT, + CtRole.IS_VARARGS, + CtRole.IS_FINAL, + CtRole.IS_SHADOW, + CtRole.IS_STATIC, + CtRole.DECLARING_TYPE, + CtRole.MODIFIER, + CtRole.EMODIFIER, + CtRole.NAME, + CtRole.POSITION, + /* END NON-STRUCTURAL ROLES */ + CtRole.BODY, // always present as a single node + CtRole.NESTED_TYPE, // falls under type member + CtRole.FIELD, // falls under type member + CtRole.METHOD // falls under type member + ).collect(Collectors.toSet()); + + static { + Map, List> rolesPerClass = new HashMap<>(); + for (Class cls : EXPLODED_TYPES) { + rolesPerClass.put(cls, getRoles(cls).filter(role -> !IGNORED_ROLES.contains(role)).collect(Collectors.toList())); + } + EXPLODED_TYPE_ROLES = Collections.unmodifiableMap(rolesPerClass); + } + /** * Wrap a CtElement in a CtWrapper. The wrapper is stored in the CtElement's metadata. If a CtElement that has * already been wrapped is passed in, then its existing wrapper is returned. In other words, each CtElement gets @@ -26,14 +79,60 @@ public class NodeFactory { * @return A wrapper around the CtElement that is more practical for hashing purposes. */ public static SpoonNode wrap(CtElement elem) { + return wrapInternal(elem); + } + + private static Node wrapInternal(CtElement elem) { Object wrapper = elem.getMetadata(WRAPPER_METADATA); if (wrapper == null) { - wrapper = new Node(elem, currentKey++); - elem.putMetadata(WRAPPER_METADATA, wrapper); + return initializeWrapper(elem); } - return (SpoonNode) wrapper; + return (Node) wrapper; + } + + private static Node initializeWrapper(CtElement elem) { + if (elem instanceof ModuleFactory.CtUnnamedModule) + return initializeWrapper(elem, ROOT); + + CtElement spoonParent = elem.getParent(); + CtRole roleInParent = elem.getRoleInParent(); + Node actualParent = wrapInternal(spoonParent); + SpoonNode effectiveParent = actualParent.hasRoleNodeFor(roleInParent) ? + actualParent.getRoleNode(roleInParent) : actualParent; + return initializeWrapper(elem, effectiveParent); + } + + private static Node initializeWrapper(CtElement elem, SpoonNode parent) { + List availableChildRoles = getVirtualNodeChildRoles(elem); + Node node = new Node(elem, parent, currentKey++, availableChildRoles); + elem.putMetadata(WRAPPER_METADATA, node); + return node; + } + + /** + * Return a list of child nodes that should be exploded into virtual types for the given element. + * + * Note that for most types, the list will be empty. + */ + private static List getVirtualNodeChildRoles(CtElement elem) { + if (CtTypeParameter.class.isAssignableFrom(elem.getClass())) { + // we ignore any subtype of CtTypeParameter as exploding them causes a large performance hit + return Collections.emptyList(); + } + + Class cls = elem.getClass(); + for (Class explodedType : EXPLODED_TYPES) { + if (explodedType.isAssignableFrom(cls)) { + return EXPLODED_TYPE_ROLES.get(explodedType); + } + } + return Collections.emptyList(); + } + + private static Stream getRoles(Class cls) { + return RoleHandlerHelper.getRoleHandlers(cls).stream().map(RoleHandler::getRole); } /** @@ -61,15 +160,26 @@ public static SpoonNode endOfChildList(SpoonNode elem) { * uses lookup tables, and CtElements have very heavy-duty equals and hash functions. For the purpose of 3DM merge, * only reference equality is needed, not deep equality. * - * This class should only be instantiated with the CtWrapperFactory singleton. + * This class should only be instantiated by {@link #wrap(CtElement)}. */ private static class Node implements SpoonNode { private final CtElement element; private final long key; + private final SpoonNode parent; + private final Map virtualRoleChildNodes; + private final CtRole role; - Node(CtElement element, long key) { + Node(CtElement element, SpoonNode parent, long key, List virtualNodeChildRoles) { this.element = element; this.key = key; + + virtualRoleChildNodes = new TreeMap<>(); + for (CtRole role : virtualNodeChildRoles) { + virtualRoleChildNodes.put(role, new RoleNode(role, this)); + } + + this.role = element.getRoleInParent(); + this.parent = parent; } @Override @@ -79,9 +189,7 @@ public CtElement getElement() { @Override public SpoonNode getParent() { - if (element instanceof ModuleFactory.CtUnnamedModule) - return NodeFactory.ROOT; - return wrap(element.getParent()); + return parent; } @Override @@ -102,6 +210,22 @@ public Revision getRevision() { return (Revision) element.getMetadata(TdmMerge.REV); } + @Override + public boolean isVirtual() { + return false; + } + + @Override + public List getVirtualNodes() { + return Stream.concat( + Stream.concat( + Stream.of(NodeFactory.startOfChildList(this)), + virtualRoleChildNodes.values().stream() + ), + Stream.of(NodeFactory.endOfChildList(this))) + .collect(Collectors.toList()); + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -114,8 +238,23 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(key); } + + public RoleNode getRoleNode(CtRole role) { + RoleNode roleNode = virtualRoleChildNodes.get(role); + if (roleNode == null) { + throw new IllegalArgumentException("No role node for " + role); + } + return roleNode; + } + + boolean hasRoleNodeFor(CtRole role) { + return role != null && virtualRoleChildNodes.containsKey(role); + } } + /** + * The root virtual node. This is a singleton, there should only be the one that exists in {@link #ROOT}. + */ private static class Root implements SpoonNode { @Override public CtElement getElement() { @@ -124,7 +263,7 @@ public CtElement getElement() { @Override public SpoonNode getParent() { - return null; + throw new UnsupportedOperationException("The virtual root has no parent"); } @Override @@ -136,6 +275,16 @@ public String toString() { public Revision getRevision() { throw new UnsupportedOperationException("The virtual root has no revision"); } + + @Override + public boolean isVirtual() { + return true; + } + + @Override + public List getVirtualNodes() { + return Arrays.asList(NodeFactory.startOfChildList(this), NodeFactory.endOfChildList(this)); + } } /** @@ -170,6 +319,11 @@ public Revision getRevision() { return parent.getRevision(); } + @Override + public List getVirtualNodes() { + throw new UnsupportedOperationException("Can't get virtual nodes from a list edge"); + } + @Override public boolean isEndOfList() { return side == Side.END; @@ -200,4 +354,62 @@ public String toString() { return side.toString(); } } + + /** + * A RoleNode is a virtual node used to separate child lists in nodes with multiple types of child lists. See + * https://github.com/KTH/spork/issues/132 for details. + */ + private static class RoleNode implements SpoonNode { + private final Node parent; + private final CtRole role; + + RoleNode(CtRole role, Node parent) { + this.role = role; + this.parent = parent; + } + + @Override + public CtElement getElement() { + throw new UnsupportedOperationException("Can't get element from a RoleNode"); + } + + @Override + public SpoonNode getParent() { + return parent; + } + + @Override + public Revision getRevision() { + return parent.getRevision(); + } + + @Override + public List getVirtualNodes() { + return Arrays.asList(NodeFactory.startOfChildList(this), NodeFactory.endOfChildList(this)); + } + + @Override + public String toString() { + return "RoleNode#" + role.toString(); + } + + @Override + public boolean isVirtual() { + return true; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RoleNode roleNode = (RoleNode) o; + return Objects.equals(parent, roleNode.parent) && + role == roleNode.role; + } + + @Override + public int hashCode() { + return Objects.hash(parent, role); + } + } } diff --git a/src/main/java/se/kth/spork/spoon/wrappers/SpoonNode.java b/src/main/java/se/kth/spork/spoon/wrappers/SpoonNode.java index a42ec72d..331e1038 100644 --- a/src/main/java/se/kth/spork/spoon/wrappers/SpoonNode.java +++ b/src/main/java/se/kth/spork/spoon/wrappers/SpoonNode.java @@ -4,10 +4,14 @@ import se.kth.spork.base3dm.Revision; import spoon.reflect.declaration.CtElement; +import java.util.List; + public interface SpoonNode extends ListNode { CtElement getElement(); SpoonNode getParent(); Revision getRevision(); + + List getVirtualNodes(); } diff --git a/src/test/java/se/kth/spork/spoon/Spoon3dmMergeTest.java b/src/test/java/se/kth/spork/spoon/Spoon3dmMergeTest.java index 7d42be4b..e6d2975f 100644 --- a/src/test/java/se/kth/spork/spoon/Spoon3dmMergeTest.java +++ b/src/test/java/se/kth/spork/spoon/Spoon3dmMergeTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; import se.kth.spork.Util; +import se.kth.spork.cli.Cli; import se.kth.spork.exception.ConflictException; import se.kth.spork.util.Pair; import spoon.reflect.declaration.*; diff --git a/src/test/resources/clean/both_modified/add_parameters_and_thrown_types/Base.java b/src/test/resources/clean/both_modified/add_parameters_and_thrown_types/Base.java new file mode 100644 index 00000000..81214dfa --- /dev/null +++ b/src/test/resources/clean/both_modified/add_parameters_and_thrown_types/Base.java @@ -0,0 +1,5 @@ +public class Main { + public int add(int a, int b) { + return a + b; + } +} diff --git a/src/test/resources/clean/both_modified/add_parameters_and_thrown_types/Expected.java b/src/test/resources/clean/both_modified/add_parameters_and_thrown_types/Expected.java new file mode 100644 index 00000000..70be0d9b --- /dev/null +++ b/src/test/resources/clean/both_modified/add_parameters_and_thrown_types/Expected.java @@ -0,0 +1,5 @@ +public class Main { + public int add(int a, int b, int c) throws IllegalArgumentException { + return a + b + c; + } +} diff --git a/src/test/resources/clean/both_modified/add_parameters_and_thrown_types/Left.java b/src/test/resources/clean/both_modified/add_parameters_and_thrown_types/Left.java new file mode 100644 index 00000000..963c829c --- /dev/null +++ b/src/test/resources/clean/both_modified/add_parameters_and_thrown_types/Left.java @@ -0,0 +1,5 @@ +public class Main { + public int add(int a, int b) throws IllegalArgumentException { + return a + b; + } +} diff --git a/src/test/resources/clean/both_modified/add_parameters_and_thrown_types/Right.java b/src/test/resources/clean/both_modified/add_parameters_and_thrown_types/Right.java new file mode 100644 index 00000000..d22a10ab --- /dev/null +++ b/src/test/resources/clean/both_modified/add_parameters_and_thrown_types/Right.java @@ -0,0 +1,5 @@ +public class Main { + public int add(int a, int b, int c) { + return a + b + c; + } +} diff --git a/src/test/resources/clean/both_modified/add_type_member_and_comment/Base.java b/src/test/resources/clean/both_modified/add_type_member_and_comment/Base.java new file mode 100644 index 00000000..32636d0b --- /dev/null +++ b/src/test/resources/clean/both_modified/add_type_member_and_comment/Base.java @@ -0,0 +1,5 @@ +package main; + +class Main { + +} \ No newline at end of file diff --git a/src/test/resources/clean/both_modified/add_type_member_and_comment/Expected.java b/src/test/resources/clean/both_modified/add_type_member_and_comment/Expected.java new file mode 100644 index 00000000..953d4154 --- /dev/null +++ b/src/test/resources/clean/both_modified/add_type_member_and_comment/Expected.java @@ -0,0 +1,6 @@ +package main; + +// this is a comment that will appear at the end of the direct children of this class +class Main { + int a = 2; +} diff --git a/src/test/resources/clean/both_modified/add_type_member_and_comment/Left.java b/src/test/resources/clean/both_modified/add_type_member_and_comment/Left.java new file mode 100644 index 00000000..c095ec38 --- /dev/null +++ b/src/test/resources/clean/both_modified/add_type_member_and_comment/Left.java @@ -0,0 +1,5 @@ +package main; + +class Main { + int a = 2; +} \ No newline at end of file diff --git a/src/test/resources/clean/both_modified/add_type_member_and_comment/Right.java b/src/test/resources/clean/both_modified/add_type_member_and_comment/Right.java new file mode 100644 index 00000000..a139acff --- /dev/null +++ b/src/test/resources/clean/both_modified/add_type_member_and_comment/Right.java @@ -0,0 +1,6 @@ +package main; + +// this is a comment that will appear at the end of the direct children of this class +class Main { + +} \ No newline at end of file diff --git a/src/test/resources/clean/left_modified/empty_parameter_list/Base.java b/src/test/resources/clean/left_modified/empty_parameter_list/Base.java new file mode 100644 index 00000000..ff63f866 --- /dev/null +++ b/src/test/resources/clean/left_modified/empty_parameter_list/Base.java @@ -0,0 +1,5 @@ +public class Main { + int generateNumber(int a, int b) { + return a + b; + } +} \ No newline at end of file diff --git a/src/test/resources/clean/left_modified/empty_parameter_list/Expected.java b/src/test/resources/clean/left_modified/empty_parameter_list/Expected.java new file mode 100644 index 00000000..e779dfea --- /dev/null +++ b/src/test/resources/clean/left_modified/empty_parameter_list/Expected.java @@ -0,0 +1,5 @@ +public class Main { + int generateNumber() { + return 42; + } +} \ No newline at end of file diff --git a/src/test/resources/clean/left_modified/empty_parameter_list/Left.java b/src/test/resources/clean/left_modified/empty_parameter_list/Left.java new file mode 100644 index 00000000..e779dfea --- /dev/null +++ b/src/test/resources/clean/left_modified/empty_parameter_list/Left.java @@ -0,0 +1,5 @@ +public class Main { + int generateNumber() { + return 42; + } +} \ No newline at end of file diff --git a/src/test/resources/clean/left_modified/empty_parameter_list/Right.java b/src/test/resources/clean/left_modified/empty_parameter_list/Right.java new file mode 100644 index 00000000..ff63f866 --- /dev/null +++ b/src/test/resources/clean/left_modified/empty_parameter_list/Right.java @@ -0,0 +1,5 @@ +public class Main { + int generateNumber(int a, int b) { + return a + b; + } +} \ No newline at end of file diff --git a/src/test/resources/clean/left_modified/empty_thrown_types/Base.java b/src/test/resources/clean/left_modified/empty_thrown_types/Base.java new file mode 100644 index 00000000..7a3cfac7 --- /dev/null +++ b/src/test/resources/clean/left_modified/empty_thrown_types/Base.java @@ -0,0 +1,7 @@ +import java.io.IOException; +import java.util.EmptyStackException; + +public class Main { + int method() throws EmptyStackException, IOException { + } +} \ No newline at end of file diff --git a/src/test/resources/clean/left_modified/empty_thrown_types/Expected.java b/src/test/resources/clean/left_modified/empty_thrown_types/Expected.java new file mode 100644 index 00000000..c5eb794b --- /dev/null +++ b/src/test/resources/clean/left_modified/empty_thrown_types/Expected.java @@ -0,0 +1,4 @@ +public class Main { + int method() { + } +} \ No newline at end of file diff --git a/src/test/resources/clean/left_modified/empty_thrown_types/Left.java b/src/test/resources/clean/left_modified/empty_thrown_types/Left.java new file mode 100644 index 00000000..c5eb794b --- /dev/null +++ b/src/test/resources/clean/left_modified/empty_thrown_types/Left.java @@ -0,0 +1,4 @@ +public class Main { + int method() { + } +} \ No newline at end of file diff --git a/src/test/resources/clean/left_modified/empty_thrown_types/Right.java b/src/test/resources/clean/left_modified/empty_thrown_types/Right.java new file mode 100644 index 00000000..7a3cfac7 --- /dev/null +++ b/src/test/resources/clean/left_modified/empty_thrown_types/Right.java @@ -0,0 +1,7 @@ +import java.io.IOException; +import java.util.EmptyStackException; + +public class Main { + int method() throws EmptyStackException, IOException { + } +} \ No newline at end of file diff --git a/src/test/resources/clean/left_modified/generify_method/Expected.java b/src/test/resources/clean/left_modified/generify_method/Expected.java index 576dbf9c..6a2b3137 100644 --- a/src/test/resources/clean/left_modified/generify_method/Expected.java +++ b/src/test/resources/clean/left_modified/generify_method/Expected.java @@ -1,5 +1,5 @@ public class Cls { - public void method(List list) { + public void method(List list) { System.out.println(list); } } \ No newline at end of file diff --git a/src/test/resources/clean/left_modified/generify_method/Left.java b/src/test/resources/clean/left_modified/generify_method/Left.java index 576dbf9c..6a2b3137 100644 --- a/src/test/resources/clean/left_modified/generify_method/Left.java +++ b/src/test/resources/clean/left_modified/generify_method/Left.java @@ -1,5 +1,5 @@ public class Cls { - public void method(List list) { + public void method(List list) { System.out.println(list); } } \ No newline at end of file