diff --git a/lib/functjonal-1.0-SNAPSHOT.jar b/lib/functjonal-1.0-SNAPSHOT.jar
index 654612e33..7e3171051 100644
Binary files a/lib/functjonal-1.0-SNAPSHOT.jar and b/lib/functjonal-1.0-SNAPSHOT.jar differ
diff --git a/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20220705.154223-1.jar.md5 b/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20220705.154223-1.jar.md5
deleted file mode 100644
index 8f7e849ba..000000000
--- a/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20220705.154223-1.jar.md5
+++ /dev/null
@@ -1 +0,0 @@
-b62dbf7b3ecb4116b474c02296408be1
\ No newline at end of file
diff --git a/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20220705.154223-1.jar.sha1 b/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20220705.154223-1.jar.sha1
deleted file mode 100644
index 26c99e453..000000000
--- a/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20220705.154223-1.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-99a3d184352a59d8b152d205ac3a4b0b3a2180d3
\ No newline at end of file
diff --git a/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20220705.154223-1.jar b/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20221108.155446-1.jar
similarity index 64%
rename from local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20220705.154223-1.jar
rename to local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20221108.155446-1.jar
index 654612e33..7e3171051 100644
Binary files a/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20220705.154223-1.jar and b/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20221108.155446-1.jar differ
diff --git a/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20221108.155446-1.jar.md5 b/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20221108.155446-1.jar.md5
new file mode 100644
index 000000000..0d80254ed
--- /dev/null
+++ b/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20221108.155446-1.jar.md5
@@ -0,0 +1 @@
+1c80d4b7ba114b2380301c357e7c78e7
\ No newline at end of file
diff --git a/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20221108.155446-1.jar.sha1 b/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20221108.155446-1.jar.sha1
new file mode 100644
index 000000000..e0781ec21
--- /dev/null
+++ b/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20221108.155446-1.jar.sha1
@@ -0,0 +1 @@
+5af42173c49ab8fca0391852570899a5c9b578c3
\ No newline at end of file
diff --git a/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20220705.154223-1.pom b/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20221108.155446-1.pom
similarity index 100%
rename from local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20220705.154223-1.pom
rename to local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20221108.155446-1.pom
diff --git a/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20220705.154223-1.pom.md5 b/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20221108.155446-1.pom.md5
similarity index 100%
rename from local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20220705.154223-1.pom.md5
rename to local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20221108.155446-1.pom.md5
diff --git a/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20220705.154223-1.pom.sha1 b/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20221108.155446-1.pom.sha1
similarity index 100%
rename from local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20220705.154223-1.pom.sha1
rename to local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/functjonal-1.0-20221108.155446-1.pom.sha1
diff --git a/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/maven-metadata.xml b/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/maven-metadata.xml
index 0eef12e7d..f60d63722 100644
--- a/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/maven-metadata.xml
+++ b/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/maven-metadata.xml
@@ -2,24 +2,24 @@
org.variantsyncfunctjonal
- 1.0-SNAPSHOT
+ 20221108155446
- 20220705.154223
+ 20221108.1554461
- 20220705154223jar
- 1.0-20220705.154223-1
- 20220705154223
+ 1.0-20221108.155446-1
+ 20221108155446pom
- 1.0-20220705.154223-1
- 20220705154223
+ 1.0-20221108.155446-1
+ 20221108155446
+ 1.0-SNAPSHOT
diff --git a/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/maven-metadata.xml.md5 b/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/maven-metadata.xml.md5
index 57fa035c3..4148ee479 100644
--- a/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/maven-metadata.xml.md5
+++ b/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/maven-metadata.xml.md5
@@ -1 +1 @@
-970fd829eb10a39db3fac3f66239d6d5
\ No newline at end of file
+8e0dc18f4c0caf15829984d35e21b416
\ No newline at end of file
diff --git a/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/maven-metadata.xml.sha1 b/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/maven-metadata.xml.sha1
index 3a43dafc4..5cd70e2a6 100644
--- a/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/maven-metadata.xml.sha1
+++ b/local-maven-repo/org/variantsync/functjonal/1.0-SNAPSHOT/maven-metadata.xml.sha1
@@ -1 +1 @@
-9dc50711cb637ba6126e6027dc96661db8def5f5
\ No newline at end of file
+a6dd456d37dde01239c14423e5d44becb5e56936
\ No newline at end of file
diff --git a/local-maven-repo/org/variantsync/functjonal/maven-metadata.xml b/local-maven-repo/org/variantsync/functjonal/maven-metadata.xml
index 209704c7e..b6b9977d1 100644
--- a/local-maven-repo/org/variantsync/functjonal/maven-metadata.xml
+++ b/local-maven-repo/org/variantsync/functjonal/maven-metadata.xml
@@ -6,6 +6,6 @@
1.0-SNAPSHOT
- 20220705154223
+ 20221108155446
diff --git a/local-maven-repo/org/variantsync/functjonal/maven-metadata.xml.md5 b/local-maven-repo/org/variantsync/functjonal/maven-metadata.xml.md5
index ed7e63873..39b7fc6a8 100644
--- a/local-maven-repo/org/variantsync/functjonal/maven-metadata.xml.md5
+++ b/local-maven-repo/org/variantsync/functjonal/maven-metadata.xml.md5
@@ -1 +1 @@
-3cfa20d6ed954ba0787eacf0781edfd6
\ No newline at end of file
+3f5fd991f33dcff72c2518ba693e3f6f
\ No newline at end of file
diff --git a/local-maven-repo/org/variantsync/functjonal/maven-metadata.xml.sha1 b/local-maven-repo/org/variantsync/functjonal/maven-metadata.xml.sha1
index e19f9959e..cd6abebd6 100644
--- a/local-maven-repo/org/variantsync/functjonal/maven-metadata.xml.sha1
+++ b/local-maven-repo/org/variantsync/functjonal/maven-metadata.xml.sha1
@@ -1 +1 @@
-41d1da59b5cda4773b7c2eaddb4e5c0a0c1c77d8
\ No newline at end of file
+2cd6cf429f095990ef3c6e68237aea734a7771e6
\ No newline at end of file
diff --git a/src/main/java/org/variantsync/diffdetective/diff/DiffLineNumber.java b/src/main/java/org/variantsync/diffdetective/diff/DiffLineNumber.java
index bd103243f..fbb48caae 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/DiffLineNumber.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/DiffLineNumber.java
@@ -1,6 +1,7 @@
package org.variantsync.diffdetective.diff;
import org.variantsync.diffdetective.diff.difftree.DiffType;
+import org.variantsync.diffdetective.diff.difftree.Time;
import java.util.Objects;
@@ -39,6 +40,22 @@ public static DiffLineNumber Invalid() {
return new DiffLineNumber(InvalidLineNumber, InvalidLineNumber, InvalidLineNumber);
}
+ public DiffLineNumber withLineNumberAtTime(int lineNumber, Time time) {
+ return new DiffLineNumber(
+ inDiff,
+ time.match(lineNumber, beforeEdit),
+ time.match(afterEdit, lineNumber)
+ );
+ }
+
+ public DiffLineNumber withLineNumberInDiff(int lineNumber) {
+ return new DiffLineNumber(
+ lineNumber,
+ beforeEdit,
+ afterEdit
+ );
+ }
+
/**
* Shifts this line number by adding the given offset.
* @param offset value to add to this line number.
@@ -77,6 +94,15 @@ public DiffLineNumber as(final DiffType diffType) {
);
}
+ /**
+ * Returns the line number at the given time.
+ * @param time the time at which to return the line range
+ * @return {@code beforeEdit} or {@code afterEdit}, depending on {@code time}
+ */
+ public int atTime(Time time) {
+ return time.match(beforeEdit, afterEdit);
+ }
+
@Override
public String toString() {
return "(old: " + beforeEdit + ", diff: " + inDiff + ", new:" + afterEdit + ")";
@@ -107,24 +133,14 @@ public static Lines rangeInDiff(final DiffLineNumber from, final DiffLineNumber
}
/**
- * Returns the range between two line numbers before the edit.
- * @see DiffLineNumber#inDiff
- * @param from The start line number.
- * @param to The end line number.
- * @return [from.beforeEdit, to.beforeEdit)
- */
- public static Lines rangeBeforeEdit(final DiffLineNumber from, final DiffLineNumber to) {
- return Lines.FromInclToExcl(from.beforeEdit, to.beforeEdit);
- }
-
- /**
- * Returns the range between two line numbers before the edit.
+ * Returns the range between two line numbers at a given time.
* @see DiffLineNumber#inDiff
* @param from The start line number.
* @param to The end line number.
- * @return [from.afterEdit, to.afterEdit)
+ * @param time The time at which to return the line range.
+ * @return [from.beforeEdit, to.beforeEdit) or [from.afterEdit, to.afterEdit)
*/
- public static Lines rangeAfterEdit(final DiffLineNumber from, final DiffLineNumber to) {
- return Lines.FromInclToExcl(from.afterEdit, to.afterEdit);
+ public static Lines rangeAtTime(final DiffLineNumber from, final DiffLineNumber to, Time time) {
+ return Lines.FromInclToExcl(from.atTime(time), to.atTime(time));
}
}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffGraph.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffGraph.java
index 94709153d..40068d658 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffGraph.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffGraph.java
@@ -2,6 +2,9 @@
import java.util.Collection;
+import static org.variantsync.diffdetective.diff.difftree.Time.AFTER;
+import static org.variantsync.diffdetective.diff.difftree.Time.BEFORE;
+
/**
* Generalisation of DiffTrees to arbitrary change graphs with variability information.
* The DiffGraph class currently does not model a graph itself but rather
@@ -33,8 +36,8 @@ public static DiffTree fromNodes(final Collection nodes, final DiffTre
.filter(DiffNode::isRoot)
.forEach(n ->
n.diffType.matchBeforeAfter(
- () -> newRoot.addBeforeChild(n),
- () -> newRoot.addAfterChild(n)
+ () -> newRoot.addChild(n, BEFORE),
+ () -> newRoot.addChild(n, AFTER)
));
return new DiffTree(newRoot, source);
}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffNode.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffNode.java
index 4d16761b1..b7e72e9e6 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffNode.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffNode.java
@@ -1,18 +1,19 @@
package org.variantsync.diffdetective.diff.difftree;
-import org.prop4j.And;
import org.prop4j.Node;
import org.variantsync.diffdetective.diff.DiffLineNumber;
import org.variantsync.diffdetective.diff.Lines;
import org.variantsync.diffdetective.util.Assert;
import org.variantsync.diffdetective.util.StringUtils;
import org.variantsync.diffdetective.util.fide.FixTrueFalse;
+import org.variantsync.diffdetective.variationtree.HasNodeType;
+import org.variantsync.diffdetective.variationtree.VariationNode;
import java.util.*;
-import java.util.function.Function;
import java.util.stream.Collectors;
-import static org.variantsync.diffdetective.util.fide.FormulaUtils.negate;
+import static org.variantsync.diffdetective.diff.difftree.Time.AFTER;
+import static org.variantsync.diffdetective.diff.difftree.Time.BEFORE;
/**
* Implementation of a node in a {@link DiffTree}.
@@ -23,9 +24,7 @@
* DiffNode's store parent and child information to build a graph.
* @author Paul Bittner, Sören Viegener, Benjamin Moosherr
*/
-public class DiffNode {
- private static final short ID_OFFSET = 3;
-
+public class DiffNode implements HasNodeType {
/**
* The diff type of this node, which determines if this node represents
* an inserted, removed, or unchanged element in a diff.
@@ -45,32 +44,35 @@ public class DiffNode {
private List lines;
/**
- * The parent {@link DiffNode} before the edit.
- *
- * Invariant: Iff {@code beforeParent != null} then
- * {@code beforeParent.childOrder.contains(this)}.
- */
- private DiffNode beforeParent;
-
- /**
- * The parent {@link DiffNode} after the edit.
+ * The parents {@link DiffNode} before and after the edit.
+ * This array has to be indexed by {@code Time.ordinal()}
*
- * Invariant: Iff {@code afterParent != null} then
- * {@code afterParent.childOrder.contains(this)}.
+ * Invariant: Iff {@code getParent(time) != null} then
+ * {@code getParent(time).childOrder.contains(this)}.
*/
- private DiffNode afterParent;
+ private DiffNode[] parents = new DiffNode[2];
/**
* We use a list for children to maintain order.
*
* Invariant: Iff {@code childOrder.contains(child)} then
- * {@code child.beforeParent == this || child.afterParent == this}.
+ * {@code child.getParent(BEFORE) == this || child.getParent(AFTER) == this}.
*
* Note that it's explicitly allowed to have
- * {@code child.beforeParent == this && child.afterParent == this}.
+ * {@code child.getParent(BEFORE) == this && child.getParent(AFTER) == this}.
*/
private final List childOrder;
+ /**
+ * Cache for before and after projections.
+ * It stores the projection node at each time so that only one instance of {@link Projection}
+ * per {@link Time} is ever created. This array has to be indexed by {@code Time.ordinal()}
+ *
+ *
This field is required to allow identity tests of {@link Projection}s with {@code ==} instead
+ * of {@link Projection#isSameAs}.
+ */
+ private Projection[] projections = new Projection[2];
+
/**
* Creates a DiffNode with the given parameters.
* @param diffType The type of change made to this node.
@@ -146,14 +148,15 @@ public void addLines(final List lines) {
/**
* Returns the lines in the diff that are represented by this DiffNode.
+ * The returned list is unmodifiable.
*/
- public List getLines() {
- return lines;
+ public List getLabelLines() {
+ return Collections.unmodifiableList(lines);
}
/**
* Returns the lines in the diff that are represented by this DiffNode as a single text.
- * @see DiffNode#getLines
+ * @see DiffNode#getLabelLines
*/
public String getLabel() {
return String.join(StringUtils.LINEBREAK, lines);
@@ -169,87 +172,20 @@ public void setLabel(String label) {
}
/**
- * Gets the first if node in the path following the before parent
- * @return The first if node in the path following the before parent
- */
- public DiffNode getBeforeIfNode() {
- if (isIf()) {
- return this;
- }
- if (isRoot()) {
- return null;
- }
- return beforeParent.getBeforeIfNode();
- }
-
- /**
- * Gets the first if node in the path following the after parent
- * @return The first if node in the path following the after parent
- */
- public DiffNode getAfterIfNode() {
- if (isIf()) {
- return this;
- }
- if (isRoot()) {
- return null;
- }
- return afterParent.getAfterIfNode();
- }
-
- /**
- * Gets the depth of the diff tree following the before parent
- * @return the depth of the diff tree following the before parent
- */
- public int getBeforeAnnotationDepth(){
- if (isRoot()) {
- return 0;
- }
-
- if (isIf()) {
- return beforeParent.getBeforeAnnotationDepth() + 1;
- }
-
- return beforeParent.getBeforeAnnotationDepth();
- }
-
- /**
- * Gets the depth of the diff tree following the after parent
- * @return the depth of the diff tree following the after parent
- */
- public int getAfterAnnotationDepth(){
- if (isRoot()) {
- return 0;
- }
-
- if (isIf()) {
- return afterParent.getAfterAnnotationDepth() + 1;
- }
-
- return afterParent.getAfterAnnotationDepth();
- }
-
- /**
- * Gets the depth of the diff tree following the before parent
- * @return the depth of the diff tree following the before parent
+ * Gets the first {@code if} node in the path from the root to this node at the time
+ * {@code time}.
+ * @return The first {@code if} node in the path to the root at the time {@code time}
*/
- public int getBeforeDepth(){
- if (isRoot()) {
- return 0;
- }
-
- return beforeParent.getBeforeDepth() + 1;
+ public DiffNode getIfNode(Time time) {
+ return projection(time).getIfNode().getBackingNode();
}
/**
- * Gets the depth of the diff tree following the after parent
- * @return the depth of the diff tree following the after parent
+ * Gets the length of the path from the root to this node at the time {@code time}.
+ * @return the depth of the this node in the diff tree at the time {@code time}
*/
- public int getAfterDepth(){
- if (isRoot()) {
- return 0;
- }
-
- return afterParent.getAfterDepth() + 1;
+ public int getDepth(Time time) {
+ return projection(time).getDepth();
}
/**
@@ -257,13 +193,13 @@ public int getAfterDepth(){
* are the very same.
*/
public boolean beforePathEqualsAfterPath() {
- if (beforeParent == afterParent) {
- if (beforeParent == null) {
+ if (getParent(BEFORE) == getParent(AFTER)) {
+ if (getParent(BEFORE) == null) {
// root
return true;
}
- return beforeParent.beforePathEqualsAfterPath();
+ return getParent(BEFORE).beforePathEqualsAfterPath();
}
return false;
@@ -277,63 +213,40 @@ public int getTotalNumberOfChildren() {
}
/**
- * Gets the amount of nodes with diff type REM in the path following the before parent
- * @return the amount of nodes with diff type REM in the path following the before parent
+ * Gets the amount of nodes on the path from the root to this node which only exist at the time
+ * {@code time}.
*/
- public int getRemAmount() {
+ public int getChangeAmount(Time time) {
if (isRoot()) {
return 0;
}
- if (isIf() && diffType.equals(DiffType.REM)) {
- return beforeParent.getRemAmount() + 1;
+ var changeType = DiffType.thatExistsOnlyAt(time);
+
+ if (isIf() && diffType.equals(changeType)) {
+ return getParent(time).getChangeAmount(time) + 1;
}
- if ((isElif() || isElse()) && diffType.equals(DiffType.REM)) {
+ if ((isElif() || isElse()) && diffType.equals(changeType)) {
// if this is a removed elif or else we do not want to count the other branches of
// this annotation
// we thus go up the tree until we get the next if and continue with the parent of it
- return beforeParent.getBeforeIfNode().beforeParent.getRemAmount() + 1;
+ return getParent(time).getIfNode(time).getParent(time).getChangeAmount(time) + 1;
}
- return beforeParent.getRemAmount();
+ return getParent(time).getChangeAmount(time);
}
/**
- * Gets the amount of nodes with diff type ADD in the path following the after parent
- * @return the amount of nodes with diff type ADD in the path following the after parent
+ * Sets the parent at {@code time} checking that this node doesn't currently have a parent.
*/
- public int getAddAmount() {
- if (isRoot()) {
- return 0;
- }
-
- if (isIf() && diffType.equals(DiffType.ADD)) {
- return afterParent.getAddAmount() + 1;
- }
-
- if ((isElif() || isElse()) && diffType.equals(DiffType.ADD)) {
- // if this is an added elif or else we do not want to count the other branches of
- // this annotation
- // we thus go up the tree until we get the next if and continue with the parent of it
- return afterParent.getAfterIfNode().afterParent.getAddAmount() + 1;
- }
-
- return afterParent.getAddAmount();
- }
-
- private void setBeforeParent(final DiffNode newBeforeParent) {
- Assert.assertTrue(beforeParent == null);
- this.beforeParent = newBeforeParent;
- }
-
- private void setAfterParent(final DiffNode newAfterParent) {
- Assert.assertTrue(afterParent == null);
- this.afterParent = newAfterParent;
+ private void setParent(final DiffNode newParent, Time time) {
+ Assert.assertTrue(getParent(time) == null);
+ parents[time.ordinal()] = newParent;
}
/**
- * Adds thus subtree below the given parents.
+ * Adds this subtree below the given parents.
* Inverse of drop.
* @param newBeforeParent Node that should be this node's before parent. May be null.
* @param newAfterParent Node that should be this node's after parent. May be null.
@@ -342,10 +255,10 @@ private void setAfterParent(final DiffNode newAfterParent) {
public boolean addBelow(final DiffNode newBeforeParent, final DiffNode newAfterParent) {
boolean success = false;
if (newBeforeParent != null) {
- success |= newBeforeParent.addBeforeChild(this);
+ success |= newBeforeParent.addChild(this, BEFORE);
}
if (newAfterParent != null) {
- success |= newAfterParent.addAfterChild(this);
+ success |= newAfterParent.addChild(this, AFTER);
}
return success;
}
@@ -355,22 +268,19 @@ public boolean addBelow(final DiffNode newBeforeParent, final DiffNode newAfterP
* Inverse of addBelow.
*/
public void drop() {
- if (beforeParent != null) {
- beforeParent.removeBeforeChild(this);
- }
- if (afterParent != null) {
- afterParent.removeAfterChild(this);
- }
- }
-
- private void dropBeforeChild(final DiffNode child) {
- Assert.assertTrue(child.beforeParent == this);
- child.beforeParent = null;
+ Time.forAll(time -> {
+ if (getParent(time) != null) {
+ getParent(time).removeChild(this, time);
+ }
+ });
}
- private void dropAfterChild(final DiffNode child) {
- Assert.assertTrue(child.afterParent == this);
- child.afterParent = null;
+ /**
+ * Remove this node as the parent of {@code child} but it doesn't change {@link childOrder}.
+ */
+ private void dropChild(final DiffNode child, Time time) {
+ Assert.assertTrue(child.getParent(time) == this);
+ child.parents[time.ordinal()] = null;
}
/**
@@ -382,132 +292,59 @@ public int indexOfChild(final DiffNode child) {
}
/**
- * Adds the given node for the given time at the given index as the child.
- * @param child The new child to add. This node should not be a child of another node for the given time.
- * @param index The index at which the node should be inserted into the children list.
- * @param time The time at which this node should be the parent of this node.
- * For example, if the time is BEFORE, then this node will become the before parent of the given node.
- * @return True iff the insertion was successful. False iff the child could not be added.
- * @see DiffNode#insertBeforeChild
- * @see DiffNode#insertAfterChild
- */
- public boolean insertChildAt(final DiffNode child, int index, Time time) {
- return switch (time) {
- case BEFORE -> insertBeforeChild(child, index);
- case AFTER -> insertAfterChild(child, index);
- };
- }
-
- /**
- * The same as {@link DiffNode#insertChildAt} but the time fixed to BEFORE.
- */
- public boolean insertBeforeChild(final DiffNode child, int index) {
- if (!child.isAdd()) {
- if (!isChild(child)) {
- childOrder.add(index, child);
- }
- child.setBeforeParent(this);
- return true;
- }
- return false;
- }
-
- /**
- * The same as {@link DiffNode#insertChildAt} but the time fixed to AFTER.
+ * Insert {@code child} as child at the time {@code time} at the position {@code index}.
*/
- public boolean insertAfterChild(final DiffNode child, int index) {
- if (!child.isRem()) {
+ public boolean insertChild(final DiffNode child, int index, Time time) {
+ if (child.getDiffType().existsAtTime(time)) {
if (!isChild(child)) {
childOrder.add(index, child);
}
- child.setAfterParent(this);
+ child.setParent(this, time);
return true;
}
return false;
}
/**
- * The same as {@link DiffNode#insertBeforeChild} but puts the node at the end of the children
+ * The same as {@link DiffNode#insertChild} but puts the node at the end of the children
* list instead of inserting it at a specific index.
*/
- public boolean addBeforeChild(final DiffNode child) {
- if (!child.isAdd()) {
- if (child.beforeParent != null) {
- throw new IllegalArgumentException("Given child " + child + " already has a before parent (" + child.beforeParent + ")!");
+ public boolean addChild(final DiffNode child, Time time) {
+ if (child.getDiffType().existsAtTime(time)) {
+ if (child.getParent(time) != null) {
+ throw new IllegalArgumentException("Given child " + child + " already has a before parent (" + child.getParent(time) + ")!");
}
if (!isChild(child)) {
childOrder.add(child);
}
- child.setBeforeParent(this);
+ child.setParent(this, time);
return true;
}
return false;
}
/**
- * The same as {@link DiffNode#insertAfterChild} but puts the node at the end of the children
- * list instead of inserting it at a specific index.
+ * Adds all given nodes at the time {@code time} as children using {@link DiffNode#addChild}.
+ * @param children Nodes to add as children.
+ * @param time whether to add {@code children} before or after the edit
*/
- public boolean addAfterChild(final DiffNode child) {
- if (!child.isRem()) {
- if (child.afterParent != null) {
- throw new IllegalArgumentException("Given child " + child + " already has an after parent (" + child.afterParent + ")!");
- }
-
- if (!isChild(child)) {
- childOrder.add(child);
- }
- child.setAfterParent(this);
- return true;
+ public void addChildren(final Collection children, Time time) {
+ for (final DiffNode child : children) {
+ addChild(child, time);
}
- return false;
}
/**
- * Adds all given nodes as before children using {@link DiffNode#addBeforeChild}.
- * @param beforeChildren Nodes to add as children before the edit.
+ * Removes the given node from this node's children before or after the edit.
+ * The node might still remain a child after or before the edit.
+ * @param child the child to remove
+ * @param time whether {@code child} should be removed before or after the edit
+ * @return True iff the child was removed, false iff it's not a child at {@code time}.
*/
- public void addBeforeChildren(final Collection beforeChildren) {
- for (final DiffNode beforeChild : beforeChildren) {
- addBeforeChild(beforeChild);
- }
- }
-
- /**
- * Adds all given nodes as after children using {@link DiffNode#addAfterChild}.
- * @param afterChildren Nodes to add as children after the edit.
- */
- public void addAfterChildren(final Collection afterChildren) {
- for (final DiffNode afterChild : afterChildren) {
- addAfterChild(afterChild);
- }
- }
-
- /**
- * Removes the given node from this node's children before the edit.
- * The node might still remain a child after the edit.
- * @param child The child to remove before the edit.
- * @return True iff the child was removed, false iff it was no before child.
- */
- public boolean removeBeforeChild(final DiffNode child) {
- if (isBeforeChild(child)) {
- dropBeforeChild(child);
- removeFromCache(child);
- return true;
- }
- return false;
- }
-
- /**
- * Removes the given node from this node's children after the edit.
- * The node might still remain a child before the edit.
- * @param child The child to remove after the edit.
- * @return True iff the child was removed, false iff it was no after child.
- */
- public boolean removeAfterChild(final DiffNode child) {
- if (isAfterChild(child)) {
- dropAfterChild(child);
+ public boolean removeChild(final DiffNode child, Time time) {
+ if (isChild(child, time)) {
+ dropChild(child, time);
removeFromCache(child);
return true;
}
@@ -521,53 +358,29 @@ public boolean removeAfterChild(final DiffNode child) {
*/
public void removeChildren(final Collection childrenToRemove) {
for (final DiffNode childToRemove : childrenToRemove) {
- removeBeforeChild(childToRemove);
- removeAfterChild(childToRemove);
+ Time.forAll(time -> removeChild(childToRemove, time));
}
}
/**
- * Removes all children before the edit.
- * Afterwards, this node will have no before children.
- * @return All removed before children.
+ * Removes all children before or after the edit.
+ * Afterwards, this node will have no children at the given time.
+ * @param time whether to remove all children before or after the edit
+ * @return All removed children.
*/
- public List removeBeforeChildren() {
+ public List removeChildren(Time time) {
final List orphans = new ArrayList<>();
// Note that the following method call can't be written using a foreach loop reusing
// {@code removeBeforeChild} because lists can't be modified during traversal.
childOrder.removeIf(child -> {
- if (!isBeforeChild(child)) {
- return false;
- }
-
- orphans.add(child);
- dropBeforeChild(child);
- return !isAfterChild(child);
- });
-
- return orphans;
- }
-
-
- /**
- * Removes all children after the edit.
- * Afterwards, this node will have no after children.
- * @return All removed after children.
- */
- public List removeAfterChildren() {
- final List orphans = new ArrayList<>();
-
- // Note that the following method call can't be written using a foreach loop reusing
- // {@code removeAfterChild} because lists can't be modified during traversal.
- childOrder.removeIf(child -> {
- if (!isAfterChild(child)) {
+ if (!isChild(child, time)) {
return false;
}
orphans.add(child);
- dropAfterChild(child);
- return !isBeforeChild(child);
+ dropChild(child, time);
+ return !isChild(child, time.other());
});
return orphans;
@@ -593,22 +406,14 @@ private void removeFromCache(final DiffNode child) {
* @param other The node whose children should be stolen.
*/
public void stealChildrenOf(final DiffNode other) {
- addBeforeChildren(other.removeBeforeChildren());
- addAfterChildren(other.removeAfterChildren());
- }
-
- /**
- * Returns the parent of this node before the edit.
- */
- public DiffNode getBeforeParent() {
- return beforeParent;
+ Time.forAll(time -> addChildren(other.removeChildren(time), time));
}
/**
- * Returns the parent of this node after the edit.
+ * Returns the parent of this node before or after the edit.
*/
- public DiffNode getAfterParent() {
- return afterParent;
+ public DiffNode getParent(Time time) {
+ return parents[time.ordinal()];
}
/**
@@ -643,19 +448,20 @@ public Lines getLinesInDiff() {
}
/**
- * Returns the range of line numbers of this node's corresponding source code before the edit.
- * @see DiffLineNumber#rangeBeforeEdit
+ * Returns the range of line numbers of this node's corresponding source code before or after
+ * the edit.
*/
- public Lines getLinesBeforeEdit() {
- return DiffLineNumber.rangeBeforeEdit(from, to);
+ public Lines getLinesAtTime(Time time) {
+ return DiffLineNumber.rangeAtTime(from, to, time);
}
/**
- * Returns the range of line numbers of this node's corresponding source code after the edit.
- * @see DiffLineNumber#rangeAfterEdit
+ * Returns the range of line numbers of this node's corresponding source code before or after
+ * the edit.
*/
- public Lines getLinesAfterEdit() {
- return DiffLineNumber.rangeAfterEdit(from, to);
+ public void setLinesAtTime(Lines lines, Time time) {
+ from = from.withLineNumberAtTime(lines.getFromInclusive(), time);
+ to = to.withLineNumberAtTime(lines.getToExclusive(), time);
}
/**
@@ -689,197 +495,35 @@ public List getAllChildren() {
* The feature mapping of {@link NodeType#ELSE} and {@link NodeType#ELIF} nodes is determined by all formulas in the respective if-elif-else chain.
* The feature mapping of an {@link NodeType#ARTIFACT artifact} node is the feature mapping of its parent.
* See Equation (1) in our paper (+ its extension to time for variation tree diffs described in Section 3.1).
- * @param parentOf Function that returns the parent of a node.
- * This function decides whether the before or after parent should be visited.
- * It thus decides whether to compute the feature mapping before or after the edit.
- * @return The feature mapping of this node for the given parent edges.
- * The returned list represents a conjunction (i.e., all clauses should be combined with boolean AND).
- */
- private List getFeatureMappingClauses(final Function parentOf) {
- final DiffNode parent = parentOf.apply(this);
-
- if (isElse() || isElif()) {
- List and = new ArrayList<>();
-
- if (isElif()) {
- and.add(getDirectFeatureMapping());
- }
-
- // Negate all previous cases
- DiffNode ancestor = parent;
- while (!ancestor.isIf()) {
- if (ancestor.isElif()) {
- and.add(negate(ancestor.getDirectFeatureMapping()));
- } else {
- throw new RuntimeException("Expected If or Elif above Else or Elif but got " + ancestor.nodeType + " from " + ancestor);
- // Assert.assertTrue(ancestor.isArtifact());
- }
- ancestor = parentOf.apply(ancestor);
- }
- and.add(negate(ancestor.getDirectFeatureMapping()));
-
- return and;
- } else if (isArtifact()) {
- return parent.getFeatureMappingClauses(parentOf);
- }
-
- return List.of(getDirectFeatureMapping());
- }
-
- /**
- * Same as {@link DiffNode#getFeatureMappingClauses} but conjuncts the returned clauses to a single formula.
- */
- private Node getFeatureMapping(Function parentOf) {
- final List fmClauses = getFeatureMappingClauses(parentOf);
- if (fmClauses.size() == 1) {
- return fmClauses.get(0);
- }
- return new And(fmClauses);
- }
-
- /**
- * Returns the full feature mapping formula of this node before the edit.
- * The feature mapping of an {@link NodeType#IF} node is its {@link DiffNode#getDirectFeatureMapping direct feature mapping}.
- * The feature mapping of {@link NodeType#ELSE} and {@link NodeType#ELIF} nodes is determined by all formulas in the respective if-elif-else chain.
- * The feature mapping of an {@link NodeType#ARTIFACT artifact} node is the feature mapping of its parent.
- * See Equation (1) in our paper (+ its extension to time for variation tree diffs described in Section 3.1).
- * @return The feature mapping of this node for the given parent edges.
- */
- public Node getBeforeFeatureMapping() {
- return getFeatureMapping(DiffNode::getBeforeParent);
- }
-
- /**
- * Returns the full feature mapping formula of this node after the edit.
- * The feature mapping of an {@link NodeType#IF} node is its {@link DiffNode#getDirectFeatureMapping direct feature mapping}.
- * The feature mapping of {@link NodeType#ELSE} and {@link NodeType#ELIF} nodes is determined by all formulas in the respective if-elif-else chain.
- * The feature mapping of an {@link NodeType#ARTIFACT artifact} node is the feature mapping of its parent.
- * See Equation (1) in our paper (+ its extension to time for variation tree diffs described in Section 3.1).
+ * @param time Whether to return the feature mapping clauses before or after the edit.
* @return The feature mapping of this node for the given parent edges.
*/
- public Node getAfterFeatureMapping() {
- return getFeatureMapping(DiffNode::getAfterParent);
- }
-
- /**
- * Depending on the given time, returns either the
- * {@link DiffNode#getBeforeFeatureMapping() before feature mapping} or
- * {@link DiffNode#getAfterFeatureMapping() after feature mapping}.
- */
public Node getFeatureMapping(Time time) {
- return time.match(
- this::getBeforeFeatureMapping,
- this::getAfterFeatureMapping
- );
- }
-
- /**
- * Returns the presence condition of this node for the respective time.
- * See Equation (2) in our paper (+ its extension to time for variation tree diffs described in Section 3.1).
- * @param parentOf Function that returns the parent of a node.
- * This function decides whether the before or after parent should be visited.
- * It thus decides whether to compute the feature mapping before or after the edit.
- * @return The presence condition of this node for the given parent edges.
- * The returned list represents a conjunction (i.e., all clauses should be combined with boolean AND).
- */
- private List getPresenceCondition(Function parentOf) {
- final DiffNode parent = parentOf.apply(this);
-
- if (isElse() || isElif()) {
- final List clauses = new ArrayList<>(getFeatureMappingClauses(parentOf));
-
- // Find corresponding if
- DiffNode correspondingIf = parent;
- while (!correspondingIf.isIf()) {
- correspondingIf = parentOf.apply(correspondingIf);
- }
-
- // If this elif-else-chain was again nested in another annotation, add its pc.
- final DiffNode outerNesting = parentOf.apply(correspondingIf);
- if (outerNesting != null) {
- clauses.addAll(outerNesting.getPresenceCondition(parentOf));
- }
-
- return clauses;
- } else if (isArtifact()) {
- return parent.getPresenceCondition(parentOf);
- }
-
- // this is mapping or root
- final List clauses;
- if (parent == null) {
- clauses = new ArrayList<>(1);
- } else {
- clauses = parent.getPresenceCondition(parentOf);
- }
- clauses.add(featureMapping);
- return clauses;
+ return projection(time).getFeatureMapping();
}
/**
- * Returns the presence condition of this node before the edit.
+ * Returns the presence condition of this node before or after the edit.
* See Equation (2) in our paper (+ its extension to time for variation tree diffs described in Section 3.1).
+ * @param time Whether to return the presence condition before or after the edit.
* @return The presence condition of this node for the given parent edges.
*/
- public Node getBeforePresenceCondition() {
- if (diffType.existsBefore()) {
- return new And(getPresenceCondition(DiffNode::getBeforeParent));
- } else {
- throw new WrongTimeException("Cannot determine before PC of added node " + this);
- }
- }
-
- /**
- * Returns the presence condition of this node after the edit.
- * See Equation (2) in our paper (+ its extension to time for variation tree diffs described in Section 3.1).
- * @return The presence condition of this node for the given parent edges.
- */
- public Node getAfterPresenceCondition() {
- if (diffType.existsAfter()) {
- return new And(getPresenceCondition(DiffNode::getAfterParent));
- } else {
- throw new WrongTimeException("Cannot determine after PC of removed node " + this);
- }
- }
-
- /**
- * Depending on the given time, returns either the
- * {@link DiffNode#getBeforePresenceCondition() before presence condition} or
- * {@link DiffNode#getAfterPresenceCondition() after presence condition}.
- */
public Node getPresenceCondition(Time time) {
- return time.match(
- this::getBeforePresenceCondition,
- this::getAfterPresenceCondition
- );
- }
-
- /**
- * Returns true iff this node is the before parent of the given node.
- */
- public boolean isBeforeChild(DiffNode child) {
- return child.beforeParent == this;
- }
-
- /**
- * Returns true iff this node is the after parent of the given node.
- */
- public boolean isAfterChild(DiffNode child) {
- return child.afterParent == this;
+ return projection(time).getPresenceCondition();
}
/**
* Returns true iff this node is the before or after parent of the given node.
*/
public boolean isChild(DiffNode child) {
- return isBeforeChild(child) || isAfterChild(child);
+ return isChild(child, BEFORE) || isChild(child, AFTER);
}
/**
* Returns true iff this node is the parent of the given node at the given time.
*/
public boolean isChild(DiffNode child, Time time) {
- return time.match(isBeforeChild(child), isAfterChild(child));
+ return child.getParent(time) == this;
}
/**
@@ -920,50 +564,16 @@ public DiffType getDiffType() {
return this.diffType;
}
- /**
- * Returns true if this node represents an ELIF annotation.
- * @see NodeType#ELIF
- */
- public boolean isElif() {
- return this.nodeType.equals(NodeType.ELIF);
- }
-
- /**
- * Returns true if this node represents a conditional annotation.
- * @see NodeType#IF
- */
- public boolean isIf() {
- return this.nodeType.equals(NodeType.IF);
- }
-
- /**
- * Returns true if this node is an artifact node.
- * @see NodeType#ARTIFACT
- */
- public boolean isArtifact() {
- return this.nodeType.equals(NodeType.ARTIFACT);
- }
-
- /**
- * Returns true if this node represents an ELSE annotation.
- * @see NodeType#ELSE
- */
- public boolean isElse() {
- return this.nodeType.equals(NodeType.ELSE);
+ @Override
+ public NodeType getNodeType() {
+ return nodeType;
}
/**
* Returns true if this node is a root node (has no parents).
*/
public boolean isRoot() {
- return getBeforeParent() == null && getAfterParent() == null;
- }
-
- /**
- * Returns {@link NodeType#isAnnotation()} for this node's {@link DiffNode#nodeType}.
- */
- public boolean isAnnotation() {
- return this.nodeType.isAnnotation();
+ return getParent(BEFORE) == null && getParent(AFTER) == null;
}
/**
@@ -977,15 +587,19 @@ public boolean isAnnotation() {
*/
public int getID() {
// Add one to ensure invalid (negative) line numbers don't cause issues.
- int lineNumber = 1 + from.inDiff();
- Assert.assertTrue((lineNumber << 2*ID_OFFSET) >> 2*ID_OFFSET == lineNumber);
+ final int lineNumber = 1 + from.inDiff();
+
+ final int usedBitCount = DiffType.getRequiredBitCount() + NodeType.getRequiredBitCount();
+ Assert.assertTrue((lineNumber << usedBitCount) >> usedBitCount == lineNumber);
int id;
id = lineNumber;
- id <<= ID_OFFSET;
- id += diffType.ordinal();
- id <<= ID_OFFSET;
- id += nodeType.ordinal();
+
+ id <<= DiffType.getRequiredBitCount();
+ id |= diffType.ordinal();
+
+ id <<= NodeType.getRequiredBitCount();
+ id |= nodeType.ordinal();
return id;
}
@@ -997,12 +611,16 @@ public int getID() {
* @param label The label the node should have.
* @return The reconstructed DiffNode.
*/
- public static DiffNode fromID(final int id, String label) {
- final int lowestBitsMask = (1 << ID_OFFSET) - 1;
+ public static DiffNode fromID(int id, String label) {
+ final int nodeTypeBitmask = (1 << NodeType.getRequiredBitCount()) - 1;
+ final int nodeTypeOrdinal = id & nodeTypeBitmask;
+ id >>= NodeType.getRequiredBitCount();
- final int nodeTypeOrdinal = id & lowestBitsMask;
- final int diffTypeOrdinal = (id >> ID_OFFSET) & lowestBitsMask;
- final int fromInDiff = (id >> (2*ID_OFFSET)) - 1;
+ final int diffTypeBitmask = (1 << DiffType.getRequiredBitCount()) - 1;
+ final int diffTypeOrdinal = id & diffTypeBitmask;
+ id >>= DiffType.getRequiredBitCount();
+
+ final int fromInDiff = id - 1;
var nodeType = NodeType.values()[nodeTypeOrdinal];
return new DiffNode(
@@ -1026,34 +644,32 @@ public void assertConsistency() {
// check consistency of children lists and edges
for (final DiffNode c : childOrder) {
Assert.assertTrue(isChild(c), () -> "Child " + c + " of " + this + " is neither a before nor an after child!");
- if (c.getBeforeParent() != null) {
- Assert.assertTrue(c.getBeforeParent().isBeforeChild(c), () -> "The beforeParent of " + c + " doesn't contain that node as child");
- }
- if (c.getAfterParent() != null) {
- Assert.assertTrue(c.getAfterParent().isAfterChild(c), () -> "The afterParent of " + c + " doesn't contain that node as child");
- }
+ Time.forAll(time -> {
+ if (c.getParent(time) != null) {
+ Assert.assertTrue(c.getParent(time).isChild(c, time), () -> "The parent " + time.toString().toLowerCase() + " the edit of " + c + " doesn't contain that node as child");
+ }
+ });
}
// a node with exactly one parent was edited
- if (beforeParent == null && afterParent != null) {
+ if (getParent(BEFORE) == null && getParent(AFTER) != null) {
Assert.assertTrue(isAdd());
}
- if (beforeParent != null && afterParent == null) {
+ if (getParent(BEFORE) != null && getParent(AFTER) == null) {
Assert.assertTrue(isRem());
}
// a node with exactly two parents was not edited
- if (beforeParent != null && afterParent != null) {
+ if (getParent(BEFORE) != null && getParent(AFTER) != null) {
Assert.assertTrue(isNon());
}
// Else and Elif nodes have an If or Elif as parent.
if (this.isElse() || this.isElif()) {
- if (beforeParent != null) {
- Assert.assertTrue(beforeParent.isIf() || beforeParent.isElif(), "Before parent " + beforeParent + " of " + this + " is neither IF nor ELIF!");
- }
- if (afterParent != null) {
- Assert.assertTrue(afterParent.isIf() || afterParent.isElif(), "After parent " + afterParent + " of " + this + " is neither IF nor ELIF!");
- }
+ Time.forAll(time -> {
+ if (getParent(time) != null) {
+ Assert.assertTrue(getParent(time).isIf() || getParent(time).isElif(), time + " parent " + getParent(time) + " of " + this + " is neither IF nor ELIF!");
+ }
+ });
}
// Only if and elif nodes have a formula
@@ -1112,6 +728,51 @@ public String toTextDiff() {
return diff.toString();
}
+ /**
+ * Returns a view of this {@code DiffNode} as a variation node at the time {@code time}.
+ *
+ *
See the {@code project} function in section 3.1 of
+ *
+ * our paper.
+ */
+ public Projection projection(Time time) {
+ Assert.assertTrue(getDiffType().existsAtTime(time));
+
+ if (projections[time.ordinal()] == null) {
+ projections[time.ordinal()] = new Projection(this, time);
+ }
+
+ return projections[time.ordinal()];
+ }
+
+ /**
+ * Transforms a {@code VariationNode} into a {@code DiffNode} by diffing {@code variationNode}
+ * to itself.
+ *
+ * This is the inverse of {@link projection} iff the original {@link DiffNode} wasn't modified
+ * (all node had a {@link getDiffType diff type} of {@link DiffType#NON}).
+ */
+ public static > DiffNode unchanged(VariationNode variationNode) {
+ int from = variationNode.getLineRange().getFromInclusive();
+ int to = variationNode.getLineRange().getToExclusive();
+
+ var diffNode = new DiffNode(
+ DiffType.NON,
+ variationNode.getNodeType(),
+ new DiffLineNumber(from, from, from),
+ new DiffLineNumber(to, to, to),
+ variationNode.getDirectFeatureMapping(),
+ variationNode.getLabelLines()
+ );
+
+ for (var variationChildNode : variationNode.getChildren()) {
+ var diffChildNode = unchanged(variationChildNode);
+ Time.forAll(time -> diffNode.addChild(diffChildNode, time));
+ }
+
+ return diffNode;
+ }
+
@Override
public String toString() {
String s;
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffTree.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffTree.java
index ed27cc7cd..d9e0b47b9 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffTree.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffTree.java
@@ -31,6 +31,8 @@
import java.util.function.Consumer;
import java.util.function.Predicate;
+import static org.variantsync.diffdetective.diff.difftree.Time.AFTER;
+import static org.variantsync.diffdetective.diff.difftree.Time.BEFORE;
import static org.variantsync.functjonal.Functjonal.when;
/**
@@ -338,17 +340,13 @@ public boolean isEmpty() {
public void removeNode(DiffNode node) {
Assert.assertTrue(node != root);
- final DiffNode beforeParent = node.getBeforeParent();
- if (beforeParent != null) {
- beforeParent.removeBeforeChild(node);
- beforeParent.addBeforeChildren(node.removeBeforeChildren());
- }
-
- final DiffNode afterParent = node.getAfterParent();
- if (afterParent != null) {
- afterParent.removeAfterChild(node);
- afterParent.addAfterChildren(node.removeAfterChildren());
- }
+ Time.forAll(time -> {
+ final DiffNode parent = node.getParent(time);
+ if (parent != null) {
+ parent.removeChild(node, time);
+ parent.addChildren(node.removeChildren(time), time);
+ }
+ });
}
/**
@@ -394,8 +392,8 @@ public boolean test(final DiffNode d) {
// The stranger is now known.
cache.putIfAbsent(id, VisitStatus.VISITED);
- final DiffNode b = d.getBeforeParent();
- final DiffNode a = d.getAfterParent();
+ final DiffNode b = d.getParent(BEFORE);
+ final DiffNode a = d.getParent(AFTER);
if (a == null && b == null) {
// We found a second root node which is invalid.
yield false;
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffType.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffType.java
index 54f8dba7c..3d9f83271 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffType.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/DiffType.java
@@ -142,4 +142,11 @@ public boolean existsAfter() {
public boolean existsAtTime(Time time) {
return (time == Time.BEFORE && this != ADD) || (time == Time.AFTER && this != REM);
}
+
+ /**
+ * Returns the number of bits required for storing {@link ordinal}.
+ */
+ public static int getRequiredBitCount() {
+ return 3;
+ }
}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/NodeType.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/NodeType.java
index 66a6feb85..9715a07e7 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/NodeType.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/NodeType.java
@@ -47,4 +47,11 @@ public static NodeType fromName(final String name) {
throw new IllegalArgumentException("Given string \"" + name + "\" is not the name of a NodeType.");
}
+
+ /**
+ * Returns the number of bits required for storing {@link ordinal}.
+ */
+ public static int getRequiredBitCount() {
+ return 3;
+ }
}
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/Projection.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/Projection.java
new file mode 100644
index 000000000..e372c973b
--- /dev/null
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/Projection.java
@@ -0,0 +1,168 @@
+package org.variantsync.diffdetective.diff.difftree;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.prop4j.Node;
+import org.variantsync.diffdetective.diff.Lines;
+import org.variantsync.diffdetective.variationtree.VariationNode;
+import org.variantsync.functjonal.list.FilteredMappedListView;
+
+/**
+ * A view of a {@link DiffNode} as variation node at a specific time.
+ *
+ *
See the {@code project} function in section 3.1 of
+ *
+ * our paper.
+ *
+ *
This class has to be instantiated using {@link DiffNode#projection}.
+ *
+ *
Implementation note: It's ensured that identity can be checked using {@code ==}. This
+ * prevents unexpected behaviour if some code uses {@code ==} instead of {@link isSameAs} as
+ * documented in {@link VariationNode}. Although this is currently guaranteed by all
+ * implementations of {@link VariationNode} it should still be considered a bug if {@code ==} is
+ * used to check for identity ({@code null} checks are still allowed).
+ *
+ * @see DiffNode#projection
+ */
+public class Projection extends VariationNode {
+ private DiffNode backingNode;
+ private Time time;
+
+ /**
+ * Creates a new projection of a {@link DiffNode}.
+ * Only {@link DiffNode} is allowed to call this method to guarantee the identity of this class
+ * (see above for details). If you want to get the projection of a {@link DiffNode} use
+ * {@link DiffNode#projection}.
+ *
+ * @param backingNode the {@link DiffNode} which should be projected
+ * @param time which projection this should be
+ */
+ Projection(DiffNode backingNode, Time time) {
+ this.backingNode = backingNode;
+ this.time = time;
+ }
+
+ public Time getTime() {
+ return time;
+ }
+
+ public DiffNode getBackingNode() {
+ return backingNode;
+ }
+
+ @Override
+ public Projection upCast() {
+ return this;
+ }
+
+
+ @Override
+ public NodeType getNodeType() {
+ return getBackingNode().nodeType;
+ }
+
+ @Override
+ public List getLabelLines() {
+ return getBackingNode().getLabelLines();
+ }
+
+ @Override
+ public Lines getLineRange() {
+ return getBackingNode().getLinesAtTime(time);
+ }
+
+ @Override
+ public void setLineRange(Lines lineRange) {
+ getBackingNode().setLinesAtTime(lineRange, time);
+ }
+
+ @Override
+ public Projection getParent() {
+ var parent = getBackingNode().getParent(time);
+
+ if (parent == null) {
+ return null;
+ } else {
+ return parent.projection(time);
+ }
+ }
+
+
+ @Override
+ public List getChildren() {
+ return FilteredMappedListView.filterMap(
+ getBackingNode().getChildOrder(),
+ (child) -> {
+ if (getBackingNode().isChild(child, time)) {
+ return Optional.of(child.projection(time));
+ } else {
+ return Optional.empty();
+ }
+ }
+ );
+ }
+
+ @Override
+ public void addChild(final Projection child) {
+ getBackingNode().addChild(child.getBackingNode(), time);
+ }
+
+ @Override
+ public void insertChild(final Projection child, int index) {
+ // The method `DiffNode.addChild` can't be used here because `index` has a different
+ // meaning: For `DiffNode.addChild` it counts all children, before and after, but here
+ // it only counts children at `time`.
+
+ var iterator = getBackingNode().getChildOrder().listIterator();
+ for (int i = 0; i < index; ) {
+ if (!iterator.hasNext()) {
+ throw new IllegalArgumentException();
+ }
+
+ if (iterator.next().getDiffType().existsAtTime(time)) {
+ ++i;
+ }
+ }
+
+ getBackingNode().insertChild(child.getBackingNode(), iterator.nextIndex(), time);
+ }
+
+ @Override
+ public boolean removeChild(final Projection child) {
+ return getBackingNode().removeChild(child.getBackingNode(), time);
+ }
+
+ @Override
+ public void removeAllChildren() {
+ getBackingNode().removeChildren(time);
+ }
+
+ @Override
+ public Node getDirectFeatureMapping() {
+ return getBackingNode().getDirectFeatureMapping();
+ }
+
+ @Override
+ public int getID() {
+ return getBackingNode().getID();
+ }
+
+ @Override
+ public boolean isSameAs(Projection other) {
+ if (other != null && getClass() == other.getClass()) {
+ Projection otherProjection = (Projection) other;
+ return time.equals(otherProjection.time) && getBackingNode() == otherProjection.getBackingNode();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Projection projection = (Projection) o;
+ return time.equals(projection.time) && getBackingNode().equals(projection.getBackingNode());
+ }
+};
diff --git a/src/main/java/org/variantsync/diffdetective/diff/difftree/Time.java b/src/main/java/org/variantsync/diffdetective/diff/difftree/Time.java
index 98306052e..21a18f1b9 100644
--- a/src/main/java/org/variantsync/diffdetective/diff/difftree/Time.java
+++ b/src/main/java/org/variantsync/diffdetective/diff/difftree/Time.java
@@ -15,7 +15,7 @@ public enum Time {
* Invoke the given function for each time value (i.e., each value in this enum).
* @param f callback
*/
- public static void forall(final Consumer