diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/patching/Matcher.java b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/Matcher.java index 76127cb..b2dc0e5 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/patching/Matcher.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/Matcher.java @@ -86,9 +86,11 @@ Set match(final Pattern pattern) { ); final Set set = new HashSet<>(); for (final Node node : preset) { - final boolean matches = this.checkNode(node, null, head); + final ActionList applicants = new ActionList(); + final boolean matches = Matcher.checkNode(node, head, applicants); if (matches) { set.add(node); + this.actions.merge(applicants); } } return set; @@ -97,12 +99,12 @@ Set match(final Pattern pattern) { /** * Checks if the node of the original tree matches the pattern node. * @param node Node of the original tree - * @param parent Parent node of the node being checked. * @param pattern Node of the difference tree (i.e. pattern) + * @param actions List of actions to be performed to apply the pattern * @return Matching result ({@code true} if matches) */ - @SuppressWarnings("PMD.UnusedFormalParameter") - private boolean checkNode(final Node node, final Node parent, final Node pattern) { + private static boolean checkNode( + final Node node, final Node pattern, final ActionList actions) { final Node sample; final Action action = Action.toAction(pattern); if (action instanceof Replace || action instanceof Delete) { @@ -113,12 +115,13 @@ private boolean checkNode(final Node node, final Node parent, final Node pattern boolean result = node.getTypeName().equals(sample.getTypeName()); if (!(pattern instanceof Hole)) { result = result && node.getData().equals(sample.getData()); - result = result && (node.getChildCount() == 0 || this.checkChildren(node, sample)); + result = result && (node.getChildCount() == 0 + || Matcher.checkChildren(node, sample, actions)); } if (result && action instanceof Replace) { - this.actions.replaceNode(node, action.getAfter()); + actions.replaceNode(node, action.getAfter()); } else if (result & action instanceof Delete) { - this.actions.deleteNode(node); + actions.deleteNode(node); } return result; } @@ -128,12 +131,15 @@ private boolean checkNode(final Node node, final Node parent, final Node pattern * matches the children of the pattern node. * @param node Node of the original tree * @param sample Node of the difference tree (i.e. pattern) + * @param actions List of actions to be performed to apply the pattern * @return Matching result ({@code true} if matches) */ - private boolean checkChildren(final Node node, final Node sample) { + private static boolean checkChildren( + final Node node, final Node sample, final ActionList actions) { final int left = node.getChildCount(); final int right = sample.getChildCount(); boolean result = false; + final ActionList applicants = new ActionList(); for (int index = 0; !result && index < left; index = index + 1) { result = true; final Iterator iterator = sample.getIteratorOverChildren(); @@ -143,20 +149,23 @@ private boolean checkChildren(final Node node, final Node sample) { final Node child = iterator.next(); final Action action = Action.toAction(child); if (action instanceof Insert) { - this.actions.insertNodeAfter(action.getAfter(), node, current); + applicants.insertNodeAfter(action.getAfter(), node, current); } else if (index + offset >= left) { result = false; } else { current = node.getChild(index + offset); - result = this.checkNode( + result = Matcher.checkNode( current, - node, - child + child, + applicants ); offset = offset + 1; } } } + if (result) { + actions.merge(applicants); + } return result; } } diff --git a/src/main/java/org/cqfn/astranaut/core/base/ActionList.java b/src/main/java/org/cqfn/astranaut/core/base/ActionList.java index 3e23625..23b4015 100644 --- a/src/main/java/org/cqfn/astranaut/core/base/ActionList.java +++ b/src/main/java/org/cqfn/astranaut/core/base/ActionList.java @@ -23,8 +23,10 @@ */ package org.cqfn.astranaut.core.base; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -33,14 +35,13 @@ /** * List of actions to be added to the tree after deserialization to produce a difference tree. - * * @since 1.1.0 */ public final class ActionList { /** * Collection of nodes to be inserted. */ - private final Set insert; + private final List insert; /** * Collection of nodes to be replaced (node before changes -> node after changes). @@ -56,7 +57,7 @@ public final class ActionList { * Constructor. */ public ActionList() { - this.insert = new HashSet<>(); + this.insert = new ArrayList<>(0); this.replace = new HashMap<>(); this.delete = new HashSet<>(); } @@ -109,6 +110,16 @@ public void deleteNode(final Node node) { this.delete.add(node); } + /** + * Adds actions from another list to the current list. + * @param other Another action list + */ + public void merge(final ActionList other) { + this.insert.addAll(other.insert); + this.replace.putAll(other.replace); + this.delete.addAll(other.delete); + } + /** * Converts the tree to a difference tree using the list of actions. * @param tree Source tree diff --git a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatcherTest.java b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatcherTest.java index 67e49d2..a2ba480 100644 --- a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatcherTest.java +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatcherTest.java @@ -29,8 +29,12 @@ import java.util.Set; import java.util.TreeMap; import org.cqfn.astranaut.core.algorithms.DiffTreeBuilder; +import org.cqfn.astranaut.core.algorithms.PatternBuilder; +import org.cqfn.astranaut.core.algorithms.mapping.Mapper; +import org.cqfn.astranaut.core.algorithms.mapping.TopDownMapper; import org.cqfn.astranaut.core.base.Builder; import org.cqfn.astranaut.core.base.DiffNode; +import org.cqfn.astranaut.core.base.DiffTree; import org.cqfn.astranaut.core.base.DraftNode; import org.cqfn.astranaut.core.base.Insertion; import org.cqfn.astranaut.core.base.Node; @@ -186,4 +190,26 @@ void patchWithPatternThatDoesNotMatch() { result = patcher.patch(tree, third); Assertions.assertTrue(tree.deepCompare(result)); } + + @Test + void mineAndPatch() { + final Node before = DraftNode.create("X(A,C(D(F(G(H)))))"); + final Node after = DraftNode.create("X(A,B,C(D(F(G(I)))))"); + final Mapper mapper = TopDownMapper.INSTANCE; + final DiffTreeBuilder diffbuilder = new DiffTreeBuilder(before); + diffbuilder.build(after, mapper); + final DiffTree diff = diffbuilder.getDiffTree(); + Assertions.assertTrue(before.deepCompare(diff.getBefore().getRoot())); + Assertions.assertTrue(after.deepCompare(diff.getAfter().getRoot())); + final Pattern pattern = new PatternBuilder(diff).getPattern(); + final Tree original = Tree.createDraft( + "Y(X(A,C(D(F(G(J,K))))),X(A,C(D(F(G(L))))),X(A,C(D(F(G(H))))))" + ); + final Patcher patcher = DefaultPatcher.INSTANCE; + final Tree patched = patcher.patch(original, pattern); + final Tree expected = Tree.createDraft( + "Y(X(A,C(D(F(G(J,K))))),X(A,C(D(F(G(L))))),X(A,B,C(D(F(G(I))))))" + ); + Assertions.assertEquals(expected.toString(), patched.toString()); + } }