From 2657c5a7ddebafc3752fddfab5cd021d7077092e Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Thu, 15 Feb 2024 14:59:33 +0300 Subject: [PATCH 01/16] Testing the construction of a difference tree with a deleted node. This node is just below the root, so that the number and type of children of the root do not change. --- .../algorithms/DifferenceTreeBuilderTest.java | 23 ++++++++++++ .../astranaut/core/example/LittleTrees.java | 35 ++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) 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 b5460c9..06f0c88 100644 --- a/src/test/java/org/cqfn/astranaut/core/algorithms/DifferenceTreeBuilderTest.java +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/DifferenceTreeBuilderTest.java @@ -52,4 +52,27 @@ void testTreeWithDeletedNode() { Assertions.assertTrue(before.deepCompare(diff.getBefore())); Assertions.assertTrue(after.deepCompare(diff.getAfter())); } + + /** + * Testing the construction of a difference tree with a deleted node. + * This node is just below the root, so that the number and type of children of the root + * do not change. + */ + @Test + void testTreeWithDeletedNodeInDepth() { + final Node before = LittleTrees.createStatementBlock( + LittleTrees.createStatementListWithThreeChildren() + ); + final Node after = LittleTrees.createStatementBlock( + LittleTrees.createStatementListWithTwoChildren() + ); + final DifferenceTreeBuilder builder = new DifferenceTreeBuilder(before); + final boolean result = builder.build(after, new BottomUpMapper()); + Assertions.assertTrue(result); + final DifferenceNode diff = builder.getRoot(); + final Node expected = LittleTrees.createTreeWithDeleteActionInDepth(); + Assertions.assertTrue(expected.deepCompare(diff)); + Assertions.assertTrue(before.deepCompare(diff.getBefore())); + Assertions.assertTrue(after.deepCompare(diff.getAfter())); + } } diff --git a/src/test/java/org/cqfn/astranaut/core/example/LittleTrees.java b/src/test/java/org/cqfn/astranaut/core/example/LittleTrees.java index 07aeb08..86d2ed0 100644 --- a/src/test/java/org/cqfn/astranaut/core/example/LittleTrees.java +++ b/src/test/java/org/cqfn/astranaut/core/example/LittleTrees.java @@ -203,7 +203,7 @@ public static DifferenceNode createTreeWithDeleteAction() { createIntegerLiteral(1) ) ), - victim, + victim, createReturnStatement( createVariable("x") ) @@ -212,4 +212,37 @@ public static DifferenceNode createTreeWithDeleteAction() { builder.deleteNode(victim); return builder.getRoot(); } + + /** + * Creates a tree that has a "delete" action in it. + * This action is just below the root, so that the number and type of children of the root + * do not change. + * @return Root node + */ + public static DifferenceNode createTreeWithDeleteActionInDepth() { + final Node victim = wrapExpressionWithStatement( + createAssignment( + createVariable("y"), + createIntegerLiteral(2) + ) + ); + final DifferenceTreeBuilder builder = new DifferenceTreeBuilder( + createStatementBlock( + createStatementBlock( + wrapExpressionWithStatement( + createAssignment( + createVariable("x"), + createIntegerLiteral(1) + ) + ), + victim, + createReturnStatement( + createVariable("x") + ) + ) + ) + ); + builder.deleteNode(victim); + return builder.getRoot(); + } } From ff9b06bcf18c975851fba73dedfface5a90b2570 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Wed, 21 Feb 2024 12:12:39 +0300 Subject: [PATCH 02/16] Replace action --- .../java/org/cqfn/astranaut/core/Replace.java | 245 ++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 src/main/java/org/cqfn/astranaut/core/Replace.java diff --git a/src/main/java/org/cqfn/astranaut/core/Replace.java b/src/main/java/org/cqfn/astranaut/core/Replace.java new file mode 100644 index 0000000..454bf0a --- /dev/null +++ b/src/main/java/org/cqfn/astranaut/core/Replace.java @@ -0,0 +1,245 @@ +/* + * 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.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Action that replaces a child element. + * + * @since 1.1.0 + */ +public final class Replace implements Action { + /** + * The type. + */ + public static final Type TYPE = new ReplaceType(); + + /** + * Child element before changes. + */ + private final Node before; + + /** + * Child element after changes. + */ + private final Node after; + + /** + * Constructor. + * @param before Child element that will be replaced. + * @param after Child element to be replaced by. + */ + public Replace(final Node before, final Node after) { + this.before = before; + this.after = after; + } + + @Override + public Node getBefore() { + return this.before; + } + + @Override + public Node getAfter() { + return this.after; + } + + @Override + public Fragment getFragment() { + return this.before.getFragment(); + } + + @Override + public Type getType() { + return Replace.TYPE; + } + + @Override + public String getData() { + return ""; + } + + @Override + public int getChildCount() { + return 1; + } + + @Override + public Node getChild(final int index) { + final Node node; + switch (index) { + case 0: + node = this.before; + break; + case 1: + node = this.after; + break; + default: + node = null; + break; + } + return node; + } + + /** + * Type of 'Replace' action. + * + * @since 1.1.0 + */ + private static final class ReplaceType implements Type { + /** + * The 'Node' string. + */ + private static final String NODE = "Node"; + + /** + * The 'ACTION' string. + */ + private static final String ACTION = "Action"; + + /** + * The 'DELETE' string. + */ + private static final String REPLACE = "Replace"; + + /** + * The list of child descriptors. + */ + private static final List CHILDREN = + Arrays.asList( + new ChildDescriptor( + ReplaceType.NODE, + false + ), + new ChildDescriptor( + ReplaceType.NODE, + false + ) + ); + + /** + * Hierarchy. + */ + private static final List HIERARCHY = + Collections.unmodifiableList( + Arrays.asList( + ReplaceType.REPLACE, + ReplaceType.ACTION + ) + ); + + /** + * Properties. + */ + private static final Map PROPERTIES = Stream.of( + new String[][] { + {"color", "blue"}, + }).collect(Collectors.toMap(data -> data[0], data -> data[1])); + + @Override + public String getName() { + return ReplaceType.REPLACE; + } + + @Override + public List getChildTypes() { + return ReplaceType.CHILDREN; + } + + @Override + public List getHierarchy() { + return ReplaceType.HIERARCHY; + } + + @Override + public String getProperty(final String name) { + return ReplaceType.PROPERTIES.getOrDefault(name, ""); + } + + @Override + public Builder createBuilder() { + return null; + } + } + + /** + * Class for 'Delete' action construction. + * + * @since 1.1.0 + */ + public static final class Constructor implements Builder { + /** + * Child node before changes. + */ + private Node before; + + /** + * Child node after changes. + */ + private Node after; + + @Override + public void setFragment(final Fragment fragment) { + // do nothing + } + + @Override + public boolean setData(final String str) { + return str.isEmpty(); + } + + @Override + public boolean setChildrenList(final List list) { + boolean result = false; + if (list.size() == 2) { + final Iterator iterator = list.iterator(); + this.before = iterator.next(); + this.after = iterator.next(); + result = true; + } + return result; + } + + @Override + public boolean isValid() { + return this.before != null && this.after != null; + } + + @Override + public Node createNode() { + Node node = EmptyTree.INSTANCE; + if (this.isValid()) { + node = new Replace(this.before, this.after); + } + return node; + } + } +} From 21c60e6d32e5cef5fa423ce6d8f9a5379fa3636b Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Wed, 21 Feb 2024 17:11:50 +0300 Subject: [PATCH 03/16] Difference tree builder supports 'replace' action but here strange bug in 'deepCompare' method --- .../cqfn/astranaut/core/DifferenceNode.java | 40 +++++++++++ .../java/org/cqfn/astranaut/core/Replace.java | 4 +- .../algorithms/DifferenceTreeBuilder.java | 18 +++++ .../mapping/BottomUpMappingAlgorithm.java | 71 +++++++++++++++++-- .../core/algorithms/mapping/Mapping.java | 9 +++ .../algorithms/DifferenceTreeBuilderTest.java | 35 ++++++++- .../mapping/BottomUpMapperTest.java | 4 +- .../astranaut/core/example/LittleTrees.java | 35 ++++++++- 8 files changed, 202 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java b/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java index 5cd78f0..b0ceb50 100644 --- a/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java +++ b/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java @@ -117,6 +117,46 @@ public Node getAfter() { return this.getBranch(DifferenceTreeItem::getAfter); } + /** + * Adds an action that replaces the node. + * The position of the node is specified by the index. + * @param index Node index + * @param replacement Child node to be replaced by + * @return Result of operation, @return {@code true} if action was added + */ + public boolean replaceNode(final int index, final Node replacement) { + boolean result = false; + if (index >= 0 && index < this.children.size()) { + final DifferenceTreeItem child = this.children.get(index); + if (child instanceof DifferenceNode) { + this.children.set( + index, + new Replace( + ((DifferenceNode) child).getPrototype(), + replacement + ) + ); + result = true; + } + } + return result; + } + + /** + * Adds an action that replaces the node. + * @param node A node + * @param replacement Child node to be replaced by + * @return Result of operation, @return {@code true} if action was added + */ + public boolean replaceNode(final Node node, final Node replacement) { + boolean result = false; + final int index = this.findChildIndex(node); + if (index >= 0) { + result = this.replaceNode(index, replacement); + } + return result; + } + /** * Adds an action that removes a node by index. * @param index Node index diff --git a/src/main/java/org/cqfn/astranaut/core/Replace.java b/src/main/java/org/cqfn/astranaut/core/Replace.java index 454bf0a..c8e929c 100644 --- a/src/main/java/org/cqfn/astranaut/core/Replace.java +++ b/src/main/java/org/cqfn/astranaut/core/Replace.java @@ -54,8 +54,8 @@ public final class Replace implements Action { /** * Constructor. - * @param before Child element that will be replaced. - * @param after Child element to be replaced by. + * @param before Child element that will be replaced + * @param after Child element to be replaced by */ public Replace(final Node before, final Node after) { this.before = before; 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 3091bda..c7fcb20 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/DifferenceTreeBuilder.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/DifferenceTreeBuilder.java @@ -66,6 +66,9 @@ public DifferenceTreeBuilder(final Node before) { public boolean build(final Node after, final Mapper mapper) { final Mapping mapping = mapper.map(this.root.getPrototype(), after); boolean result = true; + for (final Map.Entry replaced : mapping.getReplaced().entrySet()) { + result = result & this.replaceNode(replaced.getKey(), replaced.getValue()); + } for (final Node deleted : mapping.getDeleted()) { result = result & this.deleteNode(deleted); } @@ -80,6 +83,21 @@ public DifferenceNode getRoot() { return this.root; } + /** + * Adds an action to the difference tree that replaces a node. + * @param node Child element that will be replaced + * @param replacement Child element to be replaced by + * @return Result of operation, {@code true} if action was added + */ + public boolean replaceNode(final Node node, final Node replacement) { + boolean result = false; + final DifferenceNode parent = this.parents.get(node); + if (parent != null) { + result = parent.replaceNode(node, replacement); + } + return result; + } + /** * Adds an action to the difference tree that removes a node. * @param node The node to be removed diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpMappingAlgorithm.java b/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpMappingAlgorithm.java index 3e70db3..24ab76c 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpMappingAlgorithm.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpMappingAlgorithm.java @@ -80,6 +80,11 @@ class BottomUpMappingAlgorithm { */ private final Map rtl; + /** + * Map containing replaces nodes. + */ + private final Map replaced; + /** * Set of deleted nodes. */ @@ -98,6 +103,7 @@ class BottomUpMappingAlgorithm { this.right = this.createNodeSet(right); this.ltr = new HashMap<>(); this.rtl = new HashMap<>(); + this.replaced = new HashMap<>(); this.deleted = new HashSet<>(); } @@ -132,6 +138,11 @@ public Node getLeft(final Node node) { return BottomUpMappingAlgorithm.this.rtl.get(node); } + @Override + public Map getReplaced() { + return Collections.unmodifiableMap(BottomUpMappingAlgorithm.this.replaced); + } + @Override public Set getDeleted() { return Collections.unmodifiableSet(BottomUpMappingAlgorithm.this.deleted); @@ -292,16 +303,62 @@ private Node mapPartiallyMappedLeftNode(final Node node) { this.right.remove(related); this.ltr.put(node, related); this.rtl.put(related, node); - final int count = node.getChildCount(); - for (int index = 0; index < count; index = index + 1) { - final Node child = node.getChild(index); - if (!this.ltr.containsKey(child)) { - this.deleted.add(child); - } - } + this.mapChildren(node, related); next = this.parents.get(node); } while (false); this.left.remove(node); return next; } + + /** + * Maps the child nodes of partially mapped nodes. + * @param before Node before changes + * @param after Node after changes + */ + private void mapChildren(final Node before, final Node after) { + final int sign = Integer.compare(before.getChildCount(), after.getChildCount()); + if (sign > 0) { + this.mapChildrenIfDeleted(before); + } else if (sign == 0) { + this.mapChildrenIfReplaced(before, after); + } + } + + /** + * Maps the child nodes of partially mapped nodes if the node before changes + * has the same number of child nodes as the node after changes, i.e., when it is obvious + * that some nodes have been replaced. + * @param before Node before changes + * @param after Node after changes + */ + private void mapChildrenIfReplaced(final Node before, final Node after) { + final int count = before.getChildCount(); + assert count == after.getChildCount(); + for (int index = 0; index < count; index = index + 1) { + final Node first = before.getChild(index); + if (!this.ltr.containsKey(first)) { + final Node second = after.getChild(index); + this.replaced.put(first, second); + this.left.remove(first); + this.right.remove(second); + } + } + } + + /** + * Maps the child nodes of partially mapped nodes if the node before changes + * has more child nodes than the node after changes, i.e., when it is obvious + * that some nodes have been deleted. + * @param before Node before changes + */ + private void mapChildrenIfDeleted(final Node before) { + final int count = before.getChildCount(); + for (int index = 0; index < count; index = index + 1) { + final Node child = before.getChild(index); + if (!this.ltr.containsKey(child)) { + this.deleted.add(child); + this.left.remove(child); + } + } + } } diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/Mapping.java b/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/Mapping.java index 2b3fc70..da1eb2e 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/Mapping.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/Mapping.java @@ -23,6 +23,7 @@ */ package org.cqfn.astranaut.core.algorithms.mapping; +import java.util.Map; import java.util.Set; import org.cqfn.astranaut.core.Node; @@ -49,6 +50,14 @@ public interface Mapping { */ Node getLeft(Node right); + /** + * Returns relationship between the nodes of the 'left' tree that have been replaced + * by nodes of the 'right' tree. + * @return Mapping, where keys are the nodes of the 'left' tree + * and values are the corresponding nodes of the 'right' tree + */ + Map getReplaced(); + /** * Returns the set of nodes of the 'left' tree that need to be removed * to get the 'right' tree. 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 06f0c88..993e0f2 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,8 @@ import org.cqfn.astranaut.core.DifferenceNode; import org.cqfn.astranaut.core.Node; +import org.cqfn.astranaut.core.algorithms.hash.AbsoluteHash; +import org.cqfn.astranaut.core.algorithms.hash.Hash; import org.cqfn.astranaut.core.algorithms.mapping.BottomUpMapper; import org.cqfn.astranaut.core.example.LittleTrees; import org.junit.jupiter.api.Assertions; @@ -36,12 +38,39 @@ * @since 1.1.0 */ class DifferenceTreeBuilderTest { + /** + * Testing the construction of a difference tree with a replaced node. + */ + @Test + void testTreeWithReplacedNode() { + final Node before = LittleTrees.createStatementListWithThreeChildren( + LittleTrees.createIntegerLiteral(2) + ); + final Node after = LittleTrees.createStatementListWithThreeChildren( + LittleTrees.createVariable("x") + ); + final DifferenceTreeBuilder builder = new DifferenceTreeBuilder(before); + final boolean result = builder.build(after, new BottomUpMapper()); + Assertions.assertTrue(result); + final DifferenceNode diff = builder.getRoot(); + final Node expected = LittleTrees.createTreeWithReplaceAction(); + final Hash hash = new AbsoluteHash(); + final int diffhash = hash.calculate(diff); + final int expectedhash = hash.calculate(expected); + Assertions.assertEquals(expectedhash, diffhash); + Assertions.assertTrue(expected.deepCompare(diff)); + Assertions.assertTrue(before.deepCompare(diff.getBefore())); + Assertions.assertTrue(after.deepCompare(diff.getAfter())); + } + /** * Testing the construction of a difference tree with a deleted node. */ @Test void testTreeWithDeletedNode() { - final Node before = LittleTrees.createStatementListWithThreeChildren(); + final Node before = LittleTrees.createStatementListWithThreeChildren( + LittleTrees.createIntegerLiteral(2) + ); final Node after = LittleTrees.createStatementListWithTwoChildren(); final DifferenceTreeBuilder builder = new DifferenceTreeBuilder(before); final boolean result = builder.build(after, new BottomUpMapper()); @@ -61,7 +90,9 @@ void testTreeWithDeletedNode() { @Test void testTreeWithDeletedNodeInDepth() { final Node before = LittleTrees.createStatementBlock( - LittleTrees.createStatementListWithThreeChildren() + LittleTrees.createStatementListWithThreeChildren( + LittleTrees.createIntegerLiteral(2) + ) ); final Node after = LittleTrees.createStatementBlock( LittleTrees.createStatementListWithTwoChildren() diff --git a/src/test/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpMapperTest.java b/src/test/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpMapperTest.java index 875796d..dd6d2e0 100644 --- a/src/test/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpMapperTest.java +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpMapperTest.java @@ -53,7 +53,9 @@ void testIdenticalTrees() { */ @Test void testOneWasRemoved() { - final Node first = LittleTrees.createStatementListWithThreeChildren(); + final Node first = LittleTrees.createStatementListWithThreeChildren( + LittleTrees.createIntegerLiteral(2) + ); final Node second = LittleTrees.createStatementListWithTwoChildren(); final Mapper mapper = new BottomUpMapper(); final Mapping mapping = mapper.map(first, second); diff --git a/src/test/java/org/cqfn/astranaut/core/example/LittleTrees.java b/src/test/java/org/cqfn/astranaut/core/example/LittleTrees.java index 86d2ed0..beedf33 100644 --- a/src/test/java/org/cqfn/astranaut/core/example/LittleTrees.java +++ b/src/test/java/org/cqfn/astranaut/core/example/LittleTrees.java @@ -162,9 +162,10 @@ public static Node createStatementListWithTwoChildren() { /** * Creates a tree (statement list) that has three children. + * @param assignable Node whose value is assigned in the third (middle) statement * @return Root node */ - public static Node createStatementListWithThreeChildren() { + public static Node createStatementListWithThreeChildren(final Node assignable) { return createStatementBlock( wrapExpressionWithStatement( createAssignment( @@ -175,7 +176,7 @@ public static Node createStatementListWithThreeChildren() { wrapExpressionWithStatement( createAssignment( createVariable("y"), - createIntegerLiteral(2) + assignable ) ), createReturnStatement( @@ -184,6 +185,36 @@ public static Node createStatementListWithThreeChildren() { ); } + /** + * Creates a tree that has a "replace" action in it. + * @return Root node + */ + public static DifferenceNode createTreeWithReplaceAction() { + final Node before = createIntegerLiteral(2); + final Node after = createVariable("x"); + final DifferenceTreeBuilder builder = new DifferenceTreeBuilder( + createStatementBlock( + wrapExpressionWithStatement( + createAssignment( + createVariable("x"), + createIntegerLiteral(1) + ) + ), + wrapExpressionWithStatement( + createAssignment( + createVariable("y"), + before + ) + ), + createReturnStatement( + createVariable("x") + ) + ) + ); + builder.replaceNode(before, after); + return builder.getRoot(); + } + /** * Creates a tree that has a "delete" action in it. * @return Root node From 866b5778c44a2f1c32e3d0d6b392c1b0a609c7fa Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Mon, 26 Feb 2024 16:08:26 +0300 Subject: [PATCH 04/16] Fix traversing order in the mapper algorithm --- .../mapping/BottomUpMappingAlgorithm.java | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpMappingAlgorithm.java b/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpMappingAlgorithm.java index 24ab76c..c3e3c08 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpMappingAlgorithm.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpMappingAlgorithm.java @@ -28,6 +28,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -61,14 +62,14 @@ class BottomUpMappingAlgorithm { private final Map parents; /** - * Not yet processed nodes from the 'left' tree. + * Sorted nodes from the 'left' tree. */ - private final Set left; + private final List left; /** - * Not yet processed nodes from the 'right' tree. + * Sorted nodes from the 'right' tree. */ - private final Set right; + private final List right; /** * Left-to-right mapping. @@ -99,8 +100,8 @@ class BottomUpMappingAlgorithm { this.hashes = new AbsoluteHash(); this.depth = new Depth(); this.parents = new HashMap<>(); - this.left = this.createNodeSet(left); - this.right = this.createNodeSet(right); + this.left = this.createNodeList(left); + this.right = this.createNodeList(right); this.ltr = new HashMap<>(); this.rtl = new HashMap<>(); this.replaced = new HashMap<>(); @@ -151,26 +152,26 @@ public Set getDeleted() { } /** - * Creates an initial set of nodes suitable for processing from the tree. + * Creates an initial list of nodes suitable for processing from the tree. * @param root The root of the tree - * @return Set of nodes + * @return List of nodes where leaves are placed first. */ - private Set createNodeSet(final Node root) { - final Set set = new HashSet<>(); - this.createNodeSet(root, null, set); - return set; + private List createNodeList(final Node root) { + final List list = new LinkedList<>(); + this.createNodeList(root, null, list); + return list; } /** * Creates an initial set of nodes suitable for processing from the tree (recursive method). * @param node The current node * @param parent The current node parent - * @param set The resulting set + * @param list The resulting list */ - private void createNodeSet(final Node node, final Node parent, final Set set) { - set.add(node); + private void createNodeList(final Node node, final Node parent, final List list) { this.parents.put(node, parent); - node.forEachChild(child -> this.createNodeSet(child, node, set)); + node.forEachChild(child -> this.createNodeList(child, node, list)); + list.add(node); } /** From 0dbbde93fc176fb25d270f4526ec0ad95e6b3c28 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Tue, 27 Feb 2024 09:04:48 +0300 Subject: [PATCH 05/16] Fix Qulice warnings --- ...gAlgorithm.java => BottomUpAlgorithm.java} | 67 ++++++++++++------- .../algorithms/mapping/BottomUpMapper.java | 2 +- .../astranaut/core/example/LittleTrees.java | 2 +- 3 files changed, 46 insertions(+), 25 deletions(-) rename src/main/java/org/cqfn/astranaut/core/algorithms/mapping/{BottomUpMappingAlgorithm.java => BottomUpAlgorithm.java} (90%) diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpMappingAlgorithm.java b/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpAlgorithm.java similarity index 90% rename from src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpMappingAlgorithm.java rename to src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpAlgorithm.java index c3e3c08..f4bc172 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpMappingAlgorithm.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpAlgorithm.java @@ -45,7 +45,8 @@ * * @since 1.1.0 */ -class BottomUpMappingAlgorithm { +@SuppressWarnings("PMD.TooManyMethods") +class BottomUpAlgorithm { /** * Set of node hashes. */ @@ -96,7 +97,7 @@ class BottomUpMappingAlgorithm { * @param left Root node of the 'left' tree * @param right Root node of the 'right' tree */ - BottomUpMappingAlgorithm(final Node left, final Node right) { + BottomUpAlgorithm(final Node left, final Node right) { this.hashes = new AbsoluteHash(); this.depth = new Depth(); this.parents = new HashMap<>(); @@ -128,27 +129,7 @@ void execute() { * @return Result of mapping */ Mapping getResult() { - return new Mapping() { - @Override - public Node getRight(final Node node) { - return BottomUpMappingAlgorithm.this.ltr.get(node); - } - - @Override - public Node getLeft(final Node node) { - return BottomUpMappingAlgorithm.this.rtl.get(node); - } - - @Override - public Map getReplaced() { - return Collections.unmodifiableMap(BottomUpMappingAlgorithm.this.replaced); - } - - @Override - public Set getDeleted() { - return Collections.unmodifiableSet(BottomUpMappingAlgorithm.this.deleted); - } - }; + return new Result(this); } /** @@ -362,4 +343,44 @@ private void mapChildrenIfDeleted(final Node before) { } } } + + /** + * Mapping result. + * + * @since 1.1.0 + */ + private static final class Result implements Mapping { + /** + * Structure from which the mapping results can be taken. + */ + private final BottomUpAlgorithm data; + + /** + * Constructor. + * @param data Structure from which the mapping results can be taken + */ + private Result(final BottomUpAlgorithm data) { + this.data = data; + } + + @Override + public Node getRight(final Node node) { + return this.data.ltr.get(node); + } + + @Override + public Node getLeft(final Node node) { + return this.data.rtl.get(node); + } + + @Override + public Map getReplaced() { + return Collections.unmodifiableMap(this.data.replaced); + } + + @Override + public Set getDeleted() { + return Collections.unmodifiableSet(this.data.deleted); + } + } } diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpMapper.java b/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpMapper.java index 4eba07a..806c4d1 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpMapper.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpMapper.java @@ -35,7 +35,7 @@ public final class BottomUpMapper implements Mapper { @Override public Mapping map(final Node left, final Node right) { - final BottomUpMappingAlgorithm algorithm = new BottomUpMappingAlgorithm(left, right); + final BottomUpAlgorithm algorithm = new BottomUpAlgorithm(left, right); algorithm.execute(); return algorithm.getResult(); } diff --git a/src/test/java/org/cqfn/astranaut/core/example/LittleTrees.java b/src/test/java/org/cqfn/astranaut/core/example/LittleTrees.java index beedf33..152af3e 100644 --- a/src/test/java/org/cqfn/astranaut/core/example/LittleTrees.java +++ b/src/test/java/org/cqfn/astranaut/core/example/LittleTrees.java @@ -41,7 +41,7 @@ * * @since 1.1.0 */ -@SuppressWarnings("PMD.ProhibitPublicStaticMethods") +@SuppressWarnings({"PMD.ProhibitPublicStaticMethods", "PMD.TooManyMethods"}) public final class LittleTrees { /** * Private constructor. From 5531451c277af5959d52bbbdffa387b05ea00382 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Tue, 27 Feb 2024 09:31:11 +0300 Subject: [PATCH 06/16] A little optimization. --- .../algorithms/mapping/BottomUpAlgorithm.java | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpAlgorithm.java b/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpAlgorithm.java index f4bc172..6dafa8f 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpAlgorithm.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpAlgorithm.java @@ -62,6 +62,11 @@ class BottomUpAlgorithm { */ private final Map parents; + /** + * A set of nodes that have not yet been processed. + */ + private final Set unprocessed; + /** * Sorted nodes from the 'left' tree. */ @@ -101,6 +106,7 @@ class BottomUpAlgorithm { this.hashes = new AbsoluteHash(); this.depth = new Depth(); this.parents = new HashMap<>(); + this.unprocessed = new HashSet<>(); this.left = this.createNodeList(left); this.right = this.createNodeList(right); this.ltr = new HashMap<>(); @@ -153,6 +159,8 @@ private void createNodeList(final Node node, final Node parent, final List this.parents.put(node, parent); node.forEachChild(child -> this.createNodeList(child, node, list)); list.add(node); + final boolean added = this.unprocessed.add(node); + assert added; } /** @@ -194,9 +202,9 @@ private Map> performInitialMapping() { private void absorbLargestSubtrees(final Map> draft) { final List sorted = new ArrayList<>(draft.keySet()); sorted.sort( - (first, second) -> -Integer.compare( - this.depth.calculate(first), - this.depth.calculate(second) + (first, second) -> Integer.compare( + this.depth.calculate(second), + this.depth.calculate(first) ) ); for (final Node node : sorted) { @@ -223,8 +231,8 @@ private void mapSubtreesWithTheSameHash( final Map> draft) { assert this.hashes.calculate(node) == this.hashes.calculate(related); draft.remove(node); - this.left.remove(node); - this.right.remove(related); + this.unprocessed.remove(node); + this.unprocessed.remove(related); this.ltr.put(node, related); this.rtl.put(related, node); final int count = node.getChildCount(); @@ -245,12 +253,14 @@ private Node findPartiallyMappedLeftNode() { final Iterator iterator = this.left.iterator(); while (result == null && iterator.hasNext()) { final Node node = iterator.next(); - final int count = node.getChildCount(); - for (int index = 0; index < count; index = index + 1) { - final Node child = node.getChild(index); - if (this.ltr.containsKey(child)) { - result = node; - break; + if (this.unprocessed.contains(node)) { + final int count = node.getChildCount(); + for (int index = 0; index < count; index = index + 1) { + final Node child = node.getChild(index); + if (this.ltr.containsKey(child)) { + result = node; + break; + } } } } @@ -282,13 +292,13 @@ private Node mapPartiallyMappedLeftNode(final Node node) { || !node.getData().equals(related.getData())) { break; } - this.right.remove(related); + this.unprocessed.remove(related); this.ltr.put(node, related); this.rtl.put(related, node); this.mapChildren(node, related); next = this.parents.get(node); } while (false); - this.left.remove(node); + this.unprocessed.remove(node); return next; } @@ -321,8 +331,8 @@ private void mapChildrenIfReplaced(final Node before, final Node after) { if (!this.ltr.containsKey(first)) { final Node second = after.getChild(index); this.replaced.put(first, second); - this.left.remove(first); - this.right.remove(second); + this.unprocessed.remove(first); + this.unprocessed.remove(second); } } } @@ -339,7 +349,7 @@ private void mapChildrenIfDeleted(final Node before) { final Node child = before.getChild(index); if (!this.ltr.containsKey(child)) { this.deleted.add(child); - this.left.remove(child); + this.unprocessed.remove(child); } } } From 51259c9871a5376a7fcbbf5a8faf2c51af324ea5 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Tue, 27 Feb 2024 09:57:03 +0300 Subject: [PATCH 07/16] 'Insert' action --- .../java/org/cqfn/astranaut/core/Factory.java | 21 +- .../java/org/cqfn/astranaut/core/Insert.java | 220 ++++++++++++++++++ 2 files changed, 235 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/cqfn/astranaut/core/Insert.java diff --git a/src/main/java/org/cqfn/astranaut/core/Factory.java b/src/main/java/org/cqfn/astranaut/core/Factory.java index 3c374b5..ccd6d50 100644 --- a/src/main/java/org/cqfn/astranaut/core/Factory.java +++ b/src/main/java/org/cqfn/astranaut/core/Factory.java @@ -55,12 +55,21 @@ public final Builder createBuilder(final String name) { final Type type = this.types.get(name); result = type.createBuilder(); } else { - if (name.equals("Delete")) { - result = new Delete.Constructor(); - } else { - final DraftNode.Constructor draft = new DraftNode.Constructor(); - draft.setName(name); - result = draft; + switch (name) { + case "Insert": + result = new Insert.Constructor(); + break; + case "Replace": + result = new Replace.Constructor(); + break; + case "Delete": + result = new Delete.Constructor(); + break; + default: + final DraftNode.Constructor draft = new DraftNode.Constructor(); + draft.setName(name); + result = draft; + break; } } return result; diff --git a/src/main/java/org/cqfn/astranaut/core/Insert.java b/src/main/java/org/cqfn/astranaut/core/Insert.java new file mode 100644 index 0000000..b06c105 --- /dev/null +++ b/src/main/java/org/cqfn/astranaut/core/Insert.java @@ -0,0 +1,220 @@ +/* + * 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.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * An action that inserts a child element. + * + * @since 1.1.0 + */ +public final class Insert implements Action { + /** + * The type. + */ + public static final Type TYPE = new InsertType(); + + /** + * Child element. + */ + private final Node child; + + /** + * Constructor. + * @param child A child element that will be added. + */ + public Insert(final Node child) { + this.child = child; + } + + @Override + public Node getBefore() { + return null; + } + + @Override + public Node getAfter() { + return this.child; + } + + @Override + public Fragment getFragment() { + return this.child.getFragment(); + } + + @Override + public Type getType() { + return Insert.TYPE; + } + + @Override + public String getData() { + return ""; + } + + @Override + public int getChildCount() { + return 1; + } + + @Override + public Node getChild(final int index) { + final Node node; + if (index == 0) { + node = this.child; + } else { + node = null; + } + return node; + } + + /** + * Type of 'Insert' action. + * + * @since 1.1.0 + */ + private static final class InsertType implements Type { + /** + * The 'Node' string. + */ + private static final String NODE = "Node"; + + /** + * The 'ACTION' string. + */ + private static final String ACTION = "Action"; + + /** + * The 'DELETE' string. + */ + private static final String INSERT = "Insert"; + + /** + * The list of child descriptors. + */ + private static final List CHILDREN = + Collections.singletonList( + new ChildDescriptor( + InsertType.NODE, + false + ) + ); + + /** + * Hierarchy. + */ + private static final List HIERARCHY = + Collections.unmodifiableList( + Arrays.asList( + InsertType.INSERT, + InsertType.ACTION + ) + ); + + /** + * Properties. + */ + private static final Map PROPERTIES = Stream.of( + new String[][] { + {"color", "blue"}, + }).collect(Collectors.toMap(data -> data[0], data -> data[1])); + + @Override + public String getName() { + return InsertType.INSERT; + } + + @Override + public List getChildTypes() { + return InsertType.CHILDREN; + } + + @Override + public List getHierarchy() { + return InsertType.HIERARCHY; + } + + @Override + public String getProperty(final String name) { + return InsertType.PROPERTIES.getOrDefault(name, ""); + } + + @Override + public Builder createBuilder() { + return null; + } + } + + /** + * Class for 'Delete' action construction. + * + * @since 1.1.0 + */ + public static final class Constructor implements Builder { + /** + * Child node. + */ + private Node child; + + @Override + public void setFragment(final Fragment fragment) { + // do nothing + } + + @Override + public boolean setData(final String str) { + return str.isEmpty(); + } + + @Override + public boolean setChildrenList(final List list) { + boolean result = false; + if (list.size() == 1) { + this.child = list.get(0); + result = true; + } + return result; + } + + @Override + public boolean isValid() { + return this.child != null; + } + + @Override + public Node createNode() { + Node node = EmptyTree.INSTANCE; + if (this.isValid()) { + node = new Insert(this.child); + } + return node; + } + } +} From fac47fa8f147b43a2b957867d9867fdcaaca1716 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Tue, 27 Feb 2024 10:34:59 +0300 Subject: [PATCH 08/16] 'addNodeAfter' method --- .../cqfn/astranaut/core/DifferenceNode.java | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java b/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java index b0ceb50..4209124 100644 --- a/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java +++ b/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java @@ -24,7 +24,9 @@ package org.cqfn.astranaut.core; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; +import java.util.ListIterator; /** * Node containing child nodes, as well as actions on these nodes. @@ -118,7 +120,34 @@ public Node getAfter() { } /** - * Adds an action that replaces the node. + * Adds an action that inserts the node after another node. + * If no other node is specified, inserts at the beginning of the children's list. + * @param node Node to be inserted + * @param after Node after which to insert + * @return Result of operation, @return {@code true} if action was added + */ + public boolean addNodeAfter(final Node node, final Node after) { + boolean result = false; + if (after == null) { + this.children.add(0, new Insert(node)); + result = true; + } else { + final ListIterator iterator = this.children.listIterator(); + while (iterator.hasNext()) { + final Node child = iterator.next(); + if (child instanceof DifferenceNode + && ((DifferenceNode) child).getPrototype() == after) { + iterator.add(new Insert(node)); + result = true; + break; + } + } + } + return result; + } + + /** + * Adds an action that replaces a node. * The position of the node is specified by the index. * @param index Node index * @param replacement Child node to be replaced by @@ -143,7 +172,7 @@ public boolean replaceNode(final int index, final Node replacement) { } /** - * Adds an action that replaces the node. + * Adds an action that replaces a node. * @param node A node * @param replacement Child node to be replaced by * @return Result of operation, @return {@code true} if action was added @@ -194,7 +223,7 @@ public boolean deleteNode(final Node node) { */ private List initChildrenList() { final int count = this.prototype.getChildCount(); - final List result = new ArrayList<>(count); + final List result = new LinkedList<>(); for (int index = 0; index < count; index = index + 1) { result.add( new DifferenceNode(this, this.prototype.getChild(index)) From fb762c9d254f9bed8fbe330c52f79dba2e5d0c43 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Tue, 27 Feb 2024 12:27:56 +0300 Subject: [PATCH 09/16] Testing tree containing insert action --- .../cqfn/astranaut/core/DifferenceNode.java | 2 +- .../algorithms/DifferenceTreeBuilder.java | 16 ++++++ .../core/utils/deserializer/ActionList.java | 40 ++++++++++++- .../utils/deserializer/NodeDescriptor.java | 23 ++++++-- .../astranaut/core/DifferenceNodeTest.java | 31 +++++++++- .../json/tree_containing_insert_action.json | 57 +++++++++++++++++++ 6 files changed, 160 insertions(+), 9 deletions(-) create mode 100644 src/test/resources/json/tree_containing_insert_action.json diff --git a/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java b/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java index 4209124..c8d029c 100644 --- a/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java +++ b/src/main/java/org/cqfn/astranaut/core/DifferenceNode.java @@ -126,7 +126,7 @@ public Node getAfter() { * @param after Node after which to insert * @return Result of operation, @return {@code true} if action was added */ - public boolean addNodeAfter(final Node node, final Node after) { + public boolean insertNodeAfter(final Node node, final Node after) { boolean result = false; if (after == null) { this.children.add(0, new Insert(node)); 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 c7fcb20..25e1bc1 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/DifferenceTreeBuilder.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/DifferenceTreeBuilder.java @@ -83,6 +83,22 @@ public DifferenceNode getRoot() { return this.root; } + /** + * Adds an action to the difference tree that inserts a node after another node. + * If no other node is specified, inserts at the beginning of the children's list. + * @param node Child element that will be inserted + * @param after Node after which to insert + * @return Result of operation, {@code true} if action was added + */ + public boolean insertNodeAfter(final Node node, final Node after) { + boolean result = false; + final DifferenceNode parent = this.parents.get(after); + if (parent != null) { + result = parent.insertNodeAfter(node, after); + } + return result; + } + /** * Adds an action to the difference tree that replaces a node. * @param node Child element that will be replaced diff --git a/src/main/java/org/cqfn/astranaut/core/utils/deserializer/ActionList.java b/src/main/java/org/cqfn/astranaut/core/utils/deserializer/ActionList.java index e3a31f5..f320715 100644 --- a/src/main/java/org/cqfn/astranaut/core/utils/deserializer/ActionList.java +++ b/src/main/java/org/cqfn/astranaut/core/utils/deserializer/ActionList.java @@ -23,7 +23,9 @@ */ package org.cqfn.astranaut.core.utils.deserializer; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import org.cqfn.astranaut.core.DifferenceNode; import org.cqfn.astranaut.core.Node; @@ -35,6 +37,16 @@ * @since 1.1.0 */ public class ActionList { + /** + * Collection of nodes to be inserted (node -> after which to insert). + */ + private final Map insert; + + /** + * Collection of nodes to be replaced (node before changes -> node after changes). + */ + private final Map replace; + /** * Set of nodes to be deleted. */ @@ -44,6 +56,8 @@ public class ActionList { * Constructor. */ public ActionList() { + this.insert = new HashMap<>(); + this.replace = new HashMap<>(); this.delete = new HashSet<>(); } @@ -52,7 +66,25 @@ public ActionList() { * @return Checking result */ public boolean hasActions() { - return !this.delete.isEmpty(); + return !this.insert.isEmpty() || !this.replace.isEmpty() || !this.delete.isEmpty(); + } + + /** + * Adds the node to the list of nodes to be inserted. + * @param node Node to be inserted + * @param after Node after which to insert + */ + public void insertNodeAfter(final Node node, final Node after) { + this.insert.put(node, after); + } + + /** + * Adds the node to the list of nodes to be replaced. + * @param node Node to be replaced + * @param replacement Node to be replaced by + */ + public void replaceNode(final Node node, final Node replacement) { + this.replace.put(node, replacement); } /** @@ -70,6 +102,12 @@ public void deleteNode(final Node node) { */ public DifferenceNode convertTreeToDifferenceTree(final Node root) { final DifferenceTreeBuilder builder = new DifferenceTreeBuilder(root); + for (final Map.Entry pair : this.insert.entrySet()) { + builder.insertNodeAfter(pair.getKey(), pair.getValue()); + } + for (final Map.Entry pair : this.replace.entrySet()) { + builder.replaceNode(pair.getKey(), pair.getValue()); + } for (final Node node : this.delete) { builder.deleteNode(node); } diff --git a/src/main/java/org/cqfn/astranaut/core/utils/deserializer/NodeDescriptor.java b/src/main/java/org/cqfn/astranaut/core/utils/deserializer/NodeDescriptor.java index 6420f59..31af527 100644 --- a/src/main/java/org/cqfn/astranaut/core/utils/deserializer/NodeDescriptor.java +++ b/src/main/java/org/cqfn/astranaut/core/utils/deserializer/NodeDescriptor.java @@ -29,7 +29,9 @@ import org.cqfn.astranaut.core.Delete; import org.cqfn.astranaut.core.EmptyTree; import org.cqfn.astranaut.core.Factory; +import org.cqfn.astranaut.core.Insert; import org.cqfn.astranaut.core.Node; +import org.cqfn.astranaut.core.Replace; /** * Node descriptor represented as it is stored in the JSON file. @@ -95,10 +97,23 @@ private List convertChildren(final Factory factory, final ActionList actio final List list = new ArrayList<>(this.children.size()); for (final NodeDescriptor child : this.children) { final Node converted = child.convert(factory, actions); - if (converted instanceof Delete) { - final Node before = ((Delete) converted).getBefore(); - list.add(before); - actions.deleteNode(before); + if (converted instanceof Insert) { + final Node node = ((Insert) converted).getAfter(); + Node after = null; + final int size = list.size(); + if (size > 0) { + after = list.get(size - 1); + } + actions.insertNodeAfter(node, after); + } else if (converted instanceof Replace) { + final Replace action = (Replace) converted; + final Node node = action.getBefore(); + list.add(node); + actions.replaceNode(node, action.getAfter()); + } else if (converted instanceof Delete) { + final Node node = ((Delete) converted).getBefore(); + list.add(node); + actions.deleteNode(node); } else { list.add(converted); } diff --git a/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java b/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java index 986263f..51af11f 100644 --- a/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java +++ b/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java @@ -41,13 +41,38 @@ class DifferenceNodeTest { */ private static final String TESTS_PATH = "src/test/resources/json/"; + /** + * File name with tree containing 'Insert' action. + */ + private static final String TREE_BEFORE_DELETE = "before_delete_action.json"; + + + /** + * File name with tree containing 'Insert' action. + */ + private static final String TREE_WITH_INSERT = "tree_containing_insert_action.json"; + /** * File name with tree containing 'Delete' action. */ private static final String TREE_WITH_DELETE = "tree_containing_delete_action.json"; /** - * Testing {@link DifferenceNode#getBefore()} method. + * Testing {@link DifferenceNode#getAfter()} method with inserted node. + */ + @Test + void testInsertGetAfter() { + final Node root = this.loadTree(DifferenceNodeTest.TREE_WITH_INSERT); + Assertions.assertTrue(root instanceof DifferenceNode); + final DifferenceNode diff = (DifferenceNode) root; + final Node actual = diff.getAfter(); + Assertions.assertNotEquals(EmptyTree.INSTANCE, actual); + final Node expected = this.loadTree(DifferenceNodeTest.TREE_BEFORE_DELETE); + Assertions.assertTrue(expected.deepCompare(actual)); + } + + /** + * Testing {@link DifferenceNode#getBefore()} method with deleted node. */ @Test void testDeleteGetBefore() { @@ -56,12 +81,12 @@ void testDeleteGetBefore() { final DifferenceNode diff = (DifferenceNode) root; final Node actual = diff.getBefore(); Assertions.assertNotEquals(EmptyTree.INSTANCE, actual); - final Node expected = this.loadTree("before_delete_action.json"); + final Node expected = this.loadTree(DifferenceNodeTest.TREE_BEFORE_DELETE); Assertions.assertTrue(expected.deepCompare(actual)); } /** - * Testing {@link DifferenceNode#getAfter()} method. + * Testing {@link DifferenceNode#getAfter()} method with deleted node. */ @Test void testDeleteGetAfter() { diff --git a/src/test/resources/json/tree_containing_insert_action.json b/src/test/resources/json/tree_containing_insert_action.json new file mode 100644 index 0000000..d8c085f --- /dev/null +++ b/src/test/resources/json/tree_containing_insert_action.json @@ -0,0 +1,57 @@ +{ + "root": { + "type": "StatementBlock", + "children": [ + { + "type": "ExpressionStatement", + "children": [ + { + "type": "SimpleAssignment", + "children": [ + { + "type": "Variable", + "data": "x" + }, + { + "type": "IntegerLiteral", + "data": "1" + } + ] + } + ] + }, + { + "type": "Insert", + "children": [ + { + "type": "ExpressionStatement", + "children": [ + { + "type": "SimpleAssignment", + "children": [ + { + "type": "Variable", + "data": "y" + }, + { + "type": "IntegerLiteral", + "data": "2" + } + ] + } + ] + } + ] + }, + { + "type": "Return", + "children": [ + { + "type": "Variable", + "data": "x" + } + ] + } + ] + } +} \ No newline at end of file From ef6dbc53bbebf3d5e4b9842a7bb178f9af287c98 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Tue, 27 Feb 2024 12:34:59 +0300 Subject: [PATCH 10/16] Fix Qulice warnings --- .../java/org/cqfn/astranaut/core/DifferenceNodeTest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java b/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java index 51af11f..188f2aa 100644 --- a/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java +++ b/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java @@ -44,8 +44,7 @@ class DifferenceNodeTest { /** * File name with tree containing 'Insert' action. */ - private static final String TREE_BEFORE_DELETE = "before_delete_action.json"; - + private static final String TREE_BEFO_DELETE = "before_delete_action.json"; /** * File name with tree containing 'Insert' action. @@ -67,7 +66,7 @@ void testInsertGetAfter() { final DifferenceNode diff = (DifferenceNode) root; final Node actual = diff.getAfter(); Assertions.assertNotEquals(EmptyTree.INSTANCE, actual); - final Node expected = this.loadTree(DifferenceNodeTest.TREE_BEFORE_DELETE); + final Node expected = this.loadTree(DifferenceNodeTest.TREE_BEFO_DELETE); Assertions.assertTrue(expected.deepCompare(actual)); } @@ -81,7 +80,7 @@ void testDeleteGetBefore() { final DifferenceNode diff = (DifferenceNode) root; final Node actual = diff.getBefore(); Assertions.assertNotEquals(EmptyTree.INSTANCE, actual); - final Node expected = this.loadTree(DifferenceNodeTest.TREE_BEFORE_DELETE); + final Node expected = this.loadTree(DifferenceNodeTest.TREE_BEFO_DELETE); Assertions.assertTrue(expected.deepCompare(actual)); } From 189a0a84c9e84216a22fdc37ae04fa4c427745fa Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Tue, 27 Feb 2024 15:44:15 +0300 Subject: [PATCH 11/16] + test --- .../astranaut/core/DifferenceNodeTest.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java b/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java index 188f2aa..b6b4135 100644 --- a/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java +++ b/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java @@ -46,6 +46,11 @@ class DifferenceNodeTest { */ private static final String TREE_BEFO_DELETE = "before_delete_action.json"; + /** + * File name with tree containing 'Insert' action. + */ + private static final String TREE_AFTER_DELETE = "after_delete_action.json"; + /** * File name with tree containing 'Insert' action. */ @@ -56,6 +61,20 @@ class DifferenceNodeTest { */ private static final String TREE_WITH_DELETE = "tree_containing_delete_action.json"; + /** + * Testing {@link DifferenceNode#getBefore()} method with inserted node. + */ + @Test + void testInsertGetBefore() { + final Node root = this.loadTree(DifferenceNodeTest.TREE_WITH_INSERT); + Assertions.assertTrue(root instanceof DifferenceNode); + final DifferenceNode diff = (DifferenceNode) root; + final Node actual = diff.getBefore(); + Assertions.assertNotEquals(EmptyTree.INSTANCE, actual); + final Node expected = this.loadTree(DifferenceNodeTest.TREE_AFTER_DELETE); + Assertions.assertTrue(expected.deepCompare(actual)); + } + /** * Testing {@link DifferenceNode#getAfter()} method with inserted node. */ @@ -94,7 +113,7 @@ void testDeleteGetAfter() { final DifferenceNode diff = (DifferenceNode) root; final Node actual = diff.getAfter(); Assertions.assertNotEquals(EmptyTree.INSTANCE, actual); - final Node expected = this.loadTree("after_delete_action.json"); + final Node expected = this.loadTree(DifferenceNodeTest.TREE_AFTER_DELETE); Assertions.assertTrue(expected.deepCompare(actual)); } From 93001065bf27fbe3198400fe290c5c22a8c41c7c Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Tue, 27 Feb 2024 16:06:40 +0300 Subject: [PATCH 12/16] + test --- .../astranaut/core/DifferenceNodeTest.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java b/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java index b6b4135..e1e7221 100644 --- a/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java +++ b/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java @@ -23,6 +23,7 @@ */ package org.cqfn.astranaut.core; +import org.cqfn.astranaut.core.example.LittleTrees; import org.cqfn.astranaut.core.example.green.GreenFactory; import org.cqfn.astranaut.core.exceptions.BaseException; import org.cqfn.astranaut.core.utils.FilesReader; @@ -117,6 +118,44 @@ void testDeleteGetAfter() { Assertions.assertTrue(expected.deepCompare(actual)); } + /** + * Tests the case where a node is inserted at the start position of the child list. + */ + @Test + void testInsertNodeFirst() { + final Node first = LittleTrees.createReturnStatement(null); + final Node second = LittleTrees.wrapExpressionWithStatement( + LittleTrees.createAssignment( + LittleTrees.createVariable("x"), + LittleTrees.createIntegerLiteral(0) + ) + ); + final Node before = LittleTrees.createStatementBlock(first); + final Node after = LittleTrees.createStatementBlock(second, first); + final DifferenceNode diff = new DifferenceNode(before); + final boolean result = diff.insertNodeAfter(second, null); + Assertions.assertTrue(result); + Assertions.assertTrue(before.deepCompare(diff.getBefore())); + Assertions.assertTrue(after.deepCompare(diff.getAfter())); + } + + /** + * Tests the case where an attempt to insert a node fails. + */ + @Test + void testInsertNodeFails() { + final DifferenceNode diff = new DifferenceNode( + LittleTrees.createStatementBlock( + LittleTrees.createReturnStatement(null) + ) + ); + final boolean result = diff.insertNodeAfter( + LittleTrees.createVariable("x"), + LittleTrees.createVariable("y") + ); + Assertions.assertFalse(result); + } + /** * Returns content of the specified file. * @param name The name of the file From cfcec425a553264845f525bc54cf3ab259b37785 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Wed, 28 Feb 2024 11:51:02 +0300 Subject: [PATCH 13/16] + test --- .../astranaut/core/DifferenceNodeTest.java | 39 +++++++++++- .../resources/json/after_replace_action.json | 52 ++++++++++++++++ .../resources/json/before_replace_action.json | 52 ++++++++++++++++ .../json/tree_containing_replace_action.json | 61 +++++++++++++++++++ 4 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/json/after_replace_action.json create mode 100644 src/test/resources/json/before_replace_action.json create mode 100644 src/test/resources/json/tree_containing_replace_action.json diff --git a/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java b/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java index e1e7221..25694fe 100644 --- a/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java +++ b/src/test/java/org/cqfn/astranaut/core/DifferenceNodeTest.java @@ -43,20 +43,35 @@ class DifferenceNodeTest { private static final String TESTS_PATH = "src/test/resources/json/"; /** - * File name with tree containing 'Insert' action. + * File name with tree before 'Delete' action. */ private static final String TREE_BEFO_DELETE = "before_delete_action.json"; /** - * File name with tree containing 'Insert' action. + * File name with tree after 'Delete' action. */ private static final String TREE_AFTER_DELETE = "after_delete_action.json"; + /** + * File name with tree before 'Replace' action. + */ + private static final String TREE_BEFORE_REPL = "before_replace_action.json"; + + /** + * File name with tree after 'Replace' action. + */ + private static final String TREE_AFTER_REPL = "after_replace_action.json"; + /** * File name with tree containing 'Insert' action. */ private static final String TREE_WITH_INSERT = "tree_containing_insert_action.json"; + /** + * File name with tree containing 'Replace' action. + */ + private static final String TREE_WITH_REPLACE = "tree_containing_replace_action.json"; + /** * File name with tree containing 'Delete' action. */ @@ -90,6 +105,26 @@ void testInsertGetAfter() { Assertions.assertTrue(expected.deepCompare(actual)); } + /** + * Testing tree loading / composing with replaced node. + */ + @Test + void testReplace() { + final Node root = this.loadTree(DifferenceNodeTest.TREE_WITH_REPLACE); + Assertions.assertTrue(root instanceof DifferenceNode); + final DifferenceNode diff = (DifferenceNode) root; + final Node before = diff.getBefore(); + Assertions.assertNotEquals(EmptyTree.INSTANCE, before); + Assertions.assertTrue( + before.deepCompare(this.loadTree(DifferenceNodeTest.TREE_BEFORE_REPL)) + ); + final Node after = diff.getAfter(); + Assertions.assertNotEquals(EmptyTree.INSTANCE, after); + Assertions.assertTrue( + after.deepCompare(this.loadTree(DifferenceNodeTest.TREE_AFTER_REPL)) + ); + } + /** * Testing {@link DifferenceNode#getBefore()} method with deleted node. */ diff --git a/src/test/resources/json/after_replace_action.json b/src/test/resources/json/after_replace_action.json new file mode 100644 index 0000000..c881cc2 --- /dev/null +++ b/src/test/resources/json/after_replace_action.json @@ -0,0 +1,52 @@ +{ + "root": { + "type": "StatementBlock", + "children": [ + { + "type": "ExpressionStatement", + "children": [ + { + "type": "SimpleAssignment", + "children": [ + { + "type": "Variable", + "data": "x" + }, + { + "type": "IntegerLiteral", + "data": "1" + } + ] + } + ] + }, + { + "type": "ExpressionStatement", + "children": [ + { + "type": "SimpleAssignment", + "children": [ + { + "type": "Variable", + "data": "y" + }, + { + "type": "IntegerLiteral", + "data": "33" + } + ] + } + ] + }, + { + "type": "Return", + "children": [ + { + "type": "Variable", + "data": "x" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/src/test/resources/json/before_replace_action.json b/src/test/resources/json/before_replace_action.json new file mode 100644 index 0000000..7861802 --- /dev/null +++ b/src/test/resources/json/before_replace_action.json @@ -0,0 +1,52 @@ +{ + "root": { + "type": "StatementBlock", + "children": [ + { + "type": "ExpressionStatement", + "children": [ + { + "type": "SimpleAssignment", + "children": [ + { + "type": "Variable", + "data": "x" + }, + { + "type": "IntegerLiteral", + "data": "1" + } + ] + } + ] + }, + { + "type": "ExpressionStatement", + "children": [ + { + "type": "SimpleAssignment", + "children": [ + { + "type": "Variable", + "data": "y" + }, + { + "type": "Variable", + "data": "x" + } + ] + } + ] + }, + { + "type": "Return", + "children": [ + { + "type": "Variable", + "data": "x" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/src/test/resources/json/tree_containing_replace_action.json b/src/test/resources/json/tree_containing_replace_action.json new file mode 100644 index 0000000..2d1053e --- /dev/null +++ b/src/test/resources/json/tree_containing_replace_action.json @@ -0,0 +1,61 @@ +{ + "root": { + "type": "StatementBlock", + "children": [ + { + "type": "ExpressionStatement", + "children": [ + { + "type": "SimpleAssignment", + "children": [ + { + "type": "Variable", + "data": "x" + }, + { + "type": "IntegerLiteral", + "data": "1" + } + ] + } + ] + }, + { + "type": "ExpressionStatement", + "children": [ + { + "type": "SimpleAssignment", + "children": [ + { + "type": "Variable", + "data": "y" + }, + { + "type": "Replace", + "children": [ + { + "type": "Variable", + "data": "x" + }, + { + "type": "IntegerLiteral", + "data": "33" + } + ] + } + ] + } + ] + }, + { + "type": "Return", + "children": [ + { + "type": "Variable", + "data": "x" + } + ] + } + ] + } +} \ No newline at end of file From afe020b907b41ab1d1437de966140826c6568128 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Wed, 28 Feb 2024 12:33:00 +0300 Subject: [PATCH 14/16] + test --- .../java/org/cqfn/astranaut/core/Delete.java | 2 +- .../java/org/cqfn/astranaut/core/Insert.java | 2 +- .../java/org/cqfn/astranaut/core/Replace.java | 2 +- .../org/cqfn/astranaut/core/ActionTest.java | 89 +++++++++++++++++++ 4 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 src/test/java/org/cqfn/astranaut/core/ActionTest.java diff --git a/src/main/java/org/cqfn/astranaut/core/Delete.java b/src/main/java/org/cqfn/astranaut/core/Delete.java index d33dc83..622a3bd 100644 --- a/src/main/java/org/cqfn/astranaut/core/Delete.java +++ b/src/main/java/org/cqfn/astranaut/core/Delete.java @@ -168,7 +168,7 @@ public String getProperty(final String name) { @Override public Builder createBuilder() { - return null; + return new Constructor(); } } diff --git a/src/main/java/org/cqfn/astranaut/core/Insert.java b/src/main/java/org/cqfn/astranaut/core/Insert.java index b06c105..4aad07a 100644 --- a/src/main/java/org/cqfn/astranaut/core/Insert.java +++ b/src/main/java/org/cqfn/astranaut/core/Insert.java @@ -168,7 +168,7 @@ public String getProperty(final String name) { @Override public Builder createBuilder() { - return null; + return new Constructor(); } } diff --git a/src/main/java/org/cqfn/astranaut/core/Replace.java b/src/main/java/org/cqfn/astranaut/core/Replace.java index c8e929c..57159fb 100644 --- a/src/main/java/org/cqfn/astranaut/core/Replace.java +++ b/src/main/java/org/cqfn/astranaut/core/Replace.java @@ -186,7 +186,7 @@ public String getProperty(final String name) { @Override public Builder createBuilder() { - return null; + return new Constructor(); } } diff --git a/src/test/java/org/cqfn/astranaut/core/ActionTest.java b/src/test/java/org/cqfn/astranaut/core/ActionTest.java new file mode 100644 index 0000000..8a98949 --- /dev/null +++ b/src/test/java/org/cqfn/astranaut/core/ActionTest.java @@ -0,0 +1,89 @@ +/* + * 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.Arrays; +import java.util.Collections; +import java.util.List; +import org.cqfn.astranaut.core.example.LittleTrees; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Tests covering actions, i.e. {@link Action} interface and inherited classes. + * + * @since 1.1.0 + */ +class ActionTest { + /** + * The 'Insert' type. + */ + private static final String INSERT_TYPE = "Insert"; + + /** + * The 'color' property. + */ + private static final String COLOR_PROPERTY = "color"; + + /** + * The expected color property. + */ + private static final String EXPECTED_COLOR = "blue"; + + /** + * Testing {@link Insert} action. + */ + @Test + void testInsertAction() { + final Node inserted = LittleTrees.createReturnStatement(null); + final Action action = new Insert(inserted); + Assertions.assertEquals(EmptyFragment.INSTANCE, action.getFragment()); + Assertions.assertEquals("", action.getData()); + Assertions.assertEquals(1, action.getChildCount()); + Assertions.assertEquals(inserted, action.getChild(0)); + Assertions.assertNull(action.getChild(1)); + final Type type = action.getType(); + Assertions.assertEquals(ActionTest.INSERT_TYPE, type.getName()); + final List descriptors = type.getChildTypes(); + Assertions.assertFalse(descriptors.isEmpty()); + final List hierarchy = type.getHierarchy(); + Assertions.assertFalse(hierarchy.isEmpty()); + Assertions.assertEquals(type.getName(), hierarchy.get(0)); + Assertions.assertEquals( + ActionTest.EXPECTED_COLOR, + type.getProperty(ActionTest.COLOR_PROPERTY) + ); + final Builder builder = type.createBuilder(); + builder.setFragment(EmptyFragment.INSTANCE); + Assertions.assertTrue(builder.setData("")); + Assertions.assertFalse(builder.setData("abracadabra")); + Assertions.assertFalse(builder.isValid()); + Node created = builder.createNode(); + Assertions.assertEquals(EmptyTree.INSTANCE, created); + Assertions.assertTrue(builder.setChildrenList(Collections.singletonList(inserted))); + Assertions.assertFalse(builder.setChildrenList(Arrays.asList(inserted, inserted))); + created = builder.createNode(); + Assertions.assertEquals(ActionTest.INSERT_TYPE, created.getTypeName()); + } +} From 7c076853ab9d29da23e319eaeb304ea2321ce5e7 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Wed, 28 Feb 2024 12:44:30 +0300 Subject: [PATCH 15/16] + test --- .../java/org/cqfn/astranaut/core/Replace.java | 2 +- .../org/cqfn/astranaut/core/ActionTest.java | 84 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/cqfn/astranaut/core/Replace.java b/src/main/java/org/cqfn/astranaut/core/Replace.java index 57159fb..7f76983 100644 --- a/src/main/java/org/cqfn/astranaut/core/Replace.java +++ b/src/main/java/org/cqfn/astranaut/core/Replace.java @@ -89,7 +89,7 @@ public String getData() { @Override public int getChildCount() { - return 1; + return 2; } @Override diff --git a/src/test/java/org/cqfn/astranaut/core/ActionTest.java b/src/test/java/org/cqfn/astranaut/core/ActionTest.java index 8a98949..257bc70 100644 --- a/src/test/java/org/cqfn/astranaut/core/ActionTest.java +++ b/src/test/java/org/cqfn/astranaut/core/ActionTest.java @@ -41,6 +41,16 @@ class ActionTest { */ private static final String INSERT_TYPE = "Insert"; + /** + * The 'Replace' type. + */ + private static final String REPLACE_TYPE = "Replace"; + + /** + * The 'Delete' type. + */ + private static final String DELETE_TYPE = "Delete"; + /** * The 'color' property. */ @@ -86,4 +96,78 @@ void testInsertAction() { created = builder.createNode(); Assertions.assertEquals(ActionTest.INSERT_TYPE, created.getTypeName()); } + + /** + * Testing {@link Replace} action. + */ + @Test + void testReplaceAction() { + final Node before = LittleTrees.createVariable("x"); + final Node after = LittleTrees.createIntegerLiteral(0); + final Action action = new Replace(before, after); + Assertions.assertEquals(EmptyFragment.INSTANCE, action.getFragment()); + Assertions.assertEquals("", action.getData()); + Assertions.assertEquals(2, action.getChildCount()); + Assertions.assertEquals(before, action.getChild(0)); + Assertions.assertEquals(after, action.getChild(1)); + Assertions.assertNull(action.getChild(2)); + final Type type = action.getType(); + Assertions.assertEquals(ActionTest.REPLACE_TYPE, type.getName()); + final List descriptors = type.getChildTypes(); + Assertions.assertFalse(descriptors.isEmpty()); + final List hierarchy = type.getHierarchy(); + Assertions.assertFalse(hierarchy.isEmpty()); + Assertions.assertEquals(type.getName(), hierarchy.get(0)); + Assertions.assertEquals( + ActionTest.EXPECTED_COLOR, + type.getProperty(ActionTest.COLOR_PROPERTY) + ); + final Builder builder = type.createBuilder(); + builder.setFragment(EmptyFragment.INSTANCE); + Assertions.assertTrue(builder.setData("")); + Assertions.assertFalse(builder.setData("it's a kind of magic")); + Assertions.assertFalse(builder.isValid()); + Node created = builder.createNode(); + Assertions.assertEquals(EmptyTree.INSTANCE, created); + Assertions.assertTrue(builder.setChildrenList(Arrays.asList(before, after))); + Assertions.assertFalse(builder.setChildrenList(Collections.singletonList(before))); + created = builder.createNode(); + Assertions.assertEquals(ActionTest.REPLACE_TYPE, created.getTypeName()); + } + + /** + * Testing {@link Delete} action. + */ + @Test + void testDeleteAction() { + final Node deleted = LittleTrees.createReturnStatement(null); + final Action action = new Delete(deleted); + Assertions.assertEquals(EmptyFragment.INSTANCE, action.getFragment()); + Assertions.assertEquals("", action.getData()); + Assertions.assertEquals(1, action.getChildCount()); + Assertions.assertEquals(deleted, action.getChild(0)); + Assertions.assertNull(action.getChild(1)); + final Type type = action.getType(); + Assertions.assertEquals(ActionTest.DELETE_TYPE, type.getName()); + final List descriptors = type.getChildTypes(); + Assertions.assertFalse(descriptors.isEmpty()); + final List hierarchy = type.getHierarchy(); + Assertions.assertFalse(hierarchy.isEmpty()); + Assertions.assertEquals(type.getName(), hierarchy.get(0)); + Assertions.assertEquals( + ActionTest.EXPECTED_COLOR, + type.getProperty(ActionTest.COLOR_PROPERTY) + ); + final Builder builder = type.createBuilder(); + builder.setFragment(EmptyFragment.INSTANCE); + Assertions.assertTrue(builder.setData("")); + Assertions.assertFalse(builder.setData("I hate syntax trees")); + Assertions.assertFalse(builder.isValid()); + Node created = builder.createNode(); + Assertions.assertEquals(EmptyTree.INSTANCE, created); + Assertions.assertTrue(builder.setChildrenList(Collections.singletonList(deleted))); + Assertions.assertFalse(builder.setChildrenList(Arrays.asList(deleted, deleted))); + created = builder.createNode(); + Assertions.assertEquals(ActionTest.DELETE_TYPE, created.getTypeName()); + } } From 18c7f4360037b8ec7afad237fb847ee93f18d9a2 Mon Sep 17 00:00:00 2001 From: Ivan Kniazkov Date: Wed, 28 Feb 2024 14:55:00 +0300 Subject: [PATCH 16/16] Insertion parsing + tests --- .../org/cqfn/astranaut/core/Insertion.java | 93 +++++++++++++++ .../algorithms/DifferenceTreeBuilder.java | 109 ++++++++++++++---- .../algorithms/mapping/BottomUpAlgorithm.java | 39 ++++++- .../core/algorithms/mapping/Mapping.java | 7 ++ .../core/utils/deserializer/ActionList.java | 16 +-- .../utils/deserializer/NodeDescriptor.java | 2 +- .../algorithms/DifferenceTreeBuilderTest.java | 19 +++ .../astranaut/core/example/LittleTrees.java | 31 +++++ 8 files changed, 283 insertions(+), 33 deletions(-) create mode 100644 src/main/java/org/cqfn/astranaut/core/Insertion.java diff --git a/src/main/java/org/cqfn/astranaut/core/Insertion.java b/src/main/java/org/cqfn/astranaut/core/Insertion.java new file mode 100644 index 0000000..5d0697c --- /dev/null +++ b/src/main/java/org/cqfn/astranaut/core/Insertion.java @@ -0,0 +1,93 @@ +/* + * 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.Objects; + +/** + * This class contains information about the node being inserted as a child of another node. + * + * @since 1.1.0 + */ +public final class Insertion { + /** + * Node being inserted. + */ + private final Node inserted; + + /** + * Parent node into which the child node will be inserted. + */ + private final Node into; + + /** + * Child node after which to insert. + */ + private final Node after; + + /** + * Constructor. + * @param inserted Node being inserted + * @param into Parent node into which the child node will be inserted + * @param after Child node after which to insert + */ + public Insertion(final Node inserted, final Node into, final Node after) { + this.inserted = Objects.requireNonNull(inserted); + this.into = into; + this.after = after; + } + + /** + * Another constructor. + * @param inserted Node being inserted + * @param after Child node after which to insert + */ + public Insertion(final Node inserted, final Node after) { + this(inserted, null, Objects.requireNonNull(after)); + } + + /** + * Returns node being inserted. + * @return A node + */ + public Node getNode() { + return this.inserted; + } + + /** + * Returns parent node into which the child node will be inserted. + * @return A node + */ + public Node getInto() { + return this.into; + } + + /** + * Returns child node after which to insert. + * @return A node + */ + public Node getAfter() { + return this.after; + } +} 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 25e1bc1..cb8ad52 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/DifferenceTreeBuilder.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/DifferenceTreeBuilder.java @@ -26,6 +26,7 @@ import java.util.HashMap; import java.util.Map; import org.cqfn.astranaut.core.DifferenceNode; +import org.cqfn.astranaut.core.Insertion; import org.cqfn.astranaut.core.Node; import org.cqfn.astranaut.core.algorithms.mapping.Mapper; import org.cqfn.astranaut.core.algorithms.mapping.Mapping; @@ -37,11 +38,16 @@ */ public final class DifferenceTreeBuilder { /** - * The relationship of the nodes to their parents. - * This information is necessary to implement algorithms for - * adding, removing and replacing nodes. + * Default node info (to avoid null checks). */ - private final Map parents; + 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. @@ -54,7 +60,7 @@ public final class DifferenceTreeBuilder { */ public DifferenceTreeBuilder(final Node before) { this.root = new DifferenceNode(before); - this.parents = DifferenceTreeBuilder.buildParentsMap(this.root); + this.info = DifferenceTreeBuilder.buildNodeInfoMap(this.root); } /** @@ -66,6 +72,9 @@ public DifferenceTreeBuilder(final Node before) { public boolean build(final Node after, final Mapper mapper) { final Mapping mapping = mapper.map(this.root.getPrototype(), after); boolean result = true; + for (final Insertion insertion : mapping.getInserted()) { + result = result & this.insertNode(insertion); + } for (final Map.Entry replaced : mapping.getReplaced().entrySet()) { result = result & this.replaceNode(replaced.getKey(), replaced.getValue()); } @@ -86,15 +95,23 @@ public DifferenceNode getRoot() { /** * Adds an action to the difference tree that inserts a node after another node. * If no other node is specified, inserts at the beginning of the children's list. - * @param node Child element that will be inserted - * @param after Node after which to insert + * @param insertion Full information about the node being inserted * @return Result of operation, {@code true} if action was added */ - public boolean insertNodeAfter(final Node node, final Node after) { + public boolean insertNode(final Insertion insertion) { boolean result = false; - final DifferenceNode parent = this.parents.get(after); + DifferenceNode parent = this.info.getOrDefault( + insertion.getInto(), + DifferenceTreeBuilder.DEFAULT_INFO + ).getDiff(); + if (parent == null) { + parent = this.info.getOrDefault( + insertion.getAfter(), + DifferenceTreeBuilder.DEFAULT_INFO + ).getParent(); + } if (parent != null) { - result = parent.insertNodeAfter(node, after); + result = parent.insertNodeAfter(insertion.getNode(), insertion.getAfter()); } return result; } @@ -107,7 +124,8 @@ public boolean insertNodeAfter(final Node node, final Node after) { */ public boolean replaceNode(final Node node, final Node replacement) { boolean result = false; - final DifferenceNode parent = this.parents.get(node); + final DifferenceNode parent = + this.info.getOrDefault(node, DifferenceTreeBuilder.DEFAULT_INFO).getParent(); if (parent != null) { result = parent.replaceNode(node, replacement); } @@ -121,7 +139,8 @@ public boolean replaceNode(final Node node, final Node replacement) { */ public boolean deleteNode(final Node node) { boolean result = false; - final DifferenceNode parent = this.parents.get(node); + final DifferenceNode parent = + this.info.getOrDefault(node, DifferenceTreeBuilder.DEFAULT_INFO).getParent(); if (parent != null) { result = parent.deleteNode(node); } @@ -133,28 +152,72 @@ public boolean deleteNode(final Node node) { * @param root Root node * @return The map containing relationship of the nodes to their parents. */ - private static Map buildParentsMap(final DifferenceNode root) { - final Map map = new HashMap<>(); - DifferenceTreeBuilder.buildParentsMap(map, root); + private static Map buildNodeInfoMap(final DifferenceNode root) { + final Map map = new HashMap<>(); + map.put(root.getPrototype(), new NodeInfo(root, null)); + DifferenceTreeBuilder.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 node Current node + * @param parent Parent node */ - private static void buildParentsMap( - final Map map, - final DifferenceNode node) { - node.forEachChild( + private static void buildNodeInfoMap( + final Map map, + final DifferenceNode parent) { + parent.forEachChild( child -> { if (child instanceof DifferenceNode) { - final DifferenceNode diff = (DifferenceNode) child; - map.put(diff.getPrototype(), node); - DifferenceTreeBuilder.buildParentsMap(map, diff); + final DifferenceNode node = (DifferenceNode) child; + map.put(node.getPrototype(), new NodeInfo(node, parent)); + DifferenceTreeBuilder.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 difference node. + */ + private final DifferenceNode diff; + + /** + * The parent node. + */ + private final DifferenceNode parent; + + /** + * Constructor. + * @param diff The corresponding difference node + * @param parent The parent node + */ + NodeInfo(final DifferenceNode diff, final DifferenceNode parent) { + this.diff = diff; + this.parent = parent; + } + + /** + * Returns corresponding difference node. + * @return Difference node + */ + public DifferenceNode getDiff() { + return this.diff; + } + + /** + * Returns parent node. + * @return Difference node containing this node + */ + public DifferenceNode getParent() { + return this.parent; + } + } } diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpAlgorithm.java b/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpAlgorithm.java index 6dafa8f..bace428 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpAlgorithm.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/BottomUpAlgorithm.java @@ -33,6 +33,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; +import org.cqfn.astranaut.core.Insertion; import org.cqfn.astranaut.core.Node; import org.cqfn.astranaut.core.algorithms.Depth; import org.cqfn.astranaut.core.algorithms.hash.AbsoluteHash; @@ -87,6 +88,11 @@ class BottomUpAlgorithm { */ private final Map rtl; + /** + * Set containing inserted nodes. + */ + private final Set inserted; + /** * Map containing replaces nodes. */ @@ -111,6 +117,7 @@ class BottomUpAlgorithm { this.right = this.createNodeList(right); this.ltr = new HashMap<>(); this.rtl = new HashMap<>(); + this.inserted = new HashSet<>(); this.replaced = new HashMap<>(); this.deleted = new HashSet<>(); } @@ -309,13 +316,36 @@ private Node mapPartiallyMappedLeftNode(final Node node) { */ private void mapChildren(final Node before, final Node after) { final int sign = Integer.compare(before.getChildCount(), after.getChildCount()); - if (sign > 0) { + if (sign < 0) { + this.mapChildrenIfInserted(before, after); + } else if (sign > 0) { this.mapChildrenIfDeleted(before); - } else if (sign == 0) { + } else { this.mapChildrenIfReplaced(before, after); } } + /** + * Maps the child nodes of partially mapped nodes if the node before changes + * has less child nodes than the node after changes, i.e., when it is obvious + * that some nodes have been inserted. + * @param before Node before changes + * @param after Node after changes + */ + private void mapChildrenIfInserted(final Node before, final Node after) { + final int count = after.getChildCount(); + Node previous = null; + for (int index = 0; index < count; index = index + 1) { + final Node child = after.getChild(index); + if (this.rtl.containsKey(child)) { + previous = this.rtl.get(child); + } else { + this.inserted.add(new Insertion(child, before, previous)); + this.unprocessed.remove(child); + } + } + } + /** * Maps the child nodes of partially mapped nodes if the node before changes * has the same number of child nodes as the node after changes, i.e., when it is obvious @@ -383,6 +413,11 @@ public Node getLeft(final Node node) { return this.data.rtl.get(node); } + @Override + public Set getInserted() { + return Collections.unmodifiableSet(this.data.inserted); + } + @Override public Map getReplaced() { return Collections.unmodifiableMap(this.data.replaced); diff --git a/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/Mapping.java b/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/Mapping.java index da1eb2e..d8a376e 100644 --- a/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/Mapping.java +++ b/src/main/java/org/cqfn/astranaut/core/algorithms/mapping/Mapping.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Set; +import org.cqfn.astranaut.core.Insertion; import org.cqfn.astranaut.core.Node; /** @@ -50,6 +51,12 @@ public interface Mapping { */ Node getLeft(Node right); + /** + * Returns a collection of nodes that must be added to the 'left' tree to get the 'right' tree. + * @return The set of inserted nodes + */ + Set getInserted(); + /** * Returns relationship between the nodes of the 'left' tree that have been replaced * by nodes of the 'right' tree. diff --git a/src/main/java/org/cqfn/astranaut/core/utils/deserializer/ActionList.java b/src/main/java/org/cqfn/astranaut/core/utils/deserializer/ActionList.java index f320715..37e6b26 100644 --- a/src/main/java/org/cqfn/astranaut/core/utils/deserializer/ActionList.java +++ b/src/main/java/org/cqfn/astranaut/core/utils/deserializer/ActionList.java @@ -28,6 +28,7 @@ import java.util.Map; import java.util.Set; import org.cqfn.astranaut.core.DifferenceNode; +import org.cqfn.astranaut.core.Insertion; import org.cqfn.astranaut.core.Node; import org.cqfn.astranaut.core.algorithms.DifferenceTreeBuilder; @@ -38,9 +39,9 @@ */ public class ActionList { /** - * Collection of nodes to be inserted (node -> after which to insert). + * Collection of nodes to be inserted. */ - private final Map insert; + private final Set insert; /** * Collection of nodes to be replaced (node before changes -> node after changes). @@ -56,7 +57,7 @@ public class ActionList { * Constructor. */ public ActionList() { - this.insert = new HashMap<>(); + this.insert = new HashSet<>(); this.replace = new HashMap<>(); this.delete = new HashSet<>(); } @@ -72,10 +73,11 @@ public boolean hasActions() { /** * Adds the node to the list of nodes to be inserted. * @param node Node to be inserted + * @param into Parent node into which the child node will be inserted * @param after Node after which to insert */ - public void insertNodeAfter(final Node node, final Node after) { - this.insert.put(node, after); + public void insertNodeAfter(final Node node, final Node into, final Node after) { + this.insert.add(new Insertion(node, into, after)); } /** @@ -102,8 +104,8 @@ public void deleteNode(final Node node) { */ public DifferenceNode convertTreeToDifferenceTree(final Node root) { final DifferenceTreeBuilder builder = new DifferenceTreeBuilder(root); - for (final Map.Entry pair : this.insert.entrySet()) { - builder.insertNodeAfter(pair.getKey(), pair.getValue()); + for (final Insertion insertion : this.insert) { + builder.insertNode(insertion); } for (final Map.Entry pair : this.replace.entrySet()) { builder.replaceNode(pair.getKey(), pair.getValue()); diff --git a/src/main/java/org/cqfn/astranaut/core/utils/deserializer/NodeDescriptor.java b/src/main/java/org/cqfn/astranaut/core/utils/deserializer/NodeDescriptor.java index 31af527..9d95e30 100644 --- a/src/main/java/org/cqfn/astranaut/core/utils/deserializer/NodeDescriptor.java +++ b/src/main/java/org/cqfn/astranaut/core/utils/deserializer/NodeDescriptor.java @@ -104,7 +104,7 @@ private List convertChildren(final Factory factory, final ActionList actio if (size > 0) { after = list.get(size - 1); } - actions.insertNodeAfter(node, after); + actions.insertNodeAfter(node, null, after); } else if (converted instanceof Replace) { final Replace action = (Replace) converted; final Node node = action.getBefore(); 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 993e0f2..124d8dd 100644 --- a/src/test/java/org/cqfn/astranaut/core/algorithms/DifferenceTreeBuilderTest.java +++ b/src/test/java/org/cqfn/astranaut/core/algorithms/DifferenceTreeBuilderTest.java @@ -38,6 +38,25 @@ * @since 1.1.0 */ class DifferenceTreeBuilderTest { + /** + * Testing the construction of a difference tree with an inserted node. + */ + @Test + void testTreeWithInsertedNode() { + final Node before = LittleTrees.createStatementListWithTwoChildren(); + final Node after = LittleTrees.createStatementListWithThreeChildren( + LittleTrees.createIntegerLiteral(3) + ); + final DifferenceTreeBuilder builder = new DifferenceTreeBuilder(before); + final boolean result = builder.build(after, new BottomUpMapper()); + Assertions.assertTrue(result); + final DifferenceNode diff = builder.getRoot(); + final Node expected = LittleTrees.createTreeWithInsertAction(); + Assertions.assertTrue(expected.deepCompare(diff)); + Assertions.assertTrue(before.deepCompare(diff.getBefore())); + Assertions.assertTrue(after.deepCompare(diff.getAfter())); + } + /** * Testing the construction of a difference tree with a replaced node. */ diff --git a/src/test/java/org/cqfn/astranaut/core/example/LittleTrees.java b/src/test/java/org/cqfn/astranaut/core/example/LittleTrees.java index 152af3e..d3bd33c 100644 --- a/src/test/java/org/cqfn/astranaut/core/example/LittleTrees.java +++ b/src/test/java/org/cqfn/astranaut/core/example/LittleTrees.java @@ -27,6 +27,7 @@ import java.util.Collections; import org.cqfn.astranaut.core.DifferenceNode; import org.cqfn.astranaut.core.EmptyTree; +import org.cqfn.astranaut.core.Insertion; import org.cqfn.astranaut.core.Node; import org.cqfn.astranaut.core.algorithms.DifferenceTreeBuilder; import org.cqfn.astranaut.core.example.green.ExpressionStatement; @@ -185,6 +186,36 @@ public static Node createStatementListWithThreeChildren(final Node assignable) { ); } + /** + * Creates a tree that has a "insert" action in it. + * @return Root node + */ + public static DifferenceNode createTreeWithInsertAction() { + final Node after = + wrapExpressionWithStatement( + createAssignment( + createVariable("x"), + createIntegerLiteral(1) + ) + ); + final Node inserted = wrapExpressionWithStatement( + createAssignment( + createVariable("y"), + createIntegerLiteral(3) + ) + ); + final DifferenceTreeBuilder builder = new DifferenceTreeBuilder( + createStatementBlock( + after, + createReturnStatement( + createVariable("x") + ) + ) + ); + builder.insertNode(new Insertion(inserted, after)); + return builder.getRoot(); + } + /** * Creates a tree that has a "replace" action in it. * @return Root node