Skip to content

Commit

Permalink
Difference tree builder supports 'replace' action but here strange bu…
Browse files Browse the repository at this point in the history
…g in 'deepCompare' method
  • Loading branch information
kniazkov committed Feb 21, 2024
1 parent ff9b06b commit 21c60e6
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 14 deletions.
40 changes: 40 additions & 0 deletions src/main/java/org/cqfn/astranaut/core/DifferenceNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/cqfn/astranaut/core/Replace.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Node, Node> replaced : mapping.getReplaced().entrySet()) {
result = result & this.replaceNode(replaced.getKey(), replaced.getValue());
}
for (final Node deleted : mapping.getDeleted()) {
result = result & this.deleteNode(deleted);
}
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ class BottomUpMappingAlgorithm {
*/
private final Map<Node, Node> rtl;

/**
* Map containing replaces nodes.
*/
private final Map<Node, Node> replaced;

/**
* Set of deleted nodes.
*/
Expand All @@ -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<>();
}

Expand Down Expand Up @@ -132,6 +138,11 @@ public Node getLeft(final Node node) {
return BottomUpMappingAlgorithm.this.rtl.get(node);
}

@Override
public Map<Node, Node> getReplaced() {
return Collections.unmodifiableMap(BottomUpMappingAlgorithm.this.replaced);
}

@Override
public Set<Node> getDeleted() {
return Collections.unmodifiableSet(BottomUpMappingAlgorithm.this.deleted);
Expand Down Expand Up @@ -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);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<Node, Node> getReplaced();

/**
* Returns the set of nodes of the 'left' tree that need to be removed
* to get the 'right' tree.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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());
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
35 changes: 33 additions & 2 deletions src/test/java/org/cqfn/astranaut/core/example/LittleTrees.java
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -175,7 +176,7 @@ public static Node createStatementListWithThreeChildren() {
wrapExpressionWithStatement(
createAssignment(
createVariable("y"),
createIntegerLiteral(2)
assignable
)
),
createReturnStatement(
Expand All @@ -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
Expand Down

0 comments on commit 21c60e6

Please sign in to comment.