diff --git a/.gitignore b/.gitignore
new file mode 100755
index 0000000..efcb6d8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+.classpath
+.project
+*.iml
+*.ipr
+*.iws
+.idea/
+target/
diff --git a/molerat/pom.xml b/molerat/pom.xml
new file mode 100644
index 0000000..df3c0bd
--- /dev/null
+++ b/molerat/pom.xml
@@ -0,0 +1,136 @@
+
+
+
+
+ it.unitn
+ foss-vuln-tracker
+ 1.0-SNAPSHOT
+
+ 4.0.0
+ it.unitn.molerat
+ molerat
+ 1.0-SNAPSHOT
+ jar
+
+
+
+
+ org.antlr
+ antlr4-runtime
+ 4.7
+
+
+ org.mongodb
+ bson
+ 3.4.2
+
+
+ commons-cli
+ commons-cli
+ 1.4
+
+
+ it.unitn.repoman
+ repoman
+ 1.0-SNAPSHOT
+
+
+ commons-io
+ commons-io
+ 2.5
+
+
+ org.apache.commons
+ commons-lang3
+ 3.4
+
+
+ org.mongodb
+ mongodb-driver
+ 3.4.2
+
+
+ org.mongodb
+ mongodb-driver-core
+ 3.4.2
+
+
+ org.eclipse.jgit
+ org.eclipse.jgit
+ 4.2.0.201601211800-r
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.25
+
+
+ org.slf4j
+ slf4j-nop
+ 1.7.25
+
+
+ org.tmatesoft.svnkit
+ svnkit-cli
+ 1.8.13
+
+
+ org.tmatesoft.svnkit
+ svnkit
+ 1.8.13
+
+
+ org.tmatesoft.svnkit
+ svnkit-javahl16
+ 1.8.13
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 3.0.1
+
+
+ copy-dependencies
+ prepare-package
+
+ copy-dependencies
+
+
+
+ ${project.build.directory}/libs
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.0.2
+
+
+
+ true
+ libs/
+ it.unitn.molerat.cmd.Main
+
+
+
+
+
+
+
diff --git a/molerat/src/main/java/it/unitn/molerat/cmd/Main.java b/molerat/src/main/java/it/unitn/molerat/cmd/Main.java
new file mode 100644
index 0000000..8e0a3eb
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/cmd/Main.java
@@ -0,0 +1,267 @@
+package it.unitn.molerat.cmd;
+
+import it.unitn.molerat.data.csv.InputDataPoint;
+import it.unitn.molerat.data.csv.VulnEvidenceDataPoint;
+import it.unitn.molerat.data.db.MongoWrapper;
+import it.unitn.molerat.data.memory.AnalysisEntry;
+import it.unitn.molerat.evidence.VulnerabilityEvidence;
+import it.unitn.molerat.repos.trackers.vuln.VulnerabilityEvidenceTracker;
+import it.unitn.molerat.repos.trackers.vuln.VulnerabilityEvidenceTrackerFactory;
+import it.unitn.molerat.repos.utils.IORoutines;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.cli.*;
+import org.bson.types.ObjectId;
+
+public class Main {
+
+ private static MongoWrapper db = new MongoWrapper("molerat");
+
+ public static void main(String[] args) {
+ Options opts = new Options();
+
+ Option listTrackersOpt = Option.builder()
+ .longOpt("list-trackers")
+ .desc("List the available trackers for vulnerability molerat.evidence")
+ .build();
+ opts.addOption(listTrackersOpt);
+
+ Option projectNameOpt = Option.builder()
+ .longOpt("project-name")
+ .desc("The name of the project")
+ .hasArg()
+ .build();
+ opts.addOption(projectNameOpt);
+
+ Option repoTypeOpt = Option.builder()
+ .longOpt("repo-type")
+ .desc("The type of the source code repository ('git' or 'svn')")
+ .hasArg()
+ .build();
+ opts.addOption(repoTypeOpt);
+
+ Option repoPathOpt = Option.builder()
+ .longOpt("repo-path")
+ .desc("The path of the working copy of the source code repository")
+ .hasArg()
+ .build();
+ opts.addOption(repoPathOpt);
+
+ Option cveIdOpt = Option.builder()
+ .longOpt("cve-id")
+ .desc("The CVE identifier of a vulnerability")
+ .hasArg()
+ .build();
+ opts.addOption(cveIdOpt);
+
+ Option fixCommitOpt = Option.builder()
+ .longOpt("fix-commit")
+ .desc("The commit that fixed a vulnerability")
+ .hasArg()
+ .build();
+ opts.addOption(fixCommitOpt);
+
+ Option trackerTypeOpt = Option.builder()
+ .longOpt("tracker-type")
+ .desc("The type of the vulnerability molerat.evidence tracker")
+ .hasArg()
+ .build();
+ opts.addOption(trackerTypeOpt);
+
+ Option inputFileOpt = Option.builder()
+ .longOpt("input-file")
+ .desc("Path to the input .csv file")
+ .hasArg()
+ .argName("input-file-path")
+ .build();
+ opts.addOption(inputFileOpt);
+
+ Option outputFileOpt = Option.builder()
+ .longOpt("output-file")
+ .desc("Path to the output .csv file")
+ .hasArg()
+ .argName("output-file-path")
+ .build();
+ opts.addOption(outputFileOpt);
+
+ CommandLineParser cmdParser = new DefaultParser();
+ HelpFormatter helpFormatter = new HelpFormatter();
+ CommandLine cmd;
+ try {
+ cmd = cmdParser.parse(opts, args);
+ if (cmd.getOptions().length == 0) {
+ throw new ParseException("Arguments are not specified");
+ }
+
+ if (cmd.hasOption("list-trackers")) {
+ System.out.println(VulnerabilityEvidenceTrackerFactory.getTrackersList());
+ return;
+ }
+
+ if (cmd.hasOption("input-file")) {
+ String i = cmd.getOptionValue("input-file");
+ collectVulnEvidence(i);
+ }
+ if (cmd.hasOption("output-file")) {
+ String o = cmd.getOptionValue("output-file");
+ generateCsv(o);
+ }
+
+ if (!cmd.hasOption("input-file") && !cmd.hasOption("output-file")) {
+ String projectName = null;
+ String repoType = null;
+ String repoPath = null;
+ String cveId = null;
+ String fixCommit = null;
+ String trackerType = null;
+
+ if (cmd.hasOption("project-name")) {
+ projectName = cmd.getOptionValue("project-name");
+ }
+ if (cmd.hasOption("repo-type")) {
+ repoType = cmd.getOptionValue("repo-type");
+ }
+ if(cmd.hasOption("repo-path")) {
+ repoPath = cmd.getOptionValue("repo-path");
+ }
+ if(cmd.hasOption("cve-id")) {
+ cveId = cmd.getOptionValue("cve-id");
+ }
+ if(cmd.hasOption("fix-commit")) {
+ fixCommit = cmd.getOptionValue("fix-commit");
+ }
+ if(cmd.hasOption("tracker-type")) {
+ trackerType = cmd.getOptionValue("tracker-type");
+ }
+ if (projectName == null) {
+ throw new ParseException("The 'project-name' parameter is not specified");
+ }
+ if (repoType == null) {
+ throw new ParseException("The 'repo-type' parameter is not specified");
+ }
+ if (repoPath == null) {
+ throw new ParseException("The 'repo-path' parameter is not specified");
+ }
+ if (cveId == null) {
+ throw new ParseException("The 'cve-id' parameter is not specified");
+ }
+ if (fixCommit == null) {
+ throw new ParseException("The 'fix-commit' parameter is not specified");
+ }
+ if (trackerType == null) {
+ throw new ParseException("The 'tracker-type' parameter is not specified");
+ }
+ collect(projectName, repoType, repoPath, cveId, fixCommit, trackerType);
+ }
+ }
+ catch (ParseException e) {
+ System.out.println("ERROR: " + e.getMessage());
+ helpFormatter.printHelp("utility-name", opts);
+ }
+ }
+
+ private static void collectVulnEvidence(String inputCsvPath) {
+ try {
+ Set inputs = IORoutines.readInputDataPoints(inputCsvPath);
+ for (InputDataPoint ip : inputs) {
+ collect(ip.PROJECTID,
+ ip.REPO_TYPE,
+ ip.REPO_ROOT,
+ ip.CVEID,
+ ip.FIX_REV,
+ ip.TRACKER_TYPE);
+ }
+ } catch (Exception e) {
+ System.out.println("ERROR: " + e.getMessage());
+ }
+ }
+
+ private static void collect(String projectName, String repositoryType,
+ String repositoryPath, String cveName, String fixCommit, String trackerType) {
+ try {
+ boolean entryExists = db.analysisEntryExists(projectName, cveName, repositoryType, repositoryPath);
+ if (entryExists) {
+ System.out.format("WARNING: The analysis entry for '%s' from '%s' already exists, skipping\n", cveName, projectName);
+ return;
+ }
+
+ System.out.format("INFO: Collecting the molerat.evidence for '%s' from '%s'\n", cveName, projectName);
+ VulnerabilityEvidenceTracker vulnTracker = VulnerabilityEvidenceTrackerFactory.getTracker(
+ repositoryPath,
+ fixCommit,
+ repositoryType,
+ trackerType
+ );
+ vulnTracker.trackEvidence();
+
+ AnalysisEntry entry = new AnalysisEntry(
+ cveName,
+ projectName,
+ repositoryType,
+ repositoryPath,
+ fixCommit,
+ vulnTracker.getProcessedCommits(),
+ vulnTracker.getEvidences(),
+ null // we're not tracking any changes so far
+ );
+ boolean isSuccessful = db.insertAnalysisEntry(entry);
+ if (!isSuccessful) {
+ System.out.format("WARNING: Could not get any molerat.evidence for '%s' in '%s'\n", cveName, projectName);
+ }
+ System.out.println("INFO: Done!");
+ }
+ catch (Exception e) {
+ System.out.println("ERROR: " + e.getMessage());
+ }
+ }
+
+ private static void generateCsv(String outFilePath) {
+ System.out.format("INFO: generating the .csv file '%s'\n", outFilePath);
+ String csvHeader = new VulnEvidenceDataPoint().getHeader() + "\n";
+ try {
+ IORoutines.writeFile(outFilePath, csvHeader);
+ Set cveIds = db.getAllCveIds();
+ for (ObjectId cveId : cveIds) {
+ AnalysisEntry analysis = db.getAnalysisEntry(cveId);
+ if (analysis == null) {
+ continue;
+ }
+ String projectName = analysis.getProjectName();
+ String cveName = analysis.getCveName();
+
+ System.out.format("INFO: writing the entries for '%s'\n", cveName);
+ Set evidences = analysis.getVulnEvidencesSet();
+ if (evidences.size() == 0) {
+ throw new Exception(String.format("There is no molerat.evidence for '%s' in the database", cveName));
+ }
+ Map locsCount = new LinkedHashMap<>();
+ for (VulnerabilityEvidence evd : evidences) {
+ if (locsCount.containsKey(evd.getCommit())) {
+ int count = locsCount.get(evd.getCommit());
+ locsCount.remove(evd.getCommit());
+ locsCount.put(evd.getCommit(), ++count);
+ } else {
+ locsCount.put(evd.getCommit(), 1);
+ }
+ }
+ int timestamp = 0;
+ for (Map.Entry entry : locsCount.entrySet()) {
+ VulnEvidenceDataPoint dp = new VulnEvidenceDataPoint(new String[]{
+ projectName,
+ cveName,
+ entry.getKey(),
+ String.valueOf(timestamp--),
+ String.valueOf(entry.getValue())
+ });
+ IORoutines.writeFile(outFilePath, dp.toString() + "\n");
+ }
+
+ }
+ } catch (Exception e) {
+ System.out.println("ERROR: " + e.getMessage());
+ }
+ }
+}
+
diff --git a/molerat/src/main/java/it/unitn/molerat/cmd/experiments/AbstractExperiment.java b/molerat/src/main/java/it/unitn/molerat/cmd/experiments/AbstractExperiment.java
new file mode 100644
index 0000000..8b8d339
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/cmd/experiments/AbstractExperiment.java
@@ -0,0 +1,16 @@
+package it.unitn.molerat.cmd.experiments;
+
+import it.unitn.molerat.data.csv.InputDataPoint;
+import it.unitn.molerat.repos.utils.IORoutines;
+import java.util.Set;
+
+public class AbstractExperiment {
+
+ protected static Set readInputDataPoints(String path) throws Exception {
+ return IORoutines.readInputDataPoints(path);
+ }
+
+ protected static void saveOutput(String path, String output) throws Exception {
+ IORoutines.writeFile(path, output + "\n");
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/cmd/experiments/CalculateAllChangesExperiment.java b/molerat/src/main/java/it/unitn/molerat/cmd/experiments/CalculateAllChangesExperiment.java
new file mode 100644
index 0000000..151139b
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/cmd/experiments/CalculateAllChangesExperiment.java
@@ -0,0 +1,61 @@
+package it.unitn.molerat.cmd.experiments;
+
+import it.unitn.molerat.data.csv.InputDataPoint;
+import it.unitn.molerat.data.csv.OutputDataPoint;
+import it.unitn.molerat.data.csv.PerfOutputDataPoint;
+import it.unitn.molerat.repos.utils.CommitMetrics;
+import it.unitn.molerat.repos.wrappers.GitRepoWrapper;
+import it.unitn.molerat.repos.wrappers.RepoWrapper;
+
+import java.util.Set;
+
+public class CalculateAllChangesExperiment extends AbstractExperiment {
+ public static void main(String[] args) {
+ if (args.length < 2) {
+ System.out.println("USAGE: [input.csv] [output.csv]");
+ return;
+ }
+ String inputCsvPath = args[0];
+ String outputCsvPath = args[1];
+
+ try {
+ Set inputs = readInputDataPoints(inputCsvPath);
+ saveOutput(outputCsvPath, new PerfOutputDataPoint().getHeader());
+ for (InputDataPoint idp : inputs) {
+
+ System.out.println("Processing the " + idp.CVEID + "...");
+ long timeNow = System.currentTimeMillis();
+
+ RepoWrapper wrapper = new GitRepoWrapper(idp.REPO_ROOT);
+ Set processedCommits = wrapper.getRevisionNumbers(idp.FIX_REV);
+ int timestamp = 0;
+ for (String commit : processedCommits) {
+
+ int publicAPICount = CommitMetrics.getNumberOfPublicMethodsPerRevision(commit, wrapper);
+ int rem = CommitMetrics.getGlobalPublicMethodsRemoved(commit, idp.FIX_REV, wrapper);
+ double currentUntouched = (((double) publicAPICount - (double) rem) / (double) publicAPICount) * 100.0;
+ currentUntouched = Math.round(currentUntouched * 100.0) / 100.0;
+
+ String[] output = new String[]{
+ idp.CVEID,
+ idp.TRACKER_TYPE,
+ "empty",
+ commit,
+ String.valueOf(timestamp),
+ "empty",
+ String.valueOf(publicAPICount),
+ String.valueOf(rem),
+ String.valueOf(currentUntouched)
+ };
+ OutputDataPoint odp = new OutputDataPoint(output);
+ saveOutput(outputCsvPath, odp.toString());
+ timestamp--;
+ }
+ long timeAfter = System.currentTimeMillis();
+ System.out.println("TIME (ms): " + (timeAfter - timeNow));
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/cmd/experiments/ChangeEvdPerformanceExperiment.java b/molerat/src/main/java/it/unitn/molerat/cmd/experiments/ChangeEvdPerformanceExperiment.java
new file mode 100644
index 0000000..25a9f6a
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/cmd/experiments/ChangeEvdPerformanceExperiment.java
@@ -0,0 +1,53 @@
+package it.unitn.molerat.cmd.experiments;
+
+import it.unitn.molerat.data.csv.InputDataPoint;
+import it.unitn.molerat.data.csv.PerfOutputDataPoint;
+import it.unitn.molerat.repos.trackers.vuln.VulnerabilityEvidenceTracker;
+import it.unitn.molerat.repos.trackers.vuln.VulnerabilityEvidenceTrackerFactory;
+import it.unitn.molerat.repos.utils.CommitMetrics;
+import java.util.Set;
+
+public class ChangeEvdPerformanceExperiment extends AbstractExperiment {
+ public static void main(String[] args) {
+ if (args.length < 2) {
+ System.out.println("USAGE: [input.csv] [output.csv]");
+ return;
+ }
+ String inputCsvPath = args[0];
+ String outputCsvPath = args[1];
+
+ try {
+ Set inputs = readInputDataPoints(inputCsvPath);
+ saveOutput(outputCsvPath, new PerfOutputDataPoint().getHeader());
+ for (InputDataPoint idp : inputs) {
+
+ System.out.println("Processing the " + idp.CVEID + "...");
+
+ VulnerabilityEvidenceTracker vulnTracker = VulnerabilityEvidenceTrackerFactory.getTracker(idp.REPO_ROOT, idp.FIX_REV, idp.REPO_TYPE, idp.TRACKER_TYPE);
+ vulnTracker.trackEvidence();
+
+ Set processedCommits = vulnTracker.getProcessedCommits();
+ for (String commit : processedCommits) {
+ long timeNow = System.currentTimeMillis();
+
+ int publicAPICount = CommitMetrics.getNumberOfPublicMethodsPerRevision(commit, vulnTracker.getRepoWrapper());
+ int rem = CommitMetrics.getGlobalPublicMethodsRemoved(commit, vulnTracker.getFixedRevision(), vulnTracker.getRepoWrapper());
+ double currentUntouched = (((double) publicAPICount - (double) rem) / (double) publicAPICount) * 100.0;
+ currentUntouched = Math.round(currentUntouched * 100.0) / 100.0;
+
+ long timeAfter = System.currentTimeMillis();
+ String[] output = new String[]{
+ idp.CVEID,
+ idp.TRACKER_TYPE,
+ String.valueOf(timeAfter - timeNow)
+ };
+ PerfOutputDataPoint odp = new PerfOutputDataPoint(output);
+ saveOutput(outputCsvPath, odp.toString());
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/cmd/experiments/VulnEvdPerformanceExperiment.java b/molerat/src/main/java/it/unitn/molerat/cmd/experiments/VulnEvdPerformanceExperiment.java
new file mode 100644
index 0000000..7860763
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/cmd/experiments/VulnEvdPerformanceExperiment.java
@@ -0,0 +1,45 @@
+package it.unitn.molerat.cmd.experiments;
+
+import it.unitn.molerat.data.csv.InputDataPoint;
+import it.unitn.molerat.data.csv.PerfOutputDataPoint;
+import it.unitn.molerat.repos.trackers.vuln.VulnerabilityEvidenceTracker;
+import it.unitn.molerat.repos.trackers.vuln.VulnerabilityEvidenceTrackerFactory;
+import java.util.Set;
+
+public class VulnEvdPerformanceExperiment extends AbstractExperiment {
+
+ public static void main(String[] args) {
+ if (args.length < 2) {
+ System.out.println("USAGE: [input.csv] [output.csv]");
+ return;
+ }
+ String inputCsvPath = args[0];
+ String outputCsvPath = args[1];
+
+ try {
+ Set inputs = readInputDataPoints(inputCsvPath);
+ saveOutput(outputCsvPath, new PerfOutputDataPoint().getHeader());
+ for (InputDataPoint idp : inputs) {
+
+ System.out.println("Processing the " + idp.CVEID + "...");
+ long timeNow = System.currentTimeMillis();
+
+ VulnerabilityEvidenceTracker vulnTracker = VulnerabilityEvidenceTrackerFactory.getTracker(idp.REPO_ROOT, idp.FIX_REV, idp.REPO_TYPE, idp.TRACKER_TYPE);
+ vulnTracker.trackEvidence();
+
+ long timeAfter = System.currentTimeMillis();
+
+ String[] output = new String[]{
+ idp.CVEID,
+ idp.TRACKER_TYPE,
+ String.valueOf(timeAfter - timeNow)
+ };
+ PerfOutputDataPoint odp = new PerfOutputDataPoint(output);
+ saveOutput(outputCsvPath, odp.toString());
+
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/data/csv/AbstractDataPoint.java b/molerat/src/main/java/it/unitn/molerat/data/csv/AbstractDataPoint.java
new file mode 100644
index 0000000..54971e4
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/data/csv/AbstractDataPoint.java
@@ -0,0 +1,68 @@
+package it.unitn.molerat.data.csv;
+
+import java.lang.reflect.Field;
+
+public abstract class AbstractDataPoint {
+
+ public AbstractDataPoint(String[] entries) throws Exception {
+ Field[] fields = this.getClass().getFields();
+ if (entries.length != fields.length) {
+ throw new Exception("Bad entries for initializing a molerat.data point!");
+ }
+ int i = 0;
+ for (Field field : fields) {
+ if (!field.getName().equals("this$0")) {
+ try {
+ field.set(this, entries[i++]);
+ } catch (IllegalArgumentException e) {
+ System.out.println("ERROR: " + e.getMessage());
+ } catch (IllegalAccessException e) {
+ System.out.println("ERROR: " + e.getMessage());
+ }
+ }
+ }
+ }
+
+ public AbstractDataPoint(String str) throws Exception {
+ this(str.replace(" ", "").split(","));
+ }
+
+ public AbstractDataPoint() {
+
+ }
+
+ @Override
+ public final String toString() {
+ StringBuilder builder = new StringBuilder();
+ Field[] fields = this.getClass().getFields();
+ for (Field field : fields) {
+ if (!field.getName().equals("this$0")) {
+ try {
+ builder.append(field.get(this) + ",");
+ } catch (IllegalArgumentException e) {
+ System.out.println("ERROR: " + e.getMessage());
+ } catch (IllegalAccessException e) {
+ System.out.println("ERROR: " + e.getMessage());
+ }
+ }
+ }
+ builder.deleteCharAt(builder.length()-1);
+ return builder.toString();
+ }
+
+ public final String getHeader() {
+ StringBuilder builder = new StringBuilder();
+ Field[] fields = this.getClass().getFields();
+ for (Field field : fields) {
+ if (!field.getName().equals("this$0")) {
+ try {
+ builder.append(field.getName() + ",");
+ } catch (IllegalArgumentException e) {
+ System.out.println("ERROR: " + e.getMessage());
+ }
+ }
+ }
+ builder.deleteCharAt(builder.length()-1);
+ return builder.toString();
+ }
+}
\ No newline at end of file
diff --git a/molerat/src/main/java/it/unitn/molerat/data/csv/ChangeStatsDataPoint.java b/molerat/src/main/java/it/unitn/molerat/data/csv/ChangeStatsDataPoint.java
new file mode 100644
index 0000000..516accf
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/data/csv/ChangeStatsDataPoint.java
@@ -0,0 +1,25 @@
+package it.unitn.molerat.data.csv;
+
+/**
+ * Created by standash on 06/12/2016.
+ */
+public class ChangeStatsDataPoint extends AbstractDataPoint {
+ public String PROJECT;
+ public String CURRENT_COMMIT;
+ public String ADDED_LINES;
+ public String DELETED_LINES;
+ public String TOUCHED_METHODS;
+ public String TOUCHED_FILES;
+
+ public ChangeStatsDataPoint(String[] entries) throws Exception {
+ super(entries);
+ }
+
+ public ChangeStatsDataPoint(String str) throws Exception {
+ super(str);
+ }
+
+ public ChangeStatsDataPoint() {
+
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/data/csv/FixStatsDataPoint.java b/molerat/src/main/java/it/unitn/molerat/data/csv/FixStatsDataPoint.java
new file mode 100644
index 0000000..753dbe8
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/data/csv/FixStatsDataPoint.java
@@ -0,0 +1,21 @@
+package it.unitn.molerat.data.csv;
+
+public class FixStatsDataPoint extends AbstractDataPoint {
+ public String CVEID;
+ public String ADDED_LINES;
+ public String DELETED_LINES;
+ public String TOUCHED_METHODS;
+ public String TOUCHED_FILES;
+
+ public FixStatsDataPoint(String[] entries) throws Exception {
+ super(entries);
+ }
+
+ public FixStatsDataPoint(String str) throws Exception {
+ super(str);
+ }
+
+ public FixStatsDataPoint() {
+
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/data/csv/InputDataPoint.java b/molerat/src/main/java/it/unitn/molerat/data/csv/InputDataPoint.java
new file mode 100644
index 0000000..0fcbb09
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/data/csv/InputDataPoint.java
@@ -0,0 +1,23 @@
+package it.unitn.molerat.data.csv;
+
+public final class InputDataPoint extends AbstractDataPoint {
+
+ public String PROJECTID;
+ public String CVEID;
+ public String REPO_TYPE;
+ public String REPO_ROOT;
+ public String FIX_REV;
+ public String TRACKER_TYPE;
+
+ public InputDataPoint(String[] entries) throws Exception {
+ super(entries);
+ }
+
+ public InputDataPoint(String str) throws Exception {
+ super(str);
+ }
+
+ public InputDataPoint() {
+
+ }
+}
\ No newline at end of file
diff --git a/molerat/src/main/java/it/unitn/molerat/data/csv/OutputDataPoint.java b/molerat/src/main/java/it/unitn/molerat/data/csv/OutputDataPoint.java
new file mode 100644
index 0000000..cb9d8b2
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/data/csv/OutputDataPoint.java
@@ -0,0 +1,26 @@
+package it.unitn.molerat.data.csv;
+
+public class OutputDataPoint extends AbstractDataPoint {
+
+ public String CVEID;
+ public String TRACKING_METHOD;
+ public String CURRENT_FILE;
+ public String CURRENT_REV;
+ public String TIMESTAMP;
+ public String EVIDENCE_LOC;
+ //public String GLOBAL_PUBLIC_API_COUNT;
+ //public String GLOBAL_PUBLIC_API_REMOVED;
+ //public String GLOBAL_PUBLIC_API_UNTOUCHED;
+
+ public OutputDataPoint(String[] entries) throws Exception {
+ super(entries);
+ }
+
+ public OutputDataPoint(String str) throws Exception {
+ super(str);
+ }
+
+ public OutputDataPoint() {
+
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/data/csv/PerfOutputDataPoint.java b/molerat/src/main/java/it/unitn/molerat/data/csv/PerfOutputDataPoint.java
new file mode 100644
index 0000000..42df764
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/data/csv/PerfOutputDataPoint.java
@@ -0,0 +1,21 @@
+package it.unitn.molerat.data.csv;
+
+public class PerfOutputDataPoint extends AbstractDataPoint {
+
+ public String CVEID;
+ public String TRACKING_METHOD;
+ public String TIME_MS;
+
+ public PerfOutputDataPoint(String[] entries) throws Exception {
+ super(entries);
+ }
+
+ public PerfOutputDataPoint(String str) throws Exception {
+ super(str);
+ }
+
+ public PerfOutputDataPoint() {
+
+ }
+
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/data/csv/VulnEvidenceDataPoint.java b/molerat/src/main/java/it/unitn/molerat/data/csv/VulnEvidenceDataPoint.java
new file mode 100644
index 0000000..e8b3e76
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/data/csv/VulnEvidenceDataPoint.java
@@ -0,0 +1,22 @@
+package it.unitn.molerat.data.csv;
+
+public class VulnEvidenceDataPoint extends AbstractDataPoint {
+
+ public String PROJECT_NAME;
+ public String CVEID;
+ public String CURRENT_REV;
+ public String TIMESTAMP;
+ public String EVIDENCE_LOC;
+
+ public VulnEvidenceDataPoint(String[] entries) throws Exception {
+ super(entries);
+ }
+
+ public VulnEvidenceDataPoint(String str) throws Exception {
+ super(str);
+ }
+
+ public VulnEvidenceDataPoint() {
+
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/data/db/MongoWrapper.java b/molerat/src/main/java/it/unitn/molerat/data/db/MongoWrapper.java
new file mode 100644
index 0000000..201c58a
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/data/db/MongoWrapper.java
@@ -0,0 +1,245 @@
+package it.unitn.molerat.data.db;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.MongoClient;
+import com.mongodb.client.FindIterable;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.model.Indexes;
+import it.unitn.molerat.data.memory.AnalysisEntry;
+import it.unitn.molerat.evidence.VulnerabilityEvidence;
+import org.bson.Document;
+import org.bson.types.ObjectId;
+import java.util.*;
+
+public class MongoWrapper {
+
+ private static final String projectCollection = "projects";
+ private static final String vulnsCollection = "vulns";
+ private static final String vulnEvdCollection = "vuln_evidences";
+
+ private MongoClient client = null;
+ private MongoDatabase db = null;
+
+ public MongoWrapper(String dbName) {
+ this.client = new MongoClient();
+ this.db = client.getDatabase(dbName);
+ }
+
+ private FindIterable getEntries(BasicDBObject query, String collection) {
+ MongoCollection docs = db.getCollection(collection);
+ return docs.find(query);
+ }
+
+ private Document getEntry(BasicDBObject query, String collection) {
+ return getEntries(query, collection).first();
+ }
+
+ private ObjectId getEntryId(Document doc) {
+ return (doc != null) ? doc.getObjectId("_id") : null;
+ }
+
+ private Document getProjectEntry(String name, String repoType, String repoPath) {
+ BasicDBObject query = new BasicDBObject();
+ query.put("name", name);
+ query.put("repo_type", repoType);
+ query.put("repo_path", repoPath);
+ return getEntry(query, projectCollection);
+ }
+
+ private Document getProjectEntry(ObjectId projectId) {
+ BasicDBObject query = new BasicDBObject();
+ query.put("_id", projectId);
+ return getEntry(query, projectCollection);
+ }
+
+ private ObjectId getProjectId(String name, String repoType, String repoPath) {
+ return getEntryId(getProjectEntry(name, repoType, repoPath));
+ }
+
+ private ObjectId getProjectId(ObjectId cveId) {
+ return getCveEntry(cveId).getObjectId("owner_id");
+ }
+
+ private Document getCveEntry(ObjectId projectId, String cve) {
+ BasicDBObject query = new BasicDBObject();
+ query.put("cve", cve);
+ query.put("owner_id", projectId);
+ return getEntry(query, vulnsCollection);
+ }
+
+ private Document getCveEntry(ObjectId cveId) {
+ BasicDBObject query = new BasicDBObject();
+ query.put("_id", cveId);
+ return getEntry(query, vulnsCollection);
+ }
+
+ private ObjectId getCveId(ObjectId projectId, String cve) {
+ return getEntryId(getCveEntry(projectId, cve));
+ }
+
+ public Set getAllCveIds() {
+ Set ids = new LinkedHashSet<>();
+ MongoCollection vulns = db.getCollection(vulnsCollection);
+ FindIterable docs = vulns.find();
+ for (Document doc : docs) {
+ ids.add(doc.getObjectId("_id"));
+ }
+ return ids;
+ }
+
+ private FindIterable getVulnEvidences(ObjectId cveId) {
+ BasicDBObject query = new BasicDBObject() ;
+ query.put("owner_id", cveId);
+ FindIterable result = getEntries(query, vulnEvdCollection);
+ result.sort(new BasicDBObject("order", -1));
+ return result;
+ }
+
+ private ObjectId insertProject(String name, String repoType, String repoPath) {
+ Document proj = getProjectEntry(name, repoType, repoPath);
+ if (proj == null) {
+ MongoCollection projects = db.getCollection(projectCollection);
+ proj = new Document("name", name)
+ .append("repo_type", repoType)
+ .append("repo_path", repoPath);
+ projects.insertOne(proj);
+
+ }
+ return getEntryId(proj);
+ }
+
+ private ObjectId insertCve(ObjectId projectId, String cve, String fixCommit) {
+ Document vuln = getCveEntry(projectId, cve);
+ if (vuln == null) {
+ MongoCollection vulns = db.getCollection(vulnsCollection);
+ vuln = new Document("cve", cve)
+ .append("fix_commit", fixCommit)
+ .append("owner_id", projectId)
+ .append("processed", false);
+ vulns.insertOne(vuln);
+ MongoCollection projects = db.getCollection(projectCollection);
+ projects.findOneAndUpdate(new BasicDBObject("_id", projectId), new BasicDBObject("$push",
+ new BasicDBObject("vulns", getEntryId(vuln))));
+ }
+ return getEntryId(vuln);
+ }
+
+ private boolean isVulnProcessed(ObjectId cveId) {
+ MongoCollection vulns = db.getCollection(vulnsCollection);
+ Document cve = vulns.find(new BasicDBObject("_id", cveId)).first();
+ return (Boolean)cve.get("processed");
+ }
+
+ public boolean insertAnalysisEntry(AnalysisEntry entry) {
+ // if there's no molerat.evidence for some reason, return false
+ if (entry.getVulnEvidences().isEmpty()) {
+ return false;
+ }
+
+ ObjectId projectId = insertProject(entry.getProjectName(), entry.getRepositoryType(), entry.getRepositoryPath());
+ ObjectId cveId = insertCve(projectId, entry.getCveName(), entry.getFixCommit());
+ MongoCollection vulns = db.getCollection(vulnsCollection);
+ MongoCollection evds = db.getCollection(vulnEvdCollection);
+
+ if (isVulnProcessed(cveId)) {
+ vulns.updateOne(
+ new BasicDBObject("_id", cveId),
+ new BasicDBObject("$set", new BasicDBObject("processed", false))
+ );
+ evds.deleteMany(new BasicDBObject("owner_id", cveId));
+ }
+
+ List records = new LinkedList();
+ int order= 0;
+ Set commits = entry.getCommits();
+ for (String commit : commits) {
+ Set evidences = entry.getVulnEvidences().get(commit);
+ if (evidences != null) {
+ for (VulnerabilityEvidence e : evidences) {
+ BasicDBObject record = new BasicDBObject();
+ record.put("owner_id", cveId);
+ record.put("revision", commit);
+ record.put("order", order);
+ record.put("file_path", e.getPath());
+ record.put("container", e.getContainer());
+ record.put("line_number", e.getLineNumber());
+ record.put("line_contents", e.getLineContents());
+ Document doc = new Document(record);
+ records.add(doc);
+ }
+ }
+ order--;
+ }
+
+ evds.insertMany(records);
+ vulns.updateOne(
+ new BasicDBObject("_id", cveId),
+ new BasicDBObject("$set", new BasicDBObject("processed", true))
+ );
+ //create index
+ evds.createIndex(Indexes.descending("order"));
+ return true;
+ }
+
+ public AnalysisEntry getAnalysisEntry(String projectName, String cveName, String repoType, String repoPath) {
+ ObjectId projectId = getProjectId(projectName, repoType, repoPath);
+ ObjectId cveId = getCveId(projectId, cveName);
+ return getAnalysisEntry(cveId);
+ }
+
+ public boolean analysisEntryExists(String projectName, String cveName, String repoType, String repoPath) {
+ ObjectId projectId = getProjectId(projectName, repoType, repoPath);
+ ObjectId cveId = getCveId(projectId, cveName);
+ return (projectId != null && cveId != null);
+ }
+
+ public Set getAnalysisEntries() {
+ Set entries = new LinkedHashSet<>();
+ FindIterable cves = db.getCollection(vulnsCollection).find();
+ for (Document cve : cves) {
+ ObjectId cveId = cve.getObjectId("_id");
+ AnalysisEntry entry = getAnalysisEntry(cveId);
+ entries.add(entry);
+ }
+ return entries;
+ }
+
+ public AnalysisEntry getAnalysisEntry(ObjectId cveId) {
+ if (!isVulnProcessed(cveId)) {
+ return null;
+ }
+ ObjectId projectId = getProjectId(cveId);
+ Document projectDoc = getProjectEntry(projectId);
+ String projectName = projectDoc.getString("name");
+ String repoType = projectDoc.getString("repo_type");
+ String repoPath = projectDoc.getString("repo_path");
+
+ Document cveDoc = getCveEntry(cveId);
+ String cveName = cveDoc.getString("cve");
+ String fixCommit = cveDoc.getString("fix_commit");
+
+ Map> vulnEvidences = new LinkedHashMap<>();
+ FindIterable docs = getVulnEvidences(cveId);
+ for (Document doc : docs) {
+ String filePath = doc.getString("file_path");
+ String container = doc.getString("container");
+ String revision = doc.getString("revision");
+ int lineNumber = doc.getInteger("line_number");
+ String lineContents = doc.getString("line_contents");
+ VulnerabilityEvidence evidence = new VulnerabilityEvidence(filePath, revision, container, lineNumber, lineContents);
+ if (vulnEvidences.containsKey(revision)) {
+ vulnEvidences.get(revision).add(evidence);
+ }
+ else {
+ Set set = new LinkedHashSet<>();
+ set.add(evidence);
+ vulnEvidences.put(evidence.getCommit(), set);
+ }
+ }
+ /* TODO: add stuff for getting the changes molerat.evidence
+ Map> chgEvidences = new TreeMap<>();
+ */
+ return new AnalysisEntry(cveName, projectName, repoType, repoPath, fixCommit, vulnEvidences.keySet(), vulnEvidences, null);
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/data/memory/AnalysisEntry.java b/molerat/src/main/java/it/unitn/molerat/data/memory/AnalysisEntry.java
new file mode 100644
index 0000000..5f5a2be
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/data/memory/AnalysisEntry.java
@@ -0,0 +1,74 @@
+package it.unitn.molerat.data.memory;
+
+import it.unitn.molerat.evidence.ChangeEvidence;
+import it.unitn.molerat.evidence.VulnerabilityEvidence;
+
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class AnalysisEntry {
+ private final String cveId;
+ private final String projectId;
+ private final String fixCommit;
+ private final String repositoryType;
+ private final String repositoryPath;
+ private final Set commits;
+ private final Map> vulnEvidences;
+ private final Map> changEvidences;
+
+ public AnalysisEntry(String cveId, String projectId, String repositoryType, String repositoryPath,
+ String fixCommit, Set commits,
+ Map> vulnEvidences,
+ Map> changEvidences) {
+ this.cveId = cveId;
+ this.projectId = projectId;
+ this.repositoryType = repositoryType;
+ this.repositoryPath = repositoryPath;
+ this.commits = commits;
+ this.fixCommit = fixCommit;
+ this.vulnEvidences = vulnEvidences;
+ this.changEvidences = changEvidences;
+ }
+
+ public String getCveName() {
+ return cveId;
+ }
+
+ public Set getCommits() {
+ return commits;
+ }
+
+ public String getFixCommit() {
+ return fixCommit;
+ }
+
+ public String getRepositoryType() {
+ return repositoryType;
+ }
+
+ public String getRepositoryPath() {
+ return repositoryPath;
+ }
+
+ public Map> getVulnEvidences() {
+ return vulnEvidences;
+ }
+
+ public Set getVulnEvidencesSet() {
+ Set evidences = new LinkedHashSet<>();
+ for (Set eset : vulnEvidences.values()) {
+ evidences.addAll(eset);
+ }
+ return evidences;
+ }
+
+ public Map> getChangEvidences() {
+ return changEvidences;
+ }
+
+ public String getProjectName() {
+ return projectId;
+ }
+
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/data/printers/VulnerabilityEvidencePrinter.java b/molerat/src/main/java/it/unitn/molerat/data/printers/VulnerabilityEvidencePrinter.java
new file mode 100644
index 0000000..0d87184
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/data/printers/VulnerabilityEvidencePrinter.java
@@ -0,0 +1,46 @@
+package it.unitn.molerat.data.printers;
+
+import it.unitn.molerat.evidence.VulnerabilityEvidence;
+
+import java.util.*;
+
+public class VulnerabilityEvidencePrinter {
+
+ private final StringBuilder builder = new StringBuilder();
+
+ public VulnerabilityEvidencePrinter(Set evidence) {
+ List sortedEvidence = new LinkedList<>(evidence);
+ Collections.sort(sortedEvidence, new Comparator() {
+ @Override
+ public int compare(VulnerabilityEvidence o1, VulnerabilityEvidence o2) {
+ if (o1.getLineNumber() < o2.getLineNumber()) {
+ return -1;
+ } else if (o1.getLineNumber() > o2.getLineNumber()) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ });
+
+ String path = "";
+ String container = "";
+ for (VulnerabilityEvidence e : sortedEvidence) {
+ if (!path.equals(e.getPath())) {
+ path = e.getPath();
+ builder.append("<" + path + ">\n");
+ }
+ if (!container.equals(e.getContainer())) {
+ container = e.getContainer();
+ builder.append("\t" + container + "\n");
+ }
+ builder.append("\t\t" + e.getLineNumber() + ": " + e.getLineContents() + "\n");
+ }
+ }
+
+ @Override
+ public String toString() {
+ return this.builder.toString();
+ }
+
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/evidence/ChangeEvidence.java b/molerat/src/main/java/it/unitn/molerat/evidence/ChangeEvidence.java
new file mode 100644
index 0000000..b3dea9f
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/evidence/ChangeEvidence.java
@@ -0,0 +1,45 @@
+package it.unitn.molerat.evidence;
+
+public class ChangeEvidence extends GenericEvidence {
+
+ private final boolean removed;
+
+ public ChangeEvidence(String file, String commit, String container, boolean removed) {
+ super(file, commit, container);
+ this.removed = removed;
+ }
+
+ public boolean isMethodOrConstructor() {
+ return this.container.contains("(") && this.container.contains(")");
+ }
+
+ public boolean isAbsentInFix() {
+ return removed;
+ }
+
+ public String getAccessModifier() {
+ if (this.container.startsWith("public")) {
+ return "public";
+ }
+ else if (this.container.startsWith("protected")) {
+ return "protected";
+ }
+ else if (this.container.startsWith("private")) {
+ return "private";
+ }
+ return "";
+ }
+
+ public boolean isPublicMethodOrConstructor() {
+ return this.isMethodOrConstructor() && this.getAccessModifier().equals("public");
+ }
+
+ public boolean isProtectedMethodOrConstructor() {
+ return this.isMethodOrConstructor() && this.getAccessModifier().equals("protected");
+ }
+
+ public boolean isPrivateMethodOrConstructor() {
+ return this.isMethodOrConstructor() && this.getAccessModifier().equals("private");
+ }
+
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/evidence/Changes.java b/molerat/src/main/java/it/unitn/molerat/evidence/Changes.java
new file mode 100644
index 0000000..2b0000f
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/evidence/Changes.java
@@ -0,0 +1,93 @@
+package it.unitn.molerat.evidence;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+public class Changes {
+
+ private final String path;
+ private final String leftRev;
+ private final String rightRev;
+ private final Map deletions;
+ private final Map additions;
+ private String renamedTo = "";
+
+
+ public Changes(String path, String leftRev, String rightRev) {
+ this.path = path;
+ this.leftRev = leftRev;
+ this.rightRev = rightRev;
+ this.deletions = new TreeMap<>();
+ this.additions = new TreeMap<>();
+ }
+
+ public Changes(String path, String renamedTo, String leftRev, String rightRev) {
+ this(path, leftRev, rightRev);
+ this.renamedTo = renamedTo;
+ }
+
+ public Changes(Changes changes, String leftRev, String rightRev) {
+ this.path = changes.path;
+ this.leftRev = leftRev;
+ this.rightRev = rightRev;
+ this.deletions = changes.deletions;
+ this.additions = changes.additions;
+ }
+
+ public String getRenamedTo() {
+ return renamedTo;
+ }
+
+ public boolean wasRenamed() {
+ return !renamedTo.equals("");
+ }
+
+ public String getPath() {
+ return this.path;
+ }
+
+ public String getLeftRevision() {
+ return this.leftRev;
+ }
+
+ public String getRightRevision() {
+ return this.rightRev;
+ }
+
+ public void putDeletedLine(int lineNumber, String line) {
+ this.deletions.put(lineNumber, line);
+ }
+
+ public void putAddedLine(int lineNumber, String line) {
+ this.additions.put(lineNumber, line);
+ }
+
+ public Map getDeletions() {
+ return this.deletions;
+ }
+
+ public Map getAdditions() {
+ return this.additions;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("CHANGES:\n");
+ for (Map.Entry entry : this.deletions.entrySet()) {
+ builder.append("-, " + this.formatAsCSV(entry) + "\n");
+ }
+ for (Map.Entry entry : this.additions.entrySet()) {
+ builder.append("+, " + this.formatAsCSV(entry) + "\n");
+ }
+ return builder.toString();
+ }
+
+ private String formatAsCSV(Map.Entry entry) {
+ return this.leftRev + ", " +
+ this.path + ", " +
+ entry.getKey() + ", " +
+ entry.getValue();
+ }
+
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/evidence/GenericEvidence.java b/molerat/src/main/java/it/unitn/molerat/evidence/GenericEvidence.java
new file mode 100644
index 0000000..8f9583b
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/evidence/GenericEvidence.java
@@ -0,0 +1,30 @@
+package it.unitn.molerat.evidence;
+
+public class GenericEvidence {
+
+ protected final String commit;
+ protected final String container;
+ protected String path;
+
+ protected GenericEvidence(String file, String commit, String container) {
+ this.path = file;
+ this.commit = commit;
+ this.container = container;
+ }
+
+ public String getContainer() {
+ return this.container;
+ }
+
+ public String getCommit() {
+ return this.commit;
+ }
+
+ public String getPath() {
+ return this.path;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/evidence/VulnerabilityEvidence.java b/molerat/src/main/java/it/unitn/molerat/evidence/VulnerabilityEvidence.java
new file mode 100644
index 0000000..2fcc221
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/evidence/VulnerabilityEvidence.java
@@ -0,0 +1,32 @@
+package it.unitn.molerat.evidence;
+
+public class VulnerabilityEvidence extends GenericEvidence {
+
+ protected final int lineNumber;
+ protected final String lineContents;
+
+ public VulnerabilityEvidence(String file, String commit, String container, int lineNumber, String lineContents) {
+ super(file, commit, container);
+ this.lineNumber = lineNumber;
+ this.lineContents = lineContents
+ .trim()
+ .replace("\t","");
+ }
+
+ public int getLineNumber() {
+ return this.lineNumber;
+ }
+
+ public String getLineContents() {
+ return this.lineContents;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("<" + commit + ": " + path + ">\n");
+ builder.append("\t" + container + "\n");
+ builder.append("\t\t" + lineNumber + ": " + lineContents + "\n\t\n");
+ return builder.toString();
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/repos/trackers/AbstractEvidenceTracker.java b/molerat/src/main/java/it/unitn/molerat/repos/trackers/AbstractEvidenceTracker.java
new file mode 100644
index 0000000..7309af8
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/repos/trackers/AbstractEvidenceTracker.java
@@ -0,0 +1,56 @@
+package it.unitn.molerat.repos.trackers;
+
+import it.unitn.molerat.evidence.Changes;
+import it.unitn.molerat.repos.wrappers.RepoWrapper;
+import java.util.*;
+
+public abstract class AbstractEvidenceTracker {
+ protected final String fixedRevision;
+ protected final String vulnRevision;
+ protected final String repoPath;
+ protected final RepoWrapper repoWrapper;
+ protected Set changes;
+ protected Set commits = new LinkedHashSet<>();
+ protected Set restOfCommits = new LinkedHashSet<>();
+
+ public AbstractEvidenceTracker(RepoWrapper wrapper, String fixedRev) throws Exception {
+ this.fixedRevision = fixedRev;
+ this.repoPath = wrapper.getBasePath();
+ this.repoWrapper = wrapper;
+ Iterator commitsIter = repoWrapper.getRevisionNumbers(fixedRev).iterator();
+ this.vulnRevision = commitsIter.next();
+ while (commitsIter.hasNext()) {
+ restOfCommits.add(commitsIter.next());
+ }
+ String initDiff = repoWrapper.doDiff(vulnRevision, fixedRevision);
+ this.changes = repoWrapper.inferChangesFromDiff(initDiff, vulnRevision, fixedRevision);
+ }
+
+ public RepoWrapper getRepoWrapper() {
+ return this.repoWrapper;
+ }
+
+ public String getFixedRevision() {
+ return this.fixedRevision;
+ }
+
+ public Set getProcessedCommits() {
+ return this.commits;
+ }
+
+ // apart from non-java files, filter the files that contain "test" in ther name as well
+ protected Set filterNonJavaChanges(Set changes) {
+ Set filteredChanges = new HashSet<>();
+ for (Changes change : changes) {
+ if (!change.getPath().endsWith(".java") || change.getPath().toLowerCase().contains("test")) {
+ filteredChanges.add(change);
+ }
+ this.repoWrapper.filterCommentsAndBlanks(change.getDeletions());
+ this.repoWrapper.filterCommentsAndBlanks(change.getAdditions());
+ }
+ changes.removeAll(filteredChanges);
+ return changes;
+ }
+
+ public abstract void trackEvidence() throws Exception;
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/repos/trackers/changes/ChangeEvidenceTracker.java b/molerat/src/main/java/it/unitn/molerat/repos/trackers/changes/ChangeEvidenceTracker.java
new file mode 100644
index 0000000..d1dc8ae
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/repos/trackers/changes/ChangeEvidenceTracker.java
@@ -0,0 +1,108 @@
+package it.unitn.molerat.repos.trackers.changes;
+
+import it.unitn.molerat.evidence.ChangeEvidence;
+import it.unitn.molerat.repos.trackers.AbstractEvidenceTracker;
+import it.unitn.molerat.repos.utils.CommitMetrics;
+import it.unitn.molerat.repos.utils.SignatureExtractor;
+import it.unitn.molerat.repos.wrappers.RepoWrapper;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+public class ChangeEvidenceTracker extends AbstractEvidenceTracker {
+
+ protected final Map> evidences = new TreeMap<>();
+ protected Map> fixConstructs;
+
+ protected ChangeEvidenceTracker(RepoWrapper wrapper, String fixedRev) throws Exception {
+ super(wrapper, fixedRev);
+ }
+
+ public ChangeEvidenceTracker(RepoWrapper repoWrapper, String fixedCommit, Set commits) throws Exception {
+ this(repoWrapper, fixedCommit);
+ this.commits = commits;
+ this.fixConstructs = extractSignatures(this.fixedRevision);
+ }
+
+ protected Map> extractSignatures(String commit) throws Exception {
+ Map> result = new TreeMap<>();
+ Map files = CommitMetrics.getFileContentsPerRevision(commit, this.repoWrapper);
+ for (Map.Entry file : files.entrySet()) {
+ SignatureExtractor se = new SignatureExtractor(file.getValue());
+ Set signatures = new HashSet<>();
+ for (String signature : se.getSignaturesWithLines().keySet()) {
+ signatures.add(signature);
+ }
+ result.put(file.getKey(), signatures);
+ }
+ return result;
+ }
+
+
+ @Override
+ public void trackEvidence() throws Exception {
+ for (String commit : this.commits) {
+// long timeNow = System.currentTimeMillis();
+ Map> currentSignatures = extractSignatures(commit);
+ for (Map.Entry> currentSignature : currentSignatures.entrySet()) {
+ Set temp = this.fixConstructs.get(currentSignature.getKey());
+ if (temp == null) {
+ for (String sign : currentSignature.getValue()) {
+ ChangeEvidence evidence = new ChangeEvidence(
+ currentSignature.getKey(),
+ commit,
+ sign,
+ true
+ );
+ if (evidence.isPublicMethodOrConstructor()) {
+ addEvidence(evidence);
+ }
+ }
+ }
+ else {
+ for (String sign : currentSignature.getValue()) {
+ boolean removed = true;
+ for (String fixSign : temp) {
+ if (sign.equals(fixSign)) {
+ removed = false;
+ break;
+ }
+ }
+ ChangeEvidence evidence = new ChangeEvidence(
+ currentSignature.getKey(),
+ commit,
+ sign,
+ removed
+ );
+ if (evidence.isPublicMethodOrConstructor()) {
+ addEvidence(evidence);
+ }
+ }
+ }
+
+ }
+ }
+ }
+
+ protected void addEvidence(ChangeEvidence evidence) {
+ if (this.evidences.containsKey(evidence.getCommit())) {
+ this.evidences.get(evidence.getCommit()).add(evidence);
+ }
+ else {
+ Set set = new HashSet<>();
+ set.add(evidence);
+ this.evidences.put(evidence.getCommit(), set);
+ }
+ }
+
+ public Set getEvidence(String commit) {
+ Set evd = this.evidences.get(commit);
+ return (evd != null) ? evd : new HashSet<>();
+ }
+
+ public Map> getEvidences() {
+ return this.evidences;
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/repos/trackers/changes/FullChangeEvidenceTracker.java b/molerat/src/main/java/it/unitn/molerat/repos/trackers/changes/FullChangeEvidenceTracker.java
new file mode 100644
index 0000000..9437bd2
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/repos/trackers/changes/FullChangeEvidenceTracker.java
@@ -0,0 +1,13 @@
+package it.unitn.molerat.repos.trackers.changes;
+
+
+import it.unitn.molerat.repos.wrappers.RepoWrapper;
+
+public class FullChangeEvidenceTracker extends ChangeEvidenceTracker {
+
+ public FullChangeEvidenceTracker(RepoWrapper wrapper, String fixedRev) throws Exception {
+ super(wrapper, fixedRev);
+ this.commits = wrapper.getRevisionNumbers(fixedRev);
+ this.fixConstructs = extractSignatures(this.fixedRevision);
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/DeletionVulnerabilityEvidenceTracker.java b/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/DeletionVulnerabilityEvidenceTracker.java
new file mode 100644
index 0000000..650096a
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/DeletionVulnerabilityEvidenceTracker.java
@@ -0,0 +1,92 @@
+package it.unitn.molerat.repos.trackers.vuln;
+
+import it.unitn.molerat.evidence.Changes;
+import it.unitn.molerat.evidence.VulnerabilityEvidence;
+import it.unitn.molerat.repos.wrappers.RepoWrapper;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class DeletionVulnerabilityEvidenceTracker extends VulnerabilityEvidenceTracker {
+
+ public DeletionVulnerabilityEvidenceTracker(RepoWrapper wrapper, String fixedRev) throws Exception {
+ super(wrapper, fixedRev);
+ }
+
+ @Override
+ protected Set getInitialVulnerabilityEvidence(Changes changes) throws Exception {
+ return this.recordVulnerabilityEvidence(changes.getDeletions(), changes);
+ }
+
+ @Override
+ protected Set getVulnerabilityEvidence(String currentEvidenceCommit, String previousEvidenceCommit, Set changes) throws Exception {
+ Set newEvidences = new HashSet<>();
+ Set previousEvidences = getEvidences(previousEvidenceCommit);
+
+ Set changedEvidence = new HashSet<>();
+ Set changesToProcess = new HashSet<>();
+ Set stillEvidence = new HashSet<>();
+
+ // filter non-Java files
+ changes = filterNonJavaChanges(changes);
+
+ if (previousEvidences == null) {
+ return newEvidences;
+ }
+
+ for (VulnerabilityEvidence previousEvidence : previousEvidences) {
+ for (Changes change : changes ) {
+ // The file has been just renamed
+ if (change.wasRenamed()) {
+ if (change.getRenamedTo().equals(previousEvidence.getPath())) {
+ previousEvidence.setPath(change.getPath());
+ }
+ }
+ // The file has been changed
+ else if (previousEvidence.getPath().equals(change.getPath())) {
+ changedEvidence.add(previousEvidence);
+ changesToProcess.add(change);
+ }
+ }
+ }
+
+ stillEvidence.addAll(previousEvidences);
+ stillEvidence.removeAll(changedEvidence);
+
+ // "Refresh" the molerat.evidence when a file was not changed
+ for (VulnerabilityEvidence e : stillEvidence) {
+ VulnerabilityEvidence newEvidence = new VulnerabilityEvidence(
+ e.getPath(),
+ currentEvidenceCommit,
+ e.getContainer(),
+ e.getLineNumber(),
+ e.getLineContents()
+ );
+ newEvidences.add(newEvidence);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // "Triage" the molerat.evidence when a file was changed
+ for (Changes change : changesToProcess) {
+ Set retain = new HashSet<>();
+ for (VulnerabilityEvidence e : changedEvidence) {
+ if (e.getPath().equals(change.getPath())) {
+ retain.add(e);
+ }
+ }
+ Map linesToKeep = this.updateChangedLines(
+ change.getPath(),
+ change.getLeftRevision(),
+ change.getRightRevision(),
+ retain,
+ change
+ );
+ if (linesToKeep != null) {
+ newEvidences.addAll(this.recordVulnerabilityEvidence(linesToKeep, change));
+ }
+ }
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ return newEvidences;
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/EnhancedDeletionVulnerabilityEvidenceTracker.java b/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/EnhancedDeletionVulnerabilityEvidenceTracker.java
new file mode 100644
index 0000000..887ea10
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/EnhancedDeletionVulnerabilityEvidenceTracker.java
@@ -0,0 +1,72 @@
+package it.unitn.molerat.repos.trackers.vuln;
+
+import it.unitn.molerat.evidence.Changes;
+import it.unitn.molerat.evidence.VulnerabilityEvidence;
+import it.unitn.molerat.repos.utils.SignatureExtractor;
+import it.unitn.molerat.repos.wrappers.RepoWrapper;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class EnhancedDeletionVulnerabilityEvidenceTracker extends DeletionVulnerabilityEvidenceTracker{
+
+ public EnhancedDeletionVulnerabilityEvidenceTracker(RepoWrapper wrapper, String fixedRev) throws Exception {
+ super(wrapper, fixedRev);
+ }
+
+
+ @Override
+ protected Set getInitialVulnerabilityEvidence(Changes changes) throws Exception {
+ if (changes.getDeletions().size() != 0) {
+ return super.getInitialVulnerabilityEvidence(changes);
+ }
+ else {
+ SignatureExtractor se = new SignatureExtractor(repoWrapper.doCat(changes.getPath(), changes.getRightRevision()));
+ Map> rightSignatures = se.getSignaturesWithLines();
+
+ Set relevantSignatures = new HashSet<>();
+
+ for (int line : changes.getAdditions().keySet()) {
+ for (Map.Entry> entry : rightSignatures.entrySet()) {
+ if (entry.getValue().contains(line)) {
+ relevantSignatures.add(entry.getKey());
+ }
+ }
+ }
+
+ String leftFile = repoWrapper.doCat(changes.getPath(), changes.getLeftRevision());
+ Map lineMappings = repoWrapper.getLineMappings(leftFile);
+ se = new SignatureExtractor(leftFile);
+ Map> leftSignatures = se.getSignaturesWithLines();
+
+ for (int line : changes.getDeletions().keySet()) {
+ for (Map.Entry> entry : leftSignatures.entrySet()) {
+ if (entry.getValue().contains(line)) {
+ relevantSignatures.add(entry.getKey());
+ }
+ }
+ }
+
+ Set initialEvidence = new HashSet<>();
+ for (String signature : relevantSignatures) {
+ for (Map.Entry> entry : leftSignatures.entrySet()) {
+ if (signature.equals(entry.getKey())) {
+ for (int line : entry.getValue()) {
+ VulnerabilityEvidence evd = new VulnerabilityEvidence(
+ changes.getPath(),
+ changes.getLeftRevision(),
+ signature,
+ line,
+ lineMappings.get(line)
+ );
+ initialEvidence.add(evd);
+ }
+ }
+ }
+ }
+
+ return initialEvidence;
+ }
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/FixStatisticsVulnerabilityEvidenceTracker.java b/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/FixStatisticsVulnerabilityEvidenceTracker.java
new file mode 100644
index 0000000..f631932
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/FixStatisticsVulnerabilityEvidenceTracker.java
@@ -0,0 +1,71 @@
+package it.unitn.molerat.repos.trackers.vuln;
+
+import it.unitn.molerat.evidence.Changes;
+import it.unitn.molerat.evidence.VulnerabilityEvidence;
+import it.unitn.molerat.repos.wrappers.RepoWrapper;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+public class FixStatisticsVulnerabilityEvidenceTracker extends VulnerabilityEvidenceTracker {
+ private int addedLines = 0;
+ private int deletedLines = 0;
+ private int touchedFiles = 0;
+ private int touchedMethods = 0;
+
+ public FixStatisticsVulnerabilityEvidenceTracker(RepoWrapper wrapper, String fixedRev) throws Exception {
+ super(wrapper, fixedRev);
+ }
+
+ @Override
+ protected Set getInitialVulnerabilityEvidence(Changes changes) throws Exception {
+ return null;
+ }
+
+ @Override
+ protected Set getVulnerabilityEvidence(String currentEvidenceCommit, String previousEvidenceCommit, Set changes) throws Exception {
+ return null;
+ }
+
+ @Override
+ public void trackEvidence() throws Exception {
+ this.changes = filterNonJavaChanges(this.changes);
+ Iterator it = changes.iterator();
+ while (it.hasNext()) {
+ Changes currentChanges = it.next();
+ touchedFiles++;
+ addedLines += currentChanges.getAdditions().size();
+ deletedLines += currentChanges.getDeletions().size();
+
+ Set methods = new HashSet<>();
+ Set evidences = recordVulnerabilityEvidenceForRightFile(currentChanges.getAdditions(), currentChanges);
+ evidences.addAll(recordVulnerabilityEvidence(currentChanges.getDeletions(), currentChanges));
+ for (VulnerabilityEvidence evd : evidences) {
+ if (!evd.getLineContents().contains("private") &&
+ !evd.getLineContents().contains("protected") &&
+ !evd.getLineContents().contains("public")) {
+ methods.add(evd.getContainer());
+ }
+ }
+ touchedMethods += methods.size();
+ commits.add(currentChanges.getLeftRevision());
+ }
+ }
+
+ public int getNumberOfAddedLines() {
+ return addedLines;
+ }
+
+ public int getNumberOfDeletedLines() {
+ return deletedLines;
+ }
+
+ public int getNumberOfTouchedMethods() {
+ return touchedMethods;
+ }
+
+ public int getNumberOfTouchedFiles() {
+ return touchedFiles;
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/PatchedMethodBodyTracker.java b/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/PatchedMethodBodyTracker.java
new file mode 100644
index 0000000..ec381d7
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/PatchedMethodBodyTracker.java
@@ -0,0 +1,148 @@
+package it.unitn.molerat.repos.trackers.vuln;
+
+import it.unitn.molerat.evidence.Changes;
+import it.unitn.molerat.evidence.VulnerabilityEvidence;
+import it.unitn.molerat.repos.utils.SignatureExtractor;
+import it.unitn.molerat.repos.wrappers.RepoWrapper;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class PatchedMethodBodyTracker extends VulnerabilityEvidenceTracker {
+
+ public PatchedMethodBodyTracker(RepoWrapper wrapper, String fixedRev) throws Exception {
+ super(wrapper, fixedRev);
+ }
+
+ @Override
+ protected Set getInitialVulnerabilityEvidence(Changes changes) throws Exception {
+ SignatureExtractor se = new SignatureExtractor(repoWrapper.doCat(changes.getPath(), changes.getRightRevision()));
+ Map> rightSignatures = se.getSignaturesWithLines();
+
+ Set relevantSignatures = new HashSet<>();
+
+ for (int line : changes.getAdditions().keySet()) {
+ for (Map.Entry> entry : rightSignatures.entrySet()) {
+ if (entry.getValue().contains(line)) {
+ relevantSignatures.add(entry.getKey());
+ }
+ }
+ }
+
+ String leftFile = repoWrapper.doCat(changes.getPath(), changes.getLeftRevision());
+ Map lineMappings = repoWrapper.getLineMappings(leftFile);
+ se = new SignatureExtractor(leftFile);
+ Map> leftSignatures = se.getSignaturesWithLines();
+
+ for (int line : changes.getDeletions().keySet()) {
+ for (Map.Entry> entry : leftSignatures.entrySet()) {
+ if (entry.getValue().contains(line)) {
+ relevantSignatures.add(entry.getKey());
+ }
+ }
+ }
+
+ Set initialEvidence = new HashSet<>();
+ for (String signature : relevantSignatures) {
+ for (Map.Entry> entry : leftSignatures.entrySet()) {
+ if (signature.equals(entry.getKey())) {
+ for (int line : entry.getValue()) {
+ VulnerabilityEvidence evd = new VulnerabilityEvidence(
+ changes.getPath(),
+ changes.getLeftRevision(),
+ signature,
+ line,
+ lineMappings.get(line)
+ );
+ initialEvidence.add(evd);
+ }
+ }
+ }
+ }
+
+ return initialEvidence;
+ }
+
+
+ @Override
+ protected Set getVulnerabilityEvidence(String currentEvidenceCommit, String previousEvidenceCommit, Set changes) throws Exception {
+ Set newEvidences = new HashSet<>();
+ Set previousEvidences = getEvidences(previousEvidenceCommit);
+
+ Set changedEvidence = new HashSet<>();
+ Set changesToProcess = new HashSet<>();
+ Set stillEvidence = new HashSet<>();
+
+ // filter non-Java files
+ changes = filterNonJavaChanges(changes);
+
+ if (previousEvidences == null) {
+ return newEvidences;
+ }
+
+ for (VulnerabilityEvidence previousEvidence : previousEvidences) {
+ for (Changes change : changes ) {
+ // The file has been just renamed
+ if (change.wasRenamed()) {
+ if (change.getRenamedTo().equals(previousEvidence.getPath())) {
+ previousEvidence.setPath(change.getPath());
+ }
+ }
+ // The file has been changed
+ else if (previousEvidence.getPath().equals(change.getPath())) {
+ changedEvidence.add(previousEvidence);
+ changesToProcess.add(change);
+ }
+ }
+ }
+
+ stillEvidence.addAll(previousEvidences);
+ stillEvidence.removeAll(changedEvidence);
+
+ // "Refresh" the molerat.evidence when a file was not changed
+ for (VulnerabilityEvidence e : stillEvidence) {
+ VulnerabilityEvidence newEvidence = new VulnerabilityEvidence(
+ e.getPath(),
+ currentEvidenceCommit,
+ e.getContainer(),
+ e.getLineNumber(),
+ e.getLineContents()
+ );
+ newEvidences.add(newEvidence);
+ }
+
+ // "Triage" the molerat.evidence when a file was changed
+ for (Changes change : changesToProcess) {
+ Set relevantSignatures = new HashSet<>();
+ for (VulnerabilityEvidence e : changedEvidence) {
+ relevantSignatures.add(e.getContainer());
+ }
+
+ String leftFile = repoWrapper.doCat(change.getPath(), change.getLeftRevision());
+ Map lineMappings = repoWrapper.getLineMappings(leftFile);
+ SignatureExtractor se = new SignatureExtractor(leftFile);
+ Map> leftSignatures = se.getSignaturesWithLines();
+
+ for (String signature : relevantSignatures) {
+ for (Map.Entry> entry : leftSignatures.entrySet()) {
+ if (signature.equals(entry.getKey())) {
+ for (int line : entry.getValue()) {
+ VulnerabilityEvidence evd = new VulnerabilityEvidence(
+ change.getPath(),
+ change.getLeftRevision(),
+ signature,
+ line,
+ lineMappings.get(line)
+ );
+ newEvidences.add(evd);
+ }
+ }
+ }
+ }
+ }
+ return newEvidences;
+ }
+
+
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/SliceDecayVulnerabilityEvidenceTracker.java b/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/SliceDecayVulnerabilityEvidenceTracker.java
new file mode 100644
index 0000000..68d587a
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/SliceDecayVulnerabilityEvidenceTracker.java
@@ -0,0 +1,85 @@
+package it.unitn.molerat.repos.trackers.vuln;
+
+import it.unitn.molerat.evidence.Changes;
+import it.unitn.molerat.evidence.VulnerabilityEvidence;
+import it.unitn.molerat.repos.wrappers.RepoWrapper;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class SliceDecayVulnerabilityEvidenceTracker extends SliceVulnerabilityEvidenceTracker {
+
+ public SliceDecayVulnerabilityEvidenceTracker(RepoWrapper wrapper, String fixedRev) throws Exception {
+ super(wrapper, fixedRev);
+ }
+
+ @Override
+ protected Set getVulnerabilityEvidence(String currentEvidenceCommit, String previousEvidenceCommit, Set changes) throws Exception {
+ Set newEvidences = new HashSet<>();
+ Set previousEvidences = getEvidences(previousEvidenceCommit);
+ Set changedEvidence = new HashSet<>();
+
+ Set changesToProcess = new HashSet<>();
+ Set stillEvidence = new HashSet<>();
+
+ // filter non-Java files
+ changes = filterNonJavaChanges(changes);
+
+ if (previousEvidences == null) {
+ return newEvidences;
+ }
+
+ for (VulnerabilityEvidence previousEvidence : previousEvidences) {
+ for (Changes change : changes ) {
+ // The file has been just renamed
+ if (change.wasRenamed()) {
+ if (change.getRenamedTo().equals(previousEvidence.getPath())) {
+ previousEvidence.setPath(change.getPath());
+ }
+ }
+ // The file has been changed
+ else if (previousEvidence.getPath().equals(change.getPath())) {
+ changedEvidence.add(previousEvidence);
+ changesToProcess.add(change);
+ }
+ }
+ }
+
+ stillEvidence.addAll(previousEvidences);
+ stillEvidence.removeAll(changedEvidence);
+
+ // "Refresh" the molerat.evidence when a file was not changed
+ for (VulnerabilityEvidence e : stillEvidence) {
+ VulnerabilityEvidence newEvidence = new VulnerabilityEvidence(
+ e.getPath(),
+ currentEvidenceCommit,
+ e.getContainer(),
+ e.getLineNumber(),
+ e.getLineContents()
+ );
+ newEvidences.add(newEvidence);
+ }
+
+ // "Triage" the molerat.evidence when a file was changed
+ for (Changes change : changesToProcess) {
+ Set retain = new HashSet<>();
+ for (VulnerabilityEvidence e : changedEvidence) {
+ if (e.getPath().equals(change.getPath())) {
+ retain.add(e);
+ }
+ }
+ Map linesToKeep = this.updateChangedLines(
+ change.getPath(),
+ change.getLeftRevision(),
+ change.getRightRevision(),
+ retain,
+ change
+ );
+ if (linesToKeep != null) {
+ newEvidences.addAll(this.recordVulnerabilityEvidence(linesToKeep, change));
+ }
+ }
+ return newEvidences;
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/SliceVulnerabilityEvidenceTracker.java b/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/SliceVulnerabilityEvidenceTracker.java
new file mode 100644
index 0000000..38c37c1
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/SliceVulnerabilityEvidenceTracker.java
@@ -0,0 +1,139 @@
+package it.unitn.molerat.repos.trackers.vuln;
+
+import it.unitn.molerat.evidence.Changes;
+import it.unitn.molerat.evidence.VulnerabilityEvidence;
+import it.unitn.molerat.repos.wrappers.RepoWrapper;
+import it.unitn.repoman.core.lang.LanguageFactory;
+import it.unitn.repoman.core.slicers.LightweightSlice;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+public class SliceVulnerabilityEvidenceTracker extends VulnerabilityEvidenceTracker {
+
+ public SliceVulnerabilityEvidenceTracker(RepoWrapper wrapper, String fixedRev) throws Exception {
+ super(wrapper, fixedRev);
+ }
+
+ @Override
+ protected Set getInitialVulnerabilityEvidence(Changes changes) throws Exception {
+ // 1. Take the added lines as the slicing criteria and obtain the slice on the fixed revision
+ Map slicingCriteria = changes.getAdditions();
+ String rightFile = this.repoWrapper.doCat(changes.getPath(), changes.getRightRevision());
+ Map sliced = this.doSlice(slicingCriteria, rightFile);
+
+ Set slicedEvd = this.recordVulnerabilityEvidenceForRightFile(sliced, changes);
+
+ // 2. we need to check which lines from the "fixed" slice are present in the vulnerable revision
+ // 2.1 we "correlate" the lines from the "fixed" slice to the vulnerable revision
+ Map propagated = this.updateChangedLines(changes.getPath(), changes.getLeftRevision(),
+ changes.getRightRevision(), slicedEvd, changes);
+
+ // 2.2 take deleted lines + "correlated" changes from the fixed slice and take a new slice on the vulnerable revision
+ propagated.putAll(changes.getDeletions());
+
+ // 3.1 make the final slice on the vulnerable revision
+ slicingCriteria = propagated;
+ String leftFile = this.repoWrapper.doCat(changes.getPath(), changes.getLeftRevision());
+ sliced = this.doSlice(slicingCriteria, leftFile);
+ propagated.putAll(sliced);
+
+ Set initialEvidence = this.recordVulnerabilityEvidence(propagated, changes);
+ return initialEvidence;
+ }
+
+
+
+ @Override
+ protected Set getVulnerabilityEvidence(String currentEvidenceCommit, String previousEvidenceCommit, Set changes) throws Exception {
+ Set newEvidences = new HashSet<>();
+ Set previousEvidences = getEvidences(previousEvidenceCommit);
+
+ Set changedEvidence = new HashSet<>();
+ Set changesToProcess = new HashSet<>();
+ Set stillEvidence = new HashSet<>();
+
+ // filter non-Java files
+ changes = filterNonJavaChanges(changes);
+
+ if (previousEvidences == null) {
+ return newEvidences;
+ }
+
+ for (VulnerabilityEvidence previousEvidence : previousEvidences) {
+ for (Changes change : changes ) {
+ // The file has been just renamed
+ if (change.wasRenamed()) {
+ if (change.getRenamedTo().equals(previousEvidence.getPath())) {
+ previousEvidence.setPath(change.getPath());
+ }
+ }
+ // The file has been changed
+ else if (previousEvidence.getPath().equals(change.getPath())) {
+ changedEvidence.add(previousEvidence);
+ changesToProcess.add(change);
+ }
+ }
+ }
+
+ stillEvidence.addAll(previousEvidences);
+ stillEvidence.removeAll(changedEvidence);
+
+ // "Refresh" the molerat.evidence when a file was not changed
+ for (VulnerabilityEvidence e : stillEvidence) {
+ VulnerabilityEvidence newEvidence = new VulnerabilityEvidence(
+ e.getPath(),
+ currentEvidenceCommit,
+ e.getContainer(),
+ e.getLineNumber(),
+ e.getLineContents()
+ );
+ newEvidences.add(newEvidence);
+ }
+
+ // "Triage" the molerat.evidence when a file was changed
+ for (Changes change : changesToProcess) {
+ // --------------------------------------
+ Map slicingCriteria = new TreeMap<>();
+ for (VulnerabilityEvidence e : changedEvidence) {
+ if (e.getPath().equals(change.getPath())) {
+ slicingCriteria.put(e.getLineNumber(), e.getLineContents());
+ }
+ }
+ String rightFile = this.repoWrapper.doCat(change.getPath(), change.getRightRevision());
+ Map slice = this.doSlice(slicingCriteria, rightFile);
+ Set slicedEvd = this.recordVulnerabilityEvidenceForRightFile(slice, change);
+
+ Map linesToKeep = this.updateChangedLines(
+ change.getPath(),
+ change.getLeftRevision(),
+ change.getRightRevision(),
+ slicedEvd,
+ change
+ );
+ if (linesToKeep != null) {
+ String leftFile = this.repoWrapper.doCat(change.getPath(), change.getLeftRevision());
+ slice = this.doSlice(linesToKeep, leftFile);
+ newEvidences.addAll(this.recordVulnerabilityEvidence(slice, change));
+ }
+ }
+ return newEvidences;
+ }
+
+
+
+ protected Map doSlice(Map slicingCriteria, String fileContents) {
+ Map sliced = this.repoWrapper.getLineMappings(fileContents);
+ LanguageFactory.init("Java", fileContents);
+ LightweightSlice slice;
+ try {
+ slice = new LightweightSlice(LanguageFactory.getRoot(), slicingCriteria.keySet());
+ sliced.keySet().retainAll(slice.getSelectedLines());
+ } catch (Exception e) {
+ System.out.println("ERROR: " + e.getMessage());
+ }
+ return sliced;
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/VulnerabilityEvidenceTracker.java b/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/VulnerabilityEvidenceTracker.java
new file mode 100644
index 0000000..dbb2048
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/VulnerabilityEvidenceTracker.java
@@ -0,0 +1,234 @@
+package it.unitn.molerat.repos.trackers.vuln;
+
+import it.unitn.molerat.evidence.Changes;
+import it.unitn.molerat.repos.trackers.AbstractEvidenceTracker;
+import it.unitn.molerat.repos.utils.SignatureExtractor;
+import it.unitn.molerat.evidence.VulnerabilityEvidence;
+import it.unitn.molerat.repos.wrappers.RepoWrapper;
+
+import java.util.*;
+
+public abstract class VulnerabilityEvidenceTracker extends AbstractEvidenceTracker {
+
+ protected Map> evidences = new LinkedHashMap>();
+
+ public VulnerabilityEvidenceTracker(RepoWrapper wrapper, String fixedRev) throws Exception {
+ super(wrapper, fixedRev);
+ }
+
+ @Override
+ public void trackEvidence() throws Exception {
+ // I. Filter all non-Java files that have been changed
+ this.changes = filterNonJavaChanges(this.changes);
+ // -------------------------------------------------
+ // II. Get the initial molerat.evidence from the fix
+ Iterator it = changes.iterator();
+ while (it.hasNext()) {
+ Changes currentChanges = it.next();
+ Set tempVulnEvidences = getInitialVulnerabilityEvidence(currentChanges);
+ for (VulnerabilityEvidence evidence : tempVulnEvidences) {
+ addEvidence(evidence);
+ }
+ commits.add(currentChanges.getLeftRevision());
+ }
+ // -------------------------------------------------
+ // III. Collect evidences across the rest of commits
+ String rightCommit = this.vulnRevision;
+
+ Iterator commitIterator = restOfCommits.iterator();
+ String commit = "";
+ while (commitIterator.hasNext()) {
+ commit = commitIterator.next();
+ // Get the current changes
+ String leftCommit = commit;
+ String diffTxt = repoWrapper.doDiff(leftCommit, rightCommit);
+ Set currentChanges = repoWrapper.inferChangesFromDiff(diffTxt, leftCommit, rightCommit);
+
+ Set newEvidences = new HashSet<>();
+ newEvidences = getVulnerabilityEvidence(leftCommit, rightCommit, currentChanges);
+
+ // did the method/file/molerat.evidence disappear?
+ if (newEvidences.size() == 0) {
+ break;
+ } else {
+ for (VulnerabilityEvidence evidence : newEvidences) {
+ addEvidence(evidence);
+ }
+ rightCommit = leftCommit;
+ commits.add(commit);
+ }
+ }
+ if (commit.equals("")) {
+ return;
+ }
+ }
+
+ public Set getEvidences(String commit) {
+ Set evd = this.evidences.get(commit);
+ return (evd != null) ? evd : new HashSet<>();
+ }
+
+ public Map countEvidenceInFiles(String commit) {
+ Map counts = new TreeMap<>();
+ Set evd = getEvidences(commit);
+ for (VulnerabilityEvidence e : evd) {
+ String filename = e.getPath();
+ if (!counts.containsKey(filename)) {
+ if (e.getLineNumber() != 0) {
+ counts.put(filename, 1);
+ }
+ else {
+ counts.put(filename, 0);
+ }
+ }
+ else {
+ if (e.getLineNumber() == 0) {
+ counts.replace(filename, 0);
+ }
+ else {
+ int locCount = counts.get(filename);
+ locCount++;
+ counts.replace(filename, locCount);
+ }
+ }
+ }
+ return counts;
+ }
+
+ public Map> getEvidences() {
+ return this.evidences;
+ }
+
+ protected void addEvidence(VulnerabilityEvidence evidence) {
+ if (this.evidences.containsKey(evidence.getCommit())) {
+ evidences.get(evidence.getCommit()).add(evidence);
+ }
+ else {
+ Set set = new HashSet<>();
+ set.add(evidence);
+ this.evidences.put(evidence.getCommit(), set);
+ }
+ }
+
+ protected abstract Set getInitialVulnerabilityEvidence(Changes changes) throws Exception;
+
+ protected abstract Set getVulnerabilityEvidence(String currentEvidenceCommit, String previousEvidenceCommit, Set changes) throws Exception;
+
+ protected Set recordVulnerabilityEvidence(Map lines, Changes changes) throws Exception {
+ Set evidences = new HashSet<>();
+ String fileContents = this.repoWrapper.doCat(changes.getPath(), changes.getLeftRevision());
+ SignatureExtractor se = new SignatureExtractor(fileContents);
+ Map> containers = se.getSignaturesWithLines();
+ for (Map.Entry> containerEntry : containers.entrySet()) {
+ for (Map.Entry lineEntry : lines.entrySet()) {
+ if (containerEntry.getValue().contains(lineEntry.getKey())) {
+ VulnerabilityEvidence evidence = new VulnerabilityEvidence(
+ changes.getPath(),
+ changes.getLeftRevision(),
+ containerEntry.getKey(),
+ lineEntry.getKey(),
+ lineEntry.getValue()
+ );
+ evidences.add(evidence);
+ }
+ }
+ }
+ return evidences;
+ }
+
+ protected Set recordVulnerabilityEvidenceForRightFile(Map lines, Changes changes) throws Exception {
+ Set evidences = new HashSet<>();
+ String fileContents = this.repoWrapper.doCat(changes.getPath(), changes.getRightRevision());
+ SignatureExtractor se = new SignatureExtractor(fileContents);
+ Map> containers = se.getSignaturesWithLines();
+ for (Map.Entry> containerEntry : containers.entrySet()) {
+ for (Map.Entry lineEntry : lines.entrySet()) {
+ if (containerEntry.getValue().contains(lineEntry.getKey())) {
+ VulnerabilityEvidence evidence = new VulnerabilityEvidence(
+ changes.getPath(),
+ changes.getRightRevision(),
+ containerEntry.getKey(),
+ lineEntry.getKey(),
+ lineEntry.getValue()
+ );
+ evidences.add(evidence);
+ }
+ }
+ }
+ return evidences;
+ }
+
+
+ protected Map updateChangedLines(String path, String leftRev, String rightRev, Set retain, Changes change) throws Exception {
+ Map linesToKeep = new TreeMap<>();
+ String leftFile = this.repoWrapper.doCat(path, leftRev);
+ String rightFile = this.repoWrapper.doCat(path, rightRev);
+
+ SignatureExtractor rightSignatures = new SignatureExtractor(rightFile);
+ Map> rightLineSignatures = rightSignatures.getSignaturesWithLines();
+ Map rightLineMappings = this.repoWrapper.getLineMappings(rightFile);
+
+ SignatureExtractor leftSignatures = new SignatureExtractor(leftFile);
+ Map> leftLineSignatures = leftSignatures.getSignaturesWithLines();
+ Map leftLineMappings = this.repoWrapper.getLineMappings(leftFile);
+
+ for (VulnerabilityEvidence evd : retain) {
+ String container = evd.getContainer();
+ Set lineNumbers = leftLineSignatures.get(container);
+
+ Map leftChunk = new TreeMap<>();
+
+ // the left container might not exist anymore...
+ if (lineNumbers == null) {
+ continue;
+ }
+
+ // otherwise, process
+ for (int lineNumber : lineNumbers) {
+ leftChunk.put(lineNumber, leftLineMappings.get(lineNumber));
+ }
+
+ lineNumbers = rightLineSignatures.get(container);
+ Map rightChunk = new TreeMap<>();
+ for (int lineNumber : lineNumbers) {
+ rightChunk.put(lineNumber, rightLineMappings.get(lineNumber));
+ }
+
+ String leftLine = leftChunk.get(evd.getLineNumber());
+ String rightLine = rightChunk.get(evd.getLineNumber());
+
+ if (leftLine != null && rightLine != null && leftLine.equals(rightLine)) {
+ linesToKeep.put(evd.getLineNumber(), evd.getLineContents());
+ continue;
+ } else {
+ if (rightLine != null && rightLine.equals(evd.getLineContents())) {
+ Map eligibleCandidates = new TreeMap<>();
+ for (Map.Entry candidateLine : leftChunk.entrySet()) {
+ if (candidateLine.getValue().equals(evd.getLineContents())) {
+ eligibleCandidates.put(candidateLine.getKey(), evd.getLineContents());
+ continue;
+ }
+ }
+ if (eligibleCandidates.size() > 1) {
+ Iterator> it = eligibleCandidates.entrySet().iterator();
+ int leftLineNumber = it.next().getKey();
+ int rightLineNumber = evd.getLineNumber();
+ int minimumDistance = rightLineNumber - leftLineNumber;
+ while (it.hasNext()) {
+ leftLineNumber = it.next().getKey();
+ int tempMinimumDistance = rightLineNumber - leftLineNumber;
+ minimumDistance = (Math.abs(tempMinimumDistance) < Math.abs(minimumDistance)) ? tempMinimumDistance : minimumDistance;
+ }
+ int newLineNumber = rightLineNumber - minimumDistance;
+ String newLineContents = eligibleCandidates.get(newLineNumber);
+ linesToKeep.put(newLineNumber, newLineContents);
+ }
+ else {
+ linesToKeep.putAll(eligibleCandidates);
+ }
+ }
+ }
+ }
+ return linesToKeep;
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/VulnerabilityEvidenceTrackerFactory.java b/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/VulnerabilityEvidenceTrackerFactory.java
new file mode 100644
index 0000000..d95b659
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/repos/trackers/vuln/VulnerabilityEvidenceTrackerFactory.java
@@ -0,0 +1,73 @@
+package it.unitn.molerat.repos.trackers.vuln;
+
+import it.unitn.molerat.repos.wrappers.GitRepoWrapper;
+import it.unitn.molerat.repos.wrappers.RepoWrapper;
+import it.unitn.molerat.repos.wrappers.SvnRepoWrapper;
+
+public class VulnerabilityEvidenceTrackerFactory {
+
+ public static VulnerabilityEvidenceTracker getTracker(String repoRoot, String fixedRev, String wrapperType, String trackerType) throws Exception {
+ RepoWrapper wrapper = null;
+ switch (wrapperType.toLowerCase()) {
+ case "svn":
+ wrapper = new SvnRepoWrapper(repoRoot);
+ break;
+
+ case "git":
+ wrapper = new GitRepoWrapper(repoRoot);
+ break;
+
+ default:
+ throw new Exception("There is currently no support for '" + wrapperType + "'");
+ }
+ VulnerabilityEvidenceTracker vulnerabilityEvidenceTracker = null;
+ switch (trackerType.toLowerCase()) {
+ case "deletionvulnerabilityevidencetracker":
+ vulnerabilityEvidenceTracker = new DeletionVulnerabilityEvidenceTracker(wrapper, fixedRev);
+ break;
+
+ case "slicevulnerabilityevidencetracker":
+ vulnerabilityEvidenceTracker = new SliceVulnerabilityEvidenceTracker(wrapper, fixedRev);
+ break;
+
+ case "slicedecayvulnerabilityevidencetracker":
+ vulnerabilityEvidenceTracker = new SliceDecayVulnerabilityEvidenceTracker(wrapper, fixedRev);
+ break;
+
+ case "patchedmethodbodytracker":
+ vulnerabilityEvidenceTracker = new PatchedMethodBodyTracker(wrapper, fixedRev);
+ break;
+
+ case "enhanceddeletionvulnerabilityevidencetracker":
+ vulnerabilityEvidenceTracker = new EnhancedDeletionVulnerabilityEvidenceTracker(wrapper, fixedRev);
+ break;
+
+ case "fixstatisticsvulnerabilityevidencetracker":
+ vulnerabilityEvidenceTracker = new FixStatisticsVulnerabilityEvidenceTracker(wrapper, fixedRev);
+ break;
+
+ default:
+ throw new Exception("There is no such molerat.evidence tracker as '" + trackerType + "'");
+ }
+ return vulnerabilityEvidenceTracker;
+ }
+
+ public static String getTrackersList() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("--------------------\n");
+ builder.append("The list of available vulnerability molerat.evidence trackers:\n");
+ builder.append("--------------------\n");
+ builder.append("\n");
+ builder.append("\t\"SliceDecayVulnerabilityEvidenceTracker\" - track intra-procedural slice over the fixed lines.\n");
+ builder.append("\n");
+ builder.append("\t\"DeletionVulnerabilityEvidenceTracker\" - track only the lines deleted during a fix.\n");
+ builder.append("\n");
+ builder.append("\t\"PatchedMethodBodyTracker\" - track the entire fixed methods.\n");
+ builder.append("\n");
+ builder.append("\t\"EnhancedDeletionVulnerabilityEvidenceTracker\" - track only the lines deleted during a fix, " +
+ "or the entire fixed methods if there are no deleted lines.\n");
+ builder.append("\n");
+ builder.append("--------------------\n");
+ return builder.toString();
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/repos/utils/CommitMetrics.java b/molerat/src/main/java/it/unitn/molerat/repos/utils/CommitMetrics.java
new file mode 100644
index 0000000..0a73c76
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/repos/utils/CommitMetrics.java
@@ -0,0 +1,98 @@
+package it.unitn.molerat.repos.utils;
+
+import it.unitn.molerat.evidence.Changes;
+import it.unitn.molerat.repos.wrappers.RepoWrapper;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class CommitMetrics {
+
+ private static final Pattern deletedPublicMethodPattern = Pattern.compile("public .*\\([^\\)]*\\)[^;]*$");
+
+ public static int getGlobalPublicMethodsRemoved(String leftRev, String rightRev, RepoWrapper wrapper) throws Exception {
+ int removed = 0;
+ String diff = wrapper.doDiff(leftRev, rightRev);
+ Set changes = wrapper.inferChangesFromDiff(diff, leftRev, rightRev);
+ for (Changes change : changes) {
+ if (!change.getPath().endsWith(".java")) {
+ continue;
+ }
+ Map deletions = change.getDeletions();
+ wrapper.filterCommentsAndBlanks(deletions);
+ for (String del : deletions.values()) {
+ del = StringUtils.trim(del);
+ Matcher matcher = deletedPublicMethodPattern.matcher(del);
+ if (matcher.matches()) {
+ removed++;
+ }
+ matcher.reset();
+ }
+ }
+ return removed;
+ }
+
+ public static int getNumberOfPublicMethodsPerRevision(String rev, RepoWrapper wrapper) throws Exception {
+ int pubAPICount = 0;
+ Set files = wrapper.getRevisionFiles(rev, ".java");
+ for (String f : files) {
+ String fc = wrapper.doCat(f, rev);
+ pubAPICount += countPublicMethodDeclarationsInFile(fc);
+ }
+ return pubAPICount;
+ }
+
+ public static Map getFileContentsPerRevision(String rev, RepoWrapper wrapper) throws Exception {
+ Map result = new TreeMap<>();
+ Set files = wrapper.getRevisionFiles(rev, ".java");
+ for (String file : files) {
+ String contents = wrapper.doCat(file, rev);
+ result.put(file, contents);
+ }
+ return result;
+ }
+
+ @Deprecated
+ public static int countPublicMethodDeclarationsInFile(String fileContents) {
+ int pubAPICount = 0;
+ String[] lines = StringUtils.split(fileContents, System.getProperty("line.separator"));
+ for (int i=0; i readFileBrokenByLines(String path) throws IOException {
+ File javaFile = throwExceptionIfDoesNotExist(path);
+ Set lines = new LinkedHashSet();
+ BufferedReader in = new BufferedReader(new FileReader(javaFile));
+ String line = null;
+ try {
+ while ( (line = in.readLine()) != null) {
+ lines.add(line);
+ }
+ }
+ finally {
+ in.close();
+ }
+ return lines;
+ }
+
+ public static void readFilesRecursively(File source, Set results) {
+ if (!source.exists()) {
+ return;
+ }
+ else if (source.isDirectory()) {
+ String[] files = source.list();
+ for (String file : files) {
+ readFilesRecursively(new File(source,file), results);
+ }
+ }
+ else {
+ try {
+ String result = readFile(source.getAbsolutePath());
+ results.add(result);
+ } catch (IOException e) {
+ System.out.println("ERROR: " + e.getMessage());
+ }
+ }
+ }
+
+ public static Set readInputDataPoints(String path) throws Exception {
+ Set dataPointSet = new HashSet<>();
+ File file2Parse = new File(path);
+ BufferedReader reader = null;
+ try {
+ String line = "";
+ reader = new BufferedReader(new FileReader(file2Parse));
+ while ((line = reader.readLine()) != null) {
+ InputDataPoint inputDataPoint = new InputDataPoint(line);
+ dataPointSet.add(inputDataPoint);
+ }
+ } catch (Exception e) {
+ System.out.println("ERROR: " + e.getMessage());
+ }
+ finally {
+ try {
+ reader.close();
+ } catch (Exception e) {
+ System.out.println("ERROR: " + e.getMessage());
+ }
+ }
+ return dataPointSet;
+ }
+
+
+ public static File joinpaths(String path1, String path2) {
+ return new File(path1, path2);
+ }
+
+ public static void mkdir(File dir) {
+ if (!dir.exists()) {
+ dir.mkdirs();
+ }
+ }
+
+ public void copyFolders(File source, File destination) {
+ if (source.isDirectory() && destination.isDirectory()) {
+ try {
+ FileUtils.copyDirectory(source, destination);
+ } catch (IOException e) {
+ System.out.println("ERROR: " + e.getMessage());
+ }
+ }
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/repos/utils/SignatureExtractor.java b/molerat/src/main/java/it/unitn/molerat/repos/utils/SignatureExtractor.java
new file mode 100644
index 0000000..03534f6
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/repos/utils/SignatureExtractor.java
@@ -0,0 +1,233 @@
+package it.unitn.molerat.repos.utils;
+
+import it.unitn.repoman.core.lang.LanguageFactory;
+import it.unitn.repoman.core.lang.parsers.java.JavaBaseListener;
+import it.unitn.repoman.core.lang.parsers.java.JavaParser;
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.misc.NotNull;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.antlr.v4.runtime.tree.ParseTreeWalker;
+import org.antlr.v4.runtime.tree.TerminalNodeImpl;
+
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.Queue;
+import java.util.HashSet;
+
+public class SignatureExtractor extends JavaBaseListener {
+
+ private String className = "";
+ private String packageName = "";
+ private String currentSignature = "";
+ private final Map> lines = new TreeMap<>();
+ private final Queue modifiers = new LinkedList<>();
+
+
+ public SignatureExtractor(String fileContents) {
+ LanguageFactory.init("java", fileContents);
+ ParseTreeWalker walker = new ParseTreeWalker();
+ walker.walk(this, LanguageFactory.getRoot());
+ }
+
+ @Override
+ public void enterPackageDeclaration(JavaParser.PackageDeclarationContext ctx) {
+ packageName = ctx.getChild(1).getText();
+ currentSignature = packageName;
+ processLine(ctx);
+ }
+
+ @Override
+ public void exitPackageDeclaration(JavaParser.PackageDeclarationContext ctx) {
+ currentSignature = "";
+ }
+
+ @Override
+ public void enterClassOrInterfaceModifier(JavaParser.ClassOrInterfaceModifierContext ctx) {
+ modifiers.add(ctx);
+ }
+
+ @Override
+ public void enterMethodDeclaration(JavaParser.MethodDeclarationContext ctx) {
+ StringBuilder methodDeclBuilder = new StringBuilder();
+ getMethodSignature(ctx,methodDeclBuilder);
+ String currentModifiers = retrieveModifiers();
+
+ StringBuilder signatureBuilder = new StringBuilder();
+ signatureBuilder.append(currentModifiers);
+ signatureBuilder.append(": ");
+ signatureBuilder.append(joinPackageNameWithClassName());
+ signatureBuilder.append(".");
+ signatureBuilder.append(methodDeclBuilder.toString());
+ currentSignature = signatureBuilder.toString();
+ }
+
+ private String joinPackageNameWithClassName() {
+ StringBuilder builder = new StringBuilder();
+ if (!packageName.equals("")) {
+ builder.append(packageName);
+ builder.append(".");
+ }
+ builder.append(className);
+ return builder.toString();
+ }
+
+ @Override
+ public void exitMethodDeclaration(JavaParser.MethodDeclarationContext ctx) {
+ currentSignature = "";
+ }
+
+ @Override
+ public void enterConstructorDeclaration(JavaParser.ConstructorDeclarationContext ctx) {
+ StringBuilder constrDeclBuilder = new StringBuilder();
+ getConstructorSignature(ctx,constrDeclBuilder);
+ String currentModifiers = retrieveModifiers();
+
+ StringBuilder signatureBuilder = new StringBuilder();
+ signatureBuilder.append(currentModifiers);
+ signatureBuilder.append(": ");
+ signatureBuilder.append(joinPackageNameWithClassName());
+ signatureBuilder.append(".");
+ signatureBuilder.append(constrDeclBuilder.toString());
+ currentSignature = signatureBuilder.toString();
+
+ processLine(ctx);
+ }
+
+ @Override
+ public void exitConstructorDeclaration(JavaParser.ConstructorDeclarationContext ctx) {
+ currentSignature = "";
+ }
+
+ @Override
+ public void enterClassDeclaration(JavaParser.ClassDeclarationContext ctx) {
+ if (!className.equals("")) {
+ return;
+ }
+ className = ctx.getChild(1).getText();
+ String currentModifiers = retrieveModifiers();
+
+ StringBuilder signatureBuilder = new StringBuilder();
+ signatureBuilder.append(currentModifiers);
+ signatureBuilder.append(": ");
+ signatureBuilder.append(joinPackageNameWithClassName());
+ currentSignature = signatureBuilder.toString();
+
+ processLine(ctx);
+ }
+
+
+ @Override
+ public void exitClassDeclaration(JavaParser.ClassDeclarationContext ctx) {
+ currentSignature = "";
+ }
+
+ @Override
+ public void enterStatement(JavaParser.StatementContext ctx) {
+ processLine(ctx);
+ }
+
+ @Override
+ public void enterFieldDeclaration(JavaParser.FieldDeclarationContext ctx) {
+ String currentModifiers = retrieveModifiers();
+ String fieldName = ctx.getChild(1).getChild(0).getChild(0).getText();
+
+ StringBuilder signatureBuilder = new StringBuilder();
+ signatureBuilder.append(currentModifiers);
+ signatureBuilder.append(": ");
+ signatureBuilder.append(joinPackageNameWithClassName());
+ signatureBuilder.append(".");
+ signatureBuilder.append(fieldName);
+ currentSignature = signatureBuilder.toString();
+
+ processLine(ctx);
+ }
+
+ @Override
+ public void exitFieldDeclaration(JavaParser.FieldDeclarationContext ctx) {
+ currentSignature = "";
+ }
+
+ @Override
+ public void enterLocalVariableDeclaration(JavaParser.LocalVariableDeclarationContext ctx) {
+ processLine(ctx);
+ }
+
+ @Override
+ public void enterCatchClause(JavaParser.CatchClauseContext ctx) {
+ processLine(ctx);
+ }
+
+ private void processLine(ParserRuleContext ctx) {
+ int line = ctx.getStart().getLine();
+ if (currentSignature != null) {
+ Set tempLines = lines.get(currentSignature);
+ if (tempLines != null) {
+ lines.remove(currentSignature);
+ } else {
+ tempLines = new HashSet<>();
+ }
+ tempLines.add(line);
+ lines.put(currentSignature, tempLines);
+ }
+ }
+
+ /**
+ * Gets a method signature recursively
+ * @param method
+ * @param builder
+ */
+ private void getMethodSignature(ParseTree method, StringBuilder builder) {
+ int start = (method.getClass().equals(JavaParser.MethodDeclarationContext.class)) ? 1 : 0;
+ for (int i=start; i> getSignaturesWithLines() {
+ return this.lines;
+ }
+
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/repos/wrappers/GitRepoWrapper.java b/molerat/src/main/java/it/unitn/molerat/repos/wrappers/GitRepoWrapper.java
new file mode 100644
index 0000000..7495494
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/repos/wrappers/GitRepoWrapper.java
@@ -0,0 +1,204 @@
+package it.unitn.molerat.repos.wrappers;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.*;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffFormatter;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
+import org.eclipse.jgit.treewalk.AbstractTreeIterator;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.PathSuffixFilter;
+import org.eclipse.jgit.lib.Ref;
+
+public final class GitRepoWrapper extends RepoWrapper {
+
+ private final String repoRoot;
+ private final Repository repo;
+ private final Git git;
+
+ public GitRepoWrapper(String root) throws IOException {
+ this.diffFilePrefix = "diff --git a";
+ this.repoRoot = root;
+ FileRepositoryBuilder builder = new FileRepositoryBuilder();
+ builder.setGitDir(new File(this.repoRoot + "/.git"));
+ this.repo = builder.build();
+ this.git = new Git(this.repo);
+ }
+
+ public Map getTagsAndCommits() throws Exception {
+ Map tags = new HashMap<>();
+ List[ call = git.tagList().call();
+ for (Ref tagref : call) {
+ tags.put(tagref.getName(), tagref.getPeeledObjectId().getName());
+ }
+ return tags;
+ }
+
+ @Override
+ public String doDiff(String path, String leftRev, String rightRev) throws Exception {
+ if (path.startsWith("/")) {
+ path = path.substring(1);
+ }
+ OutputStream out = new ByteArrayOutputStream();
+ try { DiffFormatter df = new DiffFormatter(out);
+ df.setRepository(this.git.getRepository());
+ df.setDetectRenames(true);
+ List entries = df.scan(getTreeIterator(leftRev), getTreeIterator(rightRev));
+ df.close();
+ df.format(entries);
+ }
+ catch(Exception e) {
+ System.out.println("[JGit error] " + e.getMessage());
+ throw new Exception(e);
+ }
+ return out.toString();
+ }
+
+ @Override
+ public String doDiff(String leftRev, String rightRev) throws Exception {
+ return this.doDiff("", leftRev, rightRev);
+ }
+
+ @Override
+ public String doCat(String path, String rev) throws Exception {
+ if (path.startsWith("/")) {
+ path = path.substring(1);
+ }
+ String output = "";
+ final ObjectId id = this.repo.resolve(rev);
+ ObjectReader reader = this.repo.newObjectReader();
+ RevWalk walk = null;
+ try {
+ walk = new RevWalk(reader);
+ RevCommit commit = walk.parseCommit(id);
+ RevTree tree = commit.getTree();
+ TreeWalk treewalk = TreeWalk.forPath(reader, path, tree);
+ if (treewalk != null) {
+ byte[] data = reader.open(treewalk.getObjectId(0)).getBytes();
+ output = new String(data, "utf-8");
+ }
+ } finally {
+ reader.close();
+ if (walk != null) {
+ walk.close();
+ }
+ }
+ return output;
+ }
+
+ @Override
+ public String getBasePath() {
+ return this.repoRoot;
+ }
+
+ private AbstractTreeIterator getTreeIterator(String name) throws IOException {
+ final ObjectId id = this.repo.resolve(name);
+ if (id == null) {
+ throw new IllegalArgumentException(name);
+ }
+ final CanonicalTreeParser p = new CanonicalTreeParser();
+ try (ObjectReader or = this.repo.newObjectReader();
+ RevWalk rw = new RevWalk(this.repo)) {
+ p.reset(or, rw.parseTree(id));
+ return p;
+ }
+ }
+
+ // TODO: UNIMPLEMENTED
+ @Override
+ public void annotate(String path, String rev, Object callback) throws Exception {
+ }
+
+
+ @Override
+ public Set getRevisionNumbers(String topRev) throws Exception {
+ Set commits = new LinkedHashSet<>();
+ for (RevCommit commit : git.log().add(this.repo.resolve(topRev + "~1")).call()) {
+ commits.add(commit.getName());
+ }
+ return commits;
+ }
+
+ // TODO: UNIMPLEMENTED
+ @Override
+ public Map determineOriginatingRevision(String filePath, String revision, Map lines) throws Exception {
+ return new HashMap<>();
+ }
+
+ // TODO: UNIMPLEMENTED
+ @Override
+ protected String getReleaseTag(String release) throws Exception {
+ /*
+ Map tags = this.getTagsAndCommits();
+ for (String key : tags.keySet()) {
+ String candidate = key.replace("refs/tags/", "");
+ System.out.println("KEY: " + candidate);
+ }
+ */
+ return ("refs/tags/" + release);
+ }
+
+ @Override
+ public String getReleaseCommit(String release) throws Exception {
+ String tagName = getReleaseTag(release);
+ String tagCommit = this.getTagsAndCommits().get(tagName);
+ Iterator iter = git.log().add(this.repo.resolve(tagCommit)).call().iterator();
+ iter.next();
+ return iter.next().getName();
+ }
+
+ @Override
+ public Set getRevisionFiles(String rev, String filter) throws Exception {
+ Set files = new LinkedHashSet<>();
+ RevWalk revWalk = null;
+ TreeWalk treeWalk = null;
+ try {
+ ObjectId revId = this.repo.resolve(rev);
+ revWalk = new RevWalk(this.repo);
+ RevCommit commit = revWalk.parseCommit(revId);
+ RevTree tree = commit.getTree();
+ treeWalk = new TreeWalk(this.repo);
+ treeWalk.addTree(tree);
+ treeWalk.setRecursive(true);
+
+ treeWalk.setFilter(PathSuffixFilter.create(filter));
+ while(treeWalk.next()) {
+ files.add(treeWalk.getPathString());
+ }
+ } catch(Exception e) {
+ throw new Exception("[JGit error] " + e.getMessage());
+ } finally {
+ if (revWalk != null){
+ revWalk.close();
+ }
+ if (treeWalk != null){
+ treeWalk.close();
+ }
+ }
+ return files;
+ }
+
+ @Override
+ public Set getAllRepositoryTransactions() throws Exception {
+ Iterable logs = git.log().call();
+ Set commits = new LinkedHashSet<>();
+ Iterator it = logs.iterator();
+ if (it.hasNext()) {
+ String latestCommit = it.next().getName();
+ commits.add(latestCommit);
+ commits.addAll(this.getRevisionNumbers(latestCommit));
+ }
+ return commits;
+ }
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/repos/wrappers/RepoWrapper.java b/molerat/src/main/java/it/unitn/molerat/repos/wrappers/RepoWrapper.java
new file mode 100644
index 0000000..59e9048
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/repos/wrappers/RepoWrapper.java
@@ -0,0 +1,161 @@
+package it.unitn.molerat.repos.wrappers;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import it.unitn.molerat.evidence.Changes;
+
+public abstract class RepoWrapper {
+
+ protected String diffFilePrefix;
+
+ protected final Pattern hunkRegexPattern = Pattern.compile("@@[ ]*\\-([0-9]+)[\\,0-9 ]+\\+([0-9]+)[\\,0-9 ]+@@");
+
+ protected final Pattern commentsPattern = Pattern.compile("(\\*|\\/\\*|\\*\\/|\\/\\/)(.*)");
+
+ public abstract String doDiff(String leftRev, String rightRev) throws Exception;
+
+ public abstract String doDiff(String path, String leftRev, String rightRev) throws Exception;
+
+ public abstract void annotate(String path, String rev, Object callback) throws Exception;
+
+ public abstract String doCat(String path, String rev) throws Exception;
+
+ public abstract String getBasePath();
+
+ public abstract Set getRevisionFiles(String rev, String filter) throws Exception;
+
+ public abstract Set getRevisionNumbers(String topRev) throws Exception;
+
+ public abstract Map determineOriginatingRevision(final String filePath, final String revision, final Map lines) throws Exception;
+
+ public Set inferChangesFromDiff(String diffText, String leftRev, String rightRev) throws IOException {
+ Set relevantChanges = new LinkedHashSet<>();
+
+ Changes newRelevantChanges = null;
+ int deletionRange = -1;
+ int additionRange = -1;
+ boolean additionRangeNotSet = true;
+ boolean deletionRangeNotSet = true;
+
+ String[] diffLines = diffText.split(System.getProperty("line.separator"));
+ //for (String line : diffLines) {
+ for (int i=0; i lines) {
+ Iterator> it = lines.entrySet().iterator();
+ while (it.hasNext()) {
+ String line = it.next().getValue();
+ Matcher commentMatcher = this.commentsPattern.matcher(line.trim());
+ if (line.trim().isEmpty() || commentMatcher.matches()) {
+ it.remove();
+ }
+ }
+ }
+
+ public Map getLineMappings(final String fileContents) {
+ Map lineMappings = new TreeMap();
+ String[] lines = fileContents.split(System.getProperty("line.separator"));
+ int lineNumber = 1;
+ for (String line : lines) {
+ line = line.trim().replace("\t","");
+ lineMappings.put(lineNumber++, line);
+ }
+ return lineMappings;
+ }
+
+ public int getNumberOfLoc(final String fileContents) {
+ return this.getLineMappings(fileContents).size();
+ }
+
+ public int getNumberOfLocFiltered(final String fileContents) {
+ Map locs = this.getLineMappings(fileContents);
+ this.filterCommentsAndBlanks(locs);
+ return locs.size();
+ }
+
+ protected abstract String getReleaseTag(String release) throws Exception;
+ public abstract String getReleaseCommit(String release) throws Exception;
+ public abstract Set getAllRepositoryTransactions() throws Exception;
+}
diff --git a/molerat/src/main/java/it/unitn/molerat/repos/wrappers/SvnRepoWrapper.java b/molerat/src/main/java/it/unitn/molerat/repos/wrappers/SvnRepoWrapper.java
new file mode 100644
index 0000000..436dcb8
--- /dev/null
+++ b/molerat/src/main/java/it/unitn/molerat/repos/wrappers/SvnRepoWrapper.java
@@ -0,0 +1,185 @@
+package it.unitn.molerat.repos.wrappers;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNLogEntry;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.io.SVNRepository;
+import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
+import org.tmatesoft.svn.core.wc.SVNRevision;
+import org.tmatesoft.svn.core.wc2.ISvnObjectReceiver;
+import org.tmatesoft.svn.core.wc2.SvnAnnotate;
+import org.tmatesoft.svn.core.wc2.SvnAnnotateItem;
+import org.tmatesoft.svn.core.wc2.SvnCat;
+import org.tmatesoft.svn.core.wc2.SvnDiff;
+import org.tmatesoft.svn.core.wc2.SvnLog;
+import org.tmatesoft.svn.core.wc2.SvnOperationFactory;
+import org.tmatesoft.svn.core.wc2.SvnRevisionRange;
+import org.tmatesoft.svn.core.wc2.SvnTarget;
+
+public final class SvnRepoWrapper extends RepoWrapper {
+
+ private final SVNURL url;
+ protected SVNRepository repo = null;
+ protected final SvnOperationFactory opFactory = new SvnOperationFactory();
+
+ public SvnRepoWrapper(String url) throws SVNException {
+ this.diffFilePrefix = "Index: ";
+ this.url = SVNURL.parseURIEncoded(url);
+ this.repo = SVNRepositoryFactory.create(this.url);
+ }
+
+ protected String doDiff(SvnTarget leftRev, SvnTarget rightRev) throws SVNException {
+ final ByteArrayOutputStream output = new ByteArrayOutputStream();
+ SvnDiff diff = opFactory.createDiff();
+ diff.setOutput(output);
+ diff.setSources(leftRev, rightRev);
+ diff.run();
+ return output.toString();
+ }
+
+ public Set getRevisionNumbers(String topRev) throws SVNException {
+ Set revNumbers = new LinkedHashSet<>();
+ LinkedList logEntries = new LinkedList<>();
+ SvnLog log = opFactory.createLog();
+ SvnTarget target = SvnTarget.fromURL(this.url);
+ log.addRange(SvnRevisionRange.create(SVNRevision.create(0), SVNRevision.create(Long.parseLong(topRev))));
+ log.setSingleTarget(target);
+ log.run(logEntries);
+ Iterator it = logEntries.descendingIterator();
+ if (it.hasNext()) {
+ it.next(); // discard the topRev
+ }
+ while (it.hasNext()) {
+ SVNLogEntry entry = it.next();
+ revNumbers.add(String.valueOf(entry.getRevision()));
+ }
+ return revNumbers;
+ }
+
+
+ protected void annotate(final SvnTarget target, ISvnObjectReceiver callback) throws SVNException {
+ SvnAnnotate annotate = opFactory.createAnnotate();
+ annotate.setSingleTarget(target);
+ final SVNRevision startRev = SVNRevision.create(1);
+ final SVNRevision endRev = target.getPegRevision();
+ annotate.setStartRevision(startRev);
+ annotate.setEndRevision(endRev);
+ annotate.setReceiver(callback);
+ annotate.run();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void annotate(String path, String rev, Object callback) throws Exception {
+ long revLong = Long.parseLong(rev);
+ SvnTarget target = SvnTarget.fromURL(SVNURL.parseURIEncoded(this.getBasePath() + path), SVNRevision.create(revLong));
+ this.annotate(target, ((ISvnObjectReceiver)callback));
+ }
+
+ protected String doCat(final SvnTarget target) throws SVNException {
+ final ByteArrayOutputStream output = new ByteArrayOutputStream();
+ SvnCat cat = this.opFactory.createCat();
+ cat.addTarget(target);
+ cat.setOutput(output);
+ cat.run();
+ return output.toString();
+ }
+
+ @Override
+ public String doDiff(String leftRev, String rightRev) throws SVNException {
+ return this.doDiff("", leftRev, rightRev);
+ }
+
+ @Override
+ public String doDiff(String path, String leftRev, String rightRev) throws SVNException {
+ long leftLong = Long.parseLong(leftRev);
+ long rightLong = Long.parseLong(rightRev);
+ SvnTarget left = SvnTarget.fromURL(SVNURL.parseURIEncoded(this.url + path), SVNRevision.create(leftLong));
+ SvnTarget right = SvnTarget.fromURL(SVNURL.parseURIEncoded(this.url + path), SVNRevision.create(rightLong));
+ return this.doDiff(left, right);
+ }
+
+
+ @Override
+ public String getBasePath() {
+ return this.url.toString();
+ }
+
+ @Override
+ public String doCat(String path, String rev) throws SVNException {
+ long revLong = Long.parseLong(rev);
+ SvnTarget target = SvnTarget.fromURL(SVNURL.parseURIEncoded(this.url + path), SVNRevision.create(revLong));
+ return this.doCat(target);
+ }
+
+ //----------------------------------------------------------------------------------------------------
+ public String getTopmostRevision(String url) throws SVNException {
+ SvnLog log = opFactory.createLog();
+ SvnTarget target = SvnTarget.fromURL(SVNURL.parseURIEncoded(url));
+ log.addRange(SvnRevisionRange.create(null, null));
+ log.setSingleTarget(target);
+ SVNLogEntry entry = log.run();
+ return String.valueOf(entry.getRevision());
+ }
+ //----------------------------------------------------------------------------------------------------
+
+ /*----------------------------------------------------------------------------------------------------
+ * example found at: http://blog.gmane.org/gmane.comp.version-control.subversion.javasvn.user/month=20131101
+ ----------------------------------------------------------------------------------------------------*/
+ @Override
+ public Map determineOriginatingRevision(final String filePath,
+ final String revision, final Map lines) throws Exception {
+ final Map originating = new TreeMap();
+ final long startRev = 1;
+ final long endRev = Long.parseLong(revision);
+ this.annotate(filePath, String.valueOf(endRev), new ISvnObjectReceiver() {
+ @Override
+ public void receive(SvnTarget target, SvnAnnotateItem itm) throws SVNException {
+ if (itm.isLine()) {
+ final long revision = itm.getRevision();
+ final int lineNumber = itm.getLineNumber() + 1;
+ if (startRev < revision && revision <= endRev) {
+ if (lines.containsKey(lineNumber)) {
+ originating.put(lineNumber, String.valueOf(revision));
+ }
+ }
+ }
+ }
+ });
+ return originating;
+ }
+
+ // TODO: UNIMPLEMENTED
+ @Override
+ protected String getReleaseTag(String release) throws Exception {
+ return "";
+ }
+
+ // TODO: UNIMPLEMENTED
+ @Override
+ public String getReleaseCommit(String release) throws Exception {
+ return "";
+ }
+
+
+ // TODO: UNIMPLEMENTED
+ @Override
+ public Set getRevisionFiles(String rev, String filter) throws Exception {
+ return new LinkedHashSet<>();
+ }
+
+ // TODO: UNIMPLEMENTED
+ @Override
+ public Set]