Skip to content

Commit

Permalink
Insertion parsing + tests
Browse files Browse the repository at this point in the history
  • Loading branch information
kniazkov committed Feb 28, 2024
1 parent 7c07685 commit 18c7f43
Show file tree
Hide file tree
Showing 8 changed files with 283 additions and 33 deletions.
93 changes: 93 additions & 0 deletions src/main/java/org/cqfn/astranaut/core/Insertion.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Node, DifferenceNode> 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<Node, NodeInfo> info;

/**
* Root node.
Expand All @@ -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);
}

/**
Expand All @@ -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<Node, Node> replaced : mapping.getReplaced().entrySet()) {
result = result & this.replaceNode(replaced.getKey(), replaced.getValue());
}
Expand All @@ -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;
}
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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<Node, DifferenceNode> buildParentsMap(final DifferenceNode root) {
final Map<Node, DifferenceNode> map = new HashMap<>();
DifferenceTreeBuilder.buildParentsMap(map, root);
private static Map<Node, NodeInfo> buildNodeInfoMap(final DifferenceNode root) {
final Map<Node, NodeInfo> 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<Node, DifferenceNode> map,
final DifferenceNode node) {
node.forEachChild(
private static void buildNodeInfoMap(
final Map<Node, NodeInfo> 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -87,6 +88,11 @@ class BottomUpAlgorithm {
*/
private final Map<Node, Node> rtl;

/**
* Set containing inserted nodes.
*/
private final Set<Insertion> inserted;

/**
* Map containing replaces nodes.
*/
Expand All @@ -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<>();
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -383,6 +413,11 @@ public Node getLeft(final Node node) {
return this.data.rtl.get(node);
}

@Override
public Set<Insertion> getInserted() {
return Collections.unmodifiableSet(this.data.inserted);
}

@Override
public Map<Node, Node> getReplaced() {
return Collections.unmodifiableMap(this.data.replaced);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import java.util.Map;
import java.util.Set;
import org.cqfn.astranaut.core.Insertion;
import org.cqfn.astranaut.core.Node;

/**
Expand All @@ -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<Insertion> getInserted();

/**
* Returns relationship between the nodes of the 'left' tree that have been replaced
* by nodes of the 'right' tree.
Expand Down
Loading

0 comments on commit 18c7f43

Please sign in to comment.