diff --git a/src/main/java/org/variantsync/diffdetective/experiments/thesis_pm/Generator.java b/src/main/java/org/variantsync/diffdetective/experiments/thesis_pm/Generator.java new file mode 100644 index 000000000..6cbaeb09b --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/experiments/thesis_pm/Generator.java @@ -0,0 +1,511 @@ +package org.variantsync.diffdetective.experiments.thesis_pm; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Random; +import java.util.Set; + +import org.eclipse.jgit.diff.DiffAlgorithm; +import org.tinylog.Logger; +import org.variantsync.diffdetective.diff.git.PatchDiff; +import org.variantsync.diffdetective.diff.result.DiffParseException; +import org.variantsync.diffdetective.shell.DiffCommand; +import org.variantsync.diffdetective.shell.GnuPatchCommand; +import org.variantsync.diffdetective.shell.MPatchCommand; +import org.variantsync.diffdetective.shell.ShellException; +import org.variantsync.diffdetective.shell.ShellExecutor; +import org.variantsync.diffdetective.shell.SimpleCommand; +import org.variantsync.diffdetective.show.Show; +import org.variantsync.diffdetective.show.engine.GameEngine; +import org.variantsync.diffdetective.util.Assert; +import org.variantsync.diffdetective.variation.DiffLinesLabel; +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.parse.VariationDiffParseOptions; +import org.variantsync.diffdetective.variation.diff.patching.Patching; +import org.variantsync.diffdetective.variation.diff.transform.EliminateEmptyAlternatives; +import org.variantsync.diffdetective.variation.diff.transform.RevertSomeChanges; +import org.variantsync.diffdetective.variation.diff.view.DiffView; +import org.variantsync.diffdetective.variation.tree.VariationTree; +import org.variantsync.diffdetective.variation.tree.view.TreeView; +import org.variantsync.diffdetective.variation.tree.view.relevance.Configure; +import org.variantsync.diffdetective.variation.tree.view.relevance.ConfigureWithFullConfig; +import org.variantsync.functjonal.Pair; +import org.variantsync.functjonal.Result; + +public class Generator { + + private static Random rand1 = new Random(2025); + private static Random rand2 = new Random(9); + final private static String directory = "experiment"; + final private static String sourceVariant = "sourceVariant"; + final private static String version1 = "version1"; + final private static String version2 = "version2"; + final private static String targetVariant = "targetVariant"; + final private static String code = "code.txt"; + final private static String patch = "patch.txt"; + + enum Error { + FAILED, ERROR + }; + + /** + * This function mutates a boolean assignment by flipping some values by chance. + * This function is pure: It creates a new map and the input map is left + * unchanged. A value is flipped with a chance of (100*probability)%. On + * average, (100*probability)% of elements will end up flipped. + * + * @param probability The probability for an element being assigned 'true'. Must + * be in range [0, 1]. + */ + public static Map mutateByWeightedCoinFlip(final Map a, double probability) { + Assert.assertTrue(0 <= probability); + Assert.assertTrue(probability <= 1.0); + + final Map mutant = new HashMap<>(); + for (Entry e : a.entrySet()) { + mutant.put(e.getKey(), rand2.nextDouble() <= probability ? !e.getValue() : e.getValue()); + } + + return mutant; + } + + /** + * This function creates a random partition of the given set into two subsets. + * The output set is created by virtual coin flip on every element in the input + * set. On average, (100*probability)% of elements will end up in the 'true' set + * and (100*(1-probability))% elements will end up in the 'false' set. + * + * @param probability The probability for an element being assigned 'true'. Must + * be in range [0, 1]. + */ + public static Map randomPartition(Set s, double probability) { + Assert.assertTrue(0 <= probability); + Assert.assertTrue(probability <= 1.0); + + final Map subsets = new HashMap<>(); + for (T t : s) { + subsets.put(t, rand1.nextDouble() <= probability); + } + return subsets; + } + + private static void logDiff(String title, String diff) { + System.out.println("===== " + title + " =====\n" + diff + "\n===============\n"); + } + + // function to delete subdirectories and files: + // https://www.geeksforgeeks.org/java/java-program-to-delete-a-directory/ + public static void deleteDirectory(File file) { + if (file.listFiles() != null) { + for (File subfile : file.listFiles()) { + if (subfile.isDirectory()) { + deleteDirectory(subfile); + } + subfile.delete(); + } + } + + } + + public static void writeToFile(String text, Path filePath) { + try { + File f = new File(filePath.toUri()); + f.createNewFile(); + BufferedWriter myWriter = new BufferedWriter(new FileWriter(f)); + myWriter.write(text); + myWriter.close(); + } catch (IOException i) { + i.printStackTrace(); + } + } + + public static void writeToFile(List text, Path filePath) { + try { + File f = new File(filePath.toUri()); + f.createNewFile(); + BufferedWriter myWriter = new BufferedWriter(new FileWriter(f)); + for (int i = 0; i < text.size(); i++) { + myWriter.write(text.get(i)); + myWriter.newLine(); + } + myWriter.close(); + } catch (IOException i) { + i.printStackTrace(); + } + } + + public static PatchScenario generatePatchScenario(VariationDiff spl, String commitHash) + throws Exception { + // ## 1. Sample two variants. + // Since we have no feature model, we create a naive problem space model: + // We just collect all features without constraints. + final Set featureModel = spl.computeAllFeatureNames(); + Logger.info("Extracted feature names: {}", featureModel); + + // To sample variants, we just pick a random subset of features to set to true, + // set the rest to false + // We could use more sophisticated algorithms here, for example based on how + // often features occur. + // The configurations we produce are complete. + + final Map config1 = randomPartition(featureModel, 0.6); + final Map config2 = mutateByWeightedCoinFlip(config1, 0.5); + + Logger.info("Configuration 1: {}", config1); + Logger.info("Configuration 2: {}", config2); + + logDiff("spl before",spl.project(Time.BEFORE).unparse()); + logDiff("spl after",spl.project(Time.AFTER).unparse()); + + final ConfigureWithFullConfig configureTo1 = new ConfigureWithFullConfig(config1); + final ConfigureWithFullConfig configureTo2 = new ConfigureWithFullConfig(config2); + + // ## 2. We need two variants and two versions of each variant. + + VariationDiff sourcePatchRaw = DiffView.optimized(spl, configureTo1); // input patch to apply to the + // target variant + VariationDiff targetPatch = DiffView.optimized(spl, configureTo2); // ground truth for target patch; + // this is the "perfect" target + // patch + // Eliminate empty alternatives in source and target patch + VariationTree sourceBefore = sourcePatchRaw.project(Time.BEFORE); + new EliminateEmptyAlternatives().transform((VariationTree) sourceBefore); + VariationTree sourceAfter = sourcePatchRaw.project(Time.AFTER); + new EliminateEmptyAlternatives().transform((VariationTree) sourceAfter); + VariationDiff sourcePatchElimEmptyAlt; + try { + sourcePatchElimEmptyAlt = (VariationDiff) VariationDiff.fromLines(sourceBefore.unparse(), + sourceAfter.unparse(), sourceBefore, sourceAfter, DiffAlgorithm.SupportedAlgorithm.MYERS, VariationDiffParseOptions.Default); + } catch (DiffParseException e) { + return null; + } + + final VariationDiff sourcePatch = sourcePatchElimEmptyAlt; + + VariationTree before = targetPatch.project(Time.BEFORE); + new EliminateEmptyAlternatives().transform((VariationTree) before); + VariationTree after = targetPatch.project(Time.AFTER); + new EliminateEmptyAlternatives().transform((VariationTree) after); + VariationDiff targetPatchElimEmptyAlt; + try { + targetPatchElimEmptyAlt = (VariationDiff) VariationDiff.fromLines(before.unparse(), after.unparse(), + before, after, DiffAlgorithm.SupportedAlgorithm.MYERS, VariationDiffParseOptions.Default); + } catch (DiffParseException e) { + return null; + } + + VariationDiff targetPatchModified = targetPatchElimEmptyAlt.deepCopy(); + + // split nodes and subtrees with two parents + Patching.resolve((DiffNode) targetPatchElimEmptyAlt.getRoot(), + (VariationDiff) targetPatchModified); + + VariationDiff targetView = DiffView.optimized(targetPatchModified.deepCopy(), configureTo1); + + // revert the changes made in B but not in A + new RevertSomeChanges(node -> { + if (targetView.getNodeWithID(node.getID()) == null && !node.isNon()) { + return true; + } + return false; + }).transform((VariationDiff) targetPatchModified); + + targetPatch = targetPatchModified; + + final VariationTree sourceVariantBefore = sourcePatch.project(Time.BEFORE); // input + final VariationTree sourceVariantAfter = sourcePatch.project(Time.AFTER); // input + final VariationTree targetVariantBefore = targetPatch.project(Time.BEFORE); // input + final VariationTree targetVariantAfter = targetPatch.project(Time.AFTER); + + // ## 3. To use command-line patchers such as GNU patch and mpatch, we need to + // write our variants to disk. + deleteFilesAndCreateNewDirectories(commitHash); + Path patchPath = writeVariantsToFileSystem(sourceVariantBefore, sourceVariantAfter, targetVariantBefore, code, + patch, commitHash); + + if (!runGnuDiff(patchPath, commitHash)) { + return null; + } + + return new PatchScenario(sourcePatch, targetVariantBefore, targetPatch, targetVariantAfter, configureTo1, + configureTo2); + } + + public static boolean generateViewVariants(VariationDiff sourcePatch, + VariationTree targetVariantBefore, ConfigureWithFullConfig configureTo2, String commitHash) { + try { + deleteFilesAndCreateNewDirectories(commitHash); + } catch (Exception e) { + e.printStackTrace(); + } + final VariationDiff sourceVariantCrossVariantView = DiffView.optimized(sourcePatch, configureTo2); + final VariationTree sourceVariantCrossVariantViewBefore = sourceVariantCrossVariantView.project(Time.BEFORE); + final VariationTree sourceVariantCrossVariantViewAfter = sourceVariantCrossVariantView.project(Time.AFTER); + + Path patchPath2; + try { + patchPath2 = writeVariantsToFileSystem(sourceVariantCrossVariantViewBefore, + sourceVariantCrossVariantViewAfter, targetVariantBefore, code, patch, commitHash); + return runGnuDiff(patchPath2, commitHash); + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + private static Path writeVariantsToFileSystem(final VariationTree sourceVariantBefore, + final VariationTree sourceVariantAfter, final VariationTree targetVariantBefore, final String code, + final String patch, String commitHash) throws Exception { + final String sourceVariantCodeBefore = sourceVariantBefore.unparse(); + final String sourceVariantCodeAfter = sourceVariantAfter.unparse(); + final String targetVariantCodeBefore = targetVariantBefore.unparse(); + String newDir = directory + commitHash; + Path sourceVariantBeforePath = Path.of(newDir, sourceVariant, version1, code); + Path sourceVariantAfterPath = Path.of(newDir, sourceVariant, version2, code); + Path targetVariantBeforePath = Path.of(newDir, targetVariant, code); + Path patchPath = Path.of(newDir, targetVariant, patch); + writeToFile(sourceVariantCodeBefore, sourceVariantBeforePath); + writeToFile(sourceVariantCodeAfter, sourceVariantAfterPath); + writeToFile(targetVariantCodeBefore, targetVariantBeforePath); + return patchPath; + } + + private static void deleteFilesAndCreateNewDirectories(String commitHash) throws Exception { + String newDir = directory + commitHash; + File f = new File(Path.of(newDir).toUri()); + deleteDirectory(f); + f.delete(); + + if (!(new File(Path.of(newDir, targetVariant).toUri())).mkdirs() + || !(new File(Path.of(newDir, sourceVariant).toUri())).mkdir()) { + throw new Exception("Failed to create directories"); + } + if (!(new File(Path.of(newDir, sourceVariant, version1).toUri())).mkdirs() + || !(new File(Path.of(newDir, sourceVariant, version2).toUri())).mkdir()) { + throw new Exception("Failed to create directories"); + } + } + + private static boolean runGnuDiff(Path patchPath, String commitHash) { + try { + Path pathToVersion1Dir = Path.of(version1); + Path pathToVersion2Dir = Path.of(version2); + ShellExecutor shell = new ShellExecutor(Logger::info, Logger::error, + Path.of(directory + commitHash, sourceVariant)); + DiffCommand command = new DiffCommand("diff", "-Naur", pathToVersion1Dir.toString(), + pathToVersion2Dir.toString()); + List output = shell.execute(command); + if (command.areFilesDifferent()) { + writeToFile(output, patchPath); + } + return command.areFilesDifferent(); +// System.out.println(list); + } catch (ShellException e) { + System.out.println(e); + } + return false; + } + + public static void runPatchers(PatchScenario scenario, String commitHash) { + // ## 4. Run the patchers! + boolean isMpatchCorrect = false; + boolean isMpatchCorrect2 = false; + boolean isGnuPatchCorrect = false; + boolean isGnuPatchCorrect2 = false; + boolean isPatchTransformerCorrect = false; + List gameEngine = new ArrayList<>(); + gameEngine.add(Show.diff(scenario.sourcePatch, "source patch")); + gameEngine.add(Show.tree(scenario.targetVariantBefore, "target variant before")); + gameEngine.add(Show.diff(scenario.patchGroundTruth, "targetPatch")); + gameEngine.add(Show.tree(scenario.patchedVariantGroundTruth, "ground truth")); + + // Runs Pia's new patcher here and stores the result. + Result, Error> patchTransformerResult = runPatchTransformer(scenario.sourcePatch, + scenario.targetVariantBefore, scenario.sourceVariantConfig, scenario.targetVariantConfig); + if (patchTransformerResult.isSuccess()) { + gameEngine.add(Show.tree(patchTransformerResult.getSuccess(), "patch transformer result")); + } + isPatchTransformerCorrect = patchTransformerResult.match(tree -> { + Pair equiv = Patching.arePatchedVariantsEquivalent(tree, + scenario.sourceVariantAfterRedToCrossVarFeatures, scenario.targetVariantBeforeRedToUnchanged, + scenario.sourceVariantConfig, scenario.unchangedAfter); + return equiv.first() && equiv.second(); + }, error -> false); + + Result, Error> gnuPatchResult; + try { + gnuPatchResult = runGnuPatch(scenario.targetVariantBefore, patch, code, commitHash); + if (gnuPatchResult.isSuccess()) { + gameEngine.add(Show.tree(gnuPatchResult.getSuccess(), "gnu patch result")); + } + isGnuPatchCorrect = gnuPatchResult.match(tree -> { + Pair equiv = Patching.arePatchedVariantsEquivalent(tree, + scenario.sourceVariantAfterRedToCrossVarFeatures, scenario.targetVariantBeforeRedToUnchanged, + scenario.sourceVariantConfig, scenario.unchangedAfter); + return equiv.first() && equiv.second(); + }, error -> false); + } catch (IOException e) { + e.printStackTrace(); + } + + Result, Error> mpatchResult; + try { + mpatchResult = runMPatch(scenario.targetVariantBefore, patch, code, commitHash); + if (mpatchResult.isSuccess()) { + gameEngine.add(Show.tree(mpatchResult.getSuccess(), "mpatch result")); + } + isMpatchCorrect = mpatchResult.match(tree -> { + Pair equiv = Patching.arePatchedVariantsEquivalent(tree, + scenario.sourceVariantAfterRedToCrossVarFeatures, scenario.targetVariantBeforeRedToUnchanged, + scenario.sourceVariantConfig, scenario.unchangedAfter); + return equiv.first() && equiv.second(); + }, error -> false); + } catch (IOException e) { + e.printStackTrace(); + } + + generateViewVariants(scenario.sourcePatch, scenario.targetVariantBefore, scenario.targetVariantConfig, ""); + + Result, Error> mpatchResult2; + try { + mpatchResult2 = runMPatch(scenario.targetVariantBefore, patch, code, commitHash); + if (mpatchResult2.isSuccess()) { + gameEngine.add(Show.tree(mpatchResult2.getSuccess(), "mpatch result")); + } + isMpatchCorrect2 = mpatchResult2.match(tree -> { + Pair equiv = Patching.arePatchedVariantsEquivalent(tree, + scenario.sourceVariantAfterRedToCrossVarFeatures, scenario.targetVariantBeforeRedToUnchanged, + scenario.sourceVariantConfig, scenario.unchangedAfter); + return equiv.first() && equiv.second(); + }, error -> false); + } catch (IOException e) { + e.printStackTrace(); + } + + Result, Error> gnuPatchResult2; + try { + gnuPatchResult2 = runGnuPatch(scenario.targetVariantBefore, patch, code, commitHash); + if (gnuPatchResult2.isSuccess()) { + gameEngine.add(Show.tree(gnuPatchResult2.getSuccess(), "gnu patch result")); + } + isGnuPatchCorrect2 = gnuPatchResult2.match(tree -> { + Pair equiv = Patching.arePatchedVariantsEquivalent(tree, + scenario.sourceVariantAfterRedToCrossVarFeatures, scenario.targetVariantBeforeRedToUnchanged, + scenario.sourceVariantConfig, scenario.unchangedAfter); + return equiv.first() && equiv.second(); + }, error -> false); + } catch (IOException e) { + e.printStackTrace(); + } + + // Read the results of the patchers. The patchers should produce the + // target variants as string if they did not fail. + GameEngine[] gameEngineArray = new GameEngine[gameEngine.size()]; + gameEngineArray = gameEngine.toArray(gameEngineArray); + GameEngine.showAndAwaitAll(gameEngineArray); + + // ## 5. Compare the results of patchers here! + System.out.println("mpatch: " + isMpatchCorrect); + System.out.println("mpatch2: " + isMpatchCorrect2); + System.out.println("GNU patch: " + isGnuPatchCorrect); + System.out.println("GNU patch2: " + isGnuPatchCorrect2); + System.out.println("patch transformer " + isPatchTransformerCorrect); + // TODO + } + + public static Result, Error> runMPatch( + final VariationTree targetVariantBefore, String patch, String code, String commitHash) + throws IOException { + // configure mpatch + // reset target variant + resetTargetVariantBefore(targetVariantBefore, code, commitHash); + Path mpatchPath = Path.of("..", "..", "..", "mpatch", "target", "release", "mpatch"); + ShellExecutor shell = new ShellExecutor(Logger::info, Logger::error, + Path.of(directory + commitHash, targetVariant)); + MPatchCommand command = new MPatchCommand(mpatchPath.toString(), "--strip", "1", "--sourcedir", + Path.of("..", sourceVariant, version1).toString(), "--patchfile", patch); + try { + shell.execute(command); + } catch (ShellException e) { + Logger.error(e); + return Result.Failure(Error.ERROR); + } + if (command.isPatchingSuccessful()) { + try { + VariationTree mpatchResult = VariationTree + .fromFile(Path.of(directory + commitHash, targetVariant, code)); + return Result.Success(mpatchResult); + } catch (DiffParseException e) { + return Result.Success(null); + } + } + return Result.Failure(Error.FAILED); + } + + private static void resetTargetVariantBefore(final VariationTree targetVariantBefore, + String code, String commitHash) { + Path targetVariantBeforePath = Path.of(directory + commitHash, targetVariant, code); + writeToFile(targetVariantBefore.unparse(), targetVariantBeforePath); + } + + public static Result, Error> runGnuPatch( + final VariationTree targetVariantBefore, String patch, String code, String commitHash) + throws IOException { + // run mpatch and gnu patch. Here is a sketch for this can be done. + // reset target variant + resetTargetVariantBefore(targetVariantBefore, code, commitHash); + Path pathToTargetVariantCode = Path.of("..", targetVariant, code); + Path pathToSourceVariantPatch = Path.of("..", targetVariant, patch); + ShellExecutor shell = new ShellExecutor(Logger::info, Logger::error, + Path.of(directory + commitHash, sourceVariant)); + GnuPatchCommand command = new GnuPatchCommand("patch", pathToTargetVariantCode.toString(), + pathToSourceVariantPatch.toString()); + try { + shell.execute(command); + // reset target variant + } catch (ShellException e) { + Logger.error(e); + return Result.Failure(Error.ERROR); + } + if (command.isPatchingSuccessful()) { + try { + VariationTree gnuPatchResult = VariationTree + .fromFile(Path.of(directory + commitHash, targetVariant, code)); + return Result.Success(gnuPatchResult); + } catch (DiffParseException e) { + return Result.Success(null); + } + + } + return Result.Failure(Error.FAILED); + } + + public static Result, Error> runPatchTransformer( + VariationDiff sourcePatch, final VariationTree targetVariantBefore, + ConfigureWithFullConfig sourceVariantConfig, ConfigureWithFullConfig targetVariantConfig) { + VariationTree patchTransformerResult = null; + try { + VariationDiff diff = Patching.patch((VariationDiff) sourcePatch, + (VariationTree) targetVariantBefore, sourceVariantConfig, targetVariantConfig, + false, true); + patchTransformerResult = diff.project(Time.AFTER); + } catch (Exception e) { + e.printStackTrace(); + return Result.Failure(Error.FAILED); + } + return Result.Success(patchTransformerResult); + } +} diff --git a/src/main/java/org/variantsync/diffdetective/experiments/thesis_pm/PatchScenario.java b/src/main/java/org/variantsync/diffdetective/experiments/thesis_pm/PatchScenario.java new file mode 100644 index 000000000..aa5335634 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/experiments/thesis_pm/PatchScenario.java @@ -0,0 +1,40 @@ +package org.variantsync.diffdetective.experiments.thesis_pm; + +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.view.DiffView; +import org.variantsync.diffdetective.variation.tree.VariationTree; +import org.variantsync.diffdetective.variation.tree.view.TreeView; +import org.variantsync.diffdetective.variation.tree.view.relevance.Configure; +import org.variantsync.diffdetective.variation.tree.view.relevance.ConfigureWithFullConfig; +import org.variantsync.diffdetective.variation.tree.view.relevance.Unchanged; + +public class PatchScenario { + public VariationDiff sourcePatch; + public VariationTree targetVariantBefore; + public VariationDiff patchGroundTruth; + public VariationTree patchedVariantGroundTruth; + public ConfigureWithFullConfig sourceVariantConfig; + public ConfigureWithFullConfig targetVariantConfig; + public VariationTree sourceVariantAfterRedToCrossVarFeatures; + public Unchanged unchangedAfter; + public VariationTree targetVariantBeforeRedToUnchanged; + + public PatchScenario(VariationDiff sourcePatch, + VariationTree targetVariantBefore, VariationDiff patchGroundTruth, + VariationTree patchedVariantGroundTruth, ConfigureWithFullConfig sourceVariantConfig, ConfigureWithFullConfig targetVariantConfig) { + this.sourcePatch = sourcePatch; + this.targetVariantBefore = targetVariantBefore; + this.patchGroundTruth = patchGroundTruth; + this.patchedVariantGroundTruth = patchedVariantGroundTruth; + this.sourceVariantConfig = sourceVariantConfig; + this.targetVariantConfig = targetVariantConfig; + this.sourceVariantAfterRedToCrossVarFeatures = (VariationTree) TreeView.tree(sourcePatch.project(Time.AFTER), this.targetVariantConfig); + VariationDiff sourcePatchConfiguredToCrossVarFeatures = (VariationDiff) DiffView.optimized(sourcePatch, targetVariantConfig); + this.unchangedAfter = new Unchanged((VariationDiff) sourcePatchConfiguredToCrossVarFeatures, Time.AFTER); + Unchanged unchangedBefore = new Unchanged((VariationDiff) sourcePatchConfiguredToCrossVarFeatures, Time.BEFORE); + this.targetVariantBeforeRedToUnchanged = (VariationTree) TreeView.tree(this.targetVariantBefore, unchangedBefore); + } +} diff --git a/src/main/java/org/variantsync/diffdetective/experiments/thesis_pm/PatchingExperiment.java b/src/main/java/org/variantsync/diffdetective/experiments/thesis_pm/PatchingExperiment.java new file mode 100644 index 000000000..bffcd4d26 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/experiments/thesis_pm/PatchingExperiment.java @@ -0,0 +1,597 @@ +package org.variantsync.diffdetective.experiments.thesis_pm; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; + +import org.variantsync.diffdetective.AnalysisRunner; +import org.variantsync.diffdetective.variation.DiffLinesLabel; +import org.variantsync.diffdetective.variation.diff.patching.Patching; +import org.variantsync.diffdetective.variation.diff.transform.CutNonEditedSubtrees; +import org.variantsync.diffdetective.variation.tree.VariationTree; +import org.variantsync.diffdetective.variation.tree.view.TreeView; +import org.variantsync.diffdetective.variation.tree.view.relevance.ConfigureWithFullConfig; +import org.variantsync.diffdetective.variation.tree.view.relevance.Unchanged; +import org.variantsync.functjonal.Pair; +import org.variantsync.functjonal.Result; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.tinylog.Logger; +import org.variantsync.diffdetective.analysis.*; +import org.variantsync.diffdetective.datasets.PatchDiffParseOptions; +import org.variantsync.diffdetective.datasets.Repository; +import org.variantsync.diffdetective.experiments.thesis_pm.Generator.Error; +import org.variantsync.diffdetective.show.Show; +import org.variantsync.diffdetective.show.engine.GameEngine; +import org.variantsync.diffdetective.variation.diff.Time; +import org.variantsync.diffdetective.variation.diff.VariationDiff; +import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; + +public class PatchingExperiment implements Analysis.Hooks { + + private static final AnalysisResult.ResultKey PT_ERROR_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "PT - error patches"); + private static final AnalysisResult.ResultKey PT_REJECTED_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "PT - rejected patches"); + private static final AnalysisResult.ResultKey PT_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "PT - incorrectly applied patches"); + private static final AnalysisResult.ResultKey PT_INCORRECTLY_APPLIED_PATCHES_UNCH_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "PT - incorrectly applied patches (unchanged failed)"); + private static final AnalysisResult.ResultKey PT_INCORRECTLY_APPLIED_PATCHES_CONF_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "PT - incorrectly applied patches (configure failed)"); + private static final AnalysisResult.ResultKey PT_SUCCESSFULLY_APPLIED_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "PT - successfully applied patches"); + + private static final AnalysisResult.ResultKey GNU_ERROR_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "GNU - error patches"); + private static final AnalysisResult.ResultKey GNU_REJECTED_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "GNU - rejected patches"); + private static final AnalysisResult.ResultKey GNU_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "GNU - incorrectly applied patches"); + private static final AnalysisResult.ResultKey GNU_INCORRECTLY_APPLIED_PATCHES_UNCH_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "GNU - incorrectly applied patches (unchanged failed)"); + private static final AnalysisResult.ResultKey GNU_INCORRECTLY_APPLIED_PATCHES_CONF_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "GNU - incorrectly applied patches (configure failed)"); + private static final AnalysisResult.ResultKey GNU_SUCCESSFULLY_APPLIED_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "GNU - successfully applied patches"); + + private static final AnalysisResult.ResultKey MPATCH_ERROR_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "MPATCH - error patches"); + private static final AnalysisResult.ResultKey MPATCH_REJECTED_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "MPATCH - rejected patches"); + private static final AnalysisResult.ResultKey MPATCH_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "MPATCH - incorrectly applied patches"); + private static final AnalysisResult.ResultKey MPATCH_INCORRECTLY_APPLIED_PATCHES_UNCH_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "MPATCH - incorrectly applied patches (unchanged failed)"); + private static final AnalysisResult.ResultKey MPATCH_INCORRECTLY_APPLIED_PATCHES_CONF_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "MPATCH - incorrectly applied patches (configure failed)"); + private static final AnalysisResult.ResultKey MPATCH_SUCCESSFULLY_APPLIED_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "MPATCH - successfully applied patches"); + + private static final AnalysisResult.ResultKey GNUVIEW_ERROR_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "GNU View - error patches"); + private static final AnalysisResult.ResultKey GNUVIEW_REJECTED_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "GNU View - rejected patches"); + private static final AnalysisResult.ResultKey GNUVIEW_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "GNU View - incorrectly applied patches"); + private static final AnalysisResult.ResultKey GNUVIEW_INCORRECTLY_APPLIED_PATCHES_UNCH_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "GNU View - incorrectly applied patches (unchanged failed)"); + private static final AnalysisResult.ResultKey GNUVIEW_INCORRECTLY_APPLIED_PATCHES_CONF_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "GNU View - incorrectly applied patches (configure failed)"); + private static final AnalysisResult.ResultKey GNUVIEW_SUCCESSFULLY_APPLIED_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "GNU View - successfully applied patches"); + + private static final AnalysisResult.ResultKey MPATCHVIEW_ERROR_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "MPATCH View - error patches"); + private static final AnalysisResult.ResultKey MPATCHVIEW_REJECTED_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "MPATCH View - rejected patches"); + private static final AnalysisResult.ResultKey MPATCHVIEW_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "MPATCH View - incorrectly applied patches"); + private static final AnalysisResult.ResultKey MPATCHVIEW_INCORRECTLY_APPLIED_PATCHES_UNCH_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "MPATCH View - incorrectly applied patches (unchanged failed)"); + private static final AnalysisResult.ResultKey MPATCHVIEW_INCORRECTLY_APPLIED_PATCHES_CONF_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "MPATCH View - incorrectly applied patches (configure failed)"); + private static final AnalysisResult.ResultKey MPATCHVIEW_SUCCESSFULLY_APPLIED_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "MPATCH View - successfully applied patches"); + + private static final AnalysisResult.ResultKey PROCESSED_PATCHES_COUNTER_RESULT_KEY = new AnalysisResult.ResultKey<>( + "processed patches (my)"); + + private static final String PATCH = "patch.txt"; + private static final String CODE = "code.txt"; + + private int commits = 0; + private static int incorrectPatchesPT = 0; + private static Map, VariationTree>> failedPatches = new HashMap<>(); + private static Map> rejectedPatches = new HashMap<>(); + + private static class PTErrorPatchesCounter extends SimpleMetadata { + public PTErrorPatchesCounter() { + super(0, "PT - error patches", Integer::sum); + } + } + + private static class PTRejectedPatchesCounter extends SimpleMetadata { + public PTRejectedPatchesCounter() { + super(0, "PT - rejected patches", Integer::sum); + } + } + + private static class PTIncorrectlyAppliedPatchesCounter + extends SimpleMetadata { + public PTIncorrectlyAppliedPatchesCounter() { + super(0, "PT - incorrectly applied patches", Integer::sum); + } + } + + private static class PTIncorrectlyAppliedPatchesUnchCounter + extends SimpleMetadata { + public PTIncorrectlyAppliedPatchesUnchCounter() { + super(0, "PT - incorrectly applied patches (unchanged failed)", Integer::sum); + } + } + + private static class PTIncorrectlyAppliedPatchesConfCounter + extends SimpleMetadata { + public PTIncorrectlyAppliedPatchesConfCounter() { + super(0, "PT - incorrectly applied patches (configure failed)", Integer::sum); + } + } + + private static class PTSuccessfullyAppliedPatchesCounter + extends SimpleMetadata { + public PTSuccessfullyAppliedPatchesCounter() { + super(0, "PT - successfully applied patches", Integer::sum); + } + } + + private static class GNUErrorPatchesCounter extends SimpleMetadata { + public GNUErrorPatchesCounter() { + super(0, "GNU - error patches", Integer::sum); + } + } + + private static class GNURejectedPatchesCounter extends SimpleMetadata { + public GNURejectedPatchesCounter() { + super(0, "GNU - rejected patches", Integer::sum); + } + } + + private static class GNUIncorrectlyAppliedPatchesCounter + extends SimpleMetadata { + public GNUIncorrectlyAppliedPatchesCounter() { + super(0, "GNU - incorrectly applied patches", Integer::sum); + } + } + + private static class GNUIncorrectlyAppliedPatchesUnchCounter + extends SimpleMetadata { + public GNUIncorrectlyAppliedPatchesUnchCounter() { + super(0, "GNU - incorrectly applied patches (unchanged failed)", Integer::sum); + } + } + + private static class GNUIncorrectlyAppliedPatchesConfCounter + extends SimpleMetadata { + public GNUIncorrectlyAppliedPatchesConfCounter() { + super(0, "GNU - incorrectly applied patches (configure failed)", Integer::sum); + } + } + + private static class GNUSuccessfullyAppliedPatchesCounter + extends SimpleMetadata { + public GNUSuccessfullyAppliedPatchesCounter() { + super(0, "GNU - successfully applied patches", Integer::sum); + } + } + + private static class MPATCHErrorPatchesCounter extends SimpleMetadata { + public MPATCHErrorPatchesCounter() { + super(0, "MPATCH - error patches", Integer::sum); + } + } + + private static class MPATCHRejectedPatchesCounter extends SimpleMetadata { + public MPATCHRejectedPatchesCounter() { + super(0, "MPATCH - rejected patches", Integer::sum); + } + } + + private static class MPATCHIncorrectlyAppliedPatchesCounter + extends SimpleMetadata { + public MPATCHIncorrectlyAppliedPatchesCounter() { + super(0, "MPATCH - incorrectly applied patches", Integer::sum); + } + } + + private static class MPATCHIncorrectlyAppliedPatchesUnchCounter + extends SimpleMetadata { + public MPATCHIncorrectlyAppliedPatchesUnchCounter() { + super(0, "MPATCH - incorrectly applied patches (unchanged failed)", Integer::sum); + } + } + + private static class MPATCHIncorrectlyAppliedPatchesConfCounter + extends SimpleMetadata { + public MPATCHIncorrectlyAppliedPatchesConfCounter() { + super(0, "MPATCH - incorrectly applied patches (configure failed)", Integer::sum); + } + } + + private static class MPATCHSuccessfullyAppliedPatchesCounter + extends SimpleMetadata { + public MPATCHSuccessfullyAppliedPatchesCounter() { + super(0, "MPATCH - successfully applied patches", Integer::sum); + } + } + + private static class GNUViewErrorPatchesCounter extends SimpleMetadata { + public GNUViewErrorPatchesCounter() { + super(0, "GNU (View) - error patches", Integer::sum); + } + } + + private static class GNUViewRejectedPatchesCounter extends SimpleMetadata { + public GNUViewRejectedPatchesCounter() { + super(0, "GNU (View) - rejected patches", Integer::sum); + } + } + + private static class GNUViewIncorrectlyAppliedPatchesCounter + extends SimpleMetadata { + public GNUViewIncorrectlyAppliedPatchesCounter() { + super(0, "GNU (View) - incorrectly applied patches", Integer::sum); + } + } + + private static class GNUViewIncorrectlyAppliedPatchesUnchCounter + extends SimpleMetadata { + public GNUViewIncorrectlyAppliedPatchesUnchCounter() { + super(0, "GNU View - incorrectly applied patches (unchanged failed)", Integer::sum); + } + } + + private static class GNUViewIncorrectlyAppliedPatchesConfCounter + extends SimpleMetadata { + public GNUViewIncorrectlyAppliedPatchesConfCounter() { + super(0, "GNU View - incorrectly applied patches (configure failed)", Integer::sum); + } + } + + private static class GNUViewSuccessfullyAppliedPatchesCounter + extends SimpleMetadata { + public GNUViewSuccessfullyAppliedPatchesCounter() { + super(0, "GNU (View) - successfully applied patches", Integer::sum); + } + } + + private static class MPATCHViewErrorPatchesCounter extends SimpleMetadata { + public MPATCHViewErrorPatchesCounter() { + super(0, "MPATCH (View) - error patches", Integer::sum); + } + } + + private static class MPATCHViewRejectedPatchesCounter + extends SimpleMetadata { + public MPATCHViewRejectedPatchesCounter() { + super(0, "MPATCH (View) - rejected patches", Integer::sum); + } + } + + private static class MPATCHViewIncorrectlyAppliedPatchesCounter + extends SimpleMetadata { + public MPATCHViewIncorrectlyAppliedPatchesCounter() { + super(0, "MPATCH (View) - incorrectly applied patches", Integer::sum); + } + } + + private static class MPATCHViewIncorrectlyAppliedPatchesUnchCounter + extends SimpleMetadata { + public MPATCHViewIncorrectlyAppliedPatchesUnchCounter() { + super(0, "MPATCH View - incorrectly applied patches (unchanged failed)", Integer::sum); + } + } + + private static class MPATCHViewIncorrectlyAppliedPatchesConfCounter + extends SimpleMetadata { + public MPATCHViewIncorrectlyAppliedPatchesConfCounter() { + super(0, "MPATCH View - incorrectly applied patches (configure failed)", Integer::sum); + } + } + + private static class MPATCHViewSuccessfullyAppliedPatchesCounter + extends SimpleMetadata { + public MPATCHViewSuccessfullyAppliedPatchesCounter() { + super(0, "MPATCH (View) - successfully applied patches", Integer::sum); + } + } + + private static class ProcessedPatchesCounter extends SimpleMetadata { + public ProcessedPatchesCounter() { + super(0, "processed patches (my)", Integer::sum); + } + } + + @Override + public void initializeResults(Analysis analysis) { + analysis.append(PT_ERROR_PATCHES_COUNTER_RESULT_KEY, new PTErrorPatchesCounter()); + analysis.append(PT_REJECTED_PATCHES_COUNTER_RESULT_KEY, new PTRejectedPatchesCounter()); + analysis.append(PT_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY, new PTIncorrectlyAppliedPatchesCounter()); + analysis.append(PT_INCORRECTLY_APPLIED_PATCHES_CONF_COUNTER_RESULT_KEY, new PTIncorrectlyAppliedPatchesConfCounter()); + analysis.append(PT_INCORRECTLY_APPLIED_PATCHES_UNCH_COUNTER_RESULT_KEY, new PTIncorrectlyAppliedPatchesUnchCounter()); + analysis.append(PT_SUCCESSFULLY_APPLIED_PATCHES_COUNTER_RESULT_KEY, new PTSuccessfullyAppliedPatchesCounter()); + analysis.append(GNU_ERROR_PATCHES_COUNTER_RESULT_KEY, new GNUErrorPatchesCounter()); + analysis.append(GNU_REJECTED_PATCHES_COUNTER_RESULT_KEY, new GNURejectedPatchesCounter()); + analysis.append(GNU_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY, new GNUIncorrectlyAppliedPatchesCounter()); + analysis.append(GNU_INCORRECTLY_APPLIED_PATCHES_CONF_COUNTER_RESULT_KEY, new GNUIncorrectlyAppliedPatchesConfCounter()); + analysis.append(GNU_INCORRECTLY_APPLIED_PATCHES_UNCH_COUNTER_RESULT_KEY, new GNUIncorrectlyAppliedPatchesUnchCounter()); + analysis.append(GNU_SUCCESSFULLY_APPLIED_PATCHES_COUNTER_RESULT_KEY, + new GNUSuccessfullyAppliedPatchesCounter()); + analysis.append(MPATCH_ERROR_PATCHES_COUNTER_RESULT_KEY, new MPATCHErrorPatchesCounter()); + analysis.append(MPATCH_REJECTED_PATCHES_COUNTER_RESULT_KEY, new MPATCHRejectedPatchesCounter()); + analysis.append(MPATCH_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY, + new MPATCHIncorrectlyAppliedPatchesCounter()); + analysis.append(MPATCH_INCORRECTLY_APPLIED_PATCHES_CONF_COUNTER_RESULT_KEY, new MPATCHIncorrectlyAppliedPatchesConfCounter()); + analysis.append(MPATCH_INCORRECTLY_APPLIED_PATCHES_UNCH_COUNTER_RESULT_KEY, new MPATCHIncorrectlyAppliedPatchesUnchCounter()); + analysis.append(MPATCH_SUCCESSFULLY_APPLIED_PATCHES_COUNTER_RESULT_KEY, + new MPATCHSuccessfullyAppliedPatchesCounter()); + analysis.append(GNUVIEW_ERROR_PATCHES_COUNTER_RESULT_KEY, new GNUViewErrorPatchesCounter()); + analysis.append(GNUVIEW_REJECTED_PATCHES_COUNTER_RESULT_KEY, new GNUViewRejectedPatchesCounter()); + analysis.append(GNUVIEW_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY, + new GNUViewIncorrectlyAppliedPatchesCounter()); + analysis.append(GNUVIEW_INCORRECTLY_APPLIED_PATCHES_CONF_COUNTER_RESULT_KEY, new GNUViewIncorrectlyAppliedPatchesConfCounter()); + analysis.append(GNUVIEW_INCORRECTLY_APPLIED_PATCHES_UNCH_COUNTER_RESULT_KEY, new GNUViewIncorrectlyAppliedPatchesUnchCounter()); + analysis.append(GNUVIEW_SUCCESSFULLY_APPLIED_PATCHES_COUNTER_RESULT_KEY, + new GNUViewSuccessfullyAppliedPatchesCounter()); + analysis.append(MPATCHVIEW_ERROR_PATCHES_COUNTER_RESULT_KEY, new MPATCHViewErrorPatchesCounter()); + analysis.append(MPATCHVIEW_REJECTED_PATCHES_COUNTER_RESULT_KEY, new MPATCHViewRejectedPatchesCounter()); + analysis.append(MPATCHVIEW_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY, + new MPATCHViewIncorrectlyAppliedPatchesCounter()); + analysis.append(MPATCHVIEW_INCORRECTLY_APPLIED_PATCHES_CONF_COUNTER_RESULT_KEY, new MPATCHViewIncorrectlyAppliedPatchesConfCounter()); + analysis.append(MPATCHVIEW_INCORRECTLY_APPLIED_PATCHES_UNCH_COUNTER_RESULT_KEY, new MPATCHViewIncorrectlyAppliedPatchesUnchCounter()); + analysis.append(MPATCHVIEW_SUCCESSFULLY_APPLIED_PATCHES_COUNTER_RESULT_KEY, + new MPATCHViewSuccessfullyAppliedPatchesCounter()); + analysis.append(PROCESSED_PATCHES_COUNTER_RESULT_KEY, new ProcessedPatchesCounter()); + } + + @Override + public boolean beginCommit(Analysis analysis) throws Exception { + ++commits; + return true; + } + + @Override + public boolean analyzeVariationDiff(Analysis analysis) throws Exception { + VariationDiff diff = analysis.getCurrentVariationDiff(); + String commitHash = analysis.getCurrentCommit().getName(); + + PatchScenario scenario = Generator.generatePatchScenario(diff, commitHash); + + if (scenario == null) { + // something went wrong when generating the patching scenario + return false; + } + analysis.get(PROCESSED_PATCHES_COUNTER_RESULT_KEY).value++; + Result, Error> patchTransformerResult = Generator.runPatchTransformer( + scenario.sourcePatch, scenario.targetVariantBefore, scenario.sourceVariantConfig, + scenario.targetVariantConfig); + + patchTransformerResult.match(tree -> { + if (tree != null) { + Pair equiv = Patching.arePatchedVariantsEquivalent(tree, + scenario.sourceVariantAfterRedToCrossVarFeatures, scenario.targetVariantBeforeRedToUnchanged, + scenario.sourceVariantConfig, scenario.unchangedAfter); + if (!equiv.first()) { + analysis.get(PT_INCORRECTLY_APPLIED_PATCHES_CONF_COUNTER_RESULT_KEY).value++; + } + if (!equiv.second()) { + analysis.get(PT_INCORRECTLY_APPLIED_PATCHES_UNCH_COUNTER_RESULT_KEY).value++; + } + if (equiv.first() && equiv.second()) { + analysis.get(PT_SUCCESSFULLY_APPLIED_PATCHES_COUNTER_RESULT_KEY).value++; + } else { + analysis.get(PT_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY).value++; + } + } else { + PatchingExperiment.incorrectPatchesPT++; + PatchingExperiment.failedPatches.put(PatchingExperiment.incorrectPatchesPT, + new Pair, VariationTree>(scenario, tree)); + analysis.get(PT_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY).value++; + } + }, error -> { + PatchingExperiment.rejectedPatches.put(commitHash, scenario); + analysis.get(PT_REJECTED_PATCHES_COUNTER_RESULT_KEY).value++; + }); + + Result, Error> gnuPatchResult; + try { + gnuPatchResult = Generator.runGnuPatch(scenario.targetVariantBefore, PATCH, CODE, commitHash); + gnuPatchResult.match(tree -> { + if (tree != null) { + Pair equiv = Patching.arePatchedVariantsEquivalent(tree, + scenario.sourceVariantAfterRedToCrossVarFeatures, + scenario.targetVariantBeforeRedToUnchanged, scenario.sourceVariantConfig, + scenario.unchangedAfter); + if (!equiv.first()) { + analysis.get(GNU_INCORRECTLY_APPLIED_PATCHES_CONF_COUNTER_RESULT_KEY).value++; + } + if (!equiv.second()) { + analysis.get(GNU_INCORRECTLY_APPLIED_PATCHES_UNCH_COUNTER_RESULT_KEY).value++; + } + if (equiv.first() && equiv.second()) { + analysis.get(GNU_SUCCESSFULLY_APPLIED_PATCHES_COUNTER_RESULT_KEY).value++; + } else { + analysis.get(GNU_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY).value++; + } + } else { + analysis.get(GNU_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY).value++; + } + }, error -> analysis.get(GNU_REJECTED_PATCHES_COUNTER_RESULT_KEY).value++); + } catch (IOException e) { + analysis.get(GNU_ERROR_PATCHES_COUNTER_RESULT_KEY).value++; + } + + Result, Error> mpatchResult; + try { + mpatchResult = Generator.runMPatch(scenario.targetVariantBefore, PATCH, CODE, commitHash); + mpatchResult.match(tree -> { + if (tree != null) { + Pair equiv = Patching.arePatchedVariantsEquivalent(tree, + scenario.sourceVariantAfterRedToCrossVarFeatures, + scenario.targetVariantBeforeRedToUnchanged, scenario.sourceVariantConfig, + scenario.unchangedAfter); + if (!equiv.first()) { + analysis.get(MPATCH_INCORRECTLY_APPLIED_PATCHES_CONF_COUNTER_RESULT_KEY).value++; + } + if (!equiv.second()) { + analysis.get(MPATCH_INCORRECTLY_APPLIED_PATCHES_UNCH_COUNTER_RESULT_KEY).value++; + } + if (equiv.first() && equiv.second()) { + analysis.get(MPATCH_SUCCESSFULLY_APPLIED_PATCHES_COUNTER_RESULT_KEY).value++; + } else { + analysis.get(MPATCH_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY).value++; + } + } else { + analysis.get(MPATCH_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY).value++; + } + }, error -> analysis.get(MPATCH_REJECTED_PATCHES_COUNTER_RESULT_KEY).value++); + } catch (IOException e) { + analysis.get(MPATCH_ERROR_PATCHES_COUNTER_RESULT_KEY).value++; + } + + if (Generator.generateViewVariants(scenario.sourcePatch, scenario.targetVariantBefore, + scenario.targetVariantConfig, commitHash)) { + Result, Error> gnuPatchResultView; + try { + gnuPatchResultView = Generator.runGnuPatch(scenario.targetVariantBefore, PATCH, CODE, commitHash); + gnuPatchResultView.match(tree -> { + if (tree != null) { + Pair equiv = Patching.arePatchedVariantsEquivalent(tree, + scenario.sourceVariantAfterRedToCrossVarFeatures, + scenario.targetVariantBeforeRedToUnchanged, scenario.sourceVariantConfig, + scenario.unchangedAfter); + if (!equiv.first()) { + analysis.get(GNUVIEW_INCORRECTLY_APPLIED_PATCHES_CONF_COUNTER_RESULT_KEY).value++; + } + if (!equiv.second()) { + analysis.get(GNUVIEW_INCORRECTLY_APPLIED_PATCHES_UNCH_COUNTER_RESULT_KEY).value++; + } + if (equiv.first() && equiv.second()) { + analysis.get(GNUVIEW_SUCCESSFULLY_APPLIED_PATCHES_COUNTER_RESULT_KEY).value++; + } else { + analysis.get(GNUVIEW_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY).value++; + } + } else { + analysis.get(GNUVIEW_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY).value++; + } + }, error -> analysis.get(GNUVIEW_REJECTED_PATCHES_COUNTER_RESULT_KEY).value++); + } catch (IOException e) { + analysis.get(GNUVIEW_ERROR_PATCHES_COUNTER_RESULT_KEY).value++; + } + + Result, Error> mpatchResultView; + try { + mpatchResultView = Generator.runMPatch(scenario.targetVariantBefore, PATCH, CODE, commitHash); + mpatchResultView.match(tree -> { + if (tree != null) { + Pair equiv = Patching.arePatchedVariantsEquivalent(tree, + scenario.sourceVariantAfterRedToCrossVarFeatures, + scenario.targetVariantBeforeRedToUnchanged, scenario.sourceVariantConfig, + scenario.unchangedAfter); + if (!equiv.first()) { + analysis.get(MPATCHVIEW_INCORRECTLY_APPLIED_PATCHES_CONF_COUNTER_RESULT_KEY).value++; + } + if (!equiv.second()) { + analysis.get(MPATCHVIEW_INCORRECTLY_APPLIED_PATCHES_UNCH_COUNTER_RESULT_KEY).value++; + } + if (equiv.first() && equiv.second()) { + analysis.get(MPATCHVIEW_SUCCESSFULLY_APPLIED_PATCHES_COUNTER_RESULT_KEY).value++; + } else { + analysis.get(MPATCHVIEW_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY).value++; + } + } else { + analysis.get(MPATCHVIEW_INCORRECTLY_APPLIED_PATCHES_COUNTER_RESULT_KEY).value++; + } + }, error -> analysis.get(MPATCHVIEW_REJECTED_PATCHES_COUNTER_RESULT_KEY).value++); + } catch (IOException e) { + analysis.get(MPATCHVIEW_ERROR_PATCHES_COUNTER_RESULT_KEY).value++; + } + + } + + return true; + } + + public static void writeToFile(String s, String filename) { + try { + File f = new File(Path.of("data", "examples", filename).toUri()); + f.createNewFile(); + BufferedWriter myWriter = new BufferedWriter(new FileWriter(f)); + myWriter.write(s); + myWriter.close(); + } catch (IOException i) { + i.printStackTrace(); + } + } + + @Override + public void endBatch(Analysis analysis) throws Exception { + for (Map.Entry> entry: PatchingExperiment.rejectedPatches.entrySet()) { + writeScenarioToFilesystem("rejected", entry.getKey(), entry.getValue(), null); + } + PatchingExperiment.failedPatches.clear(); + PatchingExperiment.rejectedPatches.clear(); + Logger.info("Batch done: {} commits analyzed", commits); + } + + private void writeScenarioToFilesystem(String filePrefix, String key, PatchScenario scenario, + VariationTree patchedVariant) { + VariationDiff diff = scenario.sourcePatch; + VariationTree tree = scenario.targetVariantBefore; + PatchingExperiment.writeToFile(diff.project(Time.BEFORE).unparse(), filePrefix + key + "A1"); + PatchingExperiment.writeToFile(diff.project(Time.AFTER).unparse(), filePrefix + key + "A2"); + PatchingExperiment.writeToFile(scenario.sourceVariantAfterRedToCrossVarFeatures.unparse(), + filePrefix + key + "A2_red"); + PatchingExperiment.writeToFile(tree.unparse(), filePrefix + key + "B1"); + PatchingExperiment.writeToFile(scenario.targetVariantBeforeRedToUnchanged.unparse(), + filePrefix + key + "B1_unch"); + if (patchedVariant != null) { + PatchingExperiment.writeToFile(patchedVariant.unparse(), filePrefix + key + "B2"); + VariationTree red = TreeView.tree(patchedVariant, scenario.sourceVariantConfig); + VariationTree unch = TreeView.tree(patchedVariant, scenario.unchangedAfter); + PatchingExperiment.writeToFile(red.unparse(), filePrefix + key + "B2_red"); + PatchingExperiment.writeToFile(unch.unparse(), filePrefix + key + "B2_unch"); + + } + PatchingExperiment.writeToFile(scenario.sourceVariantConfig.toString(), filePrefix + key + "ConfigA"); + PatchingExperiment.writeToFile(scenario.targetVariantConfig.toString(), filePrefix + key + "ConfigB"); + } + + public static Analysis Create(Repository repo, Path outputDirectory, PatchingExperiment experiment) { + return new Analysis("my analysis", List.of(experiment, new StatisticsAnalysis() +// , new EditClassValidation() + ), repo, outputDirectory); + } + + public static void main(String[] args) { + PatchingExperiment experiment = new PatchingExperiment(); + final AnalysisRunner.Options defaultOptions = AnalysisRunner.Options.DEFAULT(args); + final AnalysisRunner.Options analysisOptions = new AnalysisRunner.Options(Path.of("data", "repos"), + Path.of("data", "output"), Path.of("data", "demo-dataset.md"), + repo -> new PatchDiffParseOptions(PatchDiffParseOptions.DiffStoragePolicy.DO_NOT_REMEMBER, + new VariationDiffParseOptions(false, false)), + defaultOptions.getFilterForRepo(), true, false); + try { + AnalysisRunner.run(analysisOptions, (repository, path) -> Analysis + .forEachCommit(() -> PatchingExperiment.Create(repository, path, experiment), 1000, 8)); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/org/variantsync/diffdetective/shell/DiffCommand.java b/src/main/java/org/variantsync/diffdetective/shell/DiffCommand.java new file mode 100644 index 000000000..3ddca412a --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/shell/DiffCommand.java @@ -0,0 +1,55 @@ +package org.variantsync.diffdetective.shell; + +import java.util.List; + +/** Single executable command with arguments. */ +public class DiffCommand extends ShellCommand { + private final String[] parts; + private boolean filesDifferent; + + /** + * Constructs a single command. + * The first argument has to be a path to an executable which will be given all of the + * remaining arguments as parameters on execution. + * + * @param cmd executable path and arguments for the executable + */ + public DiffCommand(final String... cmd) { + parts = cmd; + } + + @Override + public String[] parts() { + return parts; + } + + /** + * Interpret the result/exit code returned from a shell command. + * An {@code ShellException} is thrown if the result code is an error. + * + * @param resultCode the code that is to be parsed + * @param output the output of the shell command + * @return the output of the shell command + * @throws ShellException if {@code resultCode} is an error + */ + @Override + public List interpretResult(int resultCode, List output) throws ShellException { + // inputs are the same + if (resultCode == 0) { + filesDifferent = false; + return output; + } + // if inputs are different + if (resultCode == 1) { + filesDifferent = true; + return output; + } + // everything else: "serious trouble": exit code 2 + throw new ShellException(output); + + } + + public boolean areFilesDifferent() { + return filesDifferent; + } +} diff --git a/src/main/java/org/variantsync/diffdetective/shell/GnuPatchCommand.java b/src/main/java/org/variantsync/diffdetective/shell/GnuPatchCommand.java new file mode 100644 index 000000000..5f4f39371 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/shell/GnuPatchCommand.java @@ -0,0 +1,55 @@ +package org.variantsync.diffdetective.shell; + +import java.util.List; + +/** Single executable command with arguments. */ +public class GnuPatchCommand extends ShellCommand { + private final String[] parts; + private boolean patchingSuccessful; + + /** + * Constructs a single command. + * The first argument has to be a path to an executable which will be given all of the + * remaining arguments as parameters on execution. + * + * @param cmd executable path and arguments for the executable + */ + public GnuPatchCommand(final String... cmd) { + parts = cmd; + } + + @Override + public String[] parts() { + return parts; + } + + /** + * Interpret the result/exit code returned from a shell command. + * An {@code ShellException} is thrown if the result code is an error. + * + * @param resultCode the code that is to be parsed + * @param output the output of the shell command + * @return the output of the shell command + * @throws ShellException if {@code resultCode} is an error + */ + @Override + public List interpretResult(int resultCode, List output) throws ShellException { + // patching was successful + if (resultCode == 0) { + patchingSuccessful = true; + return null; + } + // some hunks cannot be applied or merge conflicts + if (resultCode == 1) { + patchingSuccessful = false; + return null; + } + // everything else: "serious trouble": exit code 2 + throw new ShellException(output); + + } + + public boolean isPatchingSuccessful() { + return patchingSuccessful; + } +} diff --git a/src/main/java/org/variantsync/diffdetective/shell/MPatchCommand.java b/src/main/java/org/variantsync/diffdetective/shell/MPatchCommand.java new file mode 100644 index 000000000..d503102c8 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/shell/MPatchCommand.java @@ -0,0 +1,55 @@ +package org.variantsync.diffdetective.shell; + +import java.util.List; + +/** Single executable command with arguments. */ +public class MPatchCommand extends ShellCommand { + private final String[] parts; + private boolean patchingSuccessful; + + /** + * Constructs a single command. + * The first argument has to be a path to an executable which will be given all of the + * remaining arguments as parameters on execution. + * + * @param cmd executable path and arguments for the executable + */ + public MPatchCommand(final String... cmd) { + parts = cmd; + } + + @Override + public String[] parts() { + return parts; + } + + /** + * Interpret the result/exit code returned from a shell command. + * An {@code ShellException} is thrown if the result code is an error. + * + * @param resultCode the code that is to be parsed + * @param output the output of the shell command + * @return the output of the shell command + * @throws ShellException if {@code resultCode} is an error + */ + @Override + public List interpretResult(int resultCode, List output) throws ShellException { + // patching was successful ??? + if (resultCode == 0) { + patchingSuccessful = true; + return null; + } + // some hunks cannot be applied or merge conflicts ??? + if (resultCode == 1) { + patchingSuccessful = false; + return null; + } + // everything else: "serious trouble": exit code 2 ??? + throw new ShellException(output); + + } + + public boolean isPatchingSuccessful() { + return patchingSuccessful; + } +} diff --git a/src/main/java/org/variantsync/diffdetective/util/StringUtils.java b/src/main/java/org/variantsync/diffdetective/util/StringUtils.java index 8f378dd21..d9b4c8337 100644 --- a/src/main/java/org/variantsync/diffdetective/util/StringUtils.java +++ b/src/main/java/org/variantsync/diffdetective/util/StringUtils.java @@ -6,7 +6,7 @@ /** A collection of useful utilities related to string processing. */ public class StringUtils { /** An operating system independent line break used in almost all internal strings. */ - public final static String LINEBREAK = "\r\n"; + public final static String LINEBREAK = "\n"; /** A regex to identify line breaks of any operating system .*/ public final static Pattern LINEBREAK_REGEX = Pattern.compile("\\r\\n|\\r|\\n"); 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 1a288996b..4792d3813 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/VariationDiff.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/VariationDiff.java @@ -378,16 +378,16 @@ public int count(final Predicate> nodesToCount) { * This method is deterministic: It will return the feature names always in the same order, assuming the variation diff is not changed inbetween. * @return A set of every occuring feature name. */ - public LinkedHashSet computeAllFeatureNames() { - LinkedHashSet features = new LinkedHashSet<>(); + public LinkedHashSet computeAllFeatureNames() { + LinkedHashSet features = new LinkedHashSet<>(); forAll(node -> { if (node.isConditionalAnnotation()) { features.addAll(node.getFormula().getUniqueContainedFeatures()); } }); // Since FeatureIDE falsely reports constants "True" and "False" as feature names, we have to remove them from the resulting set. - features.removeIf(FixTrueFalse::isTrueLiteral); - features.removeIf(FixTrueFalse::isFalseLiteral); + features.removeIf(f -> FixTrueFalse.isTrueLiteral((String) f)); + features.removeIf(f -> FixTrueFalse.isFalseLiteral((String) f)); return features; } diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/patching/Patching.java b/src/main/java/org/variantsync/diffdetective/variation/diff/patching/Patching.java new file mode 100644 index 000000000..19b240247 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/patching/Patching.java @@ -0,0 +1,629 @@ +package org.variantsync.diffdetective.variation.diff.patching; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import org.eclipse.jgit.diff.DiffAlgorithm; +import org.prop4j.And; +import org.prop4j.Literal; +import org.prop4j.Node; +import org.variantsync.diffdetective.analysis.logic.SAT; +import org.variantsync.diffdetective.diff.result.DiffParseException; +import org.variantsync.diffdetective.experiments.thesis_pm.Generator; +import org.variantsync.diffdetective.show.Show; +import org.variantsync.diffdetective.show.engine.GameEngine; +import org.variantsync.diffdetective.variation.DiffLinesLabel; +import org.variantsync.diffdetective.variation.Label; +import org.variantsync.diffdetective.variation.VariationLabel; +import org.variantsync.diffdetective.variation.diff.DiffNode; +import org.variantsync.diffdetective.variation.diff.DiffType; +import org.variantsync.diffdetective.variation.diff.Time; +import org.variantsync.diffdetective.variation.diff.VariationDiff; +import org.variantsync.diffdetective.variation.diff.construction.JGitDiff; +import org.variantsync.diffdetective.variation.diff.parse.VariationDiffParseOptions; +import org.variantsync.diffdetective.variation.diff.source.VariationDiffSource; +import org.variantsync.diffdetective.variation.diff.transform.CutNonEditedSubtrees; +import org.variantsync.diffdetective.variation.diff.view.DiffView; +import org.variantsync.diffdetective.variation.tree.VariationTree; +import org.variantsync.diffdetective.variation.tree.VariationTreeNode; +import org.variantsync.diffdetective.variation.tree.view.TreeView; +import org.variantsync.diffdetective.variation.tree.view.relevance.Configure; +import org.variantsync.diffdetective.variation.tree.view.relevance.ConfigureWithFullConfig; +import org.variantsync.diffdetective.variation.tree.view.relevance.Relevance; +import org.variantsync.diffdetective.variation.tree.view.relevance.Trace; +import org.variantsync.diffdetective.variation.tree.view.relevance.TraceSup; +import org.variantsync.diffdetective.variation.tree.view.relevance.Unchanged; +import org.variantsync.functjonal.Pair; + +public class Patching { + public static boolean hasSameLabel(L a, L b) { + String labelA = a.toString().replaceAll(" ", ""); + String labelB = b.toString().replaceAll(" ", ""); + return labelA.equals(labelB); + } + + public static boolean isSameAs(VariationDiff diff1, VariationDiff diff2) { + return isSameAs(diff1.getRoot(), diff2.getRoot()); + } + + public static boolean isSameAs(DiffNode a, DiffNode b) { + return isSameAs(a, b, new HashSet<>()); + } + + private static boolean isSameAs(DiffNode a, DiffNode b, Set> visited) { + if (!visited.add(a)) { + return true; + } + + if (!(a.getNodeType().equals(b.getNodeType()) && hasSameLabel(a.getLabel(), b.getLabel()) + && (a.getFormula() == null ? b.getFormula() == null : a.getFormula().equals(b.getFormula())))) { + return false; + } + + Iterator> aIt = a.getAllChildren().iterator(); + Iterator> bIt = b.getAllChildren().iterator(); + while (aIt.hasNext() && bIt.hasNext()) { + if (!isSameAs(aIt.next(), bIt.next(), visited)) { + return false; + } + } + + return aIt.hasNext() == bIt.hasNext(); + } + + private static Set> findRootsOfSubtrees(Set> nodes, DiffType type, + boolean debug) { + Time time = (type == DiffType.ADD) ? Time.AFTER : Time.BEFORE; + Set> subtreeRoots = new HashSet>(); + for (DiffNode node : nodes) { + if (!nodes.contains(node.getParent(time))) { + subtreeRoots.add(node); + } + } + if (debug) + System.out.println(subtreeRoots); + + return subtreeRoots; + } + + private static boolean compareAncestors(DiffNode node1, DiffNode node2, Time time, + boolean debug) { + if (node1.getParent(time) == null && node2.getParent(time) == null) + return true; + if (node1.getParent(time) != null && node2.getParent(time) == null) + return false; + if (node1.getParent(time) == null && node2.getParent(time) != null) + return false; + List> siblingsNode1 = node1.getParent(time).getChildOrder(time); + List> siblingsNode2 = node2.getParent(time).getChildOrder(time); + + if (debug) { + System.out.println("CA1: " + siblingsNode1); + System.out.println("CA2: " + siblingsNode2); + } + + int indexNode1 = siblingsNode1.indexOf(node1); + int indexNode2 = siblingsNode2.indexOf(node2); + if (indexNode1 != indexNode2) { + return false; + } + int index2 = 0; + for (int i = 0; i < siblingsNode1.size(); i++) { + if (i > indexNode1 && siblingsNode1.get(i).getDiffType() == DiffType.ADD) { + continue; + } +// if (i > index2 && siblingsNode2.get(index2).getDiffType() == DiffType.REM) + if (!hasSameLabel(siblingsNode1.get(i).getLabel(), siblingsNode2.get(index2).getLabel())) { + return false; + } + index2++; + } + return compareAncestors(node1.getParent(time), node2.getParent(time), time, debug); + } + + private static boolean checkNeighborsLabels(DiffNode root, + DiffNode targetNodeInPatch, Time time, boolean debug) { + if (root.getParent(time) == null && targetNodeInPatch == null) { + return true; + } + if ((root.getParent(time) != null && targetNodeInPatch == null) + || (root.getParent(time) == null && targetNodeInPatch != null)) { + return false; + } + return compareAncestors(root.getParent(time), targetNodeInPatch, time, debug); + } + + private static boolean isSameList(List> sourceList, + List> targetList, ConfigureWithFullConfig configSource) { + int indexTarget = 0; + // source must be equal or smaller than target list, because it is the view on + // the source patch (cross variant features) + if (sourceList.size() > targetList.size()) { + return false; + } + if (sourceList.size() == 0) { + if (targetList.size() == 0) { + return true; + } + // check that all nodes in target list are not present in source + for (DiffNode targetNode : targetList) { + if (isPresentUnderConfiguration(targetNode, configSource)) { + return false; + } + } + return true; + } + for (DiffNode sourceNode : sourceList) { + if (indexTarget >= targetList.size()) { + return false; + } + while (!isPresentUnderConfiguration(targetList.get(indexTarget), configSource)) { + indexTarget++; + if (indexTarget >= targetList.size()) { + return false; + } + } + DiffNode targetNode = targetList.get(indexTarget); + if (!hasSameLabel(sourceNode.getLabel(), targetNode.getLabel())) { + return false; + } + indexTarget++; + } + while (indexTarget != targetList.size()) { + if (isPresentUnderConfiguration(targetList.get(indexTarget), configSource)) { + return false; + } + indexTarget++; + } + return true; + } + + private static boolean isPresentUnderConfiguration(DiffNode diffNode, ConfigureWithFullConfig config) { + return config.test(diffNode.projection(Time.BEFORE)); + } + + private static DiffNode checkNeighbors2(DiffNode root, + DiffNode targetNodeInPatch, ConfigureWithFullConfig configSource, Time time, boolean debug) + throws Exception { + List> orderedChildrenTarget = targetNodeInPatch.getChildOrder(time); + List> orderedChildrenSource = root.getParent(time).getChildOrder(time); + int indexSource = orderedChildrenSource.indexOf(root); + List> candidates = new ArrayList<>(); + for (DiffNode node : orderedChildrenTarget) { + if (!hasSameLabel(node.getLabel(), root.getLabel())) { + continue; + } + int indexTarget = orderedChildrenTarget.indexOf(node); + // there are nodes in the source patch which a + List> neighborsBeforeSource = orderedChildrenSource.subList(0, indexSource); + List> neighborsAfterSource = orderedChildrenSource.subList(indexSource + 1, + orderedChildrenSource.size()); + List> neighborsBeforeTarget = orderedChildrenTarget.subList(0, indexTarget); + List> neighborsAfterTarget = orderedChildrenTarget.subList(indexTarget + 1, + orderedChildrenTarget.size()); + if (isSameList(neighborsBeforeSource, neighborsBeforeTarget, configSource) + && isSameList(neighborsAfterSource, neighborsAfterTarget, configSource)) { + candidates.add(node); + } + } + if (candidates.size() != 1) { + throw new Exception("Reject: too many nodes to remove: " + candidates.size()); + } + return candidates.get(0); + } + + private static int findInsertPosition2(DiffNode root, DiffNode targetNodeInPatch, + DiffNode targetNodeInPatchView, Time time, boolean debug) throws Exception { + List> orderedChildrenTarget = targetNodeInPatch.getChildOrder(time); + List> orderedChildrenSource = root.getParent(time).getChildOrder(time); + int indexSource = orderedChildrenSource.indexOf(root); + int indexTarget = 0; + int insertPosition = indexSource; + for (int i = 0; i < orderedChildrenSource.size(); i++) { + if (i == indexSource) { + insertPosition = indexTarget; + continue; + } + if (i > indexSource && orderedChildrenSource.get(i).getDiffType() == DiffType.ADD) { + continue; + } + if (!hasSameLabel(orderedChildrenSource.get(i).getLabel(), + orderedChildrenTarget.get(indexTarget).getLabel())) { + while (!hasSameLabel(orderedChildrenSource.get(i).getLabel(), + orderedChildrenTarget.get(indexTarget).getLabel())) { + indexTarget++; + if (indexTarget >= orderedChildrenTarget.size()) { + throw new Exception("could not find insert position"); + } + } + } + indexTarget++; + } + return insertPosition; + } + + private static void applyChanges(DiffType type, VariationDiff targetVariantDiffUnchanged, + VariationDiff targetVariantDiffPatched, List> subtreeRoots, + VariationDiffSource source, ConfigureWithFullConfig configSource, boolean debug) throws Exception { + + Time time = (type == DiffType.ADD) ? Time.AFTER : Time.BEFORE; + + for (DiffNode root : subtreeRoots) { + if (debug) { + DiffNode newRoot = DiffNode.createRoot(new DiffLinesLabel()); + newRoot.addChild(root.deepCopy(), time); + VariationDiff subTree = new VariationDiff(newRoot, source); + GameEngine.showAndAwaitAll(Show.diff(subTree)); + } + + List> targetNodes = new ArrayList>(); + + if (root.getParent(time).getDiffType().existsAtTime(time)) { + final Node presenceCondition = root.getParent(time).getPresenceCondition(time); + targetNodes = targetVariantDiffUnchanged.computeAllNodesThat( + node -> node.getPresenceCondition(Time.AFTER).equals(presenceCondition) && node.isAnnotation()); + } + + VariationDiff targetVariantDiffPatchedView = DiffView + .optimized(targetVariantDiffPatched.deepCopy(), configSource); + targetNodes = targetNodes.stream().filter(targetNode -> checkNeighborsLabels(root, + targetVariantDiffPatchedView.getNodeWithID(targetNode.getID()), time, debug)).toList(); + targetNodes = targetNodes.stream() + .map(targetNode -> targetVariantDiffPatched.getNodeWithID(targetNode.getID())).toList(); + if (targetNodes.size() != 1) { + throw new Exception("too much or too less target nodes after filtering: " + targetNodes.size()); + } + + DiffNode targetNodeInPatch = targetNodes.get(0); + if (debug) + System.out.println(targetNodeInPatch.toString()); + if (type == DiffType.ADD) { + if (debug) { + GameEngine.showAndAwaitAll(Show.tree(targetVariantDiffPatched.project(Time.AFTER))); + } + int insertPosition = findInsertPosition2(root, targetNodeInPatch, + targetVariantDiffPatchedView.getNodeWithID(targetNodeInPatch.getID()), time, debug); + if (insertPosition < 0) { + if (debug) + System.out.println("no matching insert position found"); + } + if (debug) + System.out.println("subtree added"); + targetNodeInPatch.insertChild(root.deepCopy(), insertPosition, time); + if (debug) + System.out.println(targetNodeInPatch.getChildOrder(time)); + + } else if (type == DiffType.REM) { + DiffNode nodesToRem = checkNeighbors2(root, targetNodeInPatch, configSource, time, + debug); + if (debug) + System.out.println("Nodes to remove: " + nodesToRem); + + if (debug) + System.out.println("subtree removed"); + DiffNode newRoot = DiffNode.createRoot(new DiffLinesLabel()); + newRoot.addChild(root.deepCopy(), time); + VariationDiff subTree = new VariationDiff(newRoot, source); + DiffNode newRoot2 = DiffNode.createRoot(new DiffLinesLabel()); + newRoot2.addChild(nodesToRem.deepCopy(), time); + VariationDiff subTreeB = new VariationDiff(newRoot2, + targetVariantDiffPatchedView.getSource()); + removeNode(subTreeB.project(time), targetVariantDiffPatched, subTree); + + if (debug) + System.out.println(targetNodeInPatch.getChildOrder(Time.AFTER)); + } + if (debug) { + VariationDiff targetVariantDiffPatchedCopy = targetVariantDiffPatched.deepCopy(); +// CutNonEditedSubtrees.genericTransform(targetVariantDiffPatchedCopy); + GameEngine.showAndAwaitAll(Show.diff(targetVariantDiffPatchedCopy)); + } + } + } + + private static boolean areAllChildrenPlannedToRemove(List> children, + List idsToRemove) { + for (VariationTreeNode child : children) { + if (!idsToRemove.contains(child.getID())) { + return false; + } + } + return true; + } + + private static void removeNode(VariationTree node, VariationDiff diffToRemoveFrom, + VariationDiff subtree) throws Exception { + List idsToRemove = new ArrayList<>(); + // Nodes with these labels and pc should be removed + List labelAndPC = new ArrayList<>(); + subtree.forAll(n -> { + if (!n.isRoot()) { + String identifier = calcIdentifier(n); + labelAndPC.add(identifier); + } + }); + + node.forAllPostorder(n -> { + String identifier = calcIdentifier(n); + // this node should be removed, but it can only be removed if it does not have + // any other children + if (labelAndPC.contains(identifier)) { + // n is leaf or all children should be removed + if (n.isLeaf() || areAllChildrenPlannedToRemove(n.getChildren(), idsToRemove)) { + idsToRemove.add(n.getID()); + } + } + }); + for (Integer id : idsToRemove) { + DiffNode n = diffToRemoveFrom.getNodeWithID(id); +// n.diffType = DiffType.REM; + if (n.getParent(Time.AFTER) != null) { + n.drop(Time.AFTER); + } + } + } + + private static String calcIdentifier(VariationTreeNode n) { + String identifier = ""; + List l = n.getLabel().getLines(); + if (l != null) { + for (String line : l) { + identifier += line; + } + } + List tl = n.getLabel().getTrailingLines(); + if (tl != null) { + for (String line : tl) { + identifier += line; + } + } + Node pc = n.getPresenceCondition(); + if (pc != null) { + identifier += pc.toString(); + } + return identifier; + } + + private static String calcIdentifier(DiffNode n) { + String identifier = ""; + List l = n.getLabel().getLines(); + if (l != null) { + for (String line : l) { + identifier += line; + } + } + List tl = n.getLabel().getTrailingLines(); + if (tl != null) { + for (String line : tl) { + identifier += line; + } + } + Node pc = n.getPresenceCondition(Time.BEFORE); + if (pc != null) { + identifier += pc.toString(); + } + return identifier; + } + + public static void changeType(DiffNode node, VariationDiff modDiff, DiffType type) { + if (!node.isLeaf()) { + node.getAllChildrenStream().forEach(child -> changeType(child, modDiff, type)); + } + DiffNode matchingNode = modDiff.getNodeWithID(node.getID()); + if (matchingNode == null) { + return; + } + Time time = type == DiffType.ADD ? Time.BEFORE : Time.AFTER; + if (matchingNode.isNon()) { + matchingNode.diffType = type; + matchingNode.drop(time); + } else if (matchingNode.diffType != type) { + matchingNode.drop(); + } + } + + public static void resolve(DiffNode node, VariationDiff modDiff) { + if (!node.isLeaf()) { + node.getAllChildrenStream().forEach(child -> resolve(child, modDiff)); + } + if (node.isNon() && node.getParent(Time.BEFORE) != node.getParent(Time.AFTER)) { +// System.out.println(node.getLabel()); + DiffNode matchingNode = modDiff.getNodeWithID(node.getID()); + // matchingNode can be null because it is a child from two parents + if (matchingNode == null) { + return; + } + DiffNode parentAfter = matchingNode.getParent(Time.AFTER); + DiffNode parentBefore = matchingNode.getParent(Time.BEFORE); + int indexAfter = parentAfter.getChildOrder(Time.AFTER).indexOf(matchingNode); + int indexBefore = parentBefore.getChildOrder(Time.BEFORE).indexOf(matchingNode); + matchingNode.drop(); + DiffNode nodeAfter = matchingNode.deepCopy(); + nodeAfter.diffType = DiffType.ADD; + + DiffNode newRootAfter = DiffNode.createRoot(new DiffLinesLabel()); + newRootAfter.addChild(nodeAfter.deepCopy(), Time.AFTER); + VariationDiff subTreeAfter = new VariationDiff(newRootAfter, + modDiff.getSource()); + + if (!nodeAfter.isLeaf()) { + changeType(nodeAfter, subTreeAfter, DiffType.ADD); + // remove all children with difftype REM recursively + // set recursively difftype ADD for all children which have currently difftype + // NON + } + +// GameEngine.showAndAwaitAll(Show.diff(subTreeAfter)); + + DiffNode nodeBefore = matchingNode.deepCopy(); + nodeBefore.diffType = DiffType.REM; + + DiffNode newRootBefore = DiffNode.createRoot(new DiffLinesLabel()); + newRootBefore.addChild(nodeBefore.deepCopy(), Time.BEFORE); + VariationDiff subTreeBefore = new VariationDiff(newRootBefore, + modDiff.getSource()); + + if (!nodeBefore.isLeaf()) { + changeType(nodeBefore, subTreeBefore, DiffType.REM); + } + +// GameEngine.showAndAwaitAll(Show.diff(subTreeBefore)); + + parentAfter.insertChild(subTreeAfter.getRoot().getAllChildren().iterator().next(), indexAfter, Time.AFTER); + parentBefore.insertChild(subTreeBefore.getRoot().getAllChildren().iterator().next(), indexBefore, + Time.BEFORE); + +// GameEngine.showAndAwaitAll(Show.diff(modDiff)); + + } + } + + public static VariationDiff patch(VariationDiff sourcePatch, + VariationTree targetVariant, ConfigureWithFullConfig configSource, ConfigureWithFullConfig configTarget, boolean debug, + boolean patchNewFeatures) throws Exception { + + VariationDiff optimizedDiff = DiffView.optimized(sourcePatch, configTarget); +// GameEngine.showAndAwaitAll(Show.diff(optimizedDiff)); + + if (debug) { + GameEngine.showAndAwaitAll(Show.diff(optimizedDiff), Show.tree(optimizedDiff.project(Time.AFTER))); + } + + VariationDiffSource source = optimizedDiff.getSource(); + VariationDiff targetVariantDiffUnchanged = targetVariant.deepCopy() + .toCompletelyUnchangedVariationDiff(); + VariationDiff targetVariantDiffPatched = targetVariant.deepCopy() + .toCompletelyUnchangedVariationDiff(); + + Set> removedNodes = new HashSet>(); + Set> addedNodes = new HashSet>(); + + // resolve nodes with two parents to two nodes, one added, one removed + VariationDiff diffCopy = optimizedDiff.deepCopy(); + List> nodesToResolve = new ArrayList<>(); + + optimizedDiff.forAll(node -> { + if (node.isNon() && node.getParent(Time.BEFORE) != node.getParent(Time.AFTER)) { + DiffNode matchingNode = diffCopy.getNodeWithID(node.getID()); + nodesToResolve.add(matchingNode); + } + }); + + resolve(optimizedDiff.getRoot(), diffCopy); + + if (debug) { + GameEngine.showAndAwaitAll(Show.diff(diffCopy, "resolved")); + } + optimizedDiff = diffCopy; + + // remove old nodes + optimizedDiff.forAll(node -> { + if (node.isRem()) { + removedNodes.add(node); + } + }); + Set> removedSubtreeRoots = findRootsOfSubtrees(removedNodes, DiffType.REM, debug); + List> removedSortedSubtreeRoots = removedSubtreeRoots.stream() + .sorted((n1, n2) -> Integer.compare(n1.getLinesAtTime(Time.BEFORE).fromInclusive(), + n2.getLinesAtTime(Time.BEFORE).fromInclusive())) + .collect(Collectors.toList()); + applyChanges(DiffType.REM, targetVariantDiffUnchanged, targetVariantDiffPatched, removedSortedSubtreeRoots, + source, configSource, debug); + + // add new nodes + optimizedDiff.forAll(node -> { + if (node.isAdd()) { + addedNodes.add(node); + } + }); + Set> addedSubtreeRoots = findRootsOfSubtrees(addedNodes, DiffType.ADD, debug); + List> addedSortedSubtreeRoots = addedSubtreeRoots.stream().sorted((n1, n2) -> Integer + .compare(n1.getLinesAtTime(Time.AFTER).fromInclusive(), n2.getLinesAtTime(Time.AFTER).fromInclusive())) + .collect(Collectors.toList()); + applyChanges(DiffType.ADD, targetVariantDiffUnchanged, targetVariantDiffPatched, addedSortedSubtreeRoots, + source, configSource, debug); + + if (debug) { + GameEngine.showAndAwaitAll(Show.diff(sourcePatch), Show.tree(targetVariant), Show.diff(optimizedDiff), + Show.diff(targetVariantDiffPatched), Show.tree(targetVariantDiffPatched.project(Time.AFTER))); + } + + if (debug) { + VariationDiff targetVariantDiffPatchedCopy = targetVariantDiffPatched.deepCopy(); + VariationDiff optimizedDiffCopy = optimizedDiff.deepCopy(); + CutNonEditedSubtrees.genericTransform(targetVariantDiffPatchedCopy); + CutNonEditedSubtrees.genericTransform(optimizedDiffCopy); + GameEngine.showAndAwaitAll(Show.diff(optimizedDiffCopy), Show.diff(targetVariantDiffPatchedCopy)); + } + return targetVariantDiffPatched; + } + + public static VariationDiff parseVariationDiffFromFiles(String file1, String file2) + throws IOException, DiffParseException { + Path examplesDir = Path.of("data", "examples"); + return VariationDiff.fromFiles(examplesDir.resolve(file1), examplesDir.resolve(file2), + DiffAlgorithm.SupportedAlgorithm.MYERS, VariationDiffParseOptions.Default); + } + + public static VariationDiff parseVariationDiffFromFile(String file) + throws IOException, DiffParseException { + Path examplesDir = Path.of("data", "examples"); + return VariationDiff.fromFile(examplesDir.resolve(file), VariationDiffParseOptions.Default); + } + + public static VariationTree parseVariationTreeFromFile(String file) { + Path examplesDir = Path.of("data", "examples"); + Path path = examplesDir.resolve(file); + try { + VariationTree tree = VariationTree.fromFile(path, VariationDiffParseOptions.Default); + return tree; + } catch (IOException e) { + e.printStackTrace(); + } catch (DiffParseException e) { + e.printStackTrace(); + } + return null; + } + + public static Pair arePatchedVariantsEquivalent( + VariationTree patchedTargetVariant, + VariationTree sourceVariantAfterRedToCrossVarFeatures, + VariationTree targetVariantBeforeRedToUnchanged, ConfigureWithFullConfig configSourceVariant, + Unchanged unchangedAfter) { + + VariationTree targetVariantAfterRedToCrossVarFeatures = TreeView.tree(patchedTargetVariant, + configSourceVariant); + + VariationTree patchedTargetVariantRedToUnchanged = TreeView.tree(patchedTargetVariant, + unchangedAfter); + +// GameEngine.showAndAwaitAll(Show.tree(patchedTargetVariant, "patched target variant"), +// Show.tree(patchedTargetVariantRedToUnchanged, "patched target variant red. to unchanged"), +// Show.tree(sourceVariantAfterRedToCrossVarFeatures, +// "patched source variant red. to cross variant features"), +// Show.tree(targetVariantAfterRedToCrossVarFeatures, +// "patched target variant red. to cross variant features"), +// Show.tree(targetVariantBeforeRedToUnchanged, "target variant before red. to unchanged")); + + return new Pair( + sourceVariantAfterRedToCrossVarFeatures.unparse().equals(targetVariantAfterRedToCrossVarFeatures.unparse()), + patchedTargetVariantRedToUnchanged.unparse().equals(targetVariantBeforeRedToUnchanged.unparse())); + + } + + public static boolean comparePatchedVariantWithExpectedResult(VariationTree patchedVariant, + VariationTree expectedResult) { + return Patching.isSameAs(patchedVariant.toCompletelyUnchangedVariationDiff(), + expectedResult.toCompletelyUnchangedVariationDiff()); + } + +} diff --git a/src/main/java/org/variantsync/diffdetective/variation/diff/transform/EliminateEmptyAlternatives.java b/src/main/java/org/variantsync/diffdetective/variation/diff/transform/EliminateEmptyAlternatives.java index f03201235..1b1084b28 100644 --- a/src/main/java/org/variantsync/diffdetective/variation/diff/transform/EliminateEmptyAlternatives.java +++ b/src/main/java/org/variantsync/diffdetective/variation/diff/transform/EliminateEmptyAlternatives.java @@ -83,7 +83,7 @@ private static void elim(VariationTreeNode subtree) { } // When there is exactly one child and that child is an 'else' or 'elif' we can simplify that nesting. else if (children.size() == 1) { - final VariationTreeNode child = children.getFirst(); + final VariationTreeNode child = children.get(0); if ((subtree.isIf() || subtree.isElif()) && (child.isElif() || child.isElse())) { // determine new feaure mapping @@ -105,4 +105,4 @@ else if (children.size() == 1) { public void transform(VariationTree tree) { tree.forAllPostorder(EliminateEmptyAlternatives::elim); } -} +} \ No newline at end of file diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/ConfigureWithFullConfig.java b/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/ConfigureWithFullConfig.java new file mode 100644 index 000000000..733ce38cc --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/ConfigureWithFullConfig.java @@ -0,0 +1,120 @@ +package org.variantsync.diffdetective.variation.tree.view.relevance; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import org.prop4j.Node; +import org.variantsync.diffdetective.util.Assert; +import org.variantsync.diffdetective.util.fide.FixTrueFalse; +import org.variantsync.diffdetective.variation.NodeType; +import org.variantsync.diffdetective.variation.tree.VariationNode; + +/** + * Relevance predicate that generates (partial) variants from variation trees. + * This relevance predicate is the implementation of Equation 5 in our SPLC'23 paper. + */ +public class ConfigureWithFullConfig implements Relevance { + private final Map assignment; + + public ConfigureWithFullConfig(final Map assignment) { + this.assignment = assignment; + //FIXME: Fix FixTrueFalse TrueNames and FalseNames + this.assignment.put("0", false); + this.assignment.put("1", true); + this.assignment.put("False", false); + this.assignment.put("True", true); + Map map = new HashMap<>(); + for (Map.Entry entry : assignment.entrySet()) { + map.put("!" + String.valueOf(entry.getKey()), !entry.getValue()); + } + this.assignment.putAll(map); + } + + @Override + public boolean test(VariationNode v) { + // Since FeatureIDE falsely reports constants "True" and "False" as feature names, we have to remove them from the resulting set. + try { + return v.getPresenceCondition().getValue(this.assignment); + } catch (Exception e) { + System.out.println(this.assignment); + throw e; + } + } + + private > void computeViewNodes(List vs, Consumer markRelevant) { + for (final TreeNode c : vs) { + computeViewNodes(c, markRelevant); + } + } + + @Override + public > void computeViewNodes(TreeNode v, Consumer markRelevant) { + // The implementation of this method must be semantically equivalent to the default implementation Relevance.super.computeViewNodes(v, markRelevant); + // The implementation of this method is supposed to be optimized to avoid redundant SAT calls when possible. + // This requirement is modelled explicitly in terms of the ConfigureSpec class. + + // If the child is an artifact, it has the same presence condition as v does, so it is also included in the view. + // The root must be in any view to ensure consistency. + if (v.isArtifact() || v.isRoot()) { + markRelevant.accept(v); + computeViewNodes(v.getChildren(), markRelevant); + } else { + computeViewNodesOfElifChain(v, markRelevant); + } + } + + /** + * @return true if the given node was marked relevant + */ + private > boolean computeViewNodesOfElifChain(TreeNode v, Consumer markRelevant) { + Assert.assertTrue(v.isAnnotation()); + + if (test(v)) { + markRelevant.accept(v); + computeViewNodes(v.getChildren(), markRelevant); + return true; + } else { + // If a partial configuration excludes the node v (i.e, test(v) == false), + // then any ELIF or ELSE branches might still be included. + for (final TreeNode c : v.getChildren()) { + NodeType ct = c.getNodeType(); + + // We can skip all children that are not ELSE or ELIF nodes because these are excluded because v is exluded. + // If our node v was deemed irrelevant and it has an ELSE node c, that ELSE node c must be relevant. + if (ct == NodeType.ELSE) { + markRelevant.accept(v); + markRelevant.accept(c); + computeViewNodes(c.getChildren(), markRelevant); + return true; + } + + // An ELIF might hold any formula which we have to test. + // We handle ELIF nodes recursively because ELIFs might be nested. + // If at least one branch of the ELIF chain is included, we must include also v in the view to retain consistency of the tree. + if (ct == NodeType.ELIF && computeViewNodesOfElifChain(c, markRelevant)) { + markRelevant.accept(v); + return true; + } + } + } + + return false; + } + + @Override + public String parametersToString() { + return assignment.toString(); + } + + @Override + public String getFunctionName() { + return "configure"; + } + + @Override + public String toString() { + return Relevance.toString(this); + } +} diff --git a/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/Unchanged.java b/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/Unchanged.java new file mode 100644 index 000000000..5f2b02646 --- /dev/null +++ b/src/main/java/org/variantsync/diffdetective/variation/tree/view/relevance/Unchanged.java @@ -0,0 +1,174 @@ +package org.variantsync.diffdetective.variation.tree.view.relevance; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.prop4j.Node; +import org.variantsync.diffdetective.show.Show; +import org.variantsync.diffdetective.show.engine.GameEngine; +import org.variantsync.diffdetective.variation.DiffLinesLabel; +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.patching.Patching; +import org.variantsync.diffdetective.variation.tree.VariationNode; + +public class Unchanged implements Relevance { + private final VariationDiff diff; + private final Time time; + private Map>> lookUpMap = new HashMap<>(); + + public Unchanged(VariationDiff diff, Time time) { + this.diff = diff; + this.time = time; + VariationDiff diffCopy = diff.deepCopy(); + Patching.resolve((DiffNode) diff.getRoot(), + (VariationDiff) diffCopy); + diffCopy.forAll(diffNode -> { +// if (time == Time.AFTER ? !diffNode.isRem() : !diffNode.isAdd()) { + String key = getIdentifierForNode(diffNode); + if (!lookUpMap.containsKey(key)) { + lookUpMap.put(key, new ArrayList>()); + } + lookUpMap.get(key).add(diffNode); +// } + }); + } + + private String getIdentifierForNode(DiffNode diffNode) { + String key = diffNode.getNodeType().name(); + Node formula = diffNode.getFormula(); + if (formula != null) { + key += formula.toString(); + } + for (String s : diffNode.getLabel().getLines()) { + key += s; + } + for (String s : diffNode.getLabel().getTrailingLines()) { + key += s; + } + Node pc = diffNode.isRem() ? diffNode.getPresenceCondition(Time.BEFORE) : diffNode.getPresenceCondition(Time.AFTER); + if (pc != null) { + key += pc; + } + + // add label of both siblings to identifier if existing +// DiffNode parent = diffNode.getParent(time); +// if (parent != null) { +// +// List> siblings = parent.getChildOrder(time); +// int indexDiffNode = siblings.indexOf(diffNode); +// +// if (indexDiffNode - 1 >= 0) { +// List beforeSibling = siblings.get(indexDiffNode - 1).getLabel().getLines(); +// for (String s : beforeSibling) { +// key += s; +// } +// } +// if (indexDiffNode + 1 < siblings.size()) { +// List afterSibling = siblings.get(indexDiffNode + 1).getLabel().getLines(); +// for (String s : afterSibling) { +// key += s; +// } +// } +// +// } + return key; + } + + private String getIdentifierForNode(VariationNode node) { + String key = node.getNodeType().name(); + Node formula = node.getFormula(); + if (formula != null) { + key += formula.toString(); + } + for (String s : node.getLabel().getLines()) { + key += s; + } + for (String s : node.getLabel().getTrailingLines()) { + key += s; + } + Node pc = node.getPresenceCondition(); + if (pc != null) { + key += pc; + } + + // add label of both siblings to identifier if existing +// VariationNode parent = node.getParent(); +// if (parent != null) { +// List siblings = node.getParent().getChildren(); +// int indexNode = siblings.indexOf(node); +// +// int indexBefore = indexNode - 1; +// VariationNode beforeSibling = null; +// if (indexBefore >= 0) { +// beforeSibling = (VariationNode) siblings.get(indexBefore); +// while (!configSource.test(beforeSibling)) { +// indexBefore--; +// if (indexBefore < 0) { +// break; +// } +// beforeSibling = (VariationNode) siblings.get(indexBefore); +// } +// if (beforeSibling != null) { +// for (String s : beforeSibling.getLabel().getLines()) { +// key += s; +// } +// } +// } +// int indexAfter = indexNode + 1; +// VariationNode afterSibling = null; +// if (indexAfter < siblings.size()) { +// afterSibling = (VariationNode) siblings.get(indexAfter); +// while (!configSource.test(afterSibling)) { +// indexAfter++; +// if (indexAfter >= siblings.size()) { +// break; +// } +// afterSibling = (VariationNode) siblings.get(indexAfter); +// } +// if (afterSibling != null) { +// for (String s : afterSibling.getLabel().getLines()) { +// key += s; +// } +// } +// } +// } + return key; + } + + @Override + public boolean test(VariationNode t) { + String identifier = getIdentifierForNode(t); + List> tInDiffMatches = lookUpMap.get(identifier); + if (tInDiffMatches == null) { + return true; + } + DiffNode tInDiff = tInDiffMatches.get(0); + if (tInDiffMatches.size() > 1) { + Map, Integer> map = new HashMap<>(); + for (DiffNode diffNode : tInDiffMatches) { + Time time1 = diffNode.isRem() ? Time.BEFORE : Time.AFTER; + Integer lineNumberDiffNode = diffNode.getLinesAtTime(time1).fromInclusive(); + map.put(diffNode, Math.abs(t.getLineRange().fromInclusive() - lineNumberDiffNode)); + } + List, Integer>> list = new ArrayList<>(map.entrySet()); + list.sort(Entry.comparingByValue()); + tInDiff = list.get(0).getKey(); + } + return tInDiff.isNon() && tInDiff.beforePathEqualsAfterPath(); + } + + @Override + public String getFunctionName() { + return "unchanged"; + } + + @Override + public String parametersToString() { + return diff.toString(); + } +}