Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
207aa53
delete deprecated ViewTest
pmbittner Oct 13, 2025
ed7ff62
feat: Label::observablyEqual
pmbittner Oct 13, 2025
dedd98b
feat: DiffNode::isSameAsIgnoringLineNumbers
pmbittner Oct 13, 2025
312ff2e
feat: VariationDiff:isSameAsIgnoringLineNumbers
pmbittner Oct 13, 2025
66e0970
fix: some typos in VariationTreeNode documentation
pmbittner Oct 13, 2025
99f9df5
feat: specification for Configure
pmbittner Oct 13, 2025
a51ad75
docs: refine documentation on Relevance::computeViewNodesCheckAll
pmbittner Oct 13, 2025
9f2c50d
fix: Configure falsely removed ELSE and ELIF nodes
pmbittner Oct 13, 2025
48f93b4
test: new tests for Views
pmbittner Oct 13, 2025
8c63c20
refactor: extract Transformer super interface ...
pmbittner Oct 20, 2025
9dfbfb2
VariationTreeTransformer interface
pmbittner Oct 20, 2025
fa62e30
VariationTreeNode::setFormula
pmbittner Oct 20, 2025
6b2bcb0
feat: traversing variation trees in post-order
pmbittner Oct 20, 2025
a48b4fd
feat: DiffNode::stealChildrenOf at time
pmbittner Oct 20, 2025
fd0d96e
fix: VariationNode::stealChildrenOf
pmbittner Oct 20, 2025
1939f50
feat: EliminateEmptyAlternatives transformer
pmbittner Oct 20, 2025
7f70843
fix: javadoc error in Transformer.java
pmbittner Oct 20, 2025
db2fccf
fix: javadoc error in EliminateEmptyAlternatives
pmbittner Oct 20, 2025
7daeff0
fix: VariationNode::stealChildrenOf once again
pmbittner Oct 24, 2025
2c6d651
Simplify Assertion in Configure
pmbittner Oct 26, 2025
8197880
remove boilerplate Variation(Tree|Diff)Transformer
pmbittner Oct 26, 2025
7e0365b
DiffNode::makeUnchanged()
pmbittner Oct 27, 2025
ddd4289
feat: HideSomeChanges
pmbittner Oct 27, 2025
551a1b6
fix: bug + alignment in DiffNode::makeUnchanged
pmbittner Oct 28, 2025
02804e5
feat: IndexFormat
pmbittner Oct 28, 2025
6a2c992
support IndexFormat in GUI
pmbittner Oct 28, 2025
75be10f
StringUtils::getLeadingWhitespace
pmbittner Nov 3, 2025
1d227f5
fix: EliminateEmptyAlt. making inconsistent labels
pmbittner Nov 3, 2025
02ae9a3
Merge branch 'develop' into fix-configure
pmbittner Nov 5, 2025
a991942
fix: ConfigureSpec vs new Source interface
pmbittner Nov 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.Arrays;
import java.util.List;

import org.variantsync.diffdetective.variation.diff.transform.Transformer;
import org.variantsync.diffdetective.variation.diff.transform.VariationDiffTransformer;
import org.variantsync.diffdetective.variation.DiffLinesLabel;

Expand All @@ -20,7 +21,7 @@ public PreprocessingAnalysis(VariationDiffTransformer<DiffLinesLabel>... preproc

@Override
public boolean analyzeVariationDiff(Analysis analysis) {
VariationDiffTransformer.apply(preprocessors, analysis.getCurrentVariationDiff());
Transformer.apply(preprocessors, analysis.getCurrentVariationDiff());
analysis.getCurrentVariationDiff().assertConsistency();
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.variantsync.diffdetective.variation.diff.render.RenderOptions;
import org.variantsync.diffdetective.variation.diff.render.VariationDiffRenderer;
import org.variantsync.diffdetective.variation.diff.serialize.nodeformat.MappingsDiffNodeFormat;
import org.variantsync.diffdetective.variation.diff.transform.Transformer;
import org.variantsync.diffdetective.variation.diff.transform.VariationDiffTransformer;

import java.io.IOException;
Expand Down Expand Up @@ -165,7 +166,7 @@ public static void main(String[] args) throws IOException {
final List<VariationDiffTransformer<DiffLinesLabel>> transform = VariationDiffMiner.Postprocessing(repository);
final PatchDiff patch = VariationDiffParser.parsePatch(repository, file, commit);
Assert.assertNotNull(patch != null);
VariationDiffTransformer.apply(transform, patch.getVariationDiff());
Transformer.apply(transform, patch.getVariationDiff());
renderer.render(patch, Path.of("render", repoName), RENDER_OPTIONS_TO_USE);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.variantsync.diffdetective.variation.diff.filter.ExplainedFilter;
import org.variantsync.diffdetective.variation.diff.filter.TaggedPredicate;
import org.variantsync.diffdetective.variation.diff.transform.CutNonEditedSubtrees;
import org.variantsync.diffdetective.variation.diff.transform.Transformer;
import org.variantsync.diffdetective.variation.diff.transform.VariationDiffTransformer;

import java.util.List;
Expand Down Expand Up @@ -66,7 +67,7 @@ public static <L extends Label> Postprocessor<L> Default() {
public Result<L> postprocess(final List<VariationDiff<L>> frequentSubgraphs) {
final List<VariationDiff<L>> processedTrees = frequentSubgraphs.stream()
.filter(filters)
.peek(tree -> VariationDiffTransformer.apply(transformers, tree))
.peek(tree -> Transformer.apply(transformers, tree))
.toList();

final Map<String, Integer> filterCounts = new ExplainedFilterSummary(filters).snapshot();
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/org/variantsync/diffdetective/variation/Label.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,18 @@ default Label withTimeDependentStateFrom(Label other, Time time) {
* Creates a deep copy of this label.
*/
Label clone();

/**
* Tests whether two labels are observably equal.
* Observably equal means that the two labels
* cannot be distinguished by using methods from this interface.
* The given labels might indeed be of different classes or might
* be considered unequal regarding {@link Object#equals(Object)}.
*/
static boolean observablyEqual(Label a, Label b) {
if (a == null) return false;
if (a == b) return true;
return a.getLines().equals(b.getLines())
&& a.getTrailingLines().equals(b.getTrailingLines());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -367,12 +367,21 @@ public List<DiffNode<L>> removeChildren(Time time) {
}

/**
* Removes all children from the given node and adds them as children to this node at the respective times.
* Removes all children from the given node and adds them as children to this node at the given time.
* The given node will have no children afterwards at the given time.
* @param other The node whose children should be stolen for the given time.
*/
public void stealChildrenOf(Time time, final DiffNode<L> other) {
addChildren(other.removeChildren(time), time);
}

/**
* Removes all children from the given node and adds them as children to this node (at all times).
* The given node will have no children afterwards.
* @param other The node whose children should be stolen.
*/
public void stealChildrenOf(final DiffNode<L> other) {
Time.forAll(time -> addChildren(other.removeChildren(time), time));
Time.forAll(time -> stealChildrenOf(time, other));
}

/**
Expand Down Expand Up @@ -924,6 +933,42 @@ private static <L extends Label> boolean isSameAs(DiffNode<L> a, DiffNode<L> b,
return aIt.hasNext() == bIt.hasNext();
}

/**
* Returns true if this subtree is exactly equal to {@code other} except for line numbers and other metadata in labels.
* This equality is a weaker equality than {@link DiffNode#isSameAs(DiffNode)} (i.e., whenever isSameAs returns true, so does
* isSameAsIgnoringLineNumbers).
* Labels of DiffNodes are compared via {@link Label#observablyEqual(Label, Label)}.
* This check uses equality checks instead of identity.
*/
public boolean isSameAsIgnoringLineNumbers(DiffNode<L> other) {
return isSameAsIgnoringLineNumbers(this, other, new HashSet<>());
}

private static <L extends Label> boolean isSameAsIgnoringLineNumbers(DiffNode<L> a, DiffNode<L> b, Set<DiffNode<L>> visited) {
if (!visited.add(a)) {
return true;
}

if (!(
a.getDiffType().equals(b.getDiffType()) &&
a.getNodeType().equals(b.getNodeType()) &&
Objects.equals(a.getFormula(), b.getFormula()) &&
Label.observablyEqual(a.getLabel(), b.getLabel())
)) {
return false;
}

Iterator<DiffNode<L>> aIt = a.getAllChildren().iterator();
Iterator<DiffNode<L>> bIt = b.getAllChildren().iterator();
while (aIt.hasNext() && bIt.hasNext()) {
if (!isSameAsIgnoringLineNumbers(aIt.next(), bIt.next(), visited)) {
return false;
}
}

return aIt.hasNext() == bIt.hasNext();
}

@Override
public String toString() {
String s;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -535,10 +535,20 @@ public ConsistencyResult isConsistent() {
return ConsistencyResult.Success();
}

/**
* @see DiffNode#isSameAs
*/
public boolean isSameAs(VariationDiff<L> b) {
return getRoot().isSameAs(b.getRoot());
}

/**
* @see DiffNode#isSameAsIgnoringLineNumbers
*/
public boolean isSameAsIgnoringLineNumbers(VariationDiff<L> b) {
return getRoot().isSameAsIgnoringLineNumbers(b.getRoot());
}

@Override
public String toString() {
return "VariationDiff of " + source;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class CollapseNestedNonEditedAnnotations implements VariationDiffTransfor
private final List<Stack<DiffNode<DiffLinesLabel>>> chains = new ArrayList<>();

@Override
public List<Class<? extends VariationDiffTransformer<DiffLinesLabel>>> getDependencies() {
public List<Class<? extends Transformer<VariationDiff<DiffLinesLabel>>>> getDependencies() {
return List.of(Cast.unchecked(CutNonEditedSubtrees.class));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@

package org.variantsync.diffdetective.variation.diff.transform;

import org.prop4j.Node;
import org.variantsync.diffdetective.variation.Label;
import org.variantsync.diffdetective.variation.tree.VariationTree;
import org.variantsync.diffdetective.variation.tree.VariationTreeNode;

import java.util.List;

import static org.variantsync.diffdetective.util.fide.FormulaUtils.*;

/**
* This transformer simplifies annotations such that empty alternatives do not appear in choices.
* This means, that nestings without any siblings such as
*
* <pre>{@code
* #if A
* #elif B
* #elif C
* #else
* foo
* #endif
* }</pre>
*
* will be simplified to
*
* <pre>{@code
* #if !A && !B && !C
* foo
* #endif
* }</pre>
*
* Annotations without any children also get eliminated.
*
* @author Paul Bittner
*/
public class EliminateEmptyAlternatives<L extends Label> implements VariationTreeTransformer<L> {
private void elim(VariationTreeNode<L> subtree) {
// We simplify only annotations.
if (!subtree.isAnnotation()) return;

final List<VariationTreeNode<L>> children = subtree.getChildren();

// When there are no children, 'subtree' is an empty annotation that can be eliminated.
if (children.isEmpty()) {
subtree.drop();
}
// When there is exactly one child and that child is an 'else' or 'elif' we can simplify that nesting.
else if (children.size() == 1) {
final VariationTreeNode<L> child = children.getFirst();

if ((subtree.isIf() || subtree.isElif()) && (child.isElif() || child.isElse())) {
// determine new feaure mapping
Node newFormula = negate(subtree.getFormula());
if (child.isElif()) {
newFormula = and(newFormula, child.getFormula());
}
subtree.setFormula(newFormula);

// simplify tree
child.drop();
subtree.stealChildrenOf(child);
}
}
}

@Override
public void transform(VariationTree<L> tree) {
tree.forAllPostorder(this::elim);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public void transform(VariationDiff<DiffLinesLabel> variationDiff) {
}

@Override
public List<Class<? extends VariationDiffTransformer<DiffLinesLabel>>> getDependencies() {
public List<Class<? extends Transformer<VariationDiff<DiffLinesLabel>>>> getDependencies() {
return relabelNodes.getDependencies();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.variantsync.diffdetective.variation.diff.transform;

import java.util.ArrayList;
import java.util.List;

/**
* Models an operation that changes an object of type T inplace (i.e., the object is altered).
* To model further assumptions on the given elements T (i.e., assumptions that cannot easily be expressed as a type),
* Transformers may have dependencies to other transformers, which should be applied first.
*/
public interface Transformer<T> {
/**
* Apply a transformation to the given Object inplace.
* The object will be changed.
* @param element The T object to transform.
*/
void transform(final T element);

/**
* Returns a list of dependencies to other transformers.
* A transformer should only be run, if another transformation with the respective type was run for each type on the dependencies.
* @return List of types of which instances should be run before applying this transformation.
*/
default List<Class<? extends Transformer<T>>> getDependencies() {
return new ArrayList<>(0);
}

/**
* Checks that the dependencies of all given transformers are satisfied when
* applying the transformers sequentially.
* @param transformers The transformers whose dependencies to check.
* @throws RuntimeException when a dependency is not met.
*/
static <T> void checkDependencies(final List<? extends Transformer<T>> transformers) {
for (int i = transformers.size() - 1; i >= 0; --i) {
final Transformer<T> currentTransformer = transformers.get(i);
final List<Class<? extends Transformer<T>>> currentDependencies = currentTransformer.getDependencies();
for (final Class<? extends Transformer<T>> dependency : currentDependencies) {
boolean dependencyMet = false;
for (int j = i - 1; j >= 0; --j) {
if (dependency.isInstance(transformers.get(j))) {
dependencyMet = true;
break;
}
}
if (!dependencyMet) {
throw new RuntimeException("Dependency not met! Transformer "
+ currentTransformer
+ " requires a transformer of type "
+ dependency
+ " applied before!");
}
}
}
}

/**
* Applies all given transformers to the given element sequentially.
* First checks that all dependencies between transformers are met via {@link #checkDependencies(List)}.
* @param transformers Transformers to apply sequentially.
* @param element Tree to transform inplace.
*/
static <T> void apply(final List<? extends Transformer<T>> transformers, final T element) {
checkDependencies(transformers);
for (final Transformer<T> t : transformers) {
t.transform(element);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,70 +3,10 @@
import org.variantsync.diffdetective.variation.Label;
import org.variantsync.diffdetective.variation.diff.VariationDiff;

import java.util.ArrayList;
import java.util.List;

/**
* Interface that represents inplace transformations of VariationDiffs.
* A VariationDiffTransformer is intended to alter a given VariationDiff.
* @author Paul Bittner
*/
public interface VariationDiffTransformer<L extends Label> {
/**
* Apply a transformation to the given VariationDiff inplace.
* The given tree will be changed.
* @param variationDiff The VariationDiff to transform.
*/
void transform(final VariationDiff<L> variationDiff);

/**
* Returns a list of dependencies to other transformers.
* A transformer should only be run, if another transformation with the respective type was run for each type on the dependencies.
* @return List of types of which instances should be run before applying this transformation.
*/
default List<Class<? extends VariationDiffTransformer<L>>> getDependencies() {
return new ArrayList<>(0);
}

/**
* Checks that the dependencies of all given VariationDiffTransformers are satisfied when
* applying the transformers sequentially.
* @param transformers The transformers whose dependencies to check.
* @throws RuntimeException when a dependency is not met.
*/
static <L extends Label> void checkDependencies(final List<VariationDiffTransformer<L>> transformers) {
for (int i = transformers.size() - 1; i >= 0; --i) {
final VariationDiffTransformer<L> currentTransformer = transformers.get(i);
final List<Class<? extends VariationDiffTransformer<L>>> currentDependencies = currentTransformer.getDependencies();
for (final Class<? extends VariationDiffTransformer<L>> dependency : currentDependencies) {
boolean dependencyMet = false;
for (int j = i - 1; j >= 0; --j) {
if (dependency.isInstance(transformers.get(j))) {
dependencyMet = true;
break;
}
}
if (!dependencyMet) {
throw new RuntimeException("Dependency not met! VariationDiffTransformer "
+ currentTransformer
+ " requires a transformer of type "
+ dependency
+ " applied before!");
}
}
}
}

/**
* Applies all given transformers to the given VariationDiff sequentially.
* First checks that all dependencies between transformers are met via {@link #checkDependencies(List)}.
* @param transformers Transformers to apply sequentially.
* @param tree Tree to transform inplace.
*/
static <L extends Label> void apply(final List<VariationDiffTransformer<L>> transformers, final VariationDiff<L> tree) {
checkDependencies(transformers);
for (final VariationDiffTransformer<L> t : transformers) {
t.transform(tree);
}
}
public interface VariationDiffTransformer<L extends Label> extends Transformer<VariationDiff<L>> {
}
Loading