diff --git a/README.md b/README.md index fabc3f670..7c40e9e30 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ DiffDetective is an open-source Java library for variability-aware source code differencing and the **analysis of version histories of software product lines**. This means that DiffDetective can **turn a generic differencer into a variability-aware differencer** by means of a pre- or post-processing. DiffDetective is centered around **formally verified** data structures for variability (variation trees) and variability-aware diffs (variation diffs). These data structures are **generic**, and DiffDetective currently implements **C preprocessor support** to parse respective annotations when used to implement variability. The picture below depicts the process of variability-aware differencing. -Variability-Aware Differencing Overview +Variability-Aware Differencing Overview Given two states of a C-preprocessor annotated source code file (left), for example before and after a commit, DiffDetective constructs a variability-aware diff (right) that distinguishes changes to source code from changes to variability annotations. DiffDetective can construct such a variation diff either, by first using a generic differencer, and separating the information (center path), or by first parsing both input versions to an abstract representation, a variation tree (center top and bottom), and constructing a variation diff using a tree differencing algorithm in a second step. diff --git a/src/main/java/org/variantsync/diffdetective/diff/git/GitDiffer.java b/src/main/java/org/variantsync/diffdetective/diff/git/GitDiffer.java index 600519c76..d1b2523fe 100644 --- a/src/main/java/org/variantsync/diffdetective/diff/git/GitDiffer.java +++ b/src/main/java/org/variantsync/diffdetective/diff/git/GitDiffer.java @@ -21,9 +21,11 @@ import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.diff.VariationDiff; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParser; +import org.variantsync.diffdetective.variation.tree.source.GitSource; import java.io.*; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -252,6 +254,7 @@ private static CommitDiffResult getPatchDiffs( final VariationDiff variationDiff = VariationDiffParser.createVariationDiff( fullDiff, + new GitSource(repository, childCommit.getId().name(), Path.of(filename)), repository.getParseOptions().variationDiffParseOptions() ); diff --git a/src/main/java/org/variantsync/diffdetective/diff/git/GitPatch.java b/src/main/java/org/variantsync/diffdetective/diff/git/GitPatch.java index 6bdef202e..e0ca5d9b8 100644 --- a/src/main/java/org/variantsync/diffdetective/diff/git/GitPatch.java +++ b/src/main/java/org/variantsync/diffdetective/diff/git/GitPatch.java @@ -1,17 +1,19 @@ package org.variantsync.diffdetective.diff.git; +import java.util.List; + import org.eclipse.jgit.diff.DiffEntry; import org.variantsync.diffdetective.diff.text.TextBasedDiff; +import org.variantsync.diffdetective.util.Source; import org.variantsync.diffdetective.variation.diff.Time; import org.variantsync.diffdetective.variation.diff.VariationDiff; // For Javadoc -import org.variantsync.diffdetective.variation.diff.source.VariationDiffSource; /** * Interface for patches from a git repository. * A git patch is a {@link TextBasedDiff} from which {@link VariationDiff}s can be created. * */ -public interface GitPatch extends VariationDiffSource, TextBasedDiff { +public interface GitPatch extends Source, TextBasedDiff { /** * Minimal default implementation of {@link GitPatch} * @param getDiff The diff in text form. @@ -41,6 +43,16 @@ public GitPatch shallowClone() { public String toString() { return oldFileName + "@ " + getParentCommitHash + " (parent) to " + newFileName + " @ " + getCommitHash + " (child)"; } + + @Override + public String getSourceExplanation() { + return "SimpleGitPatch"; + } + + @Override + public List getSourceArguments() { + return List.of(getChangeType(), oldFileName(), newFileName(), getCommitHash(), getParentCommitHash()); + } } /** diff --git a/src/main/java/org/variantsync/diffdetective/diff/git/PatchDiff.java b/src/main/java/org/variantsync/diffdetective/diff/git/PatchDiff.java index f6e03b0c7..d4232509e 100644 --- a/src/main/java/org/variantsync/diffdetective/diff/git/PatchDiff.java +++ b/src/main/java/org/variantsync/diffdetective/diff/git/PatchDiff.java @@ -123,4 +123,9 @@ public String toString() { public GitPatch shallowClone() { return new GitPatch.SimpleGitPatch(getDiff(), getChangeType(), getFileName(Time.BEFORE), getFileName(Time.AFTER), getCommitHash(), getParentCommitHash()); } + + @Override + public String getSourceExplanation() { + return "PatchDiff"; + } } diff --git a/src/main/java/org/variantsync/diffdetective/examplesearch/ExampleFinder.java b/src/main/java/org/variantsync/diffdetective/examplesearch/ExampleFinder.java index ebe8d1a11..38782644b 100644 --- a/src/main/java/org/variantsync/diffdetective/examplesearch/ExampleFinder.java +++ b/src/main/java/org/variantsync/diffdetective/examplesearch/ExampleFinder.java @@ -11,6 +11,7 @@ import org.variantsync.diffdetective.show.Show; import org.variantsync.diffdetective.util.Assert; import org.variantsync.diffdetective.util.IO; +import org.variantsync.diffdetective.util.Source; import org.variantsync.diffdetective.util.StringUtils; import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.diff.Time; @@ -24,7 +25,6 @@ import org.variantsync.diffdetective.variation.diff.serialize.edgeformat.DefaultEdgeLabelFormat; import org.variantsync.diffdetective.variation.diff.serialize.nodeformat.MappingsDiffNodeFormat; import org.variantsync.diffdetective.variation.diff.serialize.treeformat.CommitDiffVariationDiffLabelFormat; -import org.variantsync.diffdetective.variation.diff.source.VariationDiffSource; import java.io.IOException; import java.nio.file.Path; @@ -106,14 +106,10 @@ private boolean checkIfExample(Analysis analysis, String localDiff) { // We do not want a variationDiff for the entire file but only for the local change to have a small example. final VariationDiff localTree; try { - localTree = VariationDiff.fromDiff(localDiff, new VariationDiffParseOptions(annotationParser, true, true)); + localTree = VariationDiff.fromDiff(localDiff, Source.findFirst(variationDiff, GitPatch.class), new VariationDiffParseOptions(annotationParser, true, true)); // Not every local diff can be parsed to a VariationDiff because diffs are unaware of the underlying language (i.e., CPP). // We want only running examples whose diffs describe entire diff trees for easier understanding. - if (isGoodExample.test(localTree)) { - Assert.assertTrue(variationDiff.getSource() instanceof GitPatch); - final GitPatch variationDiffSource = (GitPatch) variationDiff.getSource(); - localTree.setSource(variationDiffSource.shallowClone()); - } else { + if (!isGoodExample.test(localTree)) { return false; } } catch (DiffParseException e) { @@ -149,9 +145,9 @@ public boolean analyzeVariationDiff(Analysis analysis) { } private void exportExample(final Analysis analysis, final String tdiff, final VariationDiff vdiff, Path outputDir) { - Assert.assertTrue(vdiff.getSource() instanceof GitPatch); final Repository repo = analysis.getRepository(); - final GitPatch patch = (GitPatch) vdiff.getSource(); + final GitPatch patch = Source.findFirst(vdiff, GitPatch.class); + Assert.assertNotNull(patch); outputDir = outputDir.resolve(Path.of(repo.getRepositoryName() + "_" + patch.getCommitHash())); final String filename = patch.getFileName(Time.AFTER); @@ -185,8 +181,8 @@ private void exportExample(final Analysis analysis, final String tdiff, final Va } static String getDiff(final VariationDiff tree) { - final VariationDiffSource source = tree.getSource(); - Assert.assertTrue(source instanceof TextBasedDiff); - return ((TextBasedDiff) source).getDiff(); + TextBasedDiff textBasedDiff = Source.findFirst(tree, TextBasedDiff.class); + Assert.assertNotNull(textBasedDiff); + return textBasedDiff.getDiff(); } } diff --git a/src/main/java/org/variantsync/diffdetective/experiments/thesis_bm/ConstructionValidation.java b/src/main/java/org/variantsync/diffdetective/experiments/thesis_bm/ConstructionValidation.java index 75c04e6a3..b90bae61d 100644 --- a/src/main/java/org/variantsync/diffdetective/experiments/thesis_bm/ConstructionValidation.java +++ b/src/main/java/org/variantsync/diffdetective/experiments/thesis_bm/ConstructionValidation.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; +import java.nio.file.Path; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -42,6 +43,7 @@ import org.variantsync.diffdetective.variation.diff.Time; import org.variantsync.diffdetective.variation.diff.filter.VariationDiffFilter; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParser; +import org.variantsync.diffdetective.variation.tree.source.GitSource; import org.variantsync.functjonal.category.InplaceSemigroup; import org.variantsync.functjonal.map.MergeMap; @@ -375,6 +377,7 @@ private void counts(VariationDiff tree, VariationDiffStatistics } private VariationDiff parseVariationTree(Analysis analysis, RevCommit commit) throws IOException, DiffParseException { + String fileName = analysis.getCurrentPatch().getFileName(AFTER); try (BufferedReader afterFile = new BufferedReader( /* @@ -386,10 +389,14 @@ private VariationDiff parseVariationTree(Analysis analysis, RevC GitDiffer.getBeforeFullFile( analysis.getRepository(), commit, - analysis.getCurrentPatch().getFileName(AFTER)), + fileName), 0xfeff)) // BOM, same as GitDiffer.BOM_PATTERN ) { - return VariationDiffParser.createVariationTree(afterFile, analysis.getRepository().getParseOptions().variationDiffParseOptions()); + return VariationDiffParser.createVariationTree( + afterFile, + new GitSource(analysis.getRepository(), commit.getId().name(), Path.of(fileName)), + analysis.getRepository().getParseOptions().variationDiffParseOptions() + ); } } diff --git a/src/main/java/org/variantsync/diffdetective/experiments/thesis_es/UnparseAnalysis.java b/src/main/java/org/variantsync/diffdetective/experiments/thesis_es/UnparseAnalysis.java index ab18674fb..278dc0d20 100644 --- a/src/main/java/org/variantsync/diffdetective/experiments/thesis_es/UnparseAnalysis.java +++ b/src/main/java/org/variantsync/diffdetective/experiments/thesis_es/UnparseAnalysis.java @@ -9,6 +9,7 @@ import org.variantsync.diffdetective.util.CSV; import org.variantsync.diffdetective.util.FileUtils; import org.variantsync.diffdetective.util.IO; +import org.variantsync.diffdetective.util.Source; import org.variantsync.diffdetective.util.StringUtils; import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.VariationUnparser; @@ -17,7 +18,6 @@ import org.variantsync.diffdetective.variation.diff.construction.JGitDiff; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; import org.variantsync.diffdetective.variation.tree.VariationTree; -import org.variantsync.diffdetective.variation.tree.source.VariationTreeSource; public class UnparseAnalysis implements Analysis.Hooks { @@ -215,7 +215,7 @@ public static String removeWhitespace(String string, boolean diff) { public static String parseUnparseTree(String text, VariationDiffParseOptions option) { String temp = "b"; try { - VariationTree tree = VariationTree.fromText(text, VariationTreeSource.Unknown, option); + VariationTree tree = VariationTree.fromText(text, Source.Unknown, option); temp = VariationUnparser.unparseTree(tree); } catch (Exception e) { e.printStackTrace(); @@ -226,7 +226,7 @@ public static String parseUnparseTree(String text, VariationDiffParseOptions opt public static String parseUnparseDiff(String textDiff, VariationDiffParseOptions option) { String temp = "b"; try { - VariationDiff diff = VariationDiff.fromDiff(textDiff, option); + VariationDiff diff = VariationDiff.fromDiff(textDiff, Source.Unknown, option); temp = VariationUnparser.unparseDiff(diff); } catch (Exception e) { e.printStackTrace(); diff --git a/src/main/java/org/variantsync/diffdetective/experiments/views/result/ViewEvaluation.java b/src/main/java/org/variantsync/diffdetective/experiments/views/result/ViewEvaluation.java index 8211574bb..e1916925d 100644 --- a/src/main/java/org/variantsync/diffdetective/experiments/views/result/ViewEvaluation.java +++ b/src/main/java/org/variantsync/diffdetective/experiments/views/result/ViewEvaluation.java @@ -85,8 +85,8 @@ public String toCSV(String delimiter) { // repo.getRepositoryName(), commit, file, - relevance.getFunctionName(), -// getQueryArguments(), + relevance.getSourceExplanation(), +// relevance.getSourceArguments(), msNaive, msOptimized, diffStatistics.nodeCount, diff --git a/src/main/java/org/variantsync/diffdetective/load/GitLoader.java b/src/main/java/org/variantsync/diffdetective/load/GitLoader.java index 5af465f8d..16a98f6b6 100644 --- a/src/main/java/org/variantsync/diffdetective/load/GitLoader.java +++ b/src/main/java/org/variantsync/diffdetective/load/GitLoader.java @@ -1,7 +1,6 @@ package org.variantsync.diffdetective.load; import net.lingala.zip4j.ZipFile; -import net.lingala.zip4j.exception.ZipException; import org.apache.commons.io.FilenameUtils; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; @@ -86,10 +85,9 @@ public static Git fromZip(Path pathToZip) { return fromDirectory(unzippedRepoName); } - try { - ZipFile zipFile = new ZipFile(pathToZip.toFile()); + try (ZipFile zipFile = new ZipFile(pathToZip.toFile())) { zipFile.extractAll(targetDir.toString()); - } catch (ZipException e) { + } catch (IOException e) { Logger.warn("Failed to extract git repo from {} to {}", pathToZip, targetDir); return null; } diff --git a/src/main/java/org/variantsync/diffdetective/mining/RWCompositePatternTreeFormat.java b/src/main/java/org/variantsync/diffdetective/mining/RWCompositePatternTreeFormat.java index 0305528d3..e0a2d551c 100644 --- a/src/main/java/org/variantsync/diffdetective/mining/RWCompositePatternTreeFormat.java +++ b/src/main/java/org/variantsync/diffdetective/mining/RWCompositePatternTreeFormat.java @@ -1,22 +1,25 @@ package org.variantsync.diffdetective.mining; -import org.variantsync.diffdetective.variation.diff.source.VariationDiffSource; +import java.util.List; + +import org.variantsync.diffdetective.util.FileSource; +import org.variantsync.diffdetective.util.Source; import org.variantsync.diffdetective.variation.diff.serialize.treeformat.VariationDiffLabelFormat; -import org.variantsync.diffdetective.variation.diff.source.PatchFile; public class RWCompositePatternTreeFormat implements VariationDiffLabelFormat { @Override - public VariationDiffSource fromLabel(String label) { + public Source fromLabel(String label) { throw new UnsupportedOperationException("Cannot read"); } @Override - public String toLabel(VariationDiffSource variationDiffSource) { - if (variationDiffSource instanceof PatchFile p) { - final String fileName = p.path().getFileName().toString(); - return fileName.substring(0, fileName.indexOf('.')).replaceAll("_", " "); + public String toLabel(Source variationDiffSource) { + List pathSources = Source.findAll(variationDiffSource, FileSource.class); + if (pathSources.size() != 1) { + throw new IllegalArgumentException("Expected a single path source but got:\n" + Source.fullExplanation(variationDiffSource)); } - throw new IllegalArgumentException("Expected a PatchFile but got " + variationDiffSource); + final String fileName = pathSources.get(0).getPath().getFileName().toString(); + return fileName.substring(0, fileName.indexOf('.')).replaceAll("_", " "); } } diff --git a/src/main/java/org/variantsync/diffdetective/show/Show.java b/src/main/java/org/variantsync/diffdetective/show/Show.java index f6383de47..9f2018951 100644 --- a/src/main/java/org/variantsync/diffdetective/show/Show.java +++ b/src/main/java/org/variantsync/diffdetective/show/Show.java @@ -6,6 +6,7 @@ import org.variantsync.diffdetective.show.engine.GameEngine; import org.variantsync.diffdetective.show.engine.geom.Vec2; import org.variantsync.diffdetective.show.variation.VariationDiffApp; +import org.variantsync.diffdetective.util.Source; import org.variantsync.diffdetective.variation.Label; import org.variantsync.diffdetective.variation.diff.DiffNode; import org.variantsync.diffdetective.variation.diff.VariationDiff; @@ -30,7 +31,7 @@ public static GameEngine diff(final VariationDiff d, final String title) { } public static GameEngine diff(final VariationDiff d) { - return diff(d, d.getSource().toString()); + return diff(d, Source.shortExplanation(d)); } public static GameEngine tree(final VariationTree t, final String title, List> availableFormats) { @@ -47,7 +48,7 @@ public static GameEngine tree(final VariationTree t, final String title) { } public static GameEngine tree(final VariationTree t) { - return tree(t, t.source().toString()); + return tree(t, Source.shortExplanation(t)); } public static GameEngine baddiff(final BadVDiff badVDiff, final String title, List> availableFormats) { @@ -80,6 +81,6 @@ public static GameEngine baddiff(final BadVDiff badVDiff, final String title) } public static GameEngine baddiff(final BadVDiff badVDiff) { - return baddiff(badVDiff, badVDiff.diff().source().toString()); + return baddiff(badVDiff, Source.shortExplanation(badVDiff.diff())); } } diff --git a/src/main/java/org/variantsync/diffdetective/util/CompositeSource.java b/src/main/java/org/variantsync/diffdetective/util/CompositeSource.java new file mode 100644 index 000000000..aa4e6e3da --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/util/CompositeSource.java @@ -0,0 +1,36 @@ +package org.variantsync.diffdetective.util; + +import java.util.Arrays; +import java.util.List; + +/** + * Represents a {@link Source} without arguments. + */ +public class CompositeSource implements Source { + private final String sourceExplanation; + private final List sources; + + /** + * @param sourceExplanation is returned verbatim by {@link getSourceExplanation} + * @param sources is returned as immutable list by {@link getSources} + */ + public CompositeSource(String sourceExplanation, Source... sources) { + this.sourceExplanation = sourceExplanation; + this.sources = Arrays.asList(sources); + } + + @Override + public String getSourceExplanation() { + return sourceExplanation; + } + + @Override + public List getSources() { + return sources; + } + + @Override + public String toString() { + return Source.shallowExplanation(this); + } +} diff --git a/src/main/java/org/variantsync/diffdetective/util/FileSource.java b/src/main/java/org/variantsync/diffdetective/util/FileSource.java new file mode 100644 index 000000000..84458a0de --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/util/FileSource.java @@ -0,0 +1,19 @@ +package org.variantsync.diffdetective.util; + +import java.nio.file.Path; +import java.util.List; + +/** + * Represents a file input in a {@link Source} hierarchy. + */ +public record FileSource(Path getPath) implements Source { + @Override + public String getSourceExplanation() { + return "File"; + } + + @Override + public List getSourceArguments() { + return List.of(getPath()); + } +} diff --git a/src/main/java/org/variantsync/diffdetective/util/Source.java b/src/main/java/org/variantsync/diffdetective/util/Source.java new file mode 100644 index 000000000..a2141ec1c --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/util/Source.java @@ -0,0 +1,275 @@ +package org.variantsync.diffdetective.util; + +import java.nio.file.Path; // For Javadoc +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.variantsync.diffdetective.diff.git.PatchDiff; // For Javadoc +import org.variantsync.diffdetective.variation.diff.ProjectionSource; +import org.variantsync.diffdetective.variation.diff.VariationDiff; // For Javadoc +import org.variantsync.functjonal.Cast; + +/** + * An interface for source traceability. + * For testing and debugging, it can be really useful to know where some value originated from. For + * example, imagine an error during you analysis of a {@link VariationDiff}. Now you want to know + * where the variation diff came from. The solution: DiffDetectice tracks the source and + * transformations of important data structures like {@link VariationDiff#getSource()} and allows + * you to inspect them using this interface. + *

+ * {@link Source} represents a hierarchy of inputs, processing steps, and results (the distinction + * between these types is not directly reflected in the API and only serve as explanation of the + * interface). + *

    + *
  • An input (e.g., {@link FileSource}) is a {@link Source} that has no {@link getSources + * children}. Inputs are the leafs of the {@link Source} hierarchy and contain the {@link + * getSourceExplanation name of the input} (e.g., {@code "file"} or {@code "URL"}) and {@link + * getSourceArguments some arguments} (e.g., the file name or URL). + *
  • A step (e.g., {@link ProjectionSource}) processed {@link getSources one or more sources}, has + * a {@link getSourceExplanation name} and may be configurable by some {@link getSourceArguments + * arguments}. Unconfigurable processing steps (or steps whose configurability should not be + * tracked) are most commonly represented by {@link CompositeSource}. Note that a step might also + * include (interim) results and might thus be considered a result too. + *
  • A result (e.g., {@link VariationDiff}) contains some data and the {@link getSources source of + * that data}. The contained data is not directly accessible by {@link Source this interface} but + * can be obtained by looking up the subclass representing that result with {@link findFirst} or + * {@link findAll}. Results are not necessary to trace the source of some data (and it might thus be + * omitted from the {@link Source} hierarchy) but can help to identify a buggy processing step. + *
+ *

+ * In summary, each {@link Source} in the {@link getSources hierarchy} has a {@link + * getSourceExplanation short explanation} and a number of {@link getSourceArguments arguments}. A + * source may contain additional data (e.g., the diff a variation diff originated from) that is too + * big to be printed by the standard formatting functions ({@link shallowExplanation}, {@link + * shortExplanation}, {@link fullExplanation}) of this interface. To access such data, or the + * arguments of specific subclasses, you can use {@link findFirst} and {@link findAll}. + *

+ * For dealing with generic sources, it is recommended to use the static methods of this interface. + * They provide a more readable interface (e.g., {@code variationDiff.shallowExplanation()} vs. + * {@code Source.shallowExplanation(variationDiff)}) and correctly deal with {@code null} sources + * (treated as {@link Unknown}). + * + * @see CompositeSource + * @see FileSource + */ +public interface Source { + /** + * Returns a short, one line explanation or identifier of this source. + * The result is used by {@link shallowExplanation} and is formatted together with {@link + * getSourceArguments} like {@code "sourceExplanation(argument1, argument2)"}. + */ + String getSourceExplanation(); + + /** + * Returns a list of arguments required to understand this source. + * Each argument in the result list should represent a {@link Object#toString string} without + * newlines and will be formatted together with {@link getSourceExplanation} like {@code + * "sourceExplanation(argument1, argument2)"} by {@link shallowExplanation}. This method is only + * intended to access the arguments for {@link Object#toString printing}. In case access to the + * well-typed object is required, use {@link findFirst} or {@link findAll} and use the accessors + * of the underlying type. + *

+ * For ease of implementing {@code Source}, the return value is a list of {@link Object}s + * instead of a list of {@link String}s to allow code like {@code List.of(arg0, arg1, arg2)} + * instead of {@code List.of(arg0.toString(), arg1.toString(), arg2.toString())}. Users of this + * function should assume nothing from the returned objects except that {@link Object#toString} + * is well behaved. + * + * @return an empty list by default + * @see shallowExplanation + */ + default List getSourceArguments() { + return Collections.emptyList(); + } + + /** + * Returns a list of sources that influenced this source. + * Noteworthy processing steps and incorporation of multiple sources are implemented as a tree + * of sources. This functions returns the children of this source. + *

+ * By default, the first child source is treated specially in {@link getRootSource}. + * + * @return an empty list by default + */ + default List getSources() { + return Collections.emptyList(); + } + + /** + * An explanation of this source disregarding all {@link getSources child sources}. + * The resulting string should not contain any newlines. + * + * @return {@code "sourceExplanation(argument1, argument2, ...)"} by default + * @see getSourceExplanation + * @see getSourceArguments + */ + default String shallowExplanation() { + return getSourceArguments() + .stream() + .map(Object::toString) + .collect(Collectors.joining(", ", getSourceExplanation() + "(", ")")); + } + + /** + * Calls {@link shallowExplanation} on {@code source} but handles {@code null} like {@link + * Unknown}. + */ + static String shallowExplanation(Source source) { + return (source == null ? Unknown : source).shallowExplanation(); + } + + /** + * Returns one representative {@link Source} that identifies the initial data. + * A good root source should help humans to understand at what data they are looking at. Good + * examples is a filename or a commit and repository combination. Note that sometimes this is an + * arbitrary decision (e.g., the state before or after a diff) and might depend on the problem + * that is currently worked on. Hence, there is might not be one correct result in all + * circumstances. + * + * @return the {@link getSources first child} or {@code this} if there are no {@link getSources children} + */ + default Source getRootSource() { + for (var source : getSources()) { + if (source != null) { + return source.getRootSource(); + } + } + + return this; + } + + /** + * Returns a {@link shallowExplanation} of {@link getRootSource the root source} of {@code source}. + */ + static String rootExplanation(Source source) { + return (source == null ? Unknown : source.getRootSource()) + .shallowExplanation(); + } + + /** + * Returns a short one line explanation of {@code source} by pairing {@link shortExplanation} + * with {@link rootExplanation}. + */ + static String shortExplanation(Source source) { + if (source == null) { + return Unknown.shallowExplanation(); + } + + Source root = source.getRootSource(); + if (root == source) { + return source.shallowExplanation(); + } else { + return source.shallowExplanation() + " of " + root.shallowExplanation(); + } + } + + /** + * Explains {@code this} source hierarchy in detail. + * The {@code result} will contain at least one line per {@link Source} in the {@link getSources + * hierarchy} (by default in the {@link shallowExplanation} format). The state of {@code result} + * must be in a new line before and after a call to this method. + * + * @param level the current indentation level (two spaces per level) that needs to be added + * before each line + * @param result the string containing the result of this method + */ + default void fullExplanation(int level, StringBuilder result) { + for (int i = 0; i < level; ++i) { + result.append(" "); + } + result.append(shallowExplanation(this)); + result.append("\n"); + + for (var child : getSources()) { + if (child == null) { + Unknown.fullExplanation(level + 1, result); + } else { + child.fullExplanation(level + 1, result); + } + } + } + + /** + * Calls {@link fullExplanation(int, StringBuilder)} on {@code source} but handles {@code null} + * like {@link Unknown} and returns the resulting string. + */ + static String fullExplanation(Source source) { + if (source == null) { + return fullExplanation(Unknown); + } + + var result = new StringBuilder(); + source.fullExplanation(0, result); + return result.toString(); + } + + /** + * Uses a depth first traversal of the {@link Source} {@link getSources hierarchy} to find and + * return all instances of {@code clazz}. + * This method is intended to provide access to the data contained in the source hierarchy. For + * example, {@code Source.findAll(src, File.class)} finds all source files. Note that arguments + * are not inspected. In case an implementation {@link Source} contains a {@link Path file} as + * {@link getSourceArguments argument} it is not included in the result. + * + * @see findFirst + */ + static List findAll(Source source, Class clazz) { + var result = new ArrayList(); + findAll(result, source, clazz); + return result; + } + + /** + * Implementation of {@link findAll(Source, Class)} using an {@code result} accumulator. + */ + private static void findAll(List result, Source source, Class clazz) { + if (source == null) { + return; + } + + if (source.getClass() == clazz) { + result.add(Cast.unchecked(source)); + } + + for (var child : source.getSources()) { + findAll(result, child, clazz); + } + } + + /** + * Uses a depth first traversal of the {@link Source} {@link getSources hierarchy} to find and + * return the first instances of {@code clazz}. + * This method is intended to provide access to the data contained in the source hierarchy. For + * example, {@code Source.findFirst(src, PatchDiff.class)} finds the first {@link PatchDiff}. + * This allows access to data and arguments like {@link PatchDiff#getDiff} but it does not + * traverse the {@link getSourceArguments} directly. + * + * @see findAll + */ + static T findFirst(Source source, Class clazz) { + if (source == null) { + return null; + } + + if (source.getClass() == clazz) { + return Cast.unchecked(source); + } + + for (var child : source.getSources()) { + var result = findFirst(child, clazz); + if (result != null) { + return result; + } + } + + return null; + } + + /** + * A placeholder for an unknown source without any explanation. + * Should be preferred to a {@code null} {@link Source} to make it easily grepable. + */ + public static Source Unknown = new CompositeSource("Unknown"); +} diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/DiffGraph.java b/src/main/java/org/variantsync/diffdetective/variation/diff/DiffGraph.java index df1d7e6e6..24da64f76 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/DiffGraph.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/DiffGraph.java @@ -1,7 +1,7 @@ package org.variantsync.diffdetective.variation.diff; +import org.variantsync.diffdetective.util.Source; import org.variantsync.diffdetective.variation.DiffLinesLabel; -import org.variantsync.diffdetective.variation.diff.source.VariationDiffSource; import java.util.Collection; @@ -20,10 +20,10 @@ public final class DiffGraph { private DiffGraph() {} /** - * Invokes {@link DiffGraph#fromNodes(Collection, VariationDiffSource)} )} with an unknown VariationDiffSource. + * Invokes {@link DiffGraph#fromNodes(Collection, Source)} )} with an unknown source. */ public static VariationDiff fromNodes(final Collection> nodes) { - return fromNodes(nodes, VariationDiffSource.Unknown); + return fromNodes(nodes, Source.Unknown); } /** @@ -33,7 +33,7 @@ public static VariationDiff fromNodes(final Collection fromNodes(final Collection> nodes, final VariationDiffSource source) { + public static VariationDiff fromNodes(final Collection> nodes, final Source source) { final DiffNode newRoot = DiffNode.createRoot(DiffLinesLabel.ofCodeBlock(DIFFGRAPH_LABEL)); nodes.stream() .filter(DiffNode::isRoot) diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/ProjectionSource.java b/src/main/java/org/variantsync/diffdetective/variation/diff/ProjectionSource.java index 7f277b69c..5f6225af1 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/ProjectionSource.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/ProjectionSource.java @@ -1,7 +1,23 @@ package org.variantsync.diffdetective.variation.diff; +import java.util.List; + +import org.variantsync.diffdetective.util.Source; import org.variantsync.diffdetective.variation.Label; -import org.variantsync.diffdetective.variation.tree.source.VariationTreeSource; -public record ProjectionSource(VariationDiff origin, Time time) implements VariationTreeSource { +public record ProjectionSource(VariationDiff origin, Time time) implements Source { + @Override + public String getSourceExplanation() { + return "Projection"; + } + + @Override + public List getSources() { + return List.of(origin); + } + + @Override + public List getSourceArguments() { + return List.of(time); + } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/VariabilityAwareDiffer.java b/src/main/java/org/variantsync/diffdetective/variation/diff/VariabilityAwareDiffer.java new file mode 100644 index 000000000..11e5a60fa --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/VariabilityAwareDiffer.java @@ -0,0 +1,56 @@ +package org.variantsync.diffdetective.variation.diff; + +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.nio.file.Path; + +import org.variantsync.diffdetective.diff.result.DiffParseException; +import org.variantsync.diffdetective.util.FileSource; +import org.variantsync.diffdetective.util.Source; +import org.variantsync.diffdetective.variation.Label; +import org.variantsync.diffdetective.variation.tree.VariationTree; // For Javadoc + +/** + * A generic interface for creating variation diffs from two states represented as text. + * This interface represents a differ that can walk any of the paths present in our visual abstract + * (see below) and thus the states before and after the edit are represented as text. For each of + * the two paths, parsing a text diff and diffing variation trees, there is a specialized version of + * this interface, {@link VariabilityAwareTextDiffer} and {@link VariabilityAwareTreeDiffer} + * respectively. In particular, {@link VariabilityAwareTreeDiffer} represents the input states as + * {@link VariationTree}s. + * + * Variability-Aware Differencing Overview. Same as in the README.md. + * + * @see VariabilityAwareDiffers + */ +@FunctionalInterface +public interface VariabilityAwareDiffer { + /** + * Create a variation diff by diffing the content of two {@link Reader}s. + * For {@link VariationDiff#getSource() traceability}, both {@link Reader}s are paired with a {@link Source}. + */ + VariationDiff diff(Reader before, Reader after, Source beforeSource, Source afterSource) throws IOException, DiffParseException; + + /** + * Create a variation diff by diffing the content of two {@link String}s. + * For {@link VariationDiff#getSource() traceability}, both {@link String}s are paired with a {@link Source}. + */ + default VariationDiff diff(String before, String after, Source beforeSource, Source afterSource) throws IOException, DiffParseException { + return diff(new StringReader(before), new StringReader(after), beforeSource, afterSource); + } + + /** + * Create a variation diff by diffing the content of two {@link Path}s. + * @see diff(Reader, Reader, Source, Source) + */ + default VariationDiff diff(Path before, Path after) throws IOException, DiffParseException { + try ( + Reader beforeStream = new FileReader(before.toFile()); + Reader afterStream = new FileReader(after.toFile()) + ) { + return diff(beforeStream, afterStream, new FileSource(before), new FileSource(after)); + } + } +} diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/VariabilityAwareDiffers.java b/src/main/java/org/variantsync/diffdetective/variation/diff/VariabilityAwareDiffers.java new file mode 100644 index 000000000..ce790d696 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/VariabilityAwareDiffers.java @@ -0,0 +1,135 @@ +package org.variantsync.diffdetective.variation.diff; + +import java.io.IOException; +import java.io.Reader; + +import org.apache.commons.io.IOUtils; +import org.eclipse.jgit.diff.DiffAlgorithm; +import org.variantsync.diffdetective.diff.result.DiffParseException; +import org.variantsync.diffdetective.util.CompositeSource; +import org.variantsync.diffdetective.util.Source; +import org.variantsync.diffdetective.variation.DiffLinesLabel; +import org.variantsync.diffdetective.variation.diff.construction.GumTreeDiff; +import org.variantsync.diffdetective.variation.diff.construction.JGitDiff; +import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; +import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParser; // For Javadoc +import org.variantsync.diffdetective.variation.tree.VariationTree; + +import com.github.gumtreediff.matchers.Matcher; +import com.github.gumtreediff.matchers.Matchers; + +/** + * A collection of differs that create variation diffs. + * @see VariabilityAwareDiffer + * @see VariationDiff + */ +public class VariabilityAwareDiffers { + /** + * Returns a differ that {@link VariationDiffParser#createVariationDiff parses} a + * {@link VariationDiff} after {@link JGitDiff#textDiff diffing} with + * {@link DiffAlgorithm.SupportedAlgorithm one of JGit's differ}. + * + * @param lineDiffAlgorithm the Git diffing algorithm to use + * @param parseOptions options for parsing the {@link VariationDiff} + * @return the diffed {@link VariationDiff} + */ + public static VariabilityAwareTextDiffer JGit(DiffAlgorithm.SupportedAlgorithm lineDiffAlgorithm, VariationDiffParseOptions parseOptions) { + return new VariabilityAwareTextDiffer() { + @Override + public String diffLines(Reader before, Reader after) throws IOException { + return JGitDiff.textDiff(IOUtils.toString(before), IOUtils.toString(after), lineDiffAlgorithm); + } + + @Override + public String diffLines(String before, String after) throws IOException { + return JGitDiff.textDiff(before, after, lineDiffAlgorithm); + } + + @Override + public VariationDiffParseOptions getParseOptions() { + return parseOptions; + } + }; + } + + /** + * Calls {@link JGit(DiffAlgorithm.SupportedAlgorithm, VariationDiffParseOptions)} with + * {@link DiffAlgorithm.SupportedAlgorithm#MYERS} and {@link VariationDiffParseOptions#Default}. + */ + public static VariabilityAwareTextDiffer JGitMyers() { + return JGit(DiffAlgorithm.SupportedAlgorithm.MYERS, VariationDiffParseOptions.Default); + } + + /** + * Returns a differ that {@link VariationTree#fromFile parses} two variation trees and then + * {@link GumTreeDiff#diffUsingMatching diffs} these variation trees. + * + * @param parseOptions options for parsing the {@link VariationTree}s + * @param matcher the matcher used for diffing the {@link VariationTree}s + * @return the diffed {@link VariationDiff} + */ + public static VariabilityAwareTreeDiffer GumTree(VariationDiffParseOptions parseOptions, Matcher matcher) { + return new VariabilityAwareTreeDiffer() { + @Override + public VariationDiff diffTrees(VariationTree before, VariationTree after) { + return GumTreeDiff.diffUsingMatching(before, after, matcher); + } + + @Override + public VariationDiffParseOptions getParseOptions() { + return parseOptions; + } + }; + } + + /** + * Calls {@link GumTree(VariationDiffParseOptions, Matcher)} with {@link + * VariationDiffParseOptions#Default} and {@link Matchers#getInstance {@code + * Matchers.getInstance().getMatcher()}}. + */ + public static VariabilityAwareTreeDiffer GumTree() { + return GumTree(VariationDiffParseOptions.Default, Matchers.getInstance().getMatcher()); + } + + /** + * Returns a differ that first parses a {@link VariationDiff} like {@link JGit} and then + * {@link GumTreeDiff#improveMatching improves} the represented matching. + * + * @param lineDiffAlgorithm the Git diffing algorithm to use + * @param parseOptions options for parsing the {@link VariationDiff} + * @param matcher the matcher used for diffing the variation trees + * @return the diffed {@link VariationDiff} + */ + public static VariabilityAwareTextDiffer JGitGumTreeHybrid(DiffAlgorithm.SupportedAlgorithm lineDiffAlgorithm, VariationDiffParseOptions parseOptions, Matcher matcher) { + return new VariabilityAwareTextDiffer() { + @Override + public String diffLines(Reader before, Reader after) throws IOException { + return JGitDiff.textDiff(IOUtils.toString(before), IOUtils.toString(after), lineDiffAlgorithm); + } + + @Override + public VariationDiffParseOptions getParseOptions() { + return parseOptions; + } + + @Override + public VariationDiff diff(Reader before, Reader after, Source beforeSource, Source afterSource) throws IOException, DiffParseException { + VariationDiff vd = VariabilityAwareTextDiffer.super.diff(before, after, beforeSource, afterSource); + return new VariationDiff<>( + GumTreeDiff.improveMatching(vd.getRoot(), matcher), + new CompositeSource("GumTreeDiff.improveMatching", vd.getSource()) + ); + }; + }; + } + + /** + * Calls {@link JGitGumTreeHybrid(DiffAlgorithm.SupportedAlgorithm, VariationDiffParseOptions, + * Matcher)} with {@link DiffAlgorithm.SupportedAlgorithm#MYERS}, {@link + * VariationDiffParseOptions#Default}, and {@link Matchers#getInstance {@code + * Matchers.getInstance().getMatcher()}}. + */ + public static VariabilityAwareTextDiffer JGitMyersGumTreeHybrid() { + return JGitGumTreeHybrid(DiffAlgorithm.SupportedAlgorithm.MYERS, VariationDiffParseOptions.Default, Matchers.getInstance().getMatcher()); + } +} diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/VariabilityAwareTextDiffer.java b/src/main/java/org/variantsync/diffdetective/variation/diff/VariabilityAwareTextDiffer.java new file mode 100644 index 000000000..0fb6042c9 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/VariabilityAwareTextDiffer.java @@ -0,0 +1,62 @@ +package org.variantsync.diffdetective.variation.diff; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + +import org.variantsync.diffdetective.diff.result.DiffParseException; +import org.variantsync.diffdetective.util.CompositeSource; +import org.variantsync.diffdetective.util.Source; +import org.variantsync.diffdetective.variation.DiffLinesLabel; +import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; +import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParser; + +/** + * A {@link VariabilityAwareDiffer} that first creates a line diff and + * {@link VariationDiffParser#createVariationDiff parses} this line diff into a + * {@link VariationDiff}. + */ +@FunctionalInterface +public interface VariabilityAwareTextDiffer extends VariabilityAwareDiffer { + /** + * Returns a line diff of {@code before} and {@code after} in the unified diff format. + */ + String diffLines(Reader before, Reader after) throws IOException; + + /** + * Returns a line diff of {@code before} and {@code after} in the unified diff format. + */ + default String diffLines(String before, String after) throws IOException { + return diffLines(new StringReader(before), new StringReader(after)); + } + + /** + * Returns the options used for {@link parseDiff parsing} the {@link VariationDiff} in {@link parseDiff}. + */ + default VariationDiffParseOptions getParseOptions() { + return VariationDiffParseOptions.Default; + } + + /** + * Parses the {@link diffLines line diff} into a {@link VariationDiff} using the options + * from {@link getParseOptions}. + */ + default VariationDiff parseDiff(String diff, Source diffSource) throws DiffParseException { + return VariationDiffParser.createVariationDiff( + diff, + new CompositeSource("VariationDiffParser.createVariationDiff", diffSource), + getParseOptions() + ); + } + + /** + * Composes {@link diffLines} and {@link parseDiff} to create a {@link VariationDiff}. + */ + @Override + default VariationDiff diff(Reader before, Reader after, Source beforeSource, Source afterSource) throws IOException, DiffParseException { + return parseDiff( + diffLines(before, after), + new CompositeSource("line diff", beforeSource, afterSource) + ); + } +} diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/VariabilityAwareTreeDiffer.java b/src/main/java/org/variantsync/diffdetective/variation/diff/VariabilityAwareTreeDiffer.java new file mode 100644 index 000000000..ec7aa0a6c --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/VariabilityAwareTreeDiffer.java @@ -0,0 +1,50 @@ +package org.variantsync.diffdetective.variation.diff; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; + +import org.variantsync.diffdetective.diff.result.DiffParseException; +import org.variantsync.diffdetective.util.Source; +import org.variantsync.diffdetective.variation.DiffLinesLabel; +import org.variantsync.diffdetective.variation.Label; +import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; +import org.variantsync.diffdetective.variation.tree.VariationTree; + +/** + * A {@link VariabilityAwareDiffer} that first {@link VariationTree#fromFile parses} {@link VariationTree}s and + * diffs these trees. + */ +@FunctionalInterface +public interface VariabilityAwareTreeDiffer extends VariabilityAwareDiffer { + /** + * Creates a {@link VariationDiff} by diffing two variation trees. + */ + VariationDiff diffTrees(VariationTree before, VariationTree after); + + /** + * Returns the options used for parsing the {@link VariationTree}s in {@link parseTree}. + */ + default VariationDiffParseOptions getParseOptions() { + return VariationDiffParseOptions.Default; + } + + /** + * {@link VariationTree#fromFile Parses} {@code input} into a {@link VariationTree} using + * the options from {@code getParseOptions}. + */ + default VariationTree parseTree(Reader input, Source source) throws IOException, DiffParseException { + return VariationTree.fromFile(new BufferedReader(input), source, getParseOptions()); + } + + /** + * Composes {@link parseTree} and {@link diffTrees} to create a {@link VariationDiff}. + */ + @Override + default VariationDiff diff(Reader before, Reader after, Source beforeSource, Source afterSource) throws IOException, DiffParseException { + return diffTrees( + parseTree(before, beforeSource), + parseTree(after, afterSource) + ); + } +} diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/VariationDiff.java b/src/main/java/org/variantsync/diffdetective/variation/diff/VariationDiff.java index 0d6c365bb..fea28736f 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/VariationDiff.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/VariationDiff.java @@ -12,19 +12,18 @@ import org.variantsync.diffdetective.diff.result.DiffError; import org.variantsync.diffdetective.diff.result.DiffParseException; import org.variantsync.diffdetective.util.Assert; +import org.variantsync.diffdetective.util.FileSource; +import org.variantsync.diffdetective.util.fide.FixTrueFalse; import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.Label; import org.variantsync.diffdetective.variation.diff.construction.GumTreeDiff; import org.variantsync.diffdetective.variation.diff.construction.JGitDiff; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParser; -import org.variantsync.diffdetective.variation.diff.source.PatchFile; -import org.variantsync.diffdetective.variation.diff.source.PatchString; -import org.variantsync.diffdetective.variation.diff.source.VariationDiffSource; import org.variantsync.diffdetective.variation.diff.traverse.VariationDiffTraversal; import org.variantsync.diffdetective.variation.diff.traverse.VariationDiffVisitor; import org.variantsync.diffdetective.variation.tree.VariationTree; -import org.variantsync.diffdetective.util.fide.FixTrueFalse; +import org.variantsync.diffdetective.util.Source; import org.variantsync.functjonal.Cast; import org.variantsync.functjonal.Result; @@ -50,23 +49,23 @@ /** * Implementation of variation tree diffs from our ESEC/FSE'22 paper. * An instance of this class represents a variation tree diff. It stores the root of the graph as a {@link DiffNode}. - * It optionally holds a {@link VariationDiffSource} that describes how the variation tree diff was obtained. + * It optionally holds a {@link Source} that describes how the variation tree diff was obtained. * The graph structure is implemented by the {@link DiffNode} class. * * @param The type of label stored in this tree. * * @author Paul Bittner, Sören Viegener */ -public class VariationDiff { +public class VariationDiff implements Source { private final DiffNode root; - private VariationDiffSource source; + private Source source; /** * Creates a VariationDiff that only consists of the single given root node. * @param root The root of this tree. */ public VariationDiff(DiffNode root) { - this(root, VariationDiffSource.Unknown); + this(root, Source.Unknown); } /** @@ -75,7 +74,7 @@ public VariationDiff(DiffNode root) { * @param root The root of this tree. * @param source The data from which the VariationDiff was created. */ - public VariationDiff(DiffNode root, VariationDiffSource source) { + public VariationDiff(DiffNode root, Source source) { this.root = root; this.source = source; } @@ -91,9 +90,7 @@ public VariationDiff(DiffNode root, VariationDiffSource source) { */ public static VariationDiff fromFile(final Path p, VariationDiffParseOptions parseOptions) throws IOException, DiffParseException { try (BufferedReader file = Files.newBufferedReader(p)) { - final VariationDiff tree = VariationDiffParser.createVariationDiff(file, parseOptions); - tree.setSource(new PatchFile(p)); - return tree; + return VariationDiffParser.createVariationDiff(file, new FileSource(p), parseOptions); } } @@ -103,13 +100,13 @@ public static VariationDiff fromFile(final Path p, VariationDiff * So just lines preceded by "+", "-", or " " are expected. * @param diff The diff as text. Lines should be separated by a newline character. Each line should be preceded by either "+", "-", or " ". * @param parseOptions {@link VariationDiffParseOptions} for the parsing process. + * @param source the {@link Source} of {@code diff} * @return A result either containing the parsed VariationDiff or an error message in case of failure. * @throws DiffParseException if {@code diff} couldn't be parsed */ - public static VariationDiff fromDiff(final String diff, final VariationDiffParseOptions parseOptions) throws DiffParseException { - final VariationDiff d; + public static VariationDiff fromDiff(final String diff, final Source source, final VariationDiffParseOptions parseOptions) throws DiffParseException { try { - d = VariationDiffParser.createVariationDiff(diff, parseOptions); + return VariationDiffParser.createVariationDiff(diff, source, parseOptions); } catch (DiffParseException e) { Logger.error(""" Could not parse diff: @@ -119,8 +116,6 @@ public static VariationDiff fromDiff(final String diff, final Va diff); throw e; } - d.setSource(new PatchString(diff)); - return d; } /** @@ -164,7 +159,7 @@ public static Result, List> fromPatch(f /** * Create a VariationDiff from two given text files. - * @see #fromLines(String, String, DiffAlgorithm.SupportedAlgorithm, VariationDiffParseOptions) + * @see #fromLines(String, String, Source, Source, DiffAlgorithm.SupportedAlgorithm, VariationDiffParseOptions) */ public static VariationDiff fromFiles( final Path beforeFile, @@ -176,22 +171,24 @@ public static VariationDiff fromFiles( try (BufferedReader b = Files.newBufferedReader(beforeFile); BufferedReader a = Files.newBufferedReader(afterFile) ) { - return fromLines(IOUtils.toString(b), IOUtils.toString(a), algorithm, options); + return fromLines(IOUtils.toString(b), IOUtils.toString(a), new FileSource(beforeFile), new FileSource(afterFile), algorithm, options); } } /** * Creates a variation diff from to line-based text inputs. * This method just forwards to: - * @see JGitDiff#diff(String, String, DiffAlgorithm.SupportedAlgorithm, VariationDiffParseOptions) + * @see JGitDiff#diff(String, String, Source, Source, DiffAlgorithm.SupportedAlgorithm, VariationDiffParseOptions) */ public static VariationDiff fromLines( String before, String after, + Source beforeSource, + Source afterSource, DiffAlgorithm.SupportedAlgorithm algorithm, VariationDiffParseOptions options) throws IOException, DiffParseException { - return JGitDiff.diff(before, after, algorithm, options); + return JGitDiff.diff(before, after, beforeSource, afterSource, algorithm, options); } /** @@ -396,20 +393,30 @@ public LinkedHashSet computeAllFeatureNames() { /** * Sets the source of this VariationDiff. - * @see VariationDiffSource + * @see Source */ - public void setSource(final VariationDiffSource source) { + public void setSource(final Source source) { this.source = source; } /** * Returns the source of this VariationDiff (i.e., the data this VariationDiff was created from). - * @see VariationDiffSource + * @see Source */ - public VariationDiffSource getSource() { + public Source getSource() { return source; } + @Override + public List getSources() { + return List.of(source); + } + + @Override + public String getSourceExplanation() { + return "VariationDiff"; + } + /** * Returns the number of nodes in this VariationDiff. */ diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/bad/BadVDiff.java b/src/main/java/org/variantsync/diffdetective/variation/diff/bad/BadVDiff.java index f7508ee41..f565ce20a 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/bad/BadVDiff.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/bad/BadVDiff.java @@ -2,13 +2,13 @@ import org.variantsync.diffdetective.diff.text.DiffLineNumberRange; import org.variantsync.diffdetective.util.Assert; +import org.variantsync.diffdetective.util.CompositeSource; import org.variantsync.diffdetective.util.StringUtils; import org.variantsync.diffdetective.variation.Label; import org.variantsync.diffdetective.variation.diff.DiffNode; import org.variantsync.diffdetective.variation.diff.VariationDiff; import org.variantsync.diffdetective.variation.diff.DiffType; import org.variantsync.diffdetective.variation.diff.Time; -import org.variantsync.diffdetective.variation.diff.source.VariationDiffSource; import org.variantsync.diffdetective.variation.tree.VariationTree; import org.variantsync.diffdetective.variation.tree.VariationTreeNode; import org.variantsync.functjonal.Cast; @@ -309,7 +309,7 @@ public EdgeToConstruct( } return new BadVDiff<>( - new VariationTree<>(root, new BadVDiffFromVariationDiffSource(d.getSource())), + new VariationTree<>(root, new CompositeSource("BadVDiff.fromGood", d.getSource())), matching, coloring, lines @@ -403,12 +403,7 @@ public EdgeToConstruct( nodeTranslation.get(e.parent()).addChild(e.child(), e.time()); } - VariationDiffSource source = VariationDiffSource.Unknown; - if (diff.source() instanceof BadVDiffFromVariationDiffSource s) { - source = s.initialVariationDiff(); - } - - return new VariationDiff<>(root, source); + return new VariationDiff<>(root, new CompositeSource("BadVDiff.toGood", diff.getSource())); } public BadVDiff deepCopy() { diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/bad/BadVDiffFromVariationDiffSource.java b/src/main/java/org/variantsync/diffdetective/variation/diff/bad/BadVDiffFromVariationDiffSource.java deleted file mode 100644 index ceb69d515..000000000 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/bad/BadVDiffFromVariationDiffSource.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.variantsync.diffdetective.variation.diff.bad; - -import org.variantsync.diffdetective.variation.diff.source.VariationDiffSource; -import org.variantsync.diffdetective.variation.tree.source.VariationTreeSource; - -public record BadVDiffFromVariationDiffSource(VariationDiffSource initialVariationDiff) implements VariationTreeSource { - @Override - public String toString() { - return initialVariationDiff.toString(); - } -} diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/construction/GumTreeDiff.java b/src/main/java/org/variantsync/diffdetective/variation/diff/construction/GumTreeDiff.java index bb0d37309..83273dce3 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/construction/GumTreeDiff.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/construction/GumTreeDiff.java @@ -9,11 +9,11 @@ import org.variantsync.diffdetective.gumtree.VariationDiffAdapter; import org.variantsync.diffdetective.gumtree.VariationTreeAdapter; import org.variantsync.diffdetective.util.Assert; +import org.variantsync.diffdetective.util.CompositeSource; import org.variantsync.diffdetective.variation.Label; import org.variantsync.diffdetective.variation.diff.DiffNode; import org.variantsync.diffdetective.variation.diff.Time; import org.variantsync.diffdetective.variation.diff.VariationDiff; -import org.variantsync.diffdetective.variation.diff.source.VariationTreeDiffSource; import org.variantsync.diffdetective.variation.diff.traverse.VariationDiffTraversal; import org.variantsync.diffdetective.variation.tree.VariationNode; import org.variantsync.diffdetective.variation.tree.VariationTree; @@ -48,7 +48,7 @@ public static VariationDiff diffUsingMatching(VariationTree matcher ); - return new VariationDiff<>(root, new VariationTreeDiffSource(before.source(), after.source())); + return new VariationDiff<>(root, new CompositeSource("diffUsingMatching", before.source(), after.source())); } /** diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/construction/JGitDiff.java b/src/main/java/org/variantsync/diffdetective/variation/diff/construction/JGitDiff.java index 6d2920585..abd988811 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/construction/JGitDiff.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/construction/JGitDiff.java @@ -4,6 +4,8 @@ import org.eclipse.jgit.diff.*; import org.variantsync.diffdetective.diff.git.GitDiffer; import org.variantsync.diffdetective.diff.result.DiffParseException; +import org.variantsync.diffdetective.util.CompositeSource; +import org.variantsync.diffdetective.util.Source; import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.diff.Time; import org.variantsync.diffdetective.variation.diff.VariationDiff; @@ -122,9 +124,11 @@ Using our own formatter without diff headers (paired with a maximum context (?)) * Uses JGit to diff the two files using the specified {@code options}, and afterwards, creates the variation diff. * Creates a variation diff from to line-based text inputs. * First creates a line-based diff with {@link #textDiff(String, String, DiffAlgorithm.SupportedAlgorithm)} - * and then parses that diff with {@link VariationDiff#fromDiff(String, VariationDiffParseOptions)}. + * and then parses that diff with {@link VariationDiff#fromDiff(String, Source, VariationDiffParseOptions)}. * @param linesBefore State of annotated lines before the change. * @param linesAfter State of annotated lines after the change. + * @param sourceBefore the {@link Source} of {@code linesBefore} + * @param sourceAfter the {@link Source} of {@code linesAfter} * @param algorithm Specification of which algorithm to use for diffing with JGit. * @param options various options for parsing * @return A variation diff comprising the changes. @@ -134,9 +138,15 @@ Using our own formatter without diff headers (paired with a maximum context (?)) public static VariationDiff diff( String linesBefore, String linesAfter, + Source sourceBefore, + Source sourceAfter, DiffAlgorithm.SupportedAlgorithm algorithm, VariationDiffParseOptions options ) throws IOException, DiffParseException { - return VariationDiff.fromDiff(textDiff(linesBefore, linesAfter, algorithm), options); + return VariationDiff.fromDiff( + textDiff(linesBefore, linesAfter, algorithm), + new CompositeSource("JGitDiff.textDiff", sourceBefore, sourceAfter), + options + ); } } diff --git a/docs/variability-aware-differencing.png b/src/main/java/org/variantsync/diffdetective/variation/diff/doc-files/variability-aware-differencing.png similarity index 100% rename from docs/variability-aware-differencing.png rename to src/main/java/org/variantsync/diffdetective/variation/diff/doc-files/variability-aware-differencing.png diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java index 9f11bc653..f8b1f2cc2 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/parse/VariationDiffParser.java @@ -17,6 +17,7 @@ import org.variantsync.diffdetective.feature.Annotation; import org.variantsync.diffdetective.feature.AnnotationType; import org.variantsync.diffdetective.util.Assert; +import org.variantsync.diffdetective.util.Source; import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.NodeType; import org.variantsync.diffdetective.variation.diff.DiffNode; @@ -117,17 +118,18 @@ public record DiffLine(DiffType diffType, String content) { /** - * The same as {@link VariationDiffParser#createVariationDiff(BufferedReader, VariationDiffParseOptions)} + * The same as {@link VariationDiffParser#createVariationDiff(BufferedReader, Source, VariationDiffParseOptions)} * but with the diff given as a single string with line breaks instead of a {@link BufferedReader}. * * @throws DiffParseException if {@code fullDiff} couldn't be parsed */ public static VariationDiff createVariationDiff( final String fullDiff, + final Source source, final VariationDiffParseOptions parseOptions ) throws DiffParseException { try { - return createVariationDiff(new BufferedReader(new StringReader(fullDiff)), parseOptions); + return createVariationDiff(new BufferedReader(new StringReader(fullDiff)), source, parseOptions); } catch (IOException e) { throw new AssertionError("No actual IO should be performed because only a StringReader is used"); } @@ -140,6 +142,7 @@ public static VariationDiff createVariationDiff( * This parsing algorithm is described in detail in Sören Viegener's bachelor's thesis. * * @param fullDiff The full diff of a patch obtained from a buffered reader. + * @param source the {@link Source} of {@code fullDiff} * @param options {@link VariationDiffParseOptions} for the parsing process. * @return A parsed {@link VariationDiff} upon success or an error indicating why parsing failed. * @throws IOException when reading from {@code fullDiff} fails. @@ -147,11 +150,12 @@ public static VariationDiff createVariationDiff( */ public static VariationDiff createVariationDiff( BufferedReader fullDiff, + Source source, final VariationDiffParseOptions options ) throws IOException, DiffParseException { return new VariationDiffParser( options - ).parse(() -> { + ).parse(source, () -> { String line = fullDiff.readLine(); if (line == null) { return null; @@ -162,10 +166,11 @@ public static VariationDiff createVariationDiff( /** * Parses a variation tree from a source file. - * This method is similar to {@link #createVariationDiff(BufferedReader, VariationDiffParseOptions)} + * This method is similar to {@link #createVariationDiff(BufferedReader, Source, VariationDiffParseOptions)} * but acts as if all lines where unmodified. * * @param file The source code file (not a diff) to be parsed. + * @param source the {@link Source} of {@code file} * @param options {@link VariationDiffParseOptions} for the parsing process. * @return A parsed {@link VariationDiff}. * @throws IOException iff {@code file} throws an {@code IOException} @@ -173,11 +178,12 @@ public static VariationDiff createVariationDiff( */ public static VariationDiff createVariationTree( BufferedReader file, + Source source, VariationDiffParseOptions options ) throws IOException, DiffParseException { return new VariationDiffParser( options - ).parse(() -> { + ).parse(source, () -> { String line = file.readLine(); if (line == null) { return null; @@ -198,7 +204,7 @@ public static VariationDiff createVariationTree( /** * Initializes the parse state. * - * @see #createVariationDiff(BufferedReader, VariationDiffParseOptions) + * @see #createVariationDiff(BufferedReader, Source, VariationDiffParseOptions) */ private VariationDiffParser( VariationDiffParseOptions options @@ -209,6 +215,7 @@ private VariationDiffParser( /** * Parses the line diff {@code fullDiff}. * + * @param source the {@link Source} of {@code lines} * @param lines should supply successive lines of the diff to be parsed, or {@code null} if * there are no more lines to be parsed. * @return the parsed {@code VariationDiff} @@ -217,6 +224,7 @@ private VariationDiffParser( * is detected */ private VariationDiff parse( + Source source, FailableSupplier lines ) throws IOException, DiffParseException { DiffNode root = DiffNode.createRoot(new DiffLinesLabel()); @@ -297,7 +305,7 @@ private VariationDiff parse( ); } - return new VariationDiff<>(root); + return new VariationDiff<>(root, source); } /** diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/render/VariationDiffRenderer.java b/src/main/java/org/variantsync/diffdetective/variation/diff/render/VariationDiffRenderer.java index 91b03e0b9..a6dacc622 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/render/VariationDiffRenderer.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/render/VariationDiffRenderer.java @@ -8,6 +8,7 @@ import org.variantsync.diffdetective.shell.ShellExecutor; import org.variantsync.diffdetective.util.Assert; import org.variantsync.diffdetective.util.IO; +import org.variantsync.diffdetective.util.Source; import org.variantsync.diffdetective.util.StringUtils; import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.Label; @@ -17,7 +18,6 @@ import org.variantsync.diffdetective.variation.diff.serialize.LineGraphConstants; import org.variantsync.diffdetective.variation.diff.serialize.LineGraphExport; import org.variantsync.diffdetective.variation.diff.serialize.LineGraphExportOptions; -import org.variantsync.diffdetective.variation.diff.source.VariationDiffSource; import java.io.IOException; import java.nio.file.Files; @@ -193,7 +193,7 @@ public boolean renderWithTreeFormat(final VariationDiff boolean render(final VariationDiff tree, final String treeAndFileName, final Path directory, RenderOptions options, LineGraphExportOptions exportOptions, BiFunction treeHeader) { + private boolean render(final VariationDiff tree, final String treeAndFileName, final Path directory, RenderOptions options, LineGraphExportOptions exportOptions, BiFunction treeHeader) { final Path tempFile = directory.resolve(treeAndFileName + ".lg"); try (var destination = IO.newBufferedOutputStream(tempFile)) { diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/LineGraphExport.java b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/LineGraphExport.java index d1483b202..fe9b8319a 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/LineGraphExport.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/LineGraphExport.java @@ -10,12 +10,12 @@ import org.variantsync.diffdetective.diff.git.CommitDiff; import org.variantsync.diffdetective.diff.git.PatchDiff; import org.variantsync.diffdetective.metadata.Metadata; +import org.variantsync.diffdetective.util.Source; import org.variantsync.diffdetective.util.StringUtils; import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.Label; import org.variantsync.diffdetective.variation.diff.Time; import org.variantsync.diffdetective.variation.diff.VariationDiff; -import org.variantsync.diffdetective.variation.diff.source.VariationDiffSource; import org.variantsync.functjonal.category.InplaceSemigroup; /** @@ -154,12 +154,12 @@ public static Statistic toLineGraphFormat(final PatchDiff patch, final LineGraph /** * Produces the final linegraph file content. - * Creates a linegraph header from the given VariationDiffSource using the {@link LineGraphExportOptions#treeFormat()} in the given options. + * Creates a linegraph header from the given Source using the {@link LineGraphExportOptions#treeFormat()} in the given options. * Then appends the already created file content for nodes and edges. - * @param source The {@link VariationDiffSource} that describes where the VariationDiff whose content is written to the file originated from. + * @param source The {@link Source} that describes where the VariationDiff whose content is written to the file originated from. * @param options {@link LineGraphExportOptions} used to determine the treeFormat for the header. */ - private static String lineGraphHeader(final VariationDiffSource source, final LineGraphExportOptions options) { + private static String lineGraphHeader(final Source source, final LineGraphExportOptions options) { return options.treeFormat().toLineGraphLine(source) + StringUtils.LINEBREAK; } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/LineGraphImport.java b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/LineGraphImport.java index 3d9750b98..dcb323eb3 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/LineGraphImport.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/LineGraphImport.java @@ -2,10 +2,10 @@ import org.variantsync.diffdetective.util.Assert; import org.variantsync.diffdetective.util.FileUtils; +import org.variantsync.diffdetective.util.Source; import org.variantsync.diffdetective.variation.diff.DiffGraph; import org.variantsync.diffdetective.variation.diff.DiffNode; import org.variantsync.diffdetective.variation.diff.VariationDiff; -import org.variantsync.diffdetective.variation.diff.source.VariationDiffSource; import org.variantsync.diffdetective.variation.diff.source.LineGraphFileSource; import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.NodeType; @@ -128,9 +128,9 @@ public static List> fromLineGraph(final BufferedRe * @return {@link VariationDiff} generated from the given, already parsed parameters. */ private static VariationDiff parseVariationDiff(final String lineGraph, final Path inFile, final List> diffNodeList, final LineGraphImportOptions options) { - VariationDiffSource variationDiffSource = options.treeFormat().fromLineGraphLine(lineGraph); + Source variationDiffSource = options.treeFormat().fromLineGraphLine(lineGraph); - if (variationDiffSource == null || VariationDiffSource.Unknown.equals(variationDiffSource)) { + if (variationDiffSource == null || Source.Unknown.equals(variationDiffSource)) { variationDiffSource = new LineGraphFileSource( lineGraph, inFile diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/DiffNodeLabelFormat.java b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/DiffNodeLabelFormat.java index 378497db9..49a26b641 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/DiffNodeLabelFormat.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/nodeformat/DiffNodeLabelFormat.java @@ -7,7 +7,6 @@ import org.variantsync.diffdetective.variation.diff.DiffNode; import org.variantsync.diffdetective.variation.diff.serialize.LineGraphConstants; import org.variantsync.diffdetective.variation.diff.serialize.LinegraphFormat; -import org.variantsync.diffdetective.variation.diff.source.VariationDiffSource; import org.variantsync.functjonal.Pair; /** @@ -49,7 +48,7 @@ default List toMultilineLabel(DiffNode node) { } /** - * Converts a line describing a graph (starting with "t # ") in line graph format into a {@link VariationDiffSource}. + * Converts a line describing a graph (starting with "t # ") in line graph format into a {@link DiffNode}. * * @param lineGraphLine A line from a line graph file starting with "t #" * @return A pair with the first element being the id of the node specified in the given lineGrapLine. diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/CommitDiffVariationDiffLabelFormat.java b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/CommitDiffVariationDiffLabelFormat.java index c4d19637e..dad73a7bc 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/CommitDiffVariationDiffLabelFormat.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/CommitDiffVariationDiffLabelFormat.java @@ -3,10 +3,10 @@ import org.apache.commons.io.FilenameUtils; import org.variantsync.diffdetective.diff.git.CommitDiff; import org.variantsync.diffdetective.diff.git.PatchDiff; +import org.variantsync.diffdetective.util.Source; import org.variantsync.diffdetective.variation.diff.Time; import org.variantsync.diffdetective.variation.diff.serialize.LineGraphConstants; import org.variantsync.diffdetective.variation.diff.source.CommitDiffVariationDiffSource; -import org.variantsync.diffdetective.variation.diff.source.VariationDiffSource; import java.nio.file.InvalidPathException; import java.nio.file.Path; @@ -19,7 +19,7 @@ */ public class CommitDiffVariationDiffLabelFormat implements VariationDiffLabelFormat { @Override - public VariationDiffSource fromLabel(final String label) { + public Source fromLabel(final String label) { String[] commit = label.split(LineGraphConstants.TREE_NAME_SEPARATOR_REGEX); try { Path filePath = Paths.get(commit[0]); @@ -31,15 +31,19 @@ public VariationDiffSource fromLabel(final String label) { } @Override - public String toLabel(final VariationDiffSource variationDiffSource) { - if (variationDiffSource instanceof CommitDiffVariationDiffSource commitDiffVariationDiffSource) { + public String toLabel(final Source variationDiffSource) { + CommitDiffVariationDiffSource commitDiffVariationDiffSource = Source.findFirst(variationDiffSource, CommitDiffVariationDiffSource.class); + if (commitDiffVariationDiffSource != null) { // write for instances of CommitDiffVariationDiffSources return FilenameUtils.separatorsToUnix(commitDiffVariationDiffSource.getFileName().toString()) + LineGraphConstants.TREE_NAME_SEPARATOR + commitDiffVariationDiffSource.getCommitHash(); - } else if (variationDiffSource instanceof PatchDiff patchDiff) { + } + + PatchDiff patchDiff = Source.findFirst(variationDiffSource, PatchDiff.class); + if (patchDiff != null) { // write for instances of PatchDiffs return FilenameUtils.separatorsToUnix(patchDiff.getFileName(Time.AFTER)) + LineGraphConstants.TREE_NAME_SEPARATOR + patchDiff.getCommitDiff().getCommitHash(); - } else { - throw new UnsupportedOperationException("There is no implementation for this VariationDiffSource type: " + variationDiffSource); } + + throw new UnsupportedOperationException("There is no implementation for this Source type: " + variationDiffSource); } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/IndexedTreeFormat.java b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/IndexedTreeFormat.java index 45a44a64d..67f7d351e 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/IndexedTreeFormat.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/IndexedTreeFormat.java @@ -1,10 +1,10 @@ package org.variantsync.diffdetective.variation.diff.serialize.treeformat; -import org.variantsync.diffdetective.variation.diff.source.VariationDiffSource; +import org.variantsync.diffdetective.util.Source; /** * Exports tree by indexing them. - * This format keeps an internal counter that is incremented on each call of {@link #toLabel(VariationDiffSource)}. + * This format keeps an internal counter that is incremented on each call of {@link #toLabel(Source)}. * Thus, every produced label will have the successive index of the previously produced label. */ public class IndexedTreeFormat implements VariationDiffLabelFormat { @@ -25,12 +25,12 @@ public void reset() { } @Override - public VariationDiffSource fromLabel(String label) { - return VariationDiffSource.Unknown; + public Source fromLabel(String label) { + return Source.Unknown; } @Override - public String toLabel(VariationDiffSource variationDiffSource) { + public String toLabel(Source variationDiffSource) { final String result = "" + nextId; ++nextId; return result; diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/VariationDiffLabelFormat.java b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/VariationDiffLabelFormat.java index f9c5d5ebb..ec3c6c56f 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/VariationDiffLabelFormat.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/serialize/treeformat/VariationDiffLabelFormat.java @@ -1,47 +1,47 @@ package org.variantsync.diffdetective.variation.diff.serialize.treeformat; +import org.variantsync.diffdetective.util.Source; import org.variantsync.diffdetective.variation.diff.serialize.LineGraphConstants; import org.variantsync.diffdetective.variation.diff.serialize.LinegraphFormat; -import org.variantsync.diffdetective.variation.diff.source.VariationDiffSource; /** - * Reads and writes {@link VariationDiffSource} from and to line graph. + * Reads and writes {@link Source} from and to line graph. * @author Paul Bittner, Kevin Jedelhauser */ public interface VariationDiffLabelFormat extends LinegraphFormat { /** - * Converts a label of line graph into a {@link VariationDiffSource}. + * Converts a label of line graph into a {@link Source}. * - * @param label A string containing the label of the {@link VariationDiffSource} - * @return The {@link VariationDiffSource} descibed by this label. + * @param label A string containing the label of the {@link Source} + * @return The {@link Source} described by this label. */ - VariationDiffSource fromLabel(final String label); + Source fromLabel(final String label); /** - * Converts a {@link VariationDiffSource} label of line graph. + * Converts a {@link Source} label of line graph. * - * @param variationDiffSource The {@link VariationDiffSource} to be converted + * @param variationDiffSource The {@link Source} to be converted * @return The corresponding line graph line */ - String toLabel(final VariationDiffSource variationDiffSource); + String toLabel(final Source variationDiffSource); /** - * Converts a line describing a graph (starting with "t # ") in line graph format into a {@link VariationDiffSource}. + * Converts a line describing a graph (starting with "t # ") in line graph format into a {@link Source}. * * @param lineGraphLine A line from a line graph file starting with "t #" - * @return The {@link VariationDiffSource} descibed by the label of this line. + * @return The {@link Source} descibed by the label of this line. */ - default VariationDiffSource fromLineGraphLine(final String lineGraphLine) { + default Source fromLineGraphLine(final String lineGraphLine) { return fromLabel(lineGraphLine.substring((LineGraphConstants.LG_TREE_HEADER + " ").length())); } /** * Prepends the {@link LineGraphConstants#LG_TREE_HEADER tree declaration} to a label and return an entire line graph line. * - * @param variationDiffSource The {@link VariationDiffSource} to be converted - * @return The entire line graph line of a {@link VariationDiffSource}. + * @param variationDiffSource The {@link Source} to be converted + * @return The entire line graph line of a {@link Source}. */ - default String toLineGraphLine(final VariationDiffSource variationDiffSource) { + default String toLineGraphLine(final Source variationDiffSource) { return LineGraphConstants.LG_TREE_HEADER + " " + toLabel(variationDiffSource); } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/source/CommitDiffVariationDiffSource.java b/src/main/java/org/variantsync/diffdetective/variation/diff/source/CommitDiffVariationDiffSource.java index 7b12106b7..b41b07397 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/source/CommitDiffVariationDiffSource.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/source/CommitDiffVariationDiffSource.java @@ -1,42 +1,29 @@ package org.variantsync.diffdetective.variation.diff.source; import java.nio.file.Path; +import java.util.List; import org.variantsync.diffdetective.diff.git.CommitDiff; // For Javadoc +import org.variantsync.diffdetective.util.Source; /** * Describes that a VariationDiff was created from a patch in a {@link CommitDiff}. + * @param getFileName Name of the modified file from whose changes the VariationDiff was parsed. + * @param getCommitHash Hash of the commit in which the edit occurred. */ -public class CommitDiffVariationDiffSource implements VariationDiffSource { - private final Path fileName; - private final String commitHash; - - /** - * Create a source that refers to changes to the given file in the given commit. - * @param fileName Name of the modified file from whose changes the VariationDiff was parsed. - * @param commitHash Hash of the commit in which the edit occurred. - */ - public CommitDiffVariationDiffSource(final Path fileName, final String commitHash) { - this.fileName = fileName; - this.commitHash = commitHash; - } - - /** - * Returns the name of the modified file from whose changes the VariationDiff was parsed. - */ - public Path getFileName() { - return fileName; +public record CommitDiffVariationDiffSource(Path getFileName, String getCommitHash) implements Source { + @Override + public String toString() { + return getFileName() + "@" + getCommitHash(); } - /** - * Returns the hash of the commit in which the edit occurred. - */ - public String getCommitHash() { - return commitHash; + @Override + public String getSourceExplanation() { + return "CommitDiff"; } @Override - public String toString() { - return fileName + "@" + commitHash; + public List getSourceArguments() { + return List.of(getFileName(), getCommitHash()); } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/source/FromVariationTreeSource.java b/src/main/java/org/variantsync/diffdetective/variation/diff/source/FromVariationTreeSource.java deleted file mode 100644 index 29ee2a23c..000000000 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/source/FromVariationTreeSource.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.variantsync.diffdetective.variation.diff.source; - -import org.variantsync.diffdetective.variation.tree.source.VariationTreeSource; - -public record FromVariationTreeSource(VariationTreeSource inner) implements VariationDiffSource { - @Override - public String toString() { - return inner.toString(); - } -} diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/source/LineGraphFileSource.java b/src/main/java/org/variantsync/diffdetective/variation/diff/source/LineGraphFileSource.java index 4b2d73e9d..7b25f2a39 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/source/LineGraphFileSource.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/source/LineGraphFileSource.java @@ -1,6 +1,9 @@ package org.variantsync.diffdetective.variation.diff.source; import java.nio.file.Path; +import java.util.List; + +import org.variantsync.diffdetective.util.Source; /** * A source for VariationDiffs that were parsed from a linegraph file. @@ -10,5 +13,14 @@ public record LineGraphFileSource( String graphHeader, Path file -) implements VariationDiffSource { +) implements Source { + @Override + public String getSourceExplanation() { + return "LineGraphFile"; + } + + @Override + public List getSourceArguments() { + return List.of(graphHeader, file); + } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/source/PatchFile.java b/src/main/java/org/variantsync/diffdetective/variation/diff/source/PatchFile.java deleted file mode 100644 index 060b909c6..000000000 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/source/PatchFile.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.variantsync.diffdetective.variation.diff.source; - -import java.nio.file.Path; - -/** - * A source for VariationDiff's that were created from patch files on disk. - * @param path Path to the patch file that was parsed to a VariationDiff. - */ -public record PatchFile(Path path) implements VariationDiffSource { -} diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/source/PatchString.java b/src/main/java/org/variantsync/diffdetective/variation/diff/source/PatchString.java index 610029183..99feb00ca 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/source/PatchString.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/source/PatchString.java @@ -1,14 +1,20 @@ package org.variantsync.diffdetective.variation.diff.source; import org.variantsync.diffdetective.diff.text.TextBasedDiff; +import org.variantsync.diffdetective.util.Source; /** * Source for VariationDiffs that were created from a patch given as a String. * @param getDiff The patch as a String. */ -public record PatchString(String getDiff) implements TextBasedDiff, VariationDiffSource { +public record PatchString(String getDiff) implements TextBasedDiff, Source { + @Override + public String getSourceExplanation() { + return "Patch"; + } + @Override public String toString() { - return "from line-based diff " + getDiff; + return getSourceExplanation() + getDiff; } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/source/VariationDiffSource.java b/src/main/java/org/variantsync/diffdetective/variation/diff/source/VariationDiffSource.java deleted file mode 100644 index 01f5a944c..000000000 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/source/VariationDiffSource.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.variantsync.diffdetective.variation.diff.source; - -/** - * Describes or identifies that data a VariationDiff was created or parsed from. - * This is typically a patch. - */ -public interface VariationDiffSource { - /** - * Constant to use when the source of a VariationDiff is unknown - * or if it was created artificially. - */ - VariationDiffSource Unknown = new VariationDiffSource() { - @Override - public int hashCode() { - return 0; - } - - @Override - public String toString() { - return "Unknown VariationDiffSource"; - } - }; -} diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/source/VariationTreeDiffSource.java b/src/main/java/org/variantsync/diffdetective/variation/diff/source/VariationTreeDiffSource.java deleted file mode 100644 index f5235f866..000000000 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/source/VariationTreeDiffSource.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.variantsync.diffdetective.variation.diff.source; - -import org.variantsync.diffdetective.variation.diff.VariationDiff; // For Javadoc -import org.variantsync.diffdetective.variation.tree.VariationTree; // For Javadoc -import org.variantsync.diffdetective.variation.tree.source.VariationTreeSource; - -/** - * Describes that a {@link VariationDiff} was created from two {@link VariationTree}s. - */ -public record VariationTreeDiffSource( - VariationTreeSource before, - VariationTreeSource after -) implements VariationDiffSource { - @Override - public String toString() { - return "from trees [" + before + "] and [" + after + "]"; - } -} diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/view/DiffView.java b/src/main/java/org/variantsync/diffdetective/variation/diff/view/DiffView.java index ebf9e0238..65e097131 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/view/DiffView.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/view/DiffView.java @@ -6,6 +6,7 @@ import org.variantsync.diffdetective.experiments.views.Main; import org.variantsync.diffdetective.util.Assert; import org.variantsync.diffdetective.util.CollectionUtils; +import org.variantsync.diffdetective.util.Source; import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.Label; import org.variantsync.diffdetective.variation.diff.*; @@ -72,12 +73,19 @@ public static BiPredicate> computeWhenNode private static VariationDiff naive(final VariationDiff d, final Relevance rho, final String[] projectionViewText) throws IOException, DiffParseException { final VariationDiff view; try { - view = JGitDiff.diff(projectionViewText[0], projectionViewText[1], DiffAlgorithm.SupportedAlgorithm.MYERS, Main.VARIATION_DIFF_PARSE_OPTIONS); + view = JGitDiff.diff( + projectionViewText[0], + projectionViewText[1], + Source.Unknown, // overridden below + Source.Unknown, // overridden below + DiffAlgorithm.SupportedAlgorithm.MYERS, + Main.VARIATION_DIFF_PARSE_OPTIONS + ); } catch (DiffParseException e) { Logger.error("Could not parse diff obtained with query {} at {}", d.getSource(), rho); throw e; } - view.setSource(new ViewSource<>(d, rho)); + view.setSource(new ViewSource<>(d, rho, "naive")); return view; } @@ -153,6 +161,7 @@ public static VariationDiff badgood(final VariationDiff // unify final VariationDiff goodDiff = badDiff.toGood(); + goodDiff.setSource(new ViewSource<>(d, rho, "badgood")); goodDiff.assertConsistency(); return goodDiff; } @@ -261,7 +270,7 @@ record Edge(DiffNode childCopy, DiffNode parentInD, Time // Step 4: Build return value Assert.assertNotNull(rootCopy[0]); - return new VariationDiff<>(rootCopy[0], new ViewSource<>(d, rho)); + return new VariationDiff<>(rootCopy[0], new ViewSource<>(d, rho, "optimized")); } /** diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/view/ViewSource.java b/src/main/java/org/variantsync/diffdetective/variation/diff/view/ViewSource.java index 6e1f0fabf..78b85b0b0 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/view/ViewSource.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/view/ViewSource.java @@ -1,15 +1,31 @@ package org.variantsync.diffdetective.variation.diff.view; +import java.util.List; + +import org.variantsync.diffdetective.util.Source; import org.variantsync.diffdetective.variation.Label; import org.variantsync.diffdetective.variation.diff.VariationDiff; -import org.variantsync.diffdetective.variation.diff.source.VariationDiffSource; import org.variantsync.diffdetective.variation.tree.view.relevance.Relevance; /** - * A {@link VariationDiffSource} that remembers that a variation diff represents a view on - * another variation diff. + * A {@link Source} that remembers that a variation diff represents a view on another variation + * diff. * @param diff The original variation diff on which the variation diff with this source is a view on. * @param relevance The relevance predicate that was used to create the view. */ -public record ViewSource(VariationDiff diff, Relevance relevance) implements VariationDiffSource { +public record ViewSource(VariationDiff diff, Relevance relevance, String method) implements Source { + @Override + public String getSourceExplanation() { + return "view"; + } + + @Override + public List getSources() { + return List.of(diff); + } + + @Override + public List getSourceArguments() { + return List.of(relevance); + } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTree.java b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTree.java index 9920b3e6a..24a0eb3f1 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTree.java +++ b/src/main/java/org/variantsync/diffdetective/variation/tree/VariationTree.java @@ -3,17 +3,17 @@ import org.variantsync.diffdetective.datasets.PatchDiffParseOptions; import org.variantsync.diffdetective.diff.result.DiffParseException; import org.variantsync.diffdetective.util.Assert; +import org.variantsync.diffdetective.util.CompositeSource; +import org.variantsync.diffdetective.util.FileSource; +import org.variantsync.diffdetective.util.Source; import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.Label; import org.variantsync.diffdetective.variation.NodeType; // For Javadoc import org.variantsync.diffdetective.variation.diff.DiffNode; -import org.variantsync.diffdetective.variation.diff.VariationDiff; import org.variantsync.diffdetective.variation.diff.Projection; +import org.variantsync.diffdetective.variation.diff.VariationDiff; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParser; -import org.variantsync.diffdetective.variation.diff.source.FromVariationTreeSource; -import org.variantsync.diffdetective.variation.tree.source.LocalFileSource; -import org.variantsync.diffdetective.variation.tree.source.VariationTreeSource; import java.io.BufferedReader; import java.io.IOException; @@ -22,6 +22,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -42,15 +43,15 @@ */ public record VariationTree( VariationTreeNode root, - VariationTreeSource source -) { + Source source +) implements Source { /** Creates a {@code VariationTree} with the given root and an unknown source. */ public VariationTree(VariationTreeNode root) { - this(root, VariationTreeSource.Unknown); + this(root, Source.Unknown); } /** Creates a {@code VariationTree} with the given root and source. */ - public VariationTree(VariationTreeNode root, VariationTreeSource source) { + public VariationTree(VariationTreeNode root, Source source) { this.root = root; this.source = source; @@ -66,7 +67,7 @@ public static VariationTree fromFile(final Path path) throws IOE } /** - * Same as {@link #fromFile(BufferedReader, VariationTreeSource, VariationDiffParseOptions)} + * Same as {@link #fromFile(BufferedReader, Source, VariationDiffParseOptions)} * but registers {@code path} as source. */ public static VariationTree fromFile( @@ -76,8 +77,8 @@ public static VariationTree fromFile( try (BufferedReader file = Files.newBufferedReader(path)) { return fromFile( file, - new LocalFileSource(path), - parseOptions + new FileSource(path), + parseOptions ); } } @@ -94,17 +95,16 @@ public static VariationTree fromFile( */ public static VariationTree fromFile( final BufferedReader input, - final VariationTreeSource source, + final Source source, final VariationDiffParseOptions parseOptions ) throws IOException, DiffParseException { - VariationTreeNode tree = VariationDiffParser - .createVariationTree(input, parseOptions) - .getRoot() - // Arbitrarily choose the BEFORE projection as both should be equal. - .projection(BEFORE) - .toVariationTree(); + VariationDiff diff = + VariationDiffParser.createVariationTree(input, source, parseOptions); - return new VariationTree<>(tree, source); + return new VariationTree<>( + // Arbitrarily choose the BEFORE projection as both should be equal. + diff.getRoot().projection(BEFORE).toVariationTree(), + diff.getSource()); } /** @@ -118,7 +118,7 @@ public static VariationTree fromFile( */ public static VariationTree fromText( final String input, - final VariationTreeSource source, + final Source source, final VariationDiffParseOptions parseOptions ) throws DiffParseException { try { @@ -129,11 +129,11 @@ public static VariationTree fromText( } } - public static VariationTree fromProjection(final Projection projection, final VariationTreeSource source) { + public static VariationTree fromProjection(final Projection projection, final Source source) { return fromVariationNode(projection, source); } - public static , L extends Label> VariationTree fromVariationNode(final VariationNode node, final VariationTreeSource source) { + public static , L extends Label> VariationTree fromVariationNode(final VariationNode node, final Source source) { return new VariationTree<>( node.toVariationTree(), source @@ -143,7 +143,7 @@ public static , L extends Label> VariationTree public VariationDiff toVariationDiff(final Function, DiffNode> nodeConverter) { return new VariationDiff<>( DiffNode.unchanged(nodeConverter, root()), - new FromVariationTreeSource(source()) + new CompositeSource("VariationTree.toVariationDiff", source()) ); } @@ -225,6 +225,20 @@ public void assertConsistency() { forAllPreorder(VariationTreeNode::assertConsistency); } + public Source getSource() { + return source(); + } + + @Override + public List getSources() { + return List.of(source); + } + + @Override + public String getSourceExplanation() { + return "VariationTree"; + } + @Override public String toString() { return "variation tree from " + source; diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/source/GitSource.java b/src/main/java/org/variantsync/diffdetective/variation/tree/source/GitSource.java index d79b70eec..9722d4418 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/tree/source/GitSource.java +++ b/src/main/java/org/variantsync/diffdetective/variation/tree/source/GitSource.java @@ -1,7 +1,10 @@ package org.variantsync.diffdetective.variation.tree.source; -import java.net.URL; import java.nio.file.Path; +import java.util.List; + +import org.variantsync.diffdetective.datasets.Repository; +import org.variantsync.diffdetective.util.Source; /** * A file at a specific commit in a Git repository. @@ -9,19 +12,29 @@ *

The parameters of this record should be suitably chosen, so that the following commands can be * executed in a shell to obtain the referenced source code: * - * git clone "$repository" repository + * git clone "${repository.getRemoteURI()}" repository * cd repository * git switch -d "$commitHash" * cat "$path" * */ public record GitSource( - URL repository, + Repository repository, String commitHash, Path path -) implements VariationTreeSource { +) implements Source { @Override public String toString() { return path.toString() + " at " + commitHash + " of " + repository; } + + @Override + public String getSourceExplanation() { + return "GitCommit"; + } + + @Override + public List getSourceArguments() { + return List.of(repository, commitHash, path); + } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/source/LocalFileSource.java b/src/main/java/org/variantsync/diffdetective/variation/tree/source/LocalFileSource.java deleted file mode 100644 index c9c60fbb9..000000000 --- a/src/main/java/org/variantsync/diffdetective/variation/tree/source/LocalFileSource.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.variantsync.diffdetective.variation.tree.source; - -import java.nio.file.Path; - -/** - * A reference to a file with path {@code path} in the local file system. - */ -public record LocalFileSource(Path path) implements VariationTreeSource { - @Override - public String toString() { - return "file://" + path.toString(); - } -} diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/source/VariationTreeSource.java b/src/main/java/org/variantsync/diffdetective/variation/tree/source/VariationTreeSource.java deleted file mode 100644 index 448ac1511..000000000 --- a/src/main/java/org/variantsync/diffdetective/variation/tree/source/VariationTreeSource.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.variantsync.diffdetective.variation.tree.source; - -/** - * A reference to the source of the variation tree. - */ -public interface VariationTreeSource { - /** - * The source of the variation tree is unknown. - * Should be avoided if possible. - */ - VariationTreeSource Unknown = new VariationTreeSource() { - @Override - public String toString() { - return "unknown"; - } - }; - - String toString(); -} diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/Configure.java b/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/Configure.java index 2b571b3f7..177007b5e 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/Configure.java +++ b/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/Configure.java @@ -7,6 +7,7 @@ import org.variantsync.diffdetective.util.fide.FixTrueFalse.Formula; import org.variantsync.diffdetective.variation.tree.VariationNode; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.function.Consumer; @@ -94,12 +95,12 @@ public boolean test(VariationNode v) { } @Override - public String parametersToString() { - return configuration.get().toString(NodeWriter.logicalSymbols); + public List getSourceArguments() { + return List.of(configuration.get().toString(NodeWriter.logicalSymbols)); } @Override - public String getFunctionName() { + public String getSourceExplanation() { return "configure"; } diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/Relevance.java b/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/Relevance.java index 8bc06b5e6..cd8097767 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/Relevance.java +++ b/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/Relevance.java @@ -1,5 +1,6 @@ package org.variantsync.diffdetective.variation.tree.view.relevance; +import org.variantsync.diffdetective.util.Source; import org.variantsync.diffdetective.variation.tree.VariationNode; import java.util.function.Consumer; @@ -13,17 +14,7 @@ * Moreover, this interface provides methods to access a predicates metadata for debugging * and (de-)serialization. */ -public interface Relevance extends Predicate> { - /** - * @return The name of this relevance predicate's type. - */ - String getFunctionName(); - - /** - * @return The parameters set for this particular relevance predicate, as a comma-separated string (without braces). - */ - String parametersToString(); - +public interface Relevance extends Predicate>, Source { /** * Delegates to {@link Relevance#computeViewNodesCheckAll(Relevance, VariationNode, Consumer)} with this relevance * as the first parameter. @@ -57,9 +48,9 @@ public interface Relevance extends Predicate> { * Default implementation for {@link Object#toString()} that can be reused by implementing classes. * The produced string will look like a function call. * @param relevance The relevance predicate to turn into a string. - * @return {@link Relevance#getFunctionName()} + "(" + {@link Relevance#parametersToString()} + ")" + * @return {@link #getSourceArguments()} + "(" + {@link #getSourceExplanation()} + ")" */ static String toString(Relevance relevance) { - return relevance.getFunctionName() + "(" + relevance.parametersToString() + ")"; + return relevance.shallowExplanation(); } } diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/Search.java b/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/Search.java index 0f557e7c2..14448078a 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/Search.java +++ b/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/Search.java @@ -1,5 +1,7 @@ package org.variantsync.diffdetective.variation.tree.view.relevance; +import java.util.List; + import org.variantsync.diffdetective.variation.tree.VariationNode; /** @@ -17,12 +19,12 @@ public boolean test(VariationNode v) { } @Override - public String parametersToString() { - return artifact(); + public List getSourceArguments() { + return List.of(artifact()); } @Override - public String getFunctionName() { + public String getSourceExplanation() { return "is"; } diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/Trace.java b/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/Trace.java index d559605a4..ed0d33c35 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/Trace.java +++ b/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/Trace.java @@ -1,5 +1,7 @@ package org.variantsync.diffdetective.variation.tree.view.relevance; +import java.util.List; + import org.variantsync.diffdetective.variation.tree.VariationNode; /** @@ -15,12 +17,12 @@ public boolean test(VariationNode v) { } @Override - public String parametersToString() { - return featureName(); + public List getSourceArguments() { + return List.of(featureName()); } @Override - public String getFunctionName() { + public String getSourceExplanation() { return "traceall"; } diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/TraceSup.java b/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/TraceSup.java index d990256c1..bae357331 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/TraceSup.java +++ b/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/TraceSup.java @@ -1,5 +1,7 @@ package org.variantsync.diffdetective.variation.tree.view.relevance; +import java.util.List; + import org.prop4j.Node; import org.prop4j.NodeWriter; import org.variantsync.diffdetective.analysis.logic.SAT; @@ -16,12 +18,12 @@ public boolean test(VariationNode variationNode) { } @Override - public String parametersToString() { - return configuration.toString(NodeWriter.logicalSymbols); + public List getSourceArguments() { + return List.of(configuration.toString(NodeWriter.logicalSymbols)); } @Override - public String getFunctionName() { + public String getSourceExplanation() { return "traceyes"; } diff --git a/src/test/java/JPPParserTest.java b/src/test/java/JPPParserTest.java index a2ac2dc8f..b73ec7ff3 100644 --- a/src/test/java/JPPParserTest.java +++ b/src/test/java/JPPParserTest.java @@ -7,6 +7,7 @@ import org.variantsync.diffdetective.feature.Annotation; import org.variantsync.diffdetective.feature.AnnotationType; import org.variantsync.diffdetective.feature.jpp.JPPAnnotationParser; +import org.variantsync.diffdetective.util.FileSource; import org.variantsync.diffdetective.util.IO; import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.diff.VariationDiff; @@ -132,6 +133,7 @@ public void fullDiffTestCase(JPPParserTest.TestCase testCase) throws try (var inputFile = Files.newBufferedReader(testCase.input)) { variationDiff = VariationDiffParser.createVariationDiff( inputFile, + new FileSource(testCase.input), new VariationDiffParseOptions( false, false diff --git a/src/test/java/TreeDiffingTest.java b/src/test/java/TreeDiffingTest.java index 1d3dd395d..24d9130e9 100644 --- a/src/test/java/TreeDiffingTest.java +++ b/src/test/java/TreeDiffingTest.java @@ -5,22 +5,21 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.variantsync.diffdetective.diff.result.DiffParseException; +import org.variantsync.diffdetective.util.FileSource; import org.variantsync.diffdetective.util.IO; +import org.variantsync.diffdetective.util.Source; import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.diff.DiffNode; import org.variantsync.diffdetective.variation.diff.VariationDiff; import org.variantsync.diffdetective.variation.diff.construction.GumTreeDiff; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; -import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParser; import org.variantsync.diffdetective.variation.diff.serialize.Format; import org.variantsync.diffdetective.variation.diff.serialize.LineGraphExporter; import org.variantsync.diffdetective.variation.diff.serialize.TikzExporter; import org.variantsync.diffdetective.variation.diff.serialize.edgeformat.ChildOrderEdgeFormat; import org.variantsync.diffdetective.variation.diff.serialize.edgeformat.DefaultEdgeLabelFormat; import org.variantsync.diffdetective.variation.diff.serialize.nodeformat.FullNodeFormat; -import org.variantsync.diffdetective.variation.diff.source.VariationDiffSource; import org.variantsync.diffdetective.variation.tree.VariationTree; -import org.variantsync.diffdetective.variation.tree.source.LocalFileSource; import java.io.IOException; import java.nio.file.Files; @@ -29,7 +28,6 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.fail; -import static org.variantsync.diffdetective.variation.diff.Time.BEFORE; public class TreeDiffingTest { private final static Path testDir = Constants.RESOURCE_DIR.resolve("tree-diffing"); @@ -83,8 +81,8 @@ private static Stream createMatchingTestCases() throws IOException { @ParameterizedTest @MethodSource("createMatchingTestCases") public void createMatchingTestCase(TestCase testCase) throws IOException, DiffParseException { - VariationTree beforeEdit = parseVariationTree(testCase.beforeEdit()); - VariationTree afterEdit = parseVariationTree(testCase.afterEdit()); + VariationTree beforeEdit = VariationTree.fromFile(testCase.beforeEdit()); + VariationTree afterEdit = VariationTree.fromFile(testCase.afterEdit()); assertExpectedVariationDiffs(testCase, GumTreeDiff.diffUsingMatching(beforeEdit, afterEdit, testCase.matcher())); } @@ -104,7 +102,7 @@ public void improveMatchingTestCase(TestCase testCase) throws IOException, DiffP ); DiffNode improvedDiffNode = GumTreeDiff.improveMatching(variationDiff.getRoot(), testCase.matcher()); - VariationDiff improvedVariationDiff = new VariationDiff<>(improvedDiffNode, VariationDiffSource.Unknown); + VariationDiff improvedVariationDiff = new VariationDiff<>(improvedDiffNode, Source.Unknown); assertExpectedVariationDiffs(testCase, improvedVariationDiff); } @@ -139,16 +137,4 @@ private static void assertExpectedVariationDiffs(TestCase testCase, VariationDif } } } - - private static VariationTree parseVariationTree(Path filename) throws IOException, DiffParseException { - try (var file = Files.newBufferedReader(filename)) { - return new VariationTree<>( - VariationDiffParser.createVariationTree( - file, - VariationDiffParseOptions.Default - ).getRoot().projection(BEFORE).toVariationTree(), - new LocalFileSource(filename) - ); - } - } } diff --git a/src/test/java/VariationDiffParserTest.java b/src/test/java/VariationDiffParserTest.java index 4c1d4e48f..7646842bf 100644 --- a/src/test/java/VariationDiffParserTest.java +++ b/src/test/java/VariationDiffParserTest.java @@ -7,7 +7,6 @@ import org.variantsync.diffdetective.variation.DiffLinesLabel; import org.variantsync.diffdetective.variation.diff.VariationDiff; import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; -import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParser; import org.variantsync.diffdetective.variation.diff.serialize.Format; import org.variantsync.diffdetective.variation.diff.serialize.LineGraphExporter; import org.variantsync.diffdetective.variation.diff.serialize.TikzExporter; @@ -60,15 +59,13 @@ public static void testCase(Path testCasePath) throws IOException, DiffParseExce var expectedPath = testCasePath.getParent().resolve(basename + "_expected.lg"); VariationDiff variationDiff; - try (var inputFile = Files.newBufferedReader(testCasePath)) { - variationDiff = VariationDiffParser.createVariationDiff( - inputFile, - new VariationDiffParseOptions( - false, - false - ) - ); - } + variationDiff = VariationDiff.fromFile( + testCasePath, + new VariationDiffParseOptions( + false, + false + ) + ); try (var output = IO.newBufferedOutputStream(actualPath)) { new LineGraphExporter<>(new Format<>(new FullNodeFormat(), new ChildOrderEdgeFormat<>()))