Skip to content

Commit 70298df

Browse files
committed
feat: create an interface for and a collection of differs
1 parent e4459da commit 70298df

File tree

6 files changed

+303
-0
lines changed

6 files changed

+303
-0
lines changed
-160 KB
Binary file not shown.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../src/main/java/org/variantsync/diffdetective/variation/diff/doc-files/variability-aware-differencing.png
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package org.variantsync.diffdetective.variation.diff;
2+
3+
import java.io.FileReader;
4+
import java.io.IOException;
5+
import java.io.Reader;
6+
import java.io.StringReader;
7+
import java.nio.file.Path;
8+
9+
import org.variantsync.diffdetective.diff.result.DiffParseException;
10+
import org.variantsync.diffdetective.util.FileSource;
11+
import org.variantsync.diffdetective.util.Source;
12+
import org.variantsync.diffdetective.variation.Label;
13+
import org.variantsync.diffdetective.variation.tree.VariationTree; // For Javadoc
14+
15+
/**
16+
* A generic interface for creating variation diffs from two states represented as text.
17+
* This interface represents a differ that can walk any of the paths present in our visual abstract
18+
* (see below) and thus the states before and after the edit are represented as text. For each of
19+
* the two paths, parsing a text diff and diffing variation trees, there is a specialized version of
20+
* this interface, {@link VariabilityAwareTextDiffer} and {@link VariabilityAwareTreeDiffer}
21+
* respectively. In particular, {@link VariabilityAwareTreeDiffer} represents the input states as
22+
* {@link VariationTree}s.
23+
*
24+
* <img alt="Variability-Aware Differencing Overview. Same as in the README.md." src="doc-files/variability-aware-differencing.png">
25+
*
26+
* @see VariabilityAwareDiffers
27+
*/
28+
@FunctionalInterface
29+
public interface VariabilityAwareDiffer<L extends Label> {
30+
/**
31+
* Create a variation diff by diffing the content of two {@link Reader}s.
32+
* For {@link VariationDiff#getSource() traceability}, both {@link Reader}s are paired with a {@link Source}.
33+
*/
34+
VariationDiff<L> diff(Reader before, Reader after, Source beforeSource, Source afterSource) throws IOException, DiffParseException;
35+
36+
/**
37+
* Create a variation diff by diffing the content of two {@link String}s.
38+
* For {@link VariationDiff#getSource() traceability}, both {@link String}s are paired with a {@link Source}.
39+
*/
40+
default VariationDiff<L> diff(String before, String after, Source beforeSource, Source afterSource) throws IOException, DiffParseException {
41+
return diff(new StringReader(before), new StringReader(after), beforeSource, afterSource);
42+
}
43+
44+
/**
45+
* Create a variation diff by diffing the content of two {@link Path}s.
46+
* @see diff(Reader, Reader, Source, Source)
47+
*/
48+
default VariationDiff<L> diff(Path before, Path after) throws IOException, DiffParseException {
49+
try (
50+
Reader beforeStream = new FileReader(before.toFile());
51+
Reader afterStream = new FileReader(after.toFile())
52+
) {
53+
return diff(beforeStream, afterStream, new FileSource(before), new FileSource(after));
54+
}
55+
}
56+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package org.variantsync.diffdetective.variation.diff;
2+
3+
import java.io.IOException;
4+
import java.io.Reader;
5+
6+
import org.apache.commons.io.IOUtils;
7+
import org.eclipse.jgit.diff.DiffAlgorithm;
8+
import org.variantsync.diffdetective.diff.result.DiffParseException;
9+
import org.variantsync.diffdetective.util.CompositeSource;
10+
import org.variantsync.diffdetective.util.Source;
11+
import org.variantsync.diffdetective.variation.DiffLinesLabel;
12+
import org.variantsync.diffdetective.variation.diff.construction.GumTreeDiff;
13+
import org.variantsync.diffdetective.variation.diff.construction.JGitDiff;
14+
import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions;
15+
import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParser; // For Javadoc
16+
import org.variantsync.diffdetective.variation.tree.VariationTree;
17+
18+
import com.github.gumtreediff.matchers.Matcher;
19+
import com.github.gumtreediff.matchers.Matchers;
20+
21+
/**
22+
* A collection of differs that create variation diffs.
23+
* @see VariabilityAwareDiffer
24+
* @see VariationDiff
25+
*/
26+
public class VariabilityAwareDiffers {
27+
/**
28+
* Returns a differ that {@link VariationDiffParser#createVariationDiff parses} a
29+
* {@link VariationDiff} after {@link JGitDiff#textDiff diffing} with
30+
* {@link DiffAlgorithm.SupportedAlgorithm one of JGit's differ}.
31+
*
32+
* @param lineDiffAlgorithm the Git diffing algorithm to use
33+
* @param parseOptions options for parsing the {@link VariationDiff}
34+
* @return the diffed {@link VariationDiff}
35+
*/
36+
public static VariabilityAwareTextDiffer JGit(DiffAlgorithm.SupportedAlgorithm lineDiffAlgorithm, VariationDiffParseOptions parseOptions) {
37+
return new VariabilityAwareTextDiffer() {
38+
@Override
39+
public String diffLines(Reader before, Reader after) throws IOException {
40+
return JGitDiff.textDiff(IOUtils.toString(before), IOUtils.toString(after), lineDiffAlgorithm);
41+
}
42+
43+
@Override
44+
public String diffLines(String before, String after) throws IOException {
45+
return JGitDiff.textDiff(before, after, lineDiffAlgorithm);
46+
}
47+
48+
@Override
49+
public VariationDiffParseOptions getParseOptions() {
50+
return parseOptions;
51+
}
52+
};
53+
}
54+
55+
/**
56+
* Calls {@link JGit(DiffAlgorithm.SupportedAlgorithm, VariationDiffParseOptions)} with
57+
* {@link DiffAlgorithm.SupportedAlgorithm#MYERS} and {@link VariationDiffParseOptions#Default}.
58+
*/
59+
public static VariabilityAwareTextDiffer JGitMyers() {
60+
return JGit(DiffAlgorithm.SupportedAlgorithm.MYERS, VariationDiffParseOptions.Default);
61+
}
62+
63+
/**
64+
* Returns a differ that {@link VariationTree#fromFile parses} two variation trees and then
65+
* {@link GumTreeDiff#diffUsingMatching diffs} these variation trees.
66+
*
67+
* @param parseOptions options for parsing the {@link VariationTree}s
68+
* @param matcher the matcher used for diffing the {@link VariationTree}s
69+
* @return the diffed {@link VariationDiff}
70+
*/
71+
public static VariabilityAwareTreeDiffer<DiffLinesLabel> GumTree(VariationDiffParseOptions parseOptions, Matcher matcher) {
72+
return new VariabilityAwareTreeDiffer<DiffLinesLabel>() {
73+
@Override
74+
public VariationDiff<DiffLinesLabel> diffTrees(VariationTree<DiffLinesLabel> before, VariationTree<DiffLinesLabel> after) {
75+
return GumTreeDiff.diffUsingMatching(before, after, matcher);
76+
}
77+
78+
@Override
79+
public VariationDiffParseOptions getParseOptions() {
80+
return parseOptions;
81+
}
82+
};
83+
}
84+
85+
/**
86+
* Calls {@link GumTree(VariationDiffParseOptions, Matcher)} with {@link
87+
* VariationDiffParseOptions#Default} and {@link Matchers#getInstance {@code
88+
* Matchers.getInstance().getMatcher()}}.
89+
*/
90+
public static VariabilityAwareTreeDiffer<DiffLinesLabel> GumTree() {
91+
return GumTree(VariationDiffParseOptions.Default, Matchers.getInstance().getMatcher());
92+
}
93+
94+
/**
95+
* Returns a differ that first parses a {@link VariationDiff} like {@link JGit} and then
96+
* {@link GumTreeDiff#improveMatching improves} the represented matching.
97+
*
98+
* @param lineDiffAlgorithm the Git diffing algorithm to use
99+
* @param parseOptions options for parsing the {@link VariationDiff}
100+
* @param matcher the matcher used for diffing the variation trees
101+
* @return the diffed {@link VariationDiff}
102+
*/
103+
public static VariabilityAwareTextDiffer JGitGumTreeHybrid(DiffAlgorithm.SupportedAlgorithm lineDiffAlgorithm, VariationDiffParseOptions parseOptions, Matcher matcher) {
104+
return new VariabilityAwareTextDiffer() {
105+
@Override
106+
public String diffLines(Reader before, Reader after) throws IOException {
107+
return JGitDiff.textDiff(IOUtils.toString(before), IOUtils.toString(after), lineDiffAlgorithm);
108+
}
109+
110+
@Override
111+
public VariationDiffParseOptions getParseOptions() {
112+
return parseOptions;
113+
}
114+
115+
@Override
116+
public VariationDiff<DiffLinesLabel> diff(Reader before, Reader after, Source beforeSource, Source afterSource) throws IOException, DiffParseException {
117+
VariationDiff<DiffLinesLabel> vd = VariabilityAwareTextDiffer.super.diff(before, after, beforeSource, afterSource);
118+
return new VariationDiff<>(
119+
GumTreeDiff.improveMatching(vd.getRoot(), matcher),
120+
new CompositeSource("GumTreeDiff.improveMatching", vd.getSource())
121+
);
122+
};
123+
};
124+
}
125+
126+
/**
127+
* Calls {@link JGitGumTreeHybrid(DiffAlgorithm.SupportedAlgorithm, VariationDiffParseOptions,
128+
* Matcher)} with {@link DiffAlgorithm.SupportedAlgorithm#MYERS}, {@link
129+
* VariationDiffParseOptions#Default}, and {@link Matchers#getInstance {@code
130+
* Matchers.getInstance().getMatcher()}}.
131+
*/
132+
public static VariabilityAwareTextDiffer JGitMyersGumTreeHybrid() {
133+
return JGitGumTreeHybrid(DiffAlgorithm.SupportedAlgorithm.MYERS, VariationDiffParseOptions.Default, Matchers.getInstance().getMatcher());
134+
}
135+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package org.variantsync.diffdetective.variation.diff;
2+
3+
import java.io.IOException;
4+
import java.io.Reader;
5+
import java.io.StringReader;
6+
7+
import org.variantsync.diffdetective.diff.result.DiffParseException;
8+
import org.variantsync.diffdetective.util.CompositeSource;
9+
import org.variantsync.diffdetective.util.Source;
10+
import org.variantsync.diffdetective.variation.DiffLinesLabel;
11+
import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions;
12+
import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParser;
13+
14+
/**
15+
* A {@link VariabilityAwareDiffer} that first creates a line diff and
16+
* {@link VariationDiffParser#createVariationDiff parses} this line diff into a
17+
* {@link VariationDiff}.
18+
*/
19+
@FunctionalInterface
20+
public interface VariabilityAwareTextDiffer extends VariabilityAwareDiffer<DiffLinesLabel> {
21+
/**
22+
* Returns a line diff of {@code before} and {@code after} in the unified diff format.
23+
*/
24+
String diffLines(Reader before, Reader after) throws IOException;
25+
26+
/**
27+
* Returns a line diff of {@code before} and {@code after} in the unified diff format.
28+
*/
29+
default String diffLines(String before, String after) throws IOException {
30+
return diffLines(new StringReader(before), new StringReader(after));
31+
}
32+
33+
/**
34+
* Returns the options used for {@link parseDiff parsing} the {@link VariationDiff} in {@link parseDiff}.
35+
*/
36+
default VariationDiffParseOptions getParseOptions() {
37+
return VariationDiffParseOptions.Default;
38+
}
39+
40+
/**
41+
* Parses the {@link diffLines line diff} into a {@link VariationDiff} using the options
42+
* from {@link getParseOptions}.
43+
*/
44+
default VariationDiff<DiffLinesLabel> parseDiff(String diff, Source diffSource) throws DiffParseException {
45+
return VariationDiffParser.createVariationDiff(
46+
diff,
47+
new CompositeSource("VariationDiffParser.createVariationDiff", diffSource),
48+
getParseOptions()
49+
);
50+
}
51+
52+
/**
53+
* Composes {@link diffLines} and {@link parseDiff} to create a {@link VariationDiff}.
54+
*/
55+
@Override
56+
default VariationDiff<DiffLinesLabel> diff(Reader before, Reader after, Source beforeSource, Source afterSource) throws IOException, DiffParseException {
57+
return parseDiff(
58+
diffLines(before, after),
59+
new CompositeSource("line diff", beforeSource, afterSource)
60+
);
61+
}
62+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package org.variantsync.diffdetective.variation.diff;
2+
3+
import java.io.BufferedReader;
4+
import java.io.IOException;
5+
import java.io.Reader;
6+
7+
import org.variantsync.diffdetective.diff.result.DiffParseException;
8+
import org.variantsync.diffdetective.util.Source;
9+
import org.variantsync.diffdetective.variation.DiffLinesLabel;
10+
import org.variantsync.diffdetective.variation.Label;
11+
import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions;
12+
import org.variantsync.diffdetective.variation.tree.VariationTree;
13+
14+
/**
15+
* A {@link VariabilityAwareDiffer} that first {@link VariationTree#fromFile parses} {@link VariationTree}s and
16+
* diffs these trees.
17+
*/
18+
@FunctionalInterface
19+
public interface VariabilityAwareTreeDiffer<L extends Label> extends VariabilityAwareDiffer<L> {
20+
/**
21+
* Creates a {@link VariationDiff} by diffing two variation trees.
22+
*/
23+
VariationDiff<L> diffTrees(VariationTree<DiffLinesLabel> before, VariationTree<DiffLinesLabel> after);
24+
25+
/**
26+
* Returns the options used for parsing the {@link VariationTree}s in {@link parseTree}.
27+
*/
28+
default VariationDiffParseOptions getParseOptions() {
29+
return VariationDiffParseOptions.Default;
30+
}
31+
32+
/**
33+
* {@link VariationTree#fromFile Parses} {@code input} into a {@link VariationTree} using
34+
* the options from {@code getParseOptions}.
35+
*/
36+
default VariationTree<DiffLinesLabel> parseTree(Reader input, Source source) throws IOException, DiffParseException {
37+
return VariationTree.fromFile(new BufferedReader(input), source, getParseOptions());
38+
}
39+
40+
/**
41+
* Composes {@link parseTree} and {@link diffTrees} to create a {@link VariationDiff}.
42+
*/
43+
@Override
44+
default VariationDiff<L> diff(Reader before, Reader after, Source beforeSource, Source afterSource) throws IOException, DiffParseException {
45+
return diffTrees(
46+
parseTree(before, beforeSource),
47+
parseTree(after, afterSource)
48+
);
49+
}
50+
}
160 KB
Loading

0 commit comments

Comments
 (0)