From fe39e27f1e71c49503fd180c23c52b2692505ca1 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Thu, 11 Apr 2024 13:55:02 +0300 Subject: [PATCH 01/19] Patcher interface --- .../core/algorithms/patching/Patcher.java | 43 +++++++++++++++++++ .../algorithms/patching/package-info.java | 31 +++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 src/main/java/org/cqfn/astranaut/core/algorithms/patching/Patcher.java create mode 100644 src/main/java/org/cqfn/astranaut/core/algorithms/patching/package-info.java diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/patching/Patcher.java b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/Patcher.java new file mode 100644 index 0000000..a56c0d4 --- /dev/null +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/Patcher.java @@ -0,0 +1,43 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ivan Kniazkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.cqfn.astranaut.core.algorithms.patching; + +import org.cqfn.astranaut.core.DifferenceNode; +import org.cqfn.astranaut.core.Node; + +/** + * Interface to an algorithm that applies patches, i.e. makes some changes in the syntax tree + * based on patterns describing such changes. Patterns are differential trees. + * + * @since 1.1.5 + */ +public interface Patcher { + /** + * Applies a pattern to a syntax tree. + * @param source Root node of a syntax tree + * @param pattern Root node af a pattern + * @return Root node of updated syntax tree + */ + Node patch(Node source, DifferenceNode pattern); +} diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/patching/package-info.java b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/package-info.java new file mode 100644 index 0000000..148ebbb --- /dev/null +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/package-info.java @@ -0,0 +1,31 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ivan Kniazkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * This package contains algorithms and data structures related to patching, i.e., changing + * syntax trees using patterns (difference trees). + * + * @since 1.1.5 + */ +package org.cqfn.astranaut.core.algorithms.patching; From 1c5a4497c27cc7fef3ce5555ce26d4158d55dfce Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Fri, 12 Apr 2024 09:26:00 +0300 Subject: [PATCH 02/19] Stub for matcher --- .../core/algorithms/patching/Matcher.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/main/java/org/cqfn/astranaut/core/algorithms/patching/Matcher.java 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 new file mode 100644 index 0000000..63b8abf --- /dev/null +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/Matcher.java @@ -0,0 +1,70 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ivan Kniazkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.cqfn.astranaut.core.algorithms.patching; + +import java.util.HashSet; +import java.util.Set; +import org.cqfn.astranaut.core.DifferenceNode; +import org.cqfn.astranaut.core.Node; + +/** + * The matcher matches syntax tree and patterns. + * + * @since 1.1.5 + */ +class Matcher { + /** + * Root node of the tree in which patterns are searched. + */ + private final Node root; + + /** + * Constructor. + * @param root Root node of the tree in which patterns are searched + */ + Matcher(final Node root) { + this.root = root; + } + + /** + * Matches the tree and the pattern. + * @param pattern Root node of the pattern + * @return Nodes that match the root node of the pattern + */ + Set match(final DifferenceNode pattern) { + final Set set = new HashSet<>(); + + return set; + } + + /** + * Matches the subtree and the pattern. + * @param node Root node of the subtree + * @param pattern Root node of the pattern + * @return Matching result ({@code true} if matches) + */ + private static boolean matchSubtree(final Node node, final DifferenceNode pattern) { + return false; + } +} From 7f5c30408b7e20c01d8538f55d6e4314d60752c6 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Wed, 15 May 2024 10:02:26 +0300 Subject: [PATCH 03/19] Matcher test --- .../org/cqfn/astranaut/core/DraftNode.java | 1 + .../core/algorithms/patching/Matcher.java | 41 +++++++++++++-- .../core/algorithms/patching/MatcherTest.java | 50 +++++++++++++++++++ .../algorithms/patching/package-info.java | 30 +++++++++++ 4 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 src/test/java/org/cqfn/astranaut/core/algorithms/patching/MatcherTest.java create mode 100644 src/test/java/org/cqfn/astranaut/core/algorithms/patching/package-info.java diff --git a/src/main/java/org/cqfn/astranaut/core/DraftNode.java b/src/main/java/org/cqfn/astranaut/core/DraftNode.java index b57a304..c3ccba3 100644 --- a/src/main/java/org/cqfn/astranaut/core/DraftNode.java +++ b/src/main/java/org/cqfn/astranaut/core/DraftNode.java @@ -196,6 +196,7 @@ private static List parseChildrenList(final CharacterIterator iterator) { next = iterator.current(); assert next == ')' || next == ',' || next == ' '; } while (next != ')'); + iterator.next(); return children; } 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 63b8abf..e8cd82a 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 @@ -24,9 +24,11 @@ package org.cqfn.astranaut.core.algorithms.patching; import java.util.HashSet; +import java.util.List; import java.util.Set; import org.cqfn.astranaut.core.DifferenceNode; import org.cqfn.astranaut.core.Node; +import org.cqfn.astranaut.core.algorithms.DeepTraversal; /** * The matcher matches syntax tree and patterns. @@ -53,18 +55,47 @@ class Matcher { * @return Nodes that match the root node of the pattern */ Set match(final DifferenceNode pattern) { + final DeepTraversal deep = new DeepTraversal(this.root); + final List preset = deep.findAll( + node -> node.getTypeName().equals(pattern.getTypeName()) && + node.getData().equals(pattern.getData()) + ); final Set set = new HashSet<>(); - + set.addAll(preset); return set; } /** - * Matches the subtree and the pattern. - * @param node Root node of the subtree - * @param pattern Root node of the pattern + * Checks if the children of the original tree node matches the children of the pattern node. + * @param node Node of the original tree + * @param diff Node of the difference tree (i.e. pattern) * @return Matching result ({@code true} if matches) */ - private static boolean matchSubtree(final Node node, final DifferenceNode pattern) { + private static boolean checkChildren(final Node node, final DifferenceNode diff) { + final int count = node.getChildCount(); + for (int left = 0; left < count; left = left + 1) { + + } + return false; + } + + /** + * Finds the first child of the original tree node that matches the first child + * of the pattern node. + * @param node Node of the original tree + * @param diff Node of the difference tree (i.e. pattern) + * @return Index of the found child or a negative number if no match is found + */ + private static int findFirstMatchingChild(final Node node, final DifferenceNode diff) { + final int left = node.getChildCount(); + final int right = diff.getChildCount(); + assert right > 0; + final Node sample = diff.getChild(0); + + for (int index = 0; left - index >= right; index = index + 1) { + final Node child = node.getChild(index); + } return false; + } } diff --git a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/MatcherTest.java b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/MatcherTest.java new file mode 100644 index 0000000..1bbcb0a --- /dev/null +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/MatcherTest.java @@ -0,0 +1,50 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ivan Kniazkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.cqfn.astranaut.core.algorithms.patching; + +import java.util.Set; +import org.cqfn.astranaut.core.DifferenceNode; +import org.cqfn.astranaut.core.DraftNode; +import org.cqfn.astranaut.core.Node; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Testing {@link Matcher} class. + * + * @since 1.1.5 + */ +class MatcherTest { + @Test + void findSubtreeInATree() { + final Node tree = DraftNode.createByDescription("X(Y(A(B,C)),A(B,C))"); + final DifferenceNode subtree = new DifferenceNode(DraftNode.createByDescription("A(B,C)")); + final Matcher matcher = new Matcher(tree); + final Set found = matcher.match(subtree); + Assertions.assertEquals(2, found.size()); + for (final Node node : found) { + Assertions.assertEquals("A", node.getTypeName()); + } + } +} diff --git a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/package-info.java b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/package-info.java new file mode 100644 index 0000000..decb7e7 --- /dev/null +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/package-info.java @@ -0,0 +1,30 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ivan Kniazkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * This package contains tests covering patching algorithms. + * + * @since 1.1.5 + */ +package org.cqfn.astranaut.core.algorithms.patching; From 8470a1281c13196ff3ea73a04d8bf1d8f8c1dd2b Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Wed, 29 May 2024 14:52:26 +0300 Subject: [PATCH 04/19] Matcher matched subtree --- .../core/algorithms/patching/Matcher.java | 53 +++++++++---------- .../core/algorithms/patching/MatcherTest.java | 2 +- 2 files changed, 27 insertions(+), 28 deletions(-) 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 e8cd82a..35b3dc1 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 @@ -61,41 +61,40 @@ Set match(final DifferenceNode pattern) { node.getData().equals(pattern.getData()) ); final Set set = new HashSet<>(); - set.addAll(preset); + for (final Node node : preset) { + final boolean matches = Matcher.checkNode(node, pattern); + if (matches) { + set.add(node); + } + } return set; } /** - * Checks if the children of the original tree node matches the children of the pattern node. + * Checks if the node of the original tree matches the pattern node. * @param node Node of the original tree - * @param diff Node of the difference tree (i.e. pattern) + * @param sample Node of the difference tree (i.e. pattern) * @return Matching result ({@code true} if matches) */ - private static boolean checkChildren(final Node node, final DifferenceNode diff) { - final int count = node.getChildCount(); - for (int left = 0; left < count; left = left + 1) { - - } - return false; - } - - /** - * Finds the first child of the original tree node that matches the first child - * of the pattern node. - * @param node Node of the original tree - * @param diff Node of the difference tree (i.e. pattern) - * @return Index of the found child or a negative number if no match is found - */ - private static int findFirstMatchingChild(final Node node, final DifferenceNode diff) { + private static boolean checkNode(final Node node, final Node sample) { final int left = node.getChildCount(); - final int right = diff.getChildCount(); - assert right > 0; - final Node sample = diff.getChild(0); - - for (int index = 0; left - index >= right; index = index + 1) { - final Node child = node.getChild(index); + final int right = sample.getChildCount(); + boolean result = left >= right && node.getTypeName().equals(sample.getTypeName()) && + node.getData().equals(sample.getData()); + if (result) { + for (int index = 0; index < left - right + 1; index = index + 1) { + result = true; + for (int offset = 0; result && offset < right; offset = offset + 1) { + result = Matcher.checkNode( + node.getChild(index + offset), + sample.getChild(offset) + ); + } + if (result) { + break; + } + } } - return false; - + return result; } } diff --git a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/MatcherTest.java b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/MatcherTest.java index 1bbcb0a..d71116e 100644 --- a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/MatcherTest.java +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/MatcherTest.java @@ -38,7 +38,7 @@ class MatcherTest { @Test void findSubtreeInATree() { - final Node tree = DraftNode.createByDescription("X(Y(A(B,C)),A(B,C))"); + final Node tree = DraftNode.createByDescription("X(Y(A(B,C)),A(B,C),A(B,D))"); final DifferenceNode subtree = new DifferenceNode(DraftNode.createByDescription("A(B,C)")); final Matcher matcher = new Matcher(tree); final Set found = matcher.match(subtree); From c51292bcd884201afa4938d47416e87bf0797b5c Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Wed, 29 May 2024 15:23:32 +0300 Subject: [PATCH 05/19] + test --- .../core/algorithms/patching/Matcher.java | 9 +++++---- .../core/algorithms/patching/MatcherTest.java | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) 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 35b3dc1..e1975df 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 @@ -57,8 +57,8 @@ class Matcher { Set match(final DifferenceNode pattern) { final DeepTraversal deep = new DeepTraversal(this.root); final List preset = deep.findAll( - node -> node.getTypeName().equals(pattern.getTypeName()) && - node.getData().equals(pattern.getData()) + node -> node.getTypeName().equals(pattern.getTypeName()) + && node.getData().equals(pattern.getData()) ); final Set set = new HashSet<>(); for (final Node node : preset) { @@ -79,8 +79,9 @@ Set match(final DifferenceNode pattern) { private static boolean checkNode(final Node node, final Node sample) { final int left = node.getChildCount(); final int right = sample.getChildCount(); - boolean result = left >= right && node.getTypeName().equals(sample.getTypeName()) && - node.getData().equals(sample.getData()); + boolean result = left >= right + && node.getTypeName().equals(sample.getTypeName()) + && node.getData().equals(sample.getData()); if (result) { for (int index = 0; index < left - right + 1; index = index + 1) { result = true; diff --git a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/MatcherTest.java b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/MatcherTest.java index d71116e..ee7db6a 100644 --- a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/MatcherTest.java +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/MatcherTest.java @@ -47,4 +47,18 @@ void findSubtreeInATree() { Assertions.assertEquals("A", node.getTypeName()); } } + + @Test + void findReducedSubtreeInATree() { + final Node tree = DraftNode.createByDescription("X(A(B,C(F),D,E),A(B,D,E),A(B))"); + final DifferenceNode subtree = new DifferenceNode( + DraftNode.createByDescription("A(C(F),D)") + ); + final Matcher matcher = new Matcher(tree); + final Set found = matcher.match(subtree); + Assertions.assertEquals(1, found.size()); + final Node node = found.iterator().next(); + Assertions.assertEquals("A", node.getTypeName()); + Assertions.assertEquals(4, node.getChildCount()); + } } From 52025a6574c6b06024de56c92e5e3783687f6320 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Thu, 30 May 2024 12:26:27 +0300 Subject: [PATCH 06/19] Draft node extended --- .../org/cqfn/astranaut/core/DraftNode.java | 42 ++++++++++++++++--- .../cqfn/astranaut/core/DraftNodeTest.java | 34 +++++++++++++++ 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/cqfn/astranaut/core/DraftNode.java b/src/main/java/org/cqfn/astranaut/core/DraftNode.java index c3ccba3..3d3cea9 100644 --- a/src/main/java/org/cqfn/astranaut/core/DraftNode.java +++ b/src/main/java/org/cqfn/astranaut/core/DraftNode.java @@ -27,9 +27,12 @@ import java.text.StringCharacterIterator; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; /** * Draft node for wrapping the results of third-party parsers @@ -121,16 +124,35 @@ public String toString() { */ @SuppressWarnings("PMD.ProhibitPublicStaticMethods") public static Node createByDescription(final String description) { + return DraftNode.createByDescription(description, null); + } + + /** + * Creates a tree from draft nodes based on description. + * Description format: A(B,C(...),...) where 'A' is the type name + * (it consists only of letters) followed by child nodes (in the same format) in parentheses + * separated by commas. + * @param description Description + * @param nodes Collection in which to place the nodes to be created, sorted by type name + * @return Root node of the tree created by description + */ + @SuppressWarnings("PMD.ProhibitPublicStaticMethods") + public static Node createByDescription( + final String description, + final Map> nodes) { final CharacterIterator iterator = new StringCharacterIterator(description); - return DraftNode.createByDescription(iterator); + return DraftNode.createByDescription(iterator, nodes); } /** * Creates a tree based on its description (recursive method). * @param iterator Iterator by description characters + * @param nodes Collection in which to place the nodes to be created, sorted by type name * @return Node of the tree with its children, created by description */ - private static Node createByDescription(final CharacterIterator iterator) { + private static Node createByDescription( + final CharacterIterator iterator, + final Map> nodes) { char symbol = iterator.current(); final Node result; final StringBuilder name = new StringBuilder(); @@ -146,9 +168,14 @@ private static Node createByDescription(final CharacterIterator iterator) { symbol = iterator.current(); } if (symbol == '(') { - builder.setChildrenList(DraftNode.parseChildrenList(iterator)); + builder.setChildrenList(DraftNode.parseChildrenList(iterator, nodes)); } result = builder.createNode(); + if (nodes != null) { + final Set set = + nodes.computeIfAbsent(result.getTypeName(), k -> new HashSet<>()); + set.add(result); + } } else { result = null; } @@ -181,15 +208,18 @@ private static String parseData(final CharacterIterator iterator) { /** * Parses children list from description. * @param iterator Iterator by description characters + * @param nodes Collection in which to place the nodes to be created, sorted by type name * @return Children list, created by description */ - private static List parseChildrenList(final CharacterIterator iterator) { + private static List parseChildrenList( + final CharacterIterator iterator, + final Map> nodes) { assert iterator.current() == '('; final List children = new LinkedList<>(); char next; do { iterator.next(); - final Node child = DraftNode.createByDescription(iterator); + final Node child = DraftNode.createByDescription(iterator, nodes); if (child != null) { children.add(child); } @@ -213,7 +243,7 @@ private static class TypeImpl implements Type { /** * Constructor. - * @param name The type name + * @param name The type name` */ TypeImpl(final String name) { this.name = name; diff --git a/src/test/java/org/cqfn/astranaut/core/DraftNodeTest.java b/src/test/java/org/cqfn/astranaut/core/DraftNodeTest.java index 84e067e..f3d5124 100644 --- a/src/test/java/org/cqfn/astranaut/core/DraftNodeTest.java +++ b/src/test/java/org/cqfn/astranaut/core/DraftNodeTest.java @@ -24,7 +24,11 @@ package org.cqfn.astranaut.core; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -76,6 +80,36 @@ void constructorTest() { Assertions.assertEquals(serialized, ctor.createNode().toString()); } + /** + * Testing {@link DraftNode#createByDescription(String, Map)} method. + */ + @Test + void testExtendedDescriptorProcessor() { + final Map> nodes = new TreeMap<>(); + final Node root = DraftNode.createByDescription("X(A,A,B(C,D))", nodes); + Assertions.assertSame( + root, + nodes.computeIfAbsent( + "X", + s -> Collections.singleton(EmptyTree.INSTANCE) + ).iterator().next() + ); + Assertions.assertEquals( + 2, + nodes.computeIfAbsent( + "A", + s -> Collections.singleton(EmptyTree.INSTANCE) + ).size() + ); + Assertions.assertEquals( + 2, + nodes.computeIfAbsent( + "B", + s -> Collections.singleton(EmptyTree.INSTANCE) + ).iterator().next().getChildCount() + ); + } + /** * Testing {@link DraftNode#createByDescription(String)} and * {@link DraftNode#toString()} methods (testing of one case). From 1df75540168cca758ddd7fea57ff4ae04851d064 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Tue, 4 Jun 2024 14:17:38 +0300 Subject: [PATCH 07/19] find pattern with replacement --- .../core/algorithms/patching/Matcher.java | 13 +++++++++++-- .../core/algorithms/patching/MatcherTest.java | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) 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 e1975df..10c16a3 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 @@ -26,8 +26,11 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.cqfn.astranaut.core.Action; +import org.cqfn.astranaut.core.Delete; import org.cqfn.astranaut.core.DifferenceNode; import org.cqfn.astranaut.core.Node; +import org.cqfn.astranaut.core.Replace; import org.cqfn.astranaut.core.algorithms.DeepTraversal; /** @@ -73,10 +76,16 @@ Set match(final DifferenceNode pattern) { /** * Checks if the node of the original tree matches the pattern node. * @param node Node of the original tree - * @param sample Node of the difference tree (i.e. pattern) + * @param pattern Node of the difference tree (i.e. pattern) * @return Matching result ({@code true} if matches) */ - private static boolean checkNode(final Node node, final Node sample) { + private static boolean checkNode(final Node node, final Node pattern) { + final Node sample; + if (pattern instanceof Replace || pattern instanceof Delete) { + sample = ((Action) pattern).getBefore(); + } else { + sample = pattern; + } final int left = node.getChildCount(); final int right = sample.getChildCount(); boolean result = left >= right diff --git a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/MatcherTest.java b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/MatcherTest.java index ee7db6a..22b6b44 100644 --- a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/MatcherTest.java +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/MatcherTest.java @@ -23,10 +23,13 @@ */ package org.cqfn.astranaut.core.algorithms.patching; +import java.util.Map; import java.util.Set; +import java.util.TreeMap; import org.cqfn.astranaut.core.DifferenceNode; import org.cqfn.astranaut.core.DraftNode; import org.cqfn.astranaut.core.Node; +import org.cqfn.astranaut.core.algorithms.DifferenceTreeBuilder; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -61,4 +64,19 @@ void findReducedSubtreeInATree() { Assertions.assertEquals("A", node.getTypeName()); Assertions.assertEquals(4, node.getChildCount()); } + + @Test + void findPatternWithReplacementInATree() { + final Map> nodes = new TreeMap<>(); + final Node prepattern = DraftNode.createByDescription("A(B, D)", nodes); + final DifferenceTreeBuilder builder = new DifferenceTreeBuilder(prepattern); + builder.replaceNode(nodes.get("B").iterator().next(), DraftNode.createByDescription("C")); + final DifferenceNode pattern = builder.getRoot(); + final Node tree = DraftNode.createByDescription("X(Y,A(B,D),Z)"); + final Matcher matcher = new Matcher(tree); + final Set found = matcher.match(pattern); + Assertions.assertEquals(1, found.size()); + final Node node = found.iterator().next(); + Assertions.assertEquals("A", node.getTypeName()); + } } From e412b861ecfe9313a0cdef9a3541a878c75e1454 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Fri, 7 Jun 2024 16:34:47 +0300 Subject: [PATCH 08/19] Pattern matcher returns action list --- .../cqfn/astranaut/core/DifferenceNode.java | 5 ++ .../core/algorithms/NodeSelector.java | 2 +- .../{Matcher.java => PatternMatcher.java} | 67 +++++++++++++------ ...tcherTest.java => PatternMatcherTest.java} | 10 +-- 4 files changed, 59 insertions(+), 25 deletions(-) rename src/main/java/org/cqfn/astranaut/core/algorithms/patching/{Matcher.java => PatternMatcher.java} (62%) rename src/test/java/org/cqfn/astranaut/core/algorithms/patching/{MatcherTest.java => PatternMatcherTest.java} (92%) diff --git a/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java b/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java index c8d029c..16f6afe 100644 --- a/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java +++ b/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java @@ -217,6 +217,11 @@ public boolean deleteNode(final Node node) { return result; } + @Override + public String toString() { + return this.prototype.toString(); + } + /** * Transforms children nodes to convertible ones. * @return List of difference nodes diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/NodeSelector.java b/src/main/java/org/cqfn/astranaut/core/algorithms/NodeSelector.java index 4e89e90..b01c899 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/NodeSelector.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/NodeSelector.java @@ -34,7 +34,7 @@ * * @since 1.1.4 */ -public class NodeSelector { +public final class NodeSelector { /** * The root node of the tree from which the nodes will be selected. */ diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/patching/Matcher.java b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcher.java similarity index 62% rename from src/main/java/org/cqfn/astranaut/core/algorithms/patching/Matcher.java rename to src/main/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcher.java index 10c16a3..025dcb1 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/patching/Matcher.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcher.java @@ -32,24 +32,39 @@ import org.cqfn.astranaut.core.Node; import org.cqfn.astranaut.core.Replace; import org.cqfn.astranaut.core.algorithms.DeepTraversal; +import org.cqfn.astranaut.core.utils.deserializer.ActionList; /** * The matcher matches syntax tree and patterns. * * @since 1.1.5 */ -class Matcher { +class PatternMatcher { /** * Root node of the tree in which patterns are searched. */ private final Node root; + /** + * List of actions to be performed on the original tree to apply the pattern. + */ + private final ActionList actions; + /** * Constructor. * @param root Root node of the tree in which patterns are searched */ - Matcher(final Node root) { + PatternMatcher(final Node root) { this.root = root; + this.actions = new ActionList(); + } + + /** + * Returns the list of actions that were compiled when the pattern was matched. + * @return Action list + */ + public ActionList getActionList() { + return this.actions; } /** @@ -65,7 +80,7 @@ Set match(final DifferenceNode pattern) { ); final Set set = new HashSet<>(); for (final Node node : preset) { - final boolean matches = Matcher.checkNode(node, pattern); + final boolean matches = this.checkNode(node, null, pattern); if (matches) { set.add(node); } @@ -76,33 +91,47 @@ Set match(final DifferenceNode 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) * @return Matching result ({@code true} if matches) */ - private static boolean checkNode(final Node node, final Node pattern) { + @SuppressWarnings("PMD.UnusedFormalParameter") + private boolean checkNode(final Node node, final Node parent, final Node pattern) { final Node sample; if (pattern instanceof Replace || pattern instanceof Delete) { sample = ((Action) pattern).getBefore(); } else { sample = pattern; } + final boolean result = node.getChildCount() >= sample.getChildCount() + && node.getTypeName().equals(sample.getTypeName()) + && node.getData().equals(sample.getData()) + && this.checkChildren(node, sample); + if (result && pattern instanceof Replace) { + this.actions.replaceNode(node, ((Action) pattern).getAfter()); + } + return result; + } + + /** + * Checks if the children of the node of the original tree + * matches the children of the pattern node. + * @param node Node of the original tree + * @param sample Node of the difference tree (i.e. pattern) + * @return Matching result ({@code true} if matches) + */ + private boolean checkChildren(final Node node, final Node sample) { final int left = node.getChildCount(); final int right = sample.getChildCount(); - boolean result = left >= right - && node.getTypeName().equals(sample.getTypeName()) - && node.getData().equals(sample.getData()); - if (result) { - for (int index = 0; index < left - right + 1; index = index + 1) { - result = true; - for (int offset = 0; result && offset < right; offset = offset + 1) { - result = Matcher.checkNode( - node.getChild(index + offset), - sample.getChild(offset) - ); - } - if (result) { - break; - } + boolean result = false; + for (int index = 0; !result && index < left - right + 1; index = index + 1) { + result = true; + for (int offset = 0; result && offset < right; offset = offset + 1) { + result = this.checkNode( + node.getChild(index + offset), + node, + sample.getChild(offset) + ); } } return result; diff --git a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/MatcherTest.java b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcherTest.java similarity index 92% rename from src/test/java/org/cqfn/astranaut/core/algorithms/patching/MatcherTest.java rename to src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcherTest.java index 22b6b44..9a0e0e6 100644 --- a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/MatcherTest.java +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcherTest.java @@ -34,16 +34,16 @@ import org.junit.jupiter.api.Test; /** - * Testing {@link Matcher} class. + * Testing {@link PatternMatcher} class. * * @since 1.1.5 */ -class MatcherTest { +class PatternMatcherTest { @Test void findSubtreeInATree() { final Node tree = DraftNode.createByDescription("X(Y(A(B,C)),A(B,C),A(B,D))"); final DifferenceNode subtree = new DifferenceNode(DraftNode.createByDescription("A(B,C)")); - final Matcher matcher = new Matcher(tree); + final PatternMatcher matcher = new PatternMatcher(tree); final Set found = matcher.match(subtree); Assertions.assertEquals(2, found.size()); for (final Node node : found) { @@ -57,7 +57,7 @@ void findReducedSubtreeInATree() { final DifferenceNode subtree = new DifferenceNode( DraftNode.createByDescription("A(C(F),D)") ); - final Matcher matcher = new Matcher(tree); + final PatternMatcher matcher = new PatternMatcher(tree); final Set found = matcher.match(subtree); Assertions.assertEquals(1, found.size()); final Node node = found.iterator().next(); @@ -73,7 +73,7 @@ void findPatternWithReplacementInATree() { builder.replaceNode(nodes.get("B").iterator().next(), DraftNode.createByDescription("C")); final DifferenceNode pattern = builder.getRoot(); final Node tree = DraftNode.createByDescription("X(Y,A(B,D),Z)"); - final Matcher matcher = new Matcher(tree); + final PatternMatcher matcher = new PatternMatcher(tree); final Set found = matcher.match(pattern); Assertions.assertEquals(1, found.size()); final Node node = found.iterator().next(); From dc4cd42e1810b85a47e34833ee42855bc8b63715 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Fri, 7 Jun 2024 17:09:52 +0300 Subject: [PATCH 09/19] Patcher with test --- .../algorithms/patching/DefaultPatcher.java | 52 ++++++++++++++++++ .../core/algorithms/patching/PatcherTest.java | 55 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 src/main/java/org/cqfn/astranaut/core/algorithms/patching/DefaultPatcher.java create mode 100644 src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatcherTest.java diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/patching/DefaultPatcher.java b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/DefaultPatcher.java new file mode 100644 index 0000000..330f3c7 --- /dev/null +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/DefaultPatcher.java @@ -0,0 +1,52 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ivan Kniazkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.cqfn.astranaut.core.algorithms.patching; + +import java.util.Set; +import org.cqfn.astranaut.core.DifferenceNode; +import org.cqfn.astranaut.core.Node; +import org.cqfn.astranaut.core.utils.deserializer.ActionList; + +/** + * Default algorithm that applies patches, i.e. makes some changes in the syntax tree + * based on patterns describing such changes. Patterns are differential trees. + * + * @since 1.1.5 + */ +public final class DefaultPatcher implements Patcher { + @Override + public Node patch(final Node source, final DifferenceNode pattern) { + final PatternMatcher matcher = new PatternMatcher(source); + final Set nodes = matcher.match(pattern); + final Node result; + if (nodes.isEmpty()) { + result = source; + } else { + final ActionList actions = matcher.getActionList(); + final DifferenceNode diff = actions.convertTreeToDifferenceTree(source); + result = diff.getAfter(); + } + return result; + } +} 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 new file mode 100644 index 0000000..f3b8122 --- /dev/null +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatcherTest.java @@ -0,0 +1,55 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ivan Kniazkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.cqfn.astranaut.core.algorithms.patching; + +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import org.cqfn.astranaut.core.DifferenceNode; +import org.cqfn.astranaut.core.DraftNode; +import org.cqfn.astranaut.core.Node; +import org.cqfn.astranaut.core.algorithms.DifferenceTreeBuilder; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Testing {@link Patcher} class. + * + * @since 1.1.5 + */ +class PatcherTest { + @Test + void patchPatternWithReplacement() { + final Map> nodes = new TreeMap<>(); + final Node prepattern = DraftNode.createByDescription("A(B, D)", nodes); + final DifferenceTreeBuilder builder = new DifferenceTreeBuilder(prepattern); + builder.replaceNode(nodes.get("B").iterator().next(), DraftNode.createByDescription("C")); + final DifferenceNode pattern = builder.getRoot(); + final Node tree = DraftNode.createByDescription("X(Y,A(B,D),Z)"); + final Patcher patcher = new DefaultPatcher(); + final Node result = patcher.patch(tree, pattern); + final Node expected = DraftNode.createByDescription("X(Y,A(C,D),Z)"); + Assertions.assertTrue(expected.deepCompare(result)); + } +} From 7a8bf12bdb3610ca48207384765e528f54819d14 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Mon, 10 Jun 2024 13:26:53 +0300 Subject: [PATCH 10/19] =?UTF-8?q?The=20patcher=20handles=20the=20=E2=80=9C?= =?UTF-8?q?Delete=E2=80=9D=20operation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/algorithms/patching/PatternMatcher.java | 2 ++ .../core/algorithms/patching/PatcherTest.java | 14 ++++++++++++++ .../algorithms/patching/PatternMatcherTest.java | 15 +++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcher.java b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcher.java index 025dcb1..38c758b 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcher.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcher.java @@ -109,6 +109,8 @@ private boolean checkNode(final Node node, final Node parent, final Node pattern && this.checkChildren(node, sample); if (result && pattern instanceof Replace) { this.actions.replaceNode(node, ((Action) pattern).getAfter()); + } else if (result & pattern instanceof Delete) { + this.actions.deleteNode(node); } return result; } 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 f3b8122..4804543 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 @@ -52,4 +52,18 @@ void patchPatternWithReplacement() { final Node expected = DraftNode.createByDescription("X(Y,A(C,D),Z)"); Assertions.assertTrue(expected.deepCompare(result)); } + + @Test + void patchPatternWithDeletion() { + final Map> nodes = new TreeMap<>(); + final Node prepattern = DraftNode.createByDescription("A(B, D)", nodes); + final DifferenceTreeBuilder builder = new DifferenceTreeBuilder(prepattern); + builder.deleteNode(nodes.get("B").iterator().next()); + final DifferenceNode pattern = builder.getRoot(); + final Node tree = DraftNode.createByDescription("X(Y,A(B,D),Z)"); + final Patcher patcher = new DefaultPatcher(); + final Node result = patcher.patch(tree, pattern); + final Node expected = DraftNode.createByDescription("X(Y,A(D),Z)"); + Assertions.assertTrue(expected.deepCompare(result)); + } } diff --git a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcherTest.java b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcherTest.java index 9a0e0e6..681171f 100644 --- a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcherTest.java +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcherTest.java @@ -79,4 +79,19 @@ void findPatternWithReplacementInATree() { final Node node = found.iterator().next(); Assertions.assertEquals("A", node.getTypeName()); } + + @Test + void findPatternWithDeletionInATree() { + final Map> nodes = new TreeMap<>(); + final Node prepattern = DraftNode.createByDescription("A(B, D)", nodes); + final DifferenceTreeBuilder builder = new DifferenceTreeBuilder(prepattern); + builder.deleteNode(nodes.get("B").iterator().next()); + final DifferenceNode pattern = builder.getRoot(); + final Node tree = DraftNode.createByDescription("X(Y,A(B,D),Z)"); + final PatternMatcher matcher = new PatternMatcher(tree); + final Set found = matcher.match(pattern); + Assertions.assertEquals(1, found.size()); + final Node node = found.iterator().next(); + Assertions.assertEquals("A", node.getTypeName()); + } } From 594bb8301ed73c9f4e3ff5634ce6faf1db7c7015 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Mon, 10 Jun 2024 16:34:06 +0300 Subject: [PATCH 11/19] =?UTF-8?q?The=20patcher=20handles=20the=20=E2=80=9C?= =?UTF-8?q?Insert=E2=80=9D=20operation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/cqfn/astranaut/core/Node.java | 50 ++++++++++++++++++- .../algorithms/patching/PatternMatcher.java | 37 ++++++++++---- .../core/algorithms/patching/PatcherTest.java | 21 ++++++++ .../patching/PatternMatcherTest.java | 22 ++++++++ 4 files changed, 118 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/cqfn/astranaut/core/Node.java b/src/main/java/org/cqfn/astranaut/core/Node.java index 154f1dd..87af1b4 100644 --- a/src/main/java/org/cqfn/astranaut/core/Node.java +++ b/src/main/java/org/cqfn/astranaut/core/Node.java @@ -24,7 +24,9 @@ package org.cqfn.astranaut.core; import java.util.Arrays; +import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; import java.util.function.Consumer; /** @@ -32,7 +34,7 @@ * * @since 1.0 */ -public interface Node { +public interface Node extends Iterable { /** * Returns the fragment associated with the node. * @return The fragment @@ -126,4 +128,50 @@ default boolean deepCompare(Node other) { } return equals; } + + @Override + default Iterator iterator() { + return new NodeIterator(this); + } + + /** + * Iterator that enumerates the children of a node. + * + * @since 1.1.5 + */ + class NodeIterator implements Iterator { + /** + * Node. + */ + private final Node node; + + /** + * Current index. + */ + private int index; + + /** + * Constructor. + * @param node Node whose children should be enumerated. + */ + private NodeIterator(final Node node) { + this.node = node; + this.index = 0; + } + + @Override + public boolean hasNext() { + return this.index < this.node.getChildCount(); + } + + @Override + public Node next() { + if (!this.hasNext()) { + throw new NoSuchElementException(); + } + final Node child = this.node.getChild(this.index); + this.index = this.index + 1; + return child; + } + } } diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcher.java b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcher.java index 38c758b..9e97371 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcher.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcher.java @@ -24,11 +24,13 @@ package org.cqfn.astranaut.core.algorithms.patching; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Set; import org.cqfn.astranaut.core.Action; import org.cqfn.astranaut.core.Delete; import org.cqfn.astranaut.core.DifferenceNode; +import org.cqfn.astranaut.core.Insert; import org.cqfn.astranaut.core.Node; import org.cqfn.astranaut.core.Replace; import org.cqfn.astranaut.core.algorithms.DeepTraversal; @@ -103,10 +105,11 @@ private boolean checkNode(final Node node, final Node parent, final Node pattern } else { sample = pattern; } - final boolean result = node.getChildCount() >= sample.getChildCount() - && node.getTypeName().equals(sample.getTypeName()) - && node.getData().equals(sample.getData()) - && this.checkChildren(node, sample); + boolean result = node.getTypeName().equals(sample.getTypeName()) + && node.getData().equals(sample.getData()); + if (result && node.getChildCount() > 0) { + result = this.checkChildren(node, sample); + } if (result && pattern instanceof Replace) { this.actions.replaceNode(node, ((Action) pattern).getAfter()); } else if (result & pattern instanceof Delete) { @@ -126,14 +129,26 @@ private boolean checkChildren(final Node node, final Node sample) { final int left = node.getChildCount(); final int right = sample.getChildCount(); boolean result = false; - for (int index = 0; !result && index < left - right + 1; index = index + 1) { + for (int index = 0; !result && index < left; index = index + 1) { result = true; - for (int offset = 0; result && offset < right; offset = offset + 1) { - result = this.checkNode( - node.getChild(index + offset), - node, - sample.getChild(offset) - ); + final Iterator iterator = sample.iterator(); + int offset = 0; + Node current = null; + while (result && offset < right && iterator.hasNext()) { + final Node child = iterator.next(); + if (child instanceof Insert) { + this.actions.insertNodeAfter(((Insert) child).getAfter(), node, current); + } else if (index + offset >= left) { + result = false; + } else { + current = node.getChild(index + offset); + result = this.checkNode( + current, + node, + child + ); + offset = offset + 1; + } } } return result; 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 4804543..e93379e 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 @@ -28,6 +28,7 @@ import java.util.TreeMap; import org.cqfn.astranaut.core.DifferenceNode; import org.cqfn.astranaut.core.DraftNode; +import org.cqfn.astranaut.core.Insertion; import org.cqfn.astranaut.core.Node; import org.cqfn.astranaut.core.algorithms.DifferenceTreeBuilder; import org.junit.jupiter.api.Assertions; @@ -39,6 +40,26 @@ * @since 1.1.5 */ class PatcherTest { + @Test + void patchPatternWithInsertion() { + final Map> nodes = new TreeMap<>(); + final Node prepattern = DraftNode.createByDescription("A(B)", nodes); + final DifferenceTreeBuilder builder = new DifferenceTreeBuilder(prepattern); + builder.insertNode( + new Insertion( + DraftNode.createByDescription("C"), + prepattern, + nodes.get("B").iterator().next() + ) + ); + final DifferenceNode pattern = builder.getRoot(); + final Node tree = DraftNode.createByDescription("X(Y,A(B),Z)"); + final Patcher patcher = new DefaultPatcher(); + final Node result = patcher.patch(tree, pattern); + final Node expected = DraftNode.createByDescription("X(Y,A(B,C),Z)"); + Assertions.assertTrue(expected.deepCompare(result)); + } + @Test void patchPatternWithReplacement() { final Map> nodes = new TreeMap<>(); diff --git a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcherTest.java b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcherTest.java index 681171f..56e8c55 100644 --- a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcherTest.java +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcherTest.java @@ -28,6 +28,7 @@ import java.util.TreeMap; import org.cqfn.astranaut.core.DifferenceNode; import org.cqfn.astranaut.core.DraftNode; +import org.cqfn.astranaut.core.Insertion; import org.cqfn.astranaut.core.Node; import org.cqfn.astranaut.core.algorithms.DifferenceTreeBuilder; import org.junit.jupiter.api.Assertions; @@ -65,6 +66,27 @@ void findReducedSubtreeInATree() { Assertions.assertEquals(4, node.getChildCount()); } + @Test + void findPatternWithInsertionInATree() { + final Map> nodes = new TreeMap<>(); + final Node prepattern = DraftNode.createByDescription("A(B)", nodes); + final DifferenceTreeBuilder builder = new DifferenceTreeBuilder(prepattern); + builder.insertNode( + new Insertion( + DraftNode.createByDescription("C"), + prepattern, + nodes.get("B").iterator().next() + ) + ); + final DifferenceNode pattern = builder.getRoot(); + final Node tree = DraftNode.createByDescription("X(Y,A(B),Z)"); + final PatternMatcher matcher = new PatternMatcher(tree); + final Set found = matcher.match(pattern); + Assertions.assertEquals(1, found.size()); + final Node node = found.iterator().next(); + Assertions.assertEquals("A", node.getTypeName()); + } + @Test void findPatternWithReplacementInATree() { final Map> nodes = new TreeMap<>(); From 8c396979325714b134be512cc940a9f6b5788942 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Thu, 13 Jun 2024 03:57:59 +0300 Subject: [PATCH 12/19] Pattern items --- .../java/org/cqfn/astranaut/core/Action.java | 2 +- .../cqfn/astranaut/core/DifferenceNode.java | 2 +- .../org/cqfn/astranaut/core/PatternItem.java | 32 +++++ .../org/cqfn/astranaut/core/PatternNode.java | 113 ++++++++++++++++++ 4 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/cqfn/astranaut/core/PatternItem.java create mode 100644 src/main/java/org/cqfn/astranaut/core/PatternNode.java diff --git a/src/main/java/org/cqfn/astranaut/core/Action.java b/src/main/java/org/cqfn/astranaut/core/Action.java index a3bff15..876bfbc 100644 --- a/src/main/java/org/cqfn/astranaut/core/Action.java +++ b/src/main/java/org/cqfn/astranaut/core/Action.java @@ -29,5 +29,5 @@ * * @since 1.1.0 */ -public interface Action extends DifferenceTreeItem { +public interface Action extends DifferenceTreeItem, PatternItem { } diff --git a/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java b/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java index 16f6afe..c145e11 100644 --- a/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java +++ b/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java @@ -223,7 +223,7 @@ public String toString() { } /** - * Transforms children nodes to convertible ones. + * Transforms children nodes to difference ones. * @return List of difference nodes */ private List initChildrenList() { diff --git a/src/main/java/org/cqfn/astranaut/core/PatternItem.java b/src/main/java/org/cqfn/astranaut/core/PatternItem.java new file mode 100644 index 0000000..ff19312 --- /dev/null +++ b/src/main/java/org/cqfn/astranaut/core/PatternItem.java @@ -0,0 +1,32 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ivan Kniazkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.cqfn.astranaut.core; + +/** + * An element of a pattern. + * + * @since 1.1.5 + */ +public interface PatternItem extends Node { +} diff --git a/src/main/java/org/cqfn/astranaut/core/PatternNode.java b/src/main/java/org/cqfn/astranaut/core/PatternNode.java new file mode 100644 index 0000000..859d5bb --- /dev/null +++ b/src/main/java/org/cqfn/astranaut/core/PatternNode.java @@ -0,0 +1,113 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ivan Kniazkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.cqfn.astranaut.core; + +import java.util.ArrayList; +import java.util.List; + +/** + * A node that contains child nodes as well as actions on those nodes. + * Child nodes can be replaced by holes. + * + * @since 1.1.5 + */ +public final class PatternNode implements PatternItem { + /** + * The prototype node, i.e. 'ordinary', non-difference original node. + */ + private final Node prototype; + + /** + * The list of children with actions. + */ + private final List children; + + /** + * Constructor. + * @param diff Node of the differential tree from which the pattern is built + */ + private PatternNode(final DifferenceNode diff) { + this.prototype = diff.getPrototype(); + this.children = PatternNode.initChildrenList(diff); + } + + /** + * Returns the prototype node. + * @return The prototype node + */ + public Node getPrototype() { + return this.prototype; + } + + @Override + public Fragment getFragment() { + return this.prototype.getFragment(); + } + + @Override + public Type getType() { + return this.prototype.getType(); + } + + @Override + public String getData() { + return this.prototype.getData(); + } + + @Override + public int getChildCount() { + return this.children.size(); + } + + @Override + public Node getChild(final int index) { + return this.children.get(index); + } + + @Override + public String toString() { + return this.prototype.toString(); + } + + /** + * Transforms children nodes of difference node to pattern items. + * @param diff Difference node + * @return List of pattern item + */ + private static List initChildrenList(final DifferenceNode diff) { + final int count = diff.getChildCount(); + final List result = new ArrayList<>(count); + for (int index = 0; index < count; index = index + 1) { + final Node child = diff.getChild(index); + if (child instanceof DifferenceNode) { + result.add( + new PatternNode((DifferenceNode) child) + ); + } else if (child instanceof Action) { + result.add((PatternItem) child); + } + } + return result; + } +} From 7fcf752335122af701abc4799c10a168054fdef2 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Thu, 13 Jun 2024 04:49:08 +0300 Subject: [PATCH 13/19] Hole class --- .../java/org/cqfn/astranaut/core/Hole.java | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 src/main/java/org/cqfn/astranaut/core/Hole.java diff --git a/src/main/java/org/cqfn/astranaut/core/Hole.java b/src/main/java/org/cqfn/astranaut/core/Hole.java new file mode 100644 index 0000000..aac186b --- /dev/null +++ b/src/main/java/org/cqfn/astranaut/core/Hole.java @@ -0,0 +1,137 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ivan Kniazkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.cqfn.astranaut.core; + +import java.util.List; + +/** + * A special pattern node that can substitute for any node of a suitable type. + * + * @since 1.1.5 + */ +public final class Hole implements PatternItem { + /** + * The type of the hole. + */ + private final Type type; + + /** + * The number of the hole. + */ + private final int number; + + /** + * Constructor. + * @param type The type of the hole + * @param number The number of the hole + */ + public Hole(final Type type, final int number) { + this.type = new HoleType(type); + this.number = number; + } + + @Override + public Fragment getFragment() { + return EmptyFragment.INSTANCE; + } + + @Override + public Type getType() { + return this.type; + } + + @Override + public String getData() { + return String.format("#%d", this.number); + } + + @Override + public int getChildCount() { + return 0; + } + + @Override + public Node getChild(final int index) { + return null; + } + + /** + * Return number of the hole. + * @return The number + */ + public int getNumber() { + return this.number; + } + + /** + * Type implementation for a hole based on a prototype. + * + * @since 1.1.5 + */ + private static final class HoleType implements Type { + /** + * Prototype. + */ + private final Type prototype; + + /** + * Constructor. + * @param prototype Prototype + */ + private HoleType(final Type prototype) { + this.prototype = prototype; + } + + @Override + public String getName() { + return this.prototype.getName(); + } + + @Override + public List getChildTypes() { + return this.prototype.getChildTypes(); + } + + @Override + public List getHierarchy() { + return this.prototype.getHierarchy(); + } + + @Override + public String getProperty(final String name) { + final String property; + if ("color".equals(name)) { + property = "purple"; + } else { + property = this.prototype.getProperty(name); + } + return property; + } + + @Override + public Builder createBuilder() { + return this.prototype.createBuilder(); + } + } +} From 6d155dbfdca95eecba713a22327e9dd03c8ca907 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Thu, 13 Jun 2024 08:36:45 +0300 Subject: [PATCH 14/19] Pattern builder --- .../org/cqfn/astranaut/core/PatternNode.java | 38 ++++- .../algorithms/DifferenceTreeBuilder.java | 2 +- .../core/algorithms/PatternBuilder.java | 159 ++++++++++++++++++ 3 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/cqfn/astranaut/core/algorithms/PatternBuilder.java diff --git a/src/main/java/org/cqfn/astranaut/core/PatternNode.java b/src/main/java/org/cqfn/astranaut/core/PatternNode.java index 859d5bb..728fe21 100644 --- a/src/main/java/org/cqfn/astranaut/core/PatternNode.java +++ b/src/main/java/org/cqfn/astranaut/core/PatternNode.java @@ -47,7 +47,7 @@ public final class PatternNode implements PatternItem { * Constructor. * @param diff Node of the differential tree from which the pattern is built */ - private PatternNode(final DifferenceNode diff) { + public PatternNode(final DifferenceNode diff) { this.prototype = diff.getPrototype(); this.children = PatternNode.initChildrenList(diff); } @@ -90,6 +90,23 @@ public String toString() { return this.prototype.toString(); } + /** + * Turns a child node into a hole. + * @param node Child node + * @param number Hole number + * @return Result of operation, @return {@code true} if node was transformer + */ + public boolean makeHole(final Node node, final int number) { + boolean result = false; + final int index = this.findChildIndex(node); + if (index >= 0) { + final Hole hole = new Hole(node.getType(), number); + this.children.set(index, hole); + result = true; + } + return result; + } + /** * Transforms children nodes of difference node to pattern items. * @param diff Difference node @@ -110,4 +127,23 @@ private static List initChildrenList(final DifferenceNode diff) { } return result; } + + /** + * Searches the index of a child element by its prototype. + * @param node Prototype of the node whose index is to be found + * @return Index or -1 if there is no such node + */ + private int findChildIndex(final Node node) { + int result = -1; + final int count = this.children.size(); + for (int index = 0; index < count; index = index + 1) { + final PatternItem child = this.children.get(index); + if (child instanceof PatternNode + && node == ((PatternNode) child).getPrototype()) { + result = index; + break; + } + } + return result; + } } diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/DifferenceTreeBuilder.java b/src/main/java/org/cqfn/astranaut/core/algorithms/DifferenceTreeBuilder.java index cb8ad52..6716767 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/DifferenceTreeBuilder.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/DifferenceTreeBuilder.java @@ -34,7 +34,7 @@ /** * Builder of difference syntax tree, that is, one that stores changes between two trees. * - * @since 1.1.0 + * @since 1.1.5 */ public final class DifferenceTreeBuilder { /** diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/PatternBuilder.java b/src/main/java/org/cqfn/astranaut/core/algorithms/PatternBuilder.java new file mode 100644 index 0000000..33ed3e5 --- /dev/null +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/PatternBuilder.java @@ -0,0 +1,159 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ivan Kniazkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.cqfn.astranaut.core.algorithms; + +import java.util.HashMap; +import java.util.Map; +import org.cqfn.astranaut.core.DifferenceNode; +import org.cqfn.astranaut.core.Node; +import org.cqfn.astranaut.core.PatternNode; + +/** + * Pattern builder. + * + * @since 1.1.5 + */ +public final class PatternBuilder { + /** + * Default node info (to avoid null checks). + */ + private static final NodeInfo DEFAULT_INFO = new NodeInfo(null, null); + + /** + * The relationship of the nodes to their parents and corresponding difference nodes. + * This information is necessary to implement algorithms for inserting, removing + * and replacing nodes. + */ + private final Map info; + + /** + * Root node. + */ + private final PatternNode root; + + /** + * Constructor. + * @param diff Node of the differential tree from which the pattern is built + */ + public PatternBuilder(final DifferenceNode diff) { + this.root = new PatternNode(diff); + this.info = PatternBuilder.buildNodeInfoMap(this.root); + } + + /** + * Returns root of resulting pattern. + * @return Root node of pattern + */ + public PatternNode getRoot() { + return this.root; + } + + /** + * Turns a child node into a hole. + * @param node Child node + * @param number Hole number + * @return Result of operation, @return {@code true} if node was transformer + */ + public boolean makeHole(final Node node, final int number) { + boolean result = false; + final PatternNode parent = + this.info.getOrDefault(node, PatternBuilder.DEFAULT_INFO).getParent(); + if (parent != null) { + result = parent.makeHole(node, number); + } + return result; + } + + /** + * Builds the map containing relationship of the nodes to their parents. + * @param root Root node + * @return The map containing relationship of the nodes to their parents. + */ + private static Map buildNodeInfoMap(final PatternNode root) { + final Map map = new HashMap<>(); + map.put(root.getPrototype(), new NodeInfo(root, null)); + PatternBuilder.buildNodeInfoMap(map, root); + return map; + } + + /** + * Builds the map containing relationship of the nodes to their parents (recursive method). + * @param map Where to put the results + * @param parent Parent node + */ + private static void buildNodeInfoMap(final Map map, final PatternNode parent) { + parent.forEachChild( + child -> { + if (child instanceof PatternNode) { + final PatternNode node = (PatternNode) child; + map.put(node.getPrototype(), new NodeInfo(node, parent)); + PatternBuilder.buildNodeInfoMap(map, node); + } + } + ); + } + + /** + * Some additional information about each node needed to insert, replace, or delete nodes. + * + * @since 1.1.0 + */ + private static final class NodeInfo { + /** + * The corresponding pattern node. + */ + private final PatternNode pnode; + + /** + * The parent node. + */ + private final PatternNode parent; + + /** + * Constructor. + * @param pnode The corresponding pattern node + * @param parent The parent node + */ + NodeInfo(final PatternNode pnode, final PatternNode parent) { + this.pnode = pnode; + this.parent = parent; + } + + /** + * Returns corresponding patter node. + * @return Pattern node + */ + public PatternNode getPatternNode() { + return this.pnode; + } + + /** + * Returns parent node. + * @return Pattern node containing this node + */ + public PatternNode getParent() { + return this.parent; + } + } +} From 6b6dba692db603e2c03cb2071109e36cdbbe068c Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Thu, 13 Jun 2024 09:16:55 +0300 Subject: [PATCH 15/19] Pattern builder test --- .../java/org/cqfn/astranaut/core/Hole.java | 5 ++ .../core/algorithms/PatternBuilderTest.java | 77 +++++++++++++++++++ .../core/example/green/Addition.java | 5 ++ .../example/green/ExpressionStatement.java | 5 ++ .../core/example/green/IntegerLiteral.java | 5 ++ .../core/example/green/SimpleAssignment.java | 5 ++ .../core/example/green/Variable.java | 5 ++ 7 files changed, 107 insertions(+) create mode 100644 src/test/java/org/cqfn/astranaut/core/algorithms/PatternBuilderTest.java diff --git a/src/main/java/org/cqfn/astranaut/core/Hole.java b/src/main/java/org/cqfn/astranaut/core/Hole.java index aac186b..049a266 100644 --- a/src/main/java/org/cqfn/astranaut/core/Hole.java +++ b/src/main/java/org/cqfn/astranaut/core/Hole.java @@ -84,6 +84,11 @@ public int getNumber() { return this.number; } + @Override + public String toString() { + return this.getData(); + } + /** * Type implementation for a hole based on a prototype. * diff --git a/src/test/java/org/cqfn/astranaut/core/algorithms/PatternBuilderTest.java b/src/test/java/org/cqfn/astranaut/core/algorithms/PatternBuilderTest.java new file mode 100644 index 0000000..affed22 --- /dev/null +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/PatternBuilderTest.java @@ -0,0 +1,77 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ivan Kniazkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.cqfn.astranaut.core.algorithms; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Optional; +import org.cqfn.astranaut.core.Builder; +import org.cqfn.astranaut.core.DifferenceNode; +import org.cqfn.astranaut.core.Hole; +import org.cqfn.astranaut.core.Node; +import org.cqfn.astranaut.core.PatternNode; +import org.cqfn.astranaut.core.example.green.Addition; +import org.cqfn.astranaut.core.example.green.ExpressionStatement; +import org.cqfn.astranaut.core.example.green.IntegerLiteral; +import org.cqfn.astranaut.core.example.green.SimpleAssignment; +import org.cqfn.astranaut.core.example.green.Variable; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Testing {@link PatternBuilder} class. + * + * @since 1.1.0 + */ +class PatternBuilderTest { + @Test + void creatingPatternWithHole() { + Builder ctor = new Variable.Constructor(); + ctor.setData("a"); + final Node first = ctor.createNode(); + ctor = new IntegerLiteral.Constructor(); + ctor.setData("1"); + final Node second = ctor.createNode(); + ctor = new Addition.Constructor(); + ctor.setChildrenList(Arrays.asList(first, second)); + final Node addition = ctor.createNode(); + ctor = new Variable.Constructor(); + ctor.setData("x"); + final Node variable = ctor.createNode(); + ctor = new SimpleAssignment.Constructor(); + ctor.setChildrenList(Arrays.asList(variable, addition)); + final Node assignment = ctor.createNode(); + ctor = new ExpressionStatement.Constructor(); + ctor.setChildrenList(Collections.singletonList(assignment)); + final Node stmt = ctor.createNode(); + final PatternBuilder builder = new PatternBuilder(new DifferenceNode(stmt)); + builder.makeHole(first, 1); + final PatternNode pattern = builder.getRoot(); + Assertions.assertNotNull(pattern); + final DeepTraversal traversal = new DeepTraversal(pattern); + final Optional hole = traversal.findFirst(node -> node instanceof Hole); + Assertions.assertTrue(hole.isPresent()); + Assertions.assertEquals("#1", hole.get().getData()); + } +} diff --git a/src/test/java/org/cqfn/astranaut/core/example/green/Addition.java b/src/test/java/org/cqfn/astranaut/core/example/green/Addition.java index 6ae7e42..51a920c 100644 --- a/src/test/java/org/cqfn/astranaut/core/example/green/Addition.java +++ b/src/test/java/org/cqfn/astranaut/core/example/green/Addition.java @@ -110,6 +110,11 @@ public Expression getRight() { return this.right; } + @Override + public String toString() { + return String.format("%s + %s", this.left.toString(), this.right.toString()); + } + /** * Type descriptor of the 'Addition' node. * diff --git a/src/test/java/org/cqfn/astranaut/core/example/green/ExpressionStatement.java b/src/test/java/org/cqfn/astranaut/core/example/green/ExpressionStatement.java index de89201..e3e3c8b 100644 --- a/src/test/java/org/cqfn/astranaut/core/example/green/ExpressionStatement.java +++ b/src/test/java/org/cqfn/astranaut/core/example/green/ExpressionStatement.java @@ -101,6 +101,11 @@ public Node getChild(final int index) { return this.children.get(index); } + @Override + public String toString() { + return String.format("%s;", this.expression.toString()); + } + /** * Returns the child with the 'expression' tag. * @return The node diff --git a/src/test/java/org/cqfn/astranaut/core/example/green/IntegerLiteral.java b/src/test/java/org/cqfn/astranaut/core/example/green/IntegerLiteral.java index 3a399d6..582d303 100644 --- a/src/test/java/org/cqfn/astranaut/core/example/green/IntegerLiteral.java +++ b/src/test/java/org/cqfn/astranaut/core/example/green/IntegerLiteral.java @@ -88,6 +88,11 @@ public Node getChild(final int index) { throw new IndexOutOfBoundsException(); } + @Override + public String toString() { + return this.getData(); + } + /** * Type descriptor of the 'Addition' node. * diff --git a/src/test/java/org/cqfn/astranaut/core/example/green/SimpleAssignment.java b/src/test/java/org/cqfn/astranaut/core/example/green/SimpleAssignment.java index 9774f39..cd46092 100644 --- a/src/test/java/org/cqfn/astranaut/core/example/green/SimpleAssignment.java +++ b/src/test/java/org/cqfn/astranaut/core/example/green/SimpleAssignment.java @@ -116,6 +116,11 @@ public Expression getRight() { return this.right; } + @Override + public String toString() { + return String.format("%s = %s", this.left.toString(), this.right.toString()); + } + /** * Type descriptor of the 'SimpleAssignment' node. * diff --git a/src/test/java/org/cqfn/astranaut/core/example/green/Variable.java b/src/test/java/org/cqfn/astranaut/core/example/green/Variable.java index 57d861d..31e0e63 100644 --- a/src/test/java/org/cqfn/astranaut/core/example/green/Variable.java +++ b/src/test/java/org/cqfn/astranaut/core/example/green/Variable.java @@ -88,6 +88,11 @@ public Node getChild(final int index) { throw new IndexOutOfBoundsException(); } + @Override + public String toString() { + return this.data; + } + /** * Type descriptor of the 'Addition' node. * From fe944dfdb408759e2b57668b8e860835346c3aec Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Thu, 13 Jun 2024 10:06:54 +0300 Subject: [PATCH 16/19] Pattern matcher matches holes --- .../algorithms/patching/DefaultPatcher.java | 3 +- .../core/algorithms/patching/Patcher.java | 4 +-- .../algorithms/patching/PatternMatcher.java | 13 ++++---- .../core/algorithms/patching/PatcherTest.java | 8 ++--- .../patching/PatternMatcherTest.java | 32 ++++++++++++++++--- 5 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/patching/DefaultPatcher.java b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/DefaultPatcher.java index 330f3c7..f70acc0 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/patching/DefaultPatcher.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/DefaultPatcher.java @@ -26,6 +26,7 @@ import java.util.Set; import org.cqfn.astranaut.core.DifferenceNode; import org.cqfn.astranaut.core.Node; +import org.cqfn.astranaut.core.PatternNode; import org.cqfn.astranaut.core.utils.deserializer.ActionList; /** @@ -36,7 +37,7 @@ */ public final class DefaultPatcher implements Patcher { @Override - public Node patch(final Node source, final DifferenceNode pattern) { + public Node patch(final Node source, final PatternNode pattern) { final PatternMatcher matcher = new PatternMatcher(source); final Set nodes = matcher.match(pattern); final Node result; diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/patching/Patcher.java b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/Patcher.java index a56c0d4..94f8f2d 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/patching/Patcher.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/Patcher.java @@ -23,8 +23,8 @@ */ package org.cqfn.astranaut.core.algorithms.patching; -import org.cqfn.astranaut.core.DifferenceNode; import org.cqfn.astranaut.core.Node; +import org.cqfn.astranaut.core.PatternNode; /** * Interface to an algorithm that applies patches, i.e. makes some changes in the syntax tree @@ -39,5 +39,5 @@ public interface Patcher { * @param pattern Root node af a pattern * @return Root node of updated syntax tree */ - Node patch(Node source, DifferenceNode pattern); + Node patch(Node source, PatternNode pattern); } diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcher.java b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcher.java index 9e97371..fca69e5 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcher.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcher.java @@ -29,9 +29,10 @@ import java.util.Set; import org.cqfn.astranaut.core.Action; import org.cqfn.astranaut.core.Delete; -import org.cqfn.astranaut.core.DifferenceNode; +import org.cqfn.astranaut.core.Hole; import org.cqfn.astranaut.core.Insert; import org.cqfn.astranaut.core.Node; +import org.cqfn.astranaut.core.PatternNode; import org.cqfn.astranaut.core.Replace; import org.cqfn.astranaut.core.algorithms.DeepTraversal; import org.cqfn.astranaut.core.utils.deserializer.ActionList; @@ -74,7 +75,7 @@ public ActionList getActionList() { * @param pattern Root node of the pattern * @return Nodes that match the root node of the pattern */ - Set match(final DifferenceNode pattern) { + Set match(final PatternNode pattern) { final DeepTraversal deep = new DeepTraversal(this.root); final List preset = deep.findAll( node -> node.getTypeName().equals(pattern.getTypeName()) @@ -105,10 +106,10 @@ private boolean checkNode(final Node node, final Node parent, final Node pattern } else { sample = pattern; } - boolean result = node.getTypeName().equals(sample.getTypeName()) - && node.getData().equals(sample.getData()); - if (result && node.getChildCount() > 0) { - result = this.checkChildren(node, sample); + 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)); } if (result && pattern instanceof Replace) { this.actions.replaceNode(node, ((Action) pattern).getAfter()); 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 e93379e..4c851cf 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 @@ -26,10 +26,10 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; -import org.cqfn.astranaut.core.DifferenceNode; import org.cqfn.astranaut.core.DraftNode; import org.cqfn.astranaut.core.Insertion; import org.cqfn.astranaut.core.Node; +import org.cqfn.astranaut.core.PatternNode; import org.cqfn.astranaut.core.algorithms.DifferenceTreeBuilder; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -52,7 +52,7 @@ void patchPatternWithInsertion() { nodes.get("B").iterator().next() ) ); - final DifferenceNode pattern = builder.getRoot(); + final PatternNode pattern = new PatternNode(builder.getRoot()); final Node tree = DraftNode.createByDescription("X(Y,A(B),Z)"); final Patcher patcher = new DefaultPatcher(); final Node result = patcher.patch(tree, pattern); @@ -66,7 +66,7 @@ void patchPatternWithReplacement() { final Node prepattern = DraftNode.createByDescription("A(B, D)", nodes); final DifferenceTreeBuilder builder = new DifferenceTreeBuilder(prepattern); builder.replaceNode(nodes.get("B").iterator().next(), DraftNode.createByDescription("C")); - final DifferenceNode pattern = builder.getRoot(); + final PatternNode pattern = new PatternNode(builder.getRoot()); final Node tree = DraftNode.createByDescription("X(Y,A(B,D),Z)"); final Patcher patcher = new DefaultPatcher(); final Node result = patcher.patch(tree, pattern); @@ -80,7 +80,7 @@ void patchPatternWithDeletion() { final Node prepattern = DraftNode.createByDescription("A(B, D)", nodes); final DifferenceTreeBuilder builder = new DifferenceTreeBuilder(prepattern); builder.deleteNode(nodes.get("B").iterator().next()); - final DifferenceNode pattern = builder.getRoot(); + final PatternNode pattern = new PatternNode(builder.getRoot()); final Node tree = DraftNode.createByDescription("X(Y,A(B,D),Z)"); final Patcher patcher = new DefaultPatcher(); final Node result = patcher.patch(tree, pattern); diff --git a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcherTest.java b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcherTest.java index 56e8c55..f82ab1c 100644 --- a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcherTest.java +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcherTest.java @@ -30,7 +30,9 @@ import org.cqfn.astranaut.core.DraftNode; import org.cqfn.astranaut.core.Insertion; import org.cqfn.astranaut.core.Node; +import org.cqfn.astranaut.core.PatternNode; import org.cqfn.astranaut.core.algorithms.DifferenceTreeBuilder; +import org.cqfn.astranaut.core.algorithms.PatternBuilder; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -44,8 +46,9 @@ class PatternMatcherTest { void findSubtreeInATree() { final Node tree = DraftNode.createByDescription("X(Y(A(B,C)),A(B,C),A(B,D))"); final DifferenceNode subtree = new DifferenceNode(DraftNode.createByDescription("A(B,C)")); + final PatternNode pattern = new PatternNode(subtree); final PatternMatcher matcher = new PatternMatcher(tree); - final Set found = matcher.match(subtree); + final Set found = matcher.match(pattern); Assertions.assertEquals(2, found.size()); for (final Node node : found) { Assertions.assertEquals("A", node.getTypeName()); @@ -59,7 +62,8 @@ void findReducedSubtreeInATree() { DraftNode.createByDescription("A(C(F),D)") ); final PatternMatcher matcher = new PatternMatcher(tree); - final Set found = matcher.match(subtree); + final PatternNode pattern = new PatternNode(subtree); + final Set found = matcher.match(pattern); Assertions.assertEquals(1, found.size()); final Node node = found.iterator().next(); Assertions.assertEquals("A", node.getTypeName()); @@ -78,7 +82,7 @@ void findPatternWithInsertionInATree() { nodes.get("B").iterator().next() ) ); - final DifferenceNode pattern = builder.getRoot(); + final PatternNode pattern = new PatternNode(builder.getRoot()); final Node tree = DraftNode.createByDescription("X(Y,A(B),Z)"); final PatternMatcher matcher = new PatternMatcher(tree); final Set found = matcher.match(pattern); @@ -93,7 +97,7 @@ void findPatternWithReplacementInATree() { final Node prepattern = DraftNode.createByDescription("A(B, D)", nodes); final DifferenceTreeBuilder builder = new DifferenceTreeBuilder(prepattern); builder.replaceNode(nodes.get("B").iterator().next(), DraftNode.createByDescription("C")); - final DifferenceNode pattern = builder.getRoot(); + final PatternNode pattern = new PatternNode(builder.getRoot()); final Node tree = DraftNode.createByDescription("X(Y,A(B,D),Z)"); final PatternMatcher matcher = new PatternMatcher(tree); final Set found = matcher.match(pattern); @@ -108,7 +112,7 @@ void findPatternWithDeletionInATree() { final Node prepattern = DraftNode.createByDescription("A(B, D)", nodes); final DifferenceTreeBuilder builder = new DifferenceTreeBuilder(prepattern); builder.deleteNode(nodes.get("B").iterator().next()); - final DifferenceNode pattern = builder.getRoot(); + final PatternNode pattern = new PatternNode(builder.getRoot()); final Node tree = DraftNode.createByDescription("X(Y,A(B,D),Z)"); final PatternMatcher matcher = new PatternMatcher(tree); final Set found = matcher.match(pattern); @@ -116,4 +120,22 @@ void findPatternWithDeletionInATree() { final Node node = found.iterator().next(); Assertions.assertEquals("A", node.getTypeName()); } + + @Test + void findPatternWithHoleInATree() { + final Map> nodes = new TreeMap<>(); + final Node prepattern = DraftNode.createByDescription("A(B, D<\"7\">)", nodes); + final DifferenceTreeBuilder dtbuilder = new DifferenceTreeBuilder(prepattern); + dtbuilder.deleteNode(nodes.get("B").iterator().next()); + final PatternBuilder pbuilder = new PatternBuilder(dtbuilder.getRoot()); + final boolean flag = pbuilder.makeHole(nodes.get("D").iterator().next(), 0); + Assertions.assertTrue(flag); + final PatternNode pattern = pbuilder.getRoot(); + final Node tree = DraftNode.createByDescription("X(Y,A(B,D<\"11\">),Z)"); + final PatternMatcher matcher = new PatternMatcher(tree); + final Set found = matcher.match(pattern); + Assertions.assertEquals(1, found.size()); + final Node node = found.iterator().next(); + Assertions.assertEquals("A", node.getTypeName()); + } } From 49b80ac1e5991c03894585c43c66169e4f586f7d Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Fri, 14 Jun 2024 14:07:14 +0300 Subject: [PATCH 17/19] Big test for patcher --- .../core/algorithms/patching/PatcherTest.java | 79 +++++++++++++++++++ .../core/example/green/Addition.java | 14 +++- 2 files changed, 92 insertions(+), 1 deletion(-) 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 4c851cf..031f215 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 @@ -23,14 +23,23 @@ */ package org.cqfn.astranaut.core.algorithms.patching; +import java.util.Arrays; +import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.TreeMap; +import org.cqfn.astranaut.core.Builder; import org.cqfn.astranaut.core.DraftNode; import org.cqfn.astranaut.core.Insertion; import org.cqfn.astranaut.core.Node; import org.cqfn.astranaut.core.PatternNode; import org.cqfn.astranaut.core.algorithms.DifferenceTreeBuilder; +import org.cqfn.astranaut.core.algorithms.PatternBuilder; +import org.cqfn.astranaut.core.example.green.Addition; +import org.cqfn.astranaut.core.example.green.ExpressionStatement; +import org.cqfn.astranaut.core.example.green.IntegerLiteral; +import org.cqfn.astranaut.core.example.green.SimpleAssignment; +import org.cqfn.astranaut.core.example.green.Variable; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -87,4 +96,74 @@ void patchPatternWithDeletion() { final Node expected = DraftNode.createByDescription("X(Y,A(D),Z)"); Assertions.assertTrue(expected.deepCompare(result)); } + + @Test + void patchPatternWithHole() { + Builder ctor = new Variable.Constructor(); + ctor.setData("a"); + Node first = ctor.createNode(); + ctor = new IntegerLiteral.Constructor(); + ctor.setData("1"); + Node second = ctor.createNode(); + ctor = new Addition.Constructor(); + ctor.setChildrenList(Arrays.asList(first, second)); + Node addition = ctor.createNode(); + ctor = new Variable.Constructor(); + ctor.setData("x"); + Node variable = ctor.createNode(); + ctor = new SimpleAssignment.Constructor(); + ctor.setChildrenList(Arrays.asList(variable, addition)); + Node assignment = ctor.createNode(); + ctor = new ExpressionStatement.Constructor(); + ctor.setChildrenList(Collections.singletonList(assignment)); + final Node stmt = ctor.createNode(); + final Patcher patcher = new DefaultPatcher(); + final PatternNode pattern = PatcherTest.createPatternWithHole(); + final Node patched = patcher.patch(stmt, pattern); + ctor = new Variable.Constructor(); + ctor.setData("a"); + first = ctor.createNode(); + ctor = new IntegerLiteral.Constructor(); + ctor.setData("2"); + second = ctor.createNode(); + ctor = new Addition.Constructor(); + ctor.setChildrenList(Arrays.asList(first, second)); + addition = ctor.createNode(); + ctor = new Variable.Constructor(); + ctor.setData("x"); + variable = ctor.createNode(); + ctor = new SimpleAssignment.Constructor(); + ctor.setChildrenList(Arrays.asList(variable, addition)); + assignment = ctor.createNode(); + ctor = new ExpressionStatement.Constructor(); + ctor.setChildrenList(Collections.singletonList(assignment)); + final Node expected = ctor.createNode(); + Assertions.assertTrue(expected.deepCompare(patched)); + } + + /** + * Creates pattern with a hole. + * @return A pattern + */ + private static PatternNode createPatternWithHole() { + Builder ctor = new Variable.Constructor(); + ctor.setData("w"); + final Node first = ctor.createNode(); + ctor = new IntegerLiteral.Constructor(); + ctor.setData("1"); + final Node second = ctor.createNode(); + ctor = new Addition.Constructor(); + ctor.setChildrenList(Arrays.asList(first, second)); + final Node addition = ctor.createNode(); + ctor = new IntegerLiteral.Constructor(); + ctor.setData("2"); + final Node replacement = ctor.createNode(); + final DifferenceTreeBuilder dtbld = new DifferenceTreeBuilder(addition); + dtbld.replaceNode(second, replacement); + final PatternBuilder pbld = new PatternBuilder(dtbld.getRoot()); + pbld.makeHole(first, 0); + final PatternNode pattern = pbld.getRoot(); + Assertions.assertNotNull(pattern); + return pattern; + } } diff --git a/src/test/java/org/cqfn/astranaut/core/example/green/Addition.java b/src/test/java/org/cqfn/astranaut/core/example/green/Addition.java index 51a920c..184a3a1 100644 --- a/src/test/java/org/cqfn/astranaut/core/example/green/Addition.java +++ b/src/test/java/org/cqfn/astranaut/core/example/green/Addition.java @@ -26,6 +26,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.cqfn.astranaut.core.Builder; import org.cqfn.astranaut.core.ChildDescriptor; import org.cqfn.astranaut.core.ChildrenMapper; @@ -159,6 +162,15 @@ private static class TypeImpl implements Type { ) ); + /** + * Properties. + */ + private static final Map PROPERTIES = Stream.of( + new String[][] { + {"color", "green"}, + {"language", "common"}, + }).collect(Collectors.toMap(data -> data[0], data -> data[1])); + @Override public String getName() { return TypeImpl.NAME; @@ -176,7 +188,7 @@ public List getHierarchy() { @Override public String getProperty(final String name) { - return ""; + return TypeImpl.PROPERTIES.getOrDefault(name, ""); } @Override From 0a6be825bbe35b5f432607b9ac6d5b489dba816c Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Fri, 14 Jun 2024 17:15:51 +0300 Subject: [PATCH 18/19] Tests --- .../org/cqfn/astranaut/core/BaseNodeTest.java | 23 +++++++++++++++++++ .../astranaut/core/DifferenceNodeTest.java | 21 +++++++++++++++++ .../core/algorithms/patching/PatcherTest.java | 14 +++++++++++ 3 files changed, 58 insertions(+) diff --git a/src/test/java/org/cqfn/astranaut/core/BaseNodeTest.java b/src/test/java/org/cqfn/astranaut/core/BaseNodeTest.java index 931bf9c..a6c1fb4 100644 --- a/src/test/java/org/cqfn/astranaut/core/BaseNodeTest.java +++ b/src/test/java/org/cqfn/astranaut/core/BaseNodeTest.java @@ -23,6 +23,8 @@ */ package org.cqfn.astranaut.core; +import java.util.Iterator; +import java.util.NoSuchElementException; import org.cqfn.astranaut.core.example.LittleTrees; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -46,4 +48,25 @@ void testDeepCompareMethod() { Assertions.assertTrue(deleted); Assertions.assertFalse(first.deepCompare(second)); } + + /** + * Testing {@link Node#deepCompare(Node)} method. + */ + @Test + void testNodeIterator() { + final Node root = DraftNode.createByDescription("A(B,C)"); + final Iterator iterator = root.iterator(); + Assertions.assertTrue(iterator.hasNext()); + Assertions.assertEquals("B", iterator.next().getTypeName()); + Assertions.assertTrue(iterator.hasNext()); + Assertions.assertEquals("C", iterator.next().getTypeName()); + Assertions.assertFalse(iterator.hasNext()); + boolean oops = false; + try { + iterator.next(); + } catch (final NoSuchElementException ignored) { + oops = true; + } + Assertions.assertTrue(oops); + } } diff --git a/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java b/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java index 25694fe..9f22f31 100644 --- a/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java +++ b/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java @@ -191,6 +191,27 @@ void testInsertNodeFails() { Assertions.assertFalse(result); } + /** + * Tests {@link DifferenceNode#getParent()} method. + */ + @Test + void testParentReference() { + final DifferenceNode root = new DifferenceNode(DraftNode.createByDescription("A(B,C)")); + final Node child = root.getChild(0); + Assertions.assertTrue(child instanceof DifferenceNode); + Assertions.assertSame(((DifferenceNode) child).getParent(), root); + } + + /** + * Tests {@link DifferenceNode#getParent()} method. + */ + @Test + void testDiffNodeAsString() { + final String description = "X(Y, Z)"; + final DifferenceNode root = new DifferenceNode(DraftNode.createByDescription(description)); + Assertions.assertEquals(description, root.toString()); + } + /** * Returns content of the specified file. * @param name The name of the file 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 031f215..02c8d94 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,6 +29,7 @@ import java.util.Set; import java.util.TreeMap; import org.cqfn.astranaut.core.Builder; +import org.cqfn.astranaut.core.DifferenceNode; import org.cqfn.astranaut.core.DraftNode; import org.cqfn.astranaut.core.Insertion; import org.cqfn.astranaut.core.Node; @@ -49,6 +50,19 @@ * @since 1.1.5 */ class PatcherTest { + @Test + void patchingByPatternThatDoesNotMatch() { + final Node source = DraftNode.createByDescription("A(B,C,D)"); + final PatternNode pattern = new PatternNode( + new DifferenceNode( + DraftNode.createByDescription("D") + ) + ); + final Patcher patcher = new DefaultPatcher(); + final Node result = patcher.patch(source, pattern); + Assertions.assertTrue(source.deepCompare(result)); + } + @Test void patchPatternWithInsertion() { final Map> nodes = new TreeMap<>(); From 12243213ac451d6752f3baa870fb56d90bef029d Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Tue, 18 Jun 2024 17:22:30 +0300 Subject: [PATCH 19/19] Moar tests --- .../core/algorithms/PatternBuilder.java | 26 ++------ .../cqfn/astranaut/core/DraftNodeTest.java | 37 +++++++++++ .../org/cqfn/astranaut/core/HoleTest.java | 54 ++++++++++++++++ .../cqfn/astranaut/core/PatternNodeTest.java | 62 +++++++++++++++++++ .../algorithms/DifferenceTreeBuilderTest.java | 36 +++++++++++ .../core/algorithms/PatternBuilderTest.java | 12 ++++ .../core/algorithms/patching/PatcherTest.java | 13 ++++ .../patching/PatternMatcherTest.java | 26 ++++++++ 8 files changed, 246 insertions(+), 20 deletions(-) create mode 100644 src/test/java/org/cqfn/astranaut/core/HoleTest.java create mode 100644 src/test/java/org/cqfn/astranaut/core/PatternNodeTest.java diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/PatternBuilder.java b/src/main/java/org/cqfn/astranaut/core/algorithms/PatternBuilder.java index 33ed3e5..012687e 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/PatternBuilder.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/PatternBuilder.java @@ -38,7 +38,7 @@ public final class PatternBuilder { /** * Default node info (to avoid null checks). */ - private static final NodeInfo DEFAULT_INFO = new NodeInfo(null, null); + private static final NodeInfo DEFAULT_INFO = new NodeInfo(null); /** * The relationship of the nodes to their parents and corresponding difference nodes. @@ -92,7 +92,7 @@ public boolean makeHole(final Node node, final int number) { */ private static Map buildNodeInfoMap(final PatternNode root) { final Map map = new HashMap<>(); - map.put(root.getPrototype(), new NodeInfo(root, null)); + map.put(root.getPrototype(), new NodeInfo(null)); PatternBuilder.buildNodeInfoMap(map, root); return map; } @@ -107,7 +107,7 @@ private static void buildNodeInfoMap(final Map map, final Patter child -> { if (child instanceof PatternNode) { final PatternNode node = (PatternNode) child; - map.put(node.getPrototype(), new NodeInfo(node, parent)); + map.put(node.getPrototype(), new NodeInfo(parent)); PatternBuilder.buildNodeInfoMap(map, node); } } @@ -115,16 +115,12 @@ private static void buildNodeInfoMap(final Map map, final Patter } /** - * Some additional information about each node needed to insert, replace, or delete nodes. + * Some additional information about each node needed to make holes. + * So far there's only a parent node here, but we may need something else. * * @since 1.1.0 */ private static final class NodeInfo { - /** - * The corresponding pattern node. - */ - private final PatternNode pnode; - /** * The parent node. */ @@ -132,22 +128,12 @@ private static final class NodeInfo { /** * Constructor. - * @param pnode The corresponding pattern node * @param parent The parent node */ - NodeInfo(final PatternNode pnode, final PatternNode parent) { - this.pnode = pnode; + NodeInfo(final PatternNode parent) { this.parent = parent; } - /** - * Returns corresponding patter node. - * @return Pattern node - */ - public PatternNode getPatternNode() { - return this.pnode; - } - /** * Returns parent node. * @return Pattern node containing this node diff --git a/src/test/java/org/cqfn/astranaut/core/DraftNodeTest.java b/src/test/java/org/cqfn/astranaut/core/DraftNodeTest.java index f3d5124..03b6f3e 100644 --- a/src/test/java/org/cqfn/astranaut/core/DraftNodeTest.java +++ b/src/test/java/org/cqfn/astranaut/core/DraftNodeTest.java @@ -80,6 +80,18 @@ void constructorTest() { Assertions.assertEquals(serialized, ctor.createNode().toString()); } + /** + * Testing {@link DraftNode.Constructor#addChild(Node)} method. + */ + @Test + void addNodeTest() { + final DraftNode.Constructor ctor = new DraftNode.Constructor(); + ctor.setName("X"); + ctor.addChild(DraftNode.createByDescription("A")); + final String serialized = "X(A)"; + Assertions.assertEquals(serialized, ctor.createNode().toString()); + } + /** * Testing {@link DraftNode#createByDescription(String, Map)} method. */ @@ -110,6 +122,31 @@ void testExtendedDescriptorProcessor() { ); } + @Test + void typeTest() { + final Node node = DraftNode.createByDescription("A"); + final Type type = node.getType(); + Assertions.assertEquals("A", type.getName()); + Assertions.assertEquals(0, type.getChildTypes().size()); + Assertions.assertEquals(1, type.getHierarchy().size()); + Assertions.assertEquals("A", type.getHierarchy().get(0)); + final Builder builder = type.createBuilder(); + final Node clone = builder.createNode(); + Assertions.assertTrue(node.deepCompare(clone)); + } + + @Test + void wrongConstructionTest() { + final DraftNode.Constructor ctor = new DraftNode.Constructor(); + boolean oops = false; + try { + ctor.createNode(); + } catch (final IllegalStateException ignored) { + oops = true; + } + Assertions.assertTrue(oops); + } + /** * Testing {@link DraftNode#createByDescription(String)} and * {@link DraftNode#toString()} methods (testing of one case). diff --git a/src/test/java/org/cqfn/astranaut/core/HoleTest.java b/src/test/java/org/cqfn/astranaut/core/HoleTest.java new file mode 100644 index 0000000..bc8c3ed --- /dev/null +++ b/src/test/java/org/cqfn/astranaut/core/HoleTest.java @@ -0,0 +1,54 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ivan Kniazkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.cqfn.astranaut.core; + +import org.cqfn.astranaut.core.example.green.IntegerLiteral; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Tests covering {@link Hole} class. + * + * @since 1.1.5 + */ +class HoleTest { + @Test + void testBaseInterface() { + final Hole hole = new Hole(IntegerLiteral.TYPE, 1); + Assertions.assertSame(EmptyFragment.INSTANCE, hole.getFragment()); + Assertions.assertEquals(0, hole.getChildCount()); + Assertions.assertNull(hole.getChild(0)); + Assertions.assertEquals(1, hole.getNumber()); + final Type type = hole.getType(); + final String typename = "IntegerLiteral"; + Assertions.assertEquals(typename, type.getName()); + Assertions.assertEquals(typename, type.getHierarchy().get(0)); + Assertions.assertEquals("purple", type.getProperty("color")); + Assertions.assertEquals("", type.getProperty("abracadabra")); + final Builder builder = type.createBuilder(); + builder.setData("0"); + final Node node = builder.createNode(); + Assertions.assertEquals(typename, node.getTypeName()); + } +} diff --git a/src/test/java/org/cqfn/astranaut/core/PatternNodeTest.java b/src/test/java/org/cqfn/astranaut/core/PatternNodeTest.java new file mode 100644 index 0000000..5a54323 --- /dev/null +++ b/src/test/java/org/cqfn/astranaut/core/PatternNodeTest.java @@ -0,0 +1,62 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ivan Kniazkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.cqfn.astranaut.core; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Tests covering {@link PatternNode} class. + * + * @since 1.1.5 + */ +class PatternNodeTest { + @Test + void testBaseInterface() { + final Fragment fragment = new Fragment() { + @Override + public Source getSource() { + return null; + } + + @Override + public Position getBegin() { + return () -> 0; + } + + @Override + public Position getEnd() { + return () -> 100; + } + }; + final DraftNode.Constructor ctor = new DraftNode.Constructor(); + ctor.setFragment(fragment); + ctor.setName("X"); + ctor.setData("test"); + final Node node = ctor.createNode(); + final PatternNode pattern = new PatternNode(new DifferenceNode(node)); + Assertions.assertEquals(100, pattern.getFragment().getEnd().getIndex()); + Assertions.assertEquals("X<\"test\">", pattern.toString()); + } +} diff --git a/src/test/java/org/cqfn/astranaut/core/algorithms/DifferenceTreeBuilderTest.java b/src/test/java/org/cqfn/astranaut/core/algorithms/DifferenceTreeBuilderTest.java index a5be0bf..6420816 100644 --- a/src/test/java/org/cqfn/astranaut/core/algorithms/DifferenceTreeBuilderTest.java +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/DifferenceTreeBuilderTest.java @@ -25,6 +25,7 @@ import org.cqfn.astranaut.core.DifferenceNode; import org.cqfn.astranaut.core.DraftNode; +import org.cqfn.astranaut.core.Insertion; import org.cqfn.astranaut.core.Node; import org.cqfn.astranaut.core.algorithms.hash.AbsoluteHash; import org.cqfn.astranaut.core.algorithms.hash.Hash; @@ -195,4 +196,39 @@ void testComplexCase() { Assertions.assertTrue(before.deepCompare(diff.getBefore())); Assertions.assertTrue(after.deepCompare(diff.getAfter())); } + + @Test + void testWrongInsertion() { + final DifferenceTreeBuilder builder = new DifferenceTreeBuilder( + DraftNode.createByDescription("X") + ); + final boolean result = builder.insertNode( + new Insertion( + DraftNode.createByDescription("A"), + DraftNode.createByDescription("B") + ) + ); + Assertions.assertFalse(result); + } + + @Test + void testWrongReplacement() { + final DifferenceTreeBuilder builder = new DifferenceTreeBuilder( + DraftNode.createByDescription("X") + ); + final boolean result = builder.replaceNode( + DraftNode.createByDescription("A"), + DraftNode.createByDescription("B") + ); + Assertions.assertFalse(result); + } + + @Test + void testWrongDeletion() { + final DifferenceTreeBuilder builder = new DifferenceTreeBuilder( + DraftNode.createByDescription("X") + ); + final boolean result = builder.deleteNode(DraftNode.createByDescription("A")); + Assertions.assertFalse(result); + } } diff --git a/src/test/java/org/cqfn/astranaut/core/algorithms/PatternBuilderTest.java b/src/test/java/org/cqfn/astranaut/core/algorithms/PatternBuilderTest.java index affed22..12b396a 100644 --- a/src/test/java/org/cqfn/astranaut/core/algorithms/PatternBuilderTest.java +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/PatternBuilderTest.java @@ -28,6 +28,7 @@ import java.util.Optional; import org.cqfn.astranaut.core.Builder; import org.cqfn.astranaut.core.DifferenceNode; +import org.cqfn.astranaut.core.DraftNode; import org.cqfn.astranaut.core.Hole; import org.cqfn.astranaut.core.Node; import org.cqfn.astranaut.core.PatternNode; @@ -74,4 +75,15 @@ void creatingPatternWithHole() { Assertions.assertTrue(hole.isPresent()); Assertions.assertEquals("#1", hole.get().getData()); } + + @Test + void wrongHole() { + final PatternBuilder builder = new PatternBuilder( + new DifferenceNode( + DraftNode.createByDescription("X") + ) + ); + final boolean result = builder.makeHole(DraftNode.createByDescription("A"), 0); + Assertions.assertFalse(result); + } } 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 02c8d94..557bf03 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 @@ -155,6 +155,19 @@ void patchPatternWithHole() { Assertions.assertTrue(expected.deepCompare(patched)); } + @Test + void patchWithPatternThatDoesNotMatch() { + final Map> nodes = new TreeMap<>(); + final Node prepattern = DraftNode.createByDescription("E(B,D)", nodes); + final DifferenceTreeBuilder builder = new DifferenceTreeBuilder(prepattern); + builder.replaceNode(nodes.get("B").iterator().next(), DraftNode.createByDescription("C")); + final PatternNode pattern = new PatternNode(builder.getRoot()); + final Node tree = DraftNode.createByDescription("X(Y,K(B,D),Z)"); + final Patcher patcher = new DefaultPatcher(); + final Node result = patcher.patch(tree, pattern); + Assertions.assertTrue(tree.deepCompare(result)); + } + /** * Creates pattern with a hole. * @return A pattern diff --git a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcherTest.java b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcherTest.java index f82ab1c..19eb02f 100644 --- a/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcherTest.java +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/patching/PatternMatcherTest.java @@ -138,4 +138,30 @@ void findPatternWithHoleInATree() { final Node node = found.iterator().next(); Assertions.assertEquals("A", node.getTypeName()); } + + @Test + void matchPatternWithData() { + final PatternNode pattern = new PatternNode( + new DifferenceNode( + DraftNode.createByDescription("A(B<\"test\">)") + ) + ); + final Node tree = DraftNode.createByDescription("X(Y, A(B<\"test\">), Z)"); + final PatternMatcher matcher = new PatternMatcher(tree); + final Set found = matcher.match(pattern); + Assertions.assertEquals(1, found.size()); + } + + @Test + void matchPatternThatIsTooBig() { + final PatternNode pattern = new PatternNode( + new DifferenceNode( + DraftNode.createByDescription("A(B,C,D,E,F)") + ) + ); + final Node tree = DraftNode.createByDescription("X(Y,A(B,C),Z)"); + final PatternMatcher matcher = new PatternMatcher(tree); + final Set found = matcher.match(pattern); + Assertions.assertEquals(0, found.size()); + } }