Skip to content

Commit 2ee3a19

Browse files
authored
Merge pull request #174 from VariantSync/fix-configure
Fix a crucial bug in the Configure relevance predicate
2 parents 1fb58fa + a991942 commit 2ee3a19

32 files changed

+718
-321
lines changed

src/main/java/org/variantsync/diffdetective/analysis/PreprocessingAnalysis.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,25 @@
33
import java.util.Arrays;
44
import java.util.List;
55

6-
import org.variantsync.diffdetective.variation.diff.transform.VariationDiffTransformer;
6+
import org.variantsync.diffdetective.variation.diff.VariationDiff;
7+
import org.variantsync.diffdetective.variation.diff.transform.Transformer;
78
import org.variantsync.diffdetective.variation.DiffLinesLabel;
89

910
public class PreprocessingAnalysis implements Analysis.Hooks {
10-
private final List<VariationDiffTransformer<DiffLinesLabel>> preprocessors;
11+
private final List<Transformer<VariationDiff<DiffLinesLabel>>> preprocessors;
1112

12-
public PreprocessingAnalysis(List<VariationDiffTransformer<DiffLinesLabel>> preprocessors) {
13+
public PreprocessingAnalysis(List<Transformer<VariationDiff<DiffLinesLabel>>> preprocessors) {
1314
this.preprocessors = preprocessors;
1415
}
1516

1617
@SafeVarargs
17-
public PreprocessingAnalysis(VariationDiffTransformer<DiffLinesLabel>... preprocessors) {
18+
public PreprocessingAnalysis(Transformer<VariationDiff<DiffLinesLabel>>... preprocessors) {
1819
this.preprocessors = Arrays.asList(preprocessors);
1920
}
2021

2122
@Override
2223
public boolean analyzeVariationDiff(Analysis analysis) {
23-
VariationDiffTransformer.apply(preprocessors, analysis.getCurrentVariationDiff());
24+
Transformer.apply(preprocessors, analysis.getCurrentVariationDiff());
2425
analysis.getCurrentVariationDiff().assertConsistency();
2526
return true;
2627
}

src/main/java/org/variantsync/diffdetective/internal/SimpleRenderer.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import org.variantsync.diffdetective.variation.diff.render.RenderOptions;
1919
import org.variantsync.diffdetective.variation.diff.render.VariationDiffRenderer;
2020
import org.variantsync.diffdetective.variation.diff.serialize.nodeformat.MappingsDiffNodeFormat;
21-
import org.variantsync.diffdetective.variation.diff.transform.VariationDiffTransformer;
21+
import org.variantsync.diffdetective.variation.diff.transform.Transformer;
2222

2323
import java.io.IOException;
2424
import java.nio.file.Files;
@@ -162,10 +162,10 @@ public static void main(String[] args) throws IOException {
162162
final Repository repository = Repository.fromDirectory(repoPath, repoName);
163163
repository.setParseOptions(repository.getParseOptions().withDiffStoragePolicy(PatchDiffParseOptions.DiffStoragePolicy.REMEMBER_STRIPPED_DIFF));
164164

165-
final List<VariationDiffTransformer<DiffLinesLabel>> transform = VariationDiffMiner.Postprocessing(repository);
165+
final List<Transformer<VariationDiff<DiffLinesLabel>>> transform = VariationDiffMiner.Postprocessing(repository);
166166
final PatchDiff patch = VariationDiffParser.parsePatch(repository, file, commit);
167167
Assert.assertNotNull(patch != null);
168-
VariationDiffTransformer.apply(transform, patch.getVariationDiff());
168+
Transformer.apply(transform, patch.getVariationDiff());
169169
renderer.render(patch, Path.of("render", repoName), RENDER_OPTIONS_TO_USE);
170170
}
171171

src/main/java/org/variantsync/diffdetective/mining/VariationDiffMiner.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@
1313
import org.variantsync.diffdetective.mining.formats.MiningNodeFormat;
1414
import org.variantsync.diffdetective.mining.formats.ReleaseMiningDiffNodeFormat;
1515
import org.variantsync.diffdetective.variation.DiffLinesLabel;
16+
import org.variantsync.diffdetective.variation.diff.VariationDiff;
1617
import org.variantsync.diffdetective.variation.diff.filter.VariationDiffFilter;
1718
import org.variantsync.diffdetective.variation.diff.serialize.GraphFormat;
1819
import org.variantsync.diffdetective.variation.diff.serialize.LineGraphExportOptions;
1920
import org.variantsync.diffdetective.variation.diff.serialize.edgeformat.EdgeLabelFormat;
2021
import org.variantsync.diffdetective.variation.diff.serialize.treeformat.CommitDiffVariationDiffLabelFormat;
2122
import org.variantsync.diffdetective.variation.diff.transform.CollapseNestedNonEditedAnnotations;
2223
import org.variantsync.diffdetective.variation.diff.transform.CutNonEditedSubtrees;
23-
import org.variantsync.diffdetective.variation.diff.transform.VariationDiffTransformer;
24+
import org.variantsync.diffdetective.variation.diff.transform.Transformer;
2425

2526
import java.io.IOException;
2627
import java.nio.file.Path;
@@ -38,8 +39,8 @@ public class VariationDiffMiner {
3839
// public static final int PRINT_LARGEST_SUBJECTS = 3;
3940
public static final boolean DEBUG_TEST = false;
4041

41-
public static List<VariationDiffTransformer<DiffLinesLabel>> Postprocessing(final Repository repository) {
42-
final List<VariationDiffTransformer<DiffLinesLabel>> processing = new ArrayList<>();
42+
public static List<Transformer<VariationDiff<DiffLinesLabel>>> Postprocessing(final Repository repository) {
43+
final List<Transformer<VariationDiff<DiffLinesLabel>>> processing = new ArrayList<>();
4344
processing.add(new CutNonEditedSubtrees<>());
4445
processing.add(new CollapseNestedNonEditedAnnotations());
4546
return processing;

src/main/java/org/variantsync/diffdetective/mining/postprocessing/Postprocessor.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import org.variantsync.diffdetective.variation.diff.filter.ExplainedFilter;
88
import org.variantsync.diffdetective.variation.diff.filter.TaggedPredicate;
99
import org.variantsync.diffdetective.variation.diff.transform.CutNonEditedSubtrees;
10-
import org.variantsync.diffdetective.variation.diff.transform.VariationDiffTransformer;
10+
import org.variantsync.diffdetective.variation.diff.transform.Transformer;
1111

1212
import java.util.List;
1313
import java.util.Map;
@@ -17,7 +17,7 @@
1717
* Patterns are represented as VariationDiffs and might be filtered or transformed.
1818
*/
1919
public class Postprocessor<L extends Label> {
20-
private final List<VariationDiffTransformer<L>> transformers;
20+
private final List<Transformer<VariationDiff<L>>> transformers;
2121
private final ExplainedFilter<VariationDiff<L>> filters;
2222

2323
/**
@@ -30,7 +30,7 @@ public class Postprocessor<L extends Label> {
3030
public record Result<L extends Label>(List<VariationDiff<L>> processedTrees, Map<String, Integer> filterCounts) {}
3131

3232
private Postprocessor(
33-
final List<VariationDiffTransformer<L>> transformers,
33+
final List<Transformer<VariationDiff<L>>> transformers,
3434
final List<TaggedPredicate<String, ? super VariationDiff<L>>> namedFilters) {
3535
this.transformers = transformers;
3636
this.filters = new ExplainedFilter<VariationDiff<L>>(namedFilters.stream());
@@ -66,7 +66,7 @@ public static <L extends Label> Postprocessor<L> Default() {
6666
public Result<L> postprocess(final List<VariationDiff<L>> frequentSubgraphs) {
6767
final List<VariationDiff<L>> processedTrees = frequentSubgraphs.stream()
6868
.filter(filters)
69-
.peek(tree -> VariationDiffTransformer.apply(transformers, tree))
69+
.peek(tree -> Transformer.apply(transformers, tree))
7070
.toList();
7171

7272
final Map<String, Integer> filterCounts = new ExplainedFilterSummary(filters).snapshot();

src/main/java/org/variantsync/diffdetective/show/variation/VariationDiffApp.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ public final static <L extends Label> List<DiffNodeLabelFormat<L>> DEFAULT_FORMA
4848
new LabelOnlyDiffNodeFormat<>(),
4949
new EditClassesDiffNodeFormat<>(),
5050
new LineNumberFormat<>(),
51-
new FormulasAndLineNumbersNodeFormat<>()
51+
new FormulasAndLineNumbersNodeFormat<>(),
52+
new IndexFormat<>()
5253
);
5354
}
5455

src/main/java/org/variantsync/diffdetective/util/StringUtils.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,17 @@ public static String prettyPrintNestedCollections(final Collection<?> collection
4848
public static String clamp(int maxlen, String s) {
4949
return s.substring(0, Math.min(s.length(), maxlen));
5050
}
51+
52+
/**
53+
* @return the longest prefix of the given string that contains only of whitespace
54+
*/
55+
public static String getLeadingWhitespace(String s) {
56+
if (s == null) return null;
57+
if (s.isEmpty()) return "";
58+
int i = 0;
59+
while (i < s.length() && Character.isWhitespace(s.charAt(i))) {
60+
++i;
61+
}
62+
return s.substring(0, i);
63+
}
5164
}

src/main/java/org/variantsync/diffdetective/variation/Label.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,18 @@ default Label withTimeDependentStateFrom(Label other, Time time) {
4848
* Creates a deep copy of this label.
4949
*/
5050
Label clone();
51+
52+
/**
53+
* Tests whether two labels are observably equal.
54+
* Observably equal means that the two labels
55+
* cannot be distinguished by using methods from this interface.
56+
* The given labels might indeed be of different classes or might
57+
* be considered unequal regarding {@link Object#equals(Object)}.
58+
*/
59+
static boolean observablyEqual(Label a, Label b) {
60+
if (a == null) return false;
61+
if (a == b) return true;
62+
return a.getLines().equals(b.getLines())
63+
&& a.getTrailingLines().equals(b.getTrailingLines());
64+
}
5165
}

src/main/java/org/variantsync/diffdetective/variation/diff/DiffNode.java

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -367,12 +367,21 @@ public List<DiffNode<L>> removeChildren(Time time) {
367367
}
368368

369369
/**
370-
* Removes all children from the given node and adds them as children to this node at the respective times.
370+
* Removes all children from the given node and adds them as children to this node at the given time.
371+
* The given node will have no children afterwards at the given time.
372+
* @param other The node whose children should be stolen for the given time.
373+
*/
374+
public void stealChildrenOf(Time time, final DiffNode<L> other) {
375+
addChildren(other.removeChildren(time), time);
376+
}
377+
378+
/**
379+
* Removes all children from the given node and adds them as children to this node (at all times).
371380
* The given node will have no children afterwards.
372381
* @param other The node whose children should be stolen.
373382
*/
374383
public void stealChildrenOf(final DiffNode<L> other) {
375-
Time.forAll(time -> addChildren(other.removeChildren(time), time));
384+
Time.forAll(time -> stealChildrenOf(time, other));
376385
}
377386

378387
/**
@@ -870,6 +879,65 @@ public DiffNode<L> shallowCopy() {
870879
);
871880
}
872881

882+
/**
883+
* Turns this node into a node with {@link DiffType} {@link DiffType#NON}.
884+
* To retain consistency of the variation diff, this node will also ensure that this
885+
* node will have a parent at all times.
886+
* To this end, the parent of this node will also be made unchanged if necessary, potentially
887+
* making some or all ancestors of this node unchanged recursively.
888+
* This method has no effect when this node is already unchanged.
889+
*/
890+
public void makeUnchanged() {
891+
if (isNon()) return;
892+
893+
this.diffType = DiffType.NON;
894+
895+
final DiffNode<L> bp = at(Time.BEFORE).parent;
896+
final DiffNode<L> ap = at(Time.AFTER).parent;
897+
898+
// If we have a parent before the change and after the change, making this node unchanged is fine.
899+
// Otherwise, if at least one parent is null, we have to set the other parent and make our parent unchanged as well.
900+
if (bp == null || ap == null) {
901+
// There is only one parent, which we store in this field.
902+
final DiffNode<L> p = bp == null ? ap : bp;
903+
final Time timeOfExistingEdge = bp == null ? AFTER : BEFORE;
904+
final Time timeOfNewEdge = timeOfExistingEdge.other();
905+
906+
Assert.assertTrue(p != null);
907+
908+
// If the parent is not unchanged, we have to make it unchanged so that it can be our
909+
// parent at all times.
910+
if (!p.isNon()) {
911+
p.makeUnchanged();
912+
}
913+
914+
// Now make p our parent at all times, not just at a single time.
915+
// To this end, we essentially have to "patch" this node into our parent scope at timeOfNewEdge.
916+
// Technically, this means that we have to add this node to the children list of p at a specific index.
917+
// We run into the alignment problem here if there is an insertion (or multiple insertions) right next to a deleted node we make unchanged or vice versa.
918+
// Hence, the index at which to patch our node is not unique.
919+
// There are multiple heuristics or strategies we could use to determine the index:
920+
// - constant index: always use index 0 for example
921+
// - line numbers: use the index right before the node with a higher line number at timeOfNewEdge
922+
// This solution requires knowledge on line numbers which are not always present (e.g., in diffs generated in code).
923+
// - context-based patching: Try to locate the node where its neighbors at timeOfNewEdge are most similar to the neighbors at timeOfExistingEdge
924+
// This requires some knowledge on the labels to match contexts.
925+
// We lightweight context-based patching here by trying to insert the node directly right of its closest unchanged left neighbor.
926+
int patchIndex = 0; // the index at which to insert this node at timeOfNewEdge
927+
final List<DiffNode<L>> siblingsAndMe = p.getChildOrder(timeOfExistingEdge);
928+
// We start walking from our closest left neighbor towards the leftmost sibling (at index 0) and try to find the first unchanged sibling.
929+
for (int i = p.indexOfChild(this, timeOfExistingEdge) - 1; i >= 0; i--) {
930+
final DiffNode<L> candidate = siblingsAndMe.get(i);
931+
if (candidate.isNon()) { // i.e., exists at timeOfNewEdge as well
932+
// Insert ourselves as the new right neighbor of the candidate node
933+
patchIndex = p.indexOfChild(candidate, timeOfNewEdge) + 1;
934+
break;
935+
}
936+
}
937+
p.insertChild(this, patchIndex, timeOfNewEdge);
938+
}
939+
}
940+
873941
/**
874942
* Transforms a {@code VariationNode} into a {@code DiffNode} by diffing {@code variationNode}
875943
* to itself. Recursively translates all children.
@@ -916,6 +984,42 @@ private static <L extends Label> boolean isSameAs(DiffNode<L> a, DiffNode<L> b,
916984
return aIt.hasNext() == bIt.hasNext();
917985
}
918986

987+
/**
988+
* Returns true if this subtree is exactly equal to {@code other} except for line numbers and other metadata in labels.
989+
* This equality is a weaker equality than {@link DiffNode#isSameAs(DiffNode)} (i.e., whenever isSameAs returns true, so does
990+
* isSameAsIgnoringLineNumbers).
991+
* Labels of DiffNodes are compared via {@link Label#observablyEqual(Label, Label)}.
992+
* This check uses equality checks instead of identity.
993+
*/
994+
public boolean isSameAsIgnoringLineNumbers(DiffNode<L> other) {
995+
return isSameAsIgnoringLineNumbers(this, other, new HashSet<>());
996+
}
997+
998+
private static <L extends Label> boolean isSameAsIgnoringLineNumbers(DiffNode<L> a, DiffNode<L> b, Set<DiffNode<L>> visited) {
999+
if (!visited.add(a)) {
1000+
return true;
1001+
}
1002+
1003+
if (!(
1004+
a.getDiffType().equals(b.getDiffType()) &&
1005+
a.getNodeType().equals(b.getNodeType()) &&
1006+
Objects.equals(a.getFormula(), b.getFormula()) &&
1007+
Label.observablyEqual(a.getLabel(), b.getLabel())
1008+
)) {
1009+
return false;
1010+
}
1011+
1012+
Iterator<DiffNode<L>> aIt = a.getAllChildren().iterator();
1013+
Iterator<DiffNode<L>> bIt = b.getAllChildren().iterator();
1014+
while (aIt.hasNext() && bIt.hasNext()) {
1015+
if (!isSameAsIgnoringLineNumbers(aIt.next(), bIt.next(), visited)) {
1016+
return false;
1017+
}
1018+
}
1019+
1020+
return aIt.hasNext() == bIt.hasNext();
1021+
}
1022+
9191023
@Override
9201024
public String toString() {
9211025
String s;

src/main/java/org/variantsync/diffdetective/variation/diff/VariationDiff.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,10 +542,20 @@ public ConsistencyResult isConsistent() {
542542
return ConsistencyResult.Success();
543543
}
544544

545+
/**
546+
* @see DiffNode#isSameAs
547+
*/
545548
public boolean isSameAs(VariationDiff<L> b) {
546549
return getRoot().isSameAs(b.getRoot());
547550
}
548551

552+
/**
553+
* @see DiffNode#isSameAsIgnoringLineNumbers
554+
*/
555+
public boolean isSameAsIgnoringLineNumbers(VariationDiff<L> b) {
556+
return getRoot().isSameAsIgnoringLineNumbers(b.getRoot());
557+
}
558+
549559
@Override
550560
public String toString() {
551561
return "VariationDiff of " + source;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.variantsync.diffdetective.variation.diff.serialize.nodeformat;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import org.variantsync.diffdetective.variation.Label;
7+
import org.variantsync.diffdetective.variation.diff.DiffNode;
8+
import org.variantsync.diffdetective.variation.diff.Time;
9+
import org.variantsync.functjonal.Cast;
10+
11+
/**
12+
* Labels nodes using their index in their parent list at all times.
13+
*/
14+
public class IndexFormat<L extends Label> implements DiffNodeLabelFormat<L> {
15+
@Override
16+
public String toLabel(final DiffNode<? extends L> n) {
17+
final DiffNode<L> node = Cast.unchecked(n);
18+
final Time[] allTimes = Time.values();
19+
final List<String> values = new ArrayList<>(allTimes.length);
20+
for (final Time t : allTimes) {
21+
values.add(node.getDiffType().existsAtTime(t) && !node.isRoot()
22+
? Integer.toString(node.getParent(t).indexOfChild(node, t))
23+
: "_");
24+
}
25+
return "(" + String.join(", ", values) + ")";
26+
}
27+
}

0 commit comments

Comments
 (0)