Преглед изворни кода

Merge pull request #487 from nestabentum/max_metric_in_web_report

Add Max Metric To Report Viewer
Timur Sağlam пре 4 година
родитељ
комит
33ecda9aea
27 измењених фајлова са 376 додато и 250 уклоњено
  1. 1 1
      jplag/src/main/java/de/jplag/JPlag.java
  2. 27 9
      jplag/src/main/java/de/jplag/JPlagResult.java
  3. 4 4
      jplag/src/main/java/de/jplag/reporting/jsonfactory/JsonFactory.java
  4. 12 26
      jplag/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java
  5. 54 0
      jplag/src/main/java/de/jplag/reporting/reportobject/mapper/MetricMapper.java
  6. 1 12
      jplag/src/main/java/de/jplag/reporting/reportobject/model/JPlagReport.java
  7. 2 36
      jplag/src/main/java/de/jplag/reporting/reportobject/model/Metric.java
  8. 2 28
      jplag/src/main/java/de/jplag/reporting/reportobject/model/TopComparison.java
  9. 3 1
      jplag/src/main/resources/de/jplag/messages.properties
  10. 3 0
      jplag/src/main/resources/de/jplag/messages_de.properties
  11. 4 0
      jplag/src/main/resources/de/jplag/messages_en.properties
  12. 3 0
      jplag/src/main/resources/de/jplag/messages_es.properties
  13. 3 0
      jplag/src/main/resources/de/jplag/messages_fr.properties
  14. 3 0
      jplag/src/main/resources/de/jplag/messages_pt.properties
  15. 5 5
      jplag/src/test/java/de/jplag/BaseCodeTest.java
  16. 2 2
      jplag/src/test/java/de/jplag/NewJavaFeaturesTest.java
  17. 13 15
      jplag/src/test/java/de/jplag/NormalComparisonTest.java
  18. 9 11
      jplag/src/test/java/de/jplag/ParallelComparisonTest.java
  19. 2 3
      jplag/src/test/java/de/jplag/clustering/ClusteringAdapterTest.java
  20. 115 0
      jplag/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java
  21. 2 2
      jplag/src/test/java/de/jplag/special/VolumeTest.java
  22. 1 0
      jplag/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
  23. 4 6
      report-viewer/src/components/DistributionDiagram.vue
  24. 39 26
      report-viewer/src/components/MetricButton.vue
  25. 1 0
      report-viewer/src/model/Metric.ts
  26. 60 61
      report-viewer/src/model/factories/OverviewFactory.ts
  27. 1 2
      report-viewer/src/views/OverviewView.vue

+ 1 - 1
jplag/src/main/java/de/jplag/JPlag.java

@@ -96,7 +96,7 @@ public class JPlag {
         JPlagResult result = comparisonStrategy.compareSubmissions(submissionSet);
         JPlagResult result = comparisonStrategy.compareSubmissions(submissionSet);
         errorCollector.print("\nTotal time for comparing submissions: " + TimeUtil.formatDuration(result.getDuration()), null);
         errorCollector.print("\nTotal time for comparing submissions: " + TimeUtil.formatDuration(result.getDuration()), null);
 
 
-        result.setClusteringResult(ClusteringFactory.getClusterings(result.getComparisons(), options.getClusteringOptions()));
+        result.setClusteringResult(ClusteringFactory.getClusterings(result.getAllComparisons(), options.getClusteringOptions()));
 
 
         return result;
         return result;
     }
     }

+ 27 - 9
jplag/src/main/java/de/jplag/JPlagResult.java

@@ -1,9 +1,11 @@
 package de.jplag;
 package de.jplag;
 
 
 import java.util.List;
 import java.util.List;
+import java.util.function.Function;
 
 
 import de.jplag.clustering.ClusteringResult;
 import de.jplag.clustering.ClusteringResult;
 import de.jplag.options.JPlagOptions;
 import de.jplag.options.JPlagOptions;
+import de.jplag.options.SimilarityMetric;
 
 
 /**
 /**
  * Encapsulates the results of a comparison of a set of source code submissions.
  * Encapsulates the results of a comparison of a set of source code submissions.
@@ -21,6 +23,7 @@ public class JPlagResult {
     private final int[] similarityDistribution; // 10-element array representing the similarity distribution of the detected matches.
     private final int[] similarityDistribution; // 10-element array representing the similarity distribution of the detected matches.
 
 
     private List<ClusteringResult<Submission>> clusteringResult;
     private List<ClusteringResult<Submission>> clusteringResult;
+    private final int SIMILARITY_DISTRIBUTION_SIZE = 10;
 
 
     public JPlagResult(List<JPlagComparison> comparisons, SubmissionSet submissions, long durationInMillis, JPlagOptions options) {
     public JPlagResult(List<JPlagComparison> comparisons, SubmissionSet submissions, long durationInMillis, JPlagOptions options) {
         this.comparisons = comparisons;
         this.comparisons = comparisons;
@@ -47,7 +50,7 @@ public class JPlagResult {
     /**
     /**
      * @return a list of all comparisons sorted by percentage (descending)
      * @return a list of all comparisons sorted by percentage (descending)
      */
      */
-    public List<JPlagComparison> getComparisons() {
+    public List<JPlagComparison> getAllComparisons() {
         return comparisons;
         return comparisons;
     }
     }
 
 
@@ -93,22 +96,34 @@ public class JPlagResult {
     }
     }
 
 
     /**
     /**
-     * Returns the similarity distribution of detected matches in a 10-element array. Each entry represents the absolute
-     * frequency of matches whose similarity lies within the respective interval. Intervals: 0: [0% - 10%), 1: [10% - 20%),
-     * 2: [20% - 30%), ..., 9: [90% - 100%]
+     * For the {@link SimilarityMetric} JPlag was run with, this returns the similarity distribution of detected matches in
+     * a 10-element array. Each entry represents the absolute frequency of matches whose similarity lies within the
+     * respective interval. Intervals: 0: [0% - 10%), 1: [10% - 20%), 2: [20% - 30%), ..., 9: [90% - 100%]
      * @return the similarity distribution array.
      * @return the similarity distribution array.
      */
      */
     public int[] getSimilarityDistribution() {
     public int[] getSimilarityDistribution() {
         return similarityDistribution;
         return similarityDistribution;
     }
     }
 
 
+    /**
+     * For the {@link SimilarityMetric#MAX} that is built in to every {@link JPlagComparison}, this returns the similarity
+     * distribution of detected matches in a 10-element array. Each entry represents the absolute frequency of matches whose
+     * similarity lies within the respective interval. Intervals: 0: [0% - 10%), 1: [10% - 20%), 2: [20% - 30%), ..., 9:
+     * [90% - 100%]
+     * @return the similarity distribution array. When JPlag was run with the {@link SimilarityMetric#MAX}, this will return
+     * the same distribution as {@link JPlagResult#getSimilarityDistribution()}
+     */
+    public int[] getMaxSimilarityDistribution() {
+        return calculateDistributionFor(comparisons, (JPlagComparison::maximalSimilarity));
+    }
+
     public List<ClusteringResult<Submission>> getClusteringResult() {
     public List<ClusteringResult<Submission>> getClusteringResult() {
         return this.clusteringResult;
         return this.clusteringResult;
     }
     }
 
 
     @Override
     @Override
     public String toString() {
     public String toString() {
-        return String.format("JPlagResult { comparisons: %d, duration: %d ms, language: %s, submissions: %d }", getComparisons().size(),
+        return String.format("JPlagResult { comparisons: %d, duration: %d ms, language: %s, submissions: %d }", getAllComparisons().size(),
                 getDuration(), getOptions().getLanguageOption(), submissions.numberOfSubmissions());
                 getDuration(), getOptions().getLanguageOption(), submissions.numberOfSubmissions());
     }
     }
 
 
@@ -116,11 +131,14 @@ public class JPlagResult {
      * Note: Before, comparisons with a similarity below the given threshold were also included in the similarity matrix.
      * Note: Before, comparisons with a similarity below the given threshold were also included in the similarity matrix.
      */
      */
     private int[] calculateSimilarityDistribution(List<JPlagComparison> comparisons) {
     private int[] calculateSimilarityDistribution(List<JPlagComparison> comparisons) {
-        int[] similarityDistribution = new int[10];
-
-        comparisons.stream().map(JPlagComparison::similarity).map(percent -> percent / 10).map(Float::intValue).map(index -> index == 10 ? 9 : index)
-                .forEach(index -> similarityDistribution[index]++);
+        return calculateDistributionFor(comparisons, (JPlagComparison::similarity));
+    }
 
 
+    private int[] calculateDistributionFor(List<JPlagComparison> comparisons, Function<JPlagComparison, Float> similarityExtractor) {
+        int[] similarityDistribution = new int[SIMILARITY_DISTRIBUTION_SIZE];
+        comparisons.stream().map(similarityExtractor).map(percent -> percent / SIMILARITY_DISTRIBUTION_SIZE).map(Float::intValue)
+                .map(index -> index == SIMILARITY_DISTRIBUTION_SIZE ? SIMILARITY_DISTRIBUTION_SIZE - 1 : index)
+                .forEach(index -> similarityDistribution[SIMILARITY_DISTRIBUTION_SIZE - 1 - index]++);
         return similarityDistribution;
         return similarityDistribution;
     }
     }
 }
 }

+ 4 - 4
jplag/src/main/java/de/jplag/reporting/jsonfactory/JsonFactory.java

@@ -31,8 +31,8 @@ public class JsonFactory {
     public static List<String> getJsonStrings(JPlagReport jPlagReport) {
     public static List<String> getJsonStrings(JPlagReport jPlagReport) {
         List<String> jsonReports = new ArrayList<>();
         List<String> jsonReports = new ArrayList<>();
         try {
         try {
-            jsonReports.add(mapper.writeValueAsString(jPlagReport.getOverviewReport()));
-            for (ComparisonReport comparisonReport : jPlagReport.getComparisons()) {
+            jsonReports.add(mapper.writeValueAsString(jPlagReport.overviewReport()));
+            for (ComparisonReport comparisonReport : jPlagReport.comparisons()) {
                 jsonReports.add(mapper.writeValueAsString(comparisonReport));
                 jsonReports.add(mapper.writeValueAsString(comparisonReport));
             }
             }
         } catch (JsonProcessingException e) {
         } catch (JsonProcessingException e) {
@@ -48,8 +48,8 @@ public class JsonFactory {
     public static boolean saveJsonFiles(JPlagReport jPlagReport, String folderPath) {
     public static boolean saveJsonFiles(JPlagReport jPlagReport, String folderPath) {
         try {
         try {
             ObjectMapper mapper = new ObjectMapper();
             ObjectMapper mapper = new ObjectMapper();
-            mapper.writeValue(Path.of(folderPath, "overview.json").toFile(), jPlagReport.getOverviewReport());
-            for (ComparisonReport report : jPlagReport.getComparisons()) {
+            mapper.writeValue(Path.of(folderPath, "overview.json").toFile(), jPlagReport.overviewReport());
+            for (ComparisonReport report : jPlagReport.comparisons()) {
                 String name = report.getFirstSubmissionId().concat("-").concat(report.getSecondSubmissionId()).concat(".json");
                 String name = report.getFirstSubmissionId().concat("-").concat(report.getSecondSubmissionId()).concat(".json");
                 mapper.writeValue(Path.of(folderPath, name).toFile(), report);
                 mapper.writeValue(Path.of(folderPath, name).toFile(), report);
             }
             }

+ 12 - 26
jplag/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java

@@ -13,6 +13,7 @@ import org.slf4j.LoggerFactory;
 
 
 import de.jplag.*;
 import de.jplag.*;
 import de.jplag.reporting.reportobject.mapper.ClusteringResultMapper;
 import de.jplag.reporting.reportobject.mapper.ClusteringResultMapper;
+import de.jplag.reporting.reportobject.mapper.MetricMapper;
 import de.jplag.reporting.reportobject.model.*;
 import de.jplag.reporting.reportobject.model.*;
 import de.jplag.reporting.reportobject.model.Match;
 import de.jplag.reporting.reportobject.model.Match;
 
 
@@ -23,6 +24,7 @@ public class ReportObjectFactory {
 
 
     private static final Logger logger = LoggerFactory.getLogger(ReportObjectFactory.class);
     private static final Logger logger = LoggerFactory.getLogger(ReportObjectFactory.class);
     private static final ClusteringResultMapper clusteringResultMapper = new ClusteringResultMapper();
     private static final ClusteringResultMapper clusteringResultMapper = new ClusteringResultMapper();
+    private static final MetricMapper metricMapper = new MetricMapper();
 
 
     /**
     /**
      * Converts a JPlagResult to a JPlagReport.
      * Converts a JPlagResult to a JPlagReport.
@@ -38,8 +40,7 @@ public class ReportObjectFactory {
      * Generates an Overview DTO of a JPlagResult.
      * Generates an Overview DTO of a JPlagResult.
      */
      */
     private static OverviewReport generateOverviewReport(JPlagResult result) {
     private static OverviewReport generateOverviewReport(JPlagResult result) {
-        int numberOfComparisons = result.getOptions().getMaximumNumberOfComparisons();
-        List<JPlagComparison> comparisons = result.getComparisons(numberOfComparisons);
+        List<JPlagComparison> comparisons = getComparisons(result);
         OverviewReport overviewReport = new OverviewReport();
         OverviewReport overviewReport = new OverviewReport();
 
 
         // TODO: Consider to treat entries that were checked differently from old entries with prior work.
         // TODO: Consider to treat entries that were checked differently from old entries with prior work.
@@ -72,8 +73,7 @@ public class ReportObjectFactory {
      */
      */
     private static List<ComparisonReport> generateComparisonReports(JPlagResult result) {
     private static List<ComparisonReport> generateComparisonReports(JPlagResult result) {
         List<ComparisonReport> comparisons = new ArrayList<>();
         List<ComparisonReport> comparisons = new ArrayList<>();
-        int numberOfComparisons = result.getOptions().getMaximumNumberOfComparisons();
-        result.getComparisons(numberOfComparisons).forEach(comparison -> comparisons.add( //
+        getComparisons(result).forEach(comparison -> comparisons.add( //
                 new ComparisonReport(comparison.getFirstSubmission().getName(), //
                 new ComparisonReport(comparison.getFirstSubmission().getName(), //
                         comparison.getSecondSubmission().getName(), //
                         comparison.getSecondSubmission().getName(), //
                         comparison.similarity(), //
                         comparison.similarity(), //
@@ -84,6 +84,11 @@ public class ReportObjectFactory {
         return comparisons;
         return comparisons;
     }
     }
 
 
+    private static List<JPlagComparison> getComparisons(JPlagResult result) {
+        int numberOfComparisons = result.getOptions().getMaximumNumberOfComparisons();
+        return result.getComparisons(numberOfComparisons);
+    }
+
     private static List<Match> convertMatchesToReportMatches(JPlagResult result, JPlagComparison comparison, List<de.jplag.Match> matches) {
     private static List<Match> convertMatchesToReportMatches(JPlagResult result, JPlagComparison comparison, List<de.jplag.Match> matches) {
         return matches.stream().map(match -> convertMatchToReportMatch(comparison, match, result.getOptions().getLanguage().usesIndex()))
         return matches.stream().map(match -> convertMatchToReportMatch(comparison, match, result.getOptions().getLanguage().usesIndex()))
                 .collect(Collectors.toList());
                 .collect(Collectors.toList());
@@ -113,29 +118,13 @@ public class ReportObjectFactory {
         return names;
         return names;
     }
     }
 
 
-    // Currently, only one metric can be obtained.
-
     /**
     /**
-     * Gets the used metric in a JPlag comparison.
+     * Gets the used metrics in a JPlag comparison. As Max Metric is included in every JPlag run, this always include Max
+     * Metric.
      * @return A list contains Metric DTOs.
      * @return A list contains Metric DTOs.
      */
      */
     private static List<Metric> getMetrics(JPlagResult result) {
     private static List<Metric> getMetrics(JPlagResult result) {
-        List<Metric> metrics = new ArrayList<>();
-        metrics.add(new Metric(result.getOptions().getSimilarityMetric().name(), result.getOptions().getSimilarityThreshold(),
-                intArrayToList(result.getSimilarityDistribution()), getTopComparisons(result.getComparisons())));
-        return metrics;
-    }
-
-    /**
-     * Converts JPlagComparison to a DTO for displaying only comparisons. See
-     * {@link #generateComparisonReports(JPlagResult)} for a more detailed representation of a comparison.
-     * @return List containing TopComparison DTOs.
-     */
-    private static List<TopComparison> getTopComparisons(List<JPlagComparison> comparisons) {
-        List<TopComparison> topComparisons = new ArrayList<>();
-        comparisons.forEach(comparison -> topComparisons.add(
-                new TopComparison(comparison.getFirstSubmission().getName(), comparison.getSecondSubmission().getName(), comparison.similarity())));
-        return topComparisons;
+        return List.of(metricMapper.getAverageMetric(result), metricMapper.getMaxMetric(result));
     }
     }
 
 
     /**
     /**
@@ -189,7 +178,4 @@ public class ReportObjectFactory {
         return dateFormat.format(date);
         return dateFormat.format(date);
     }
     }
 
 
-    private static List<Integer> intArrayToList(int[] array) {
-        return Arrays.stream(array).boxed().collect(Collectors.toList());
-    }
 }
 }

+ 54 - 0
jplag/src/main/java/de/jplag/reporting/reportobject/mapper/MetricMapper.java

@@ -0,0 +1,54 @@
+package de.jplag.reporting.reportobject.mapper;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import de.jplag.JPlagComparison;
+import de.jplag.JPlagResult;
+import de.jplag.Messages;
+import de.jplag.options.SimilarityMetric;
+import de.jplag.reporting.reportobject.model.Metric;
+import de.jplag.reporting.reportobject.model.TopComparison;
+
+/**
+ * Extracts and maps metrics from the JPlagResult to the corresponding JSON DTO
+ */
+public class MetricMapper {
+    public Metric getAverageMetric(JPlagResult result) {
+        return new Metric(SimilarityMetric.AVG.name(), intArrayToList(result.getSimilarityDistribution()), getTopComparisons(getComparisons(result)),
+                Messages.getString("SimilarityMetric.Avg.Description"));
+    }
+
+    public Metric getMaxMetric(JPlagResult result) {
+        return new Metric(SimilarityMetric.MAX.name(), intArrayToList(result.getMaxSimilarityDistribution()),
+                getMaxSimilarityTopComparisons(getComparisons(result)), Messages.getString("SimilarityMetric.Max.Description"));
+    }
+
+    private List<JPlagComparison> getComparisons(JPlagResult result) {
+        int maxNumberOfComparisons = result.getOptions().getMaximumNumberOfComparisons();
+        return result.getComparisons(maxNumberOfComparisons);
+    }
+
+    private static List<Integer> intArrayToList(int[] array) {
+        return Arrays.stream(array).boxed().collect(Collectors.toList());
+    }
+
+    private static List<TopComparison> getTopComparisons(List<JPlagComparison> comparisons, Function<JPlagComparison, Float> similarityExtractor) {
+        List<TopComparison> topComparisons = new ArrayList<>();
+        comparisons.forEach(comparison -> topComparisons.add(new TopComparison(comparison.getFirstSubmission().getName(),
+                comparison.getSecondSubmission().getName(), similarityExtractor.apply(comparison))));
+        return topComparisons;
+    }
+
+    private static List<TopComparison> getTopComparisons(List<JPlagComparison> comparisons) {
+        return getTopComparisons(comparisons, JPlagComparison::similarity);
+    }
+
+    private static List<TopComparison> getMaxSimilarityTopComparisons(List<JPlagComparison> comparisons) {
+        return getTopComparisons(comparisons, JPlagComparison::maximalSimilarity);
+    }
+
+}

+ 1 - 12
jplag/src/main/java/de/jplag/reporting/reportobject/model/JPlagReport.java

@@ -2,20 +2,9 @@ package de.jplag.reporting.reportobject.model;
 
 
 import java.util.List;
 import java.util.List;
 
 
-public class JPlagReport {
-    private final OverviewReport overviewReport;
-    private final List<ComparisonReport> comparisons;
-
+public record JPlagReport(OverviewReport overviewReport, List<ComparisonReport> comparisons) {
     public JPlagReport(OverviewReport overviewReport, List<ComparisonReport> comparisons) {
     public JPlagReport(OverviewReport overviewReport, List<ComparisonReport> comparisons) {
         this.overviewReport = overviewReport;
         this.overviewReport = overviewReport;
         this.comparisons = List.copyOf(comparisons);
         this.comparisons = List.copyOf(comparisons);
     }
     }
-
-    public OverviewReport getOverviewReport() {
-        return overviewReport;
-    }
-
-    public List<ComparisonReport> getComparisons() {
-        return comparisons;
-    }
 }
 }

+ 2 - 36
jplag/src/main/java/de/jplag/reporting/reportobject/model/Metric.java

@@ -4,40 +4,6 @@ import java.util.List;
 
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
 
-public class Metric {
-
-    @JsonProperty("name")
-    private final String name;
-
-    @JsonProperty("threshold")
-    private final float threshold;
-
-    @JsonProperty("distribution")
-    private final List<Integer> distribution;
-
-    @JsonProperty("topComparisons")
-    private final List<TopComparison> topComparisons;
-
-    public Metric(String name, float threshold, List<Integer> distribution, List<TopComparison> topComparisons) {
-        this.name = name;
-        this.threshold = threshold;
-        this.distribution = List.copyOf(distribution);
-        this.topComparisons = List.copyOf(topComparisons);
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public float getThreshold() {
-        return threshold;
-    }
-
-    public List<Integer> getDistribution() {
-        return distribution;
-    }
-
-    public List<TopComparison> getTopComparisons() {
-        return topComparisons;
-    }
+public record Metric(@JsonProperty("name") String name, @JsonProperty("distribution") List<Integer> distribution,
+        @JsonProperty("topComparisons") List<TopComparison> topComparisons, @JsonProperty String description) {
 }
 }

+ 2 - 28
jplag/src/main/java/de/jplag/reporting/reportobject/model/TopComparison.java

@@ -2,33 +2,7 @@ package de.jplag.reporting.reportobject.model;
 
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
 
-public class TopComparison {
-
-    @JsonProperty("first_submission")
-    private final String firstSubmission;
-
-    @JsonProperty("second_submission")
-    private final String secondSubmission;
-
-    @JsonProperty("match_percentage")
-    private final float matchPercentage;
-
-    public TopComparison(String firstSubmission, String secondSubmission, float matchPercentage) {
-        this.firstSubmission = firstSubmission;
-        this.secondSubmission = secondSubmission;
-        this.matchPercentage = matchPercentage;
-    }
-
-    public String getFirstSubmission() {
-        return firstSubmission;
-    }
-
-    public String getSecondSubmission() {
-        return secondSubmission;
-    }
-
-    public float getMatchPercentage() {
-        return matchPercentage;
-    }
+public record TopComparison(@JsonProperty("first_submission") String firstSubmission, @JsonProperty("second_submission") String secondSubmission,
+        @JsonProperty("match_percentage") float matchPercentage) {
 
 
 }
 }

+ 3 - 1
jplag/src/main/resources/de/jplag/messages.properties

@@ -26,4 +26,6 @@ CommandLineArgument.ClusterAgglomerativeInterClusterSimilarity=How to measure th
 CommandLineArgument.ClusterPreprocessingNone=Do not use any preprocessing before clustering. Not recommended for spectral clustering.
 CommandLineArgument.ClusterPreprocessingNone=Do not use any preprocessing before clustering. Not recommended for spectral clustering.
 CommandLineArgument.ClusterPreprocessingCdf=Before clustering, the value of the cumulative distribution function of all similarities is estimated. The similarities are multiplied with these estimates. This has the effect of supressing similarities that are low compared to other similarities.
 CommandLineArgument.ClusterPreprocessingCdf=Before clustering, the value of the cumulative distribution function of all similarities is estimated. The similarities are multiplied with these estimates. This has the effect of supressing similarities that are low compared to other similarities.
 CommandLineArgument.ClusterPreprocessingPercentile=Any similarity smaller than the given percentile will be suppressed during clustering.
 CommandLineArgument.ClusterPreprocessingPercentile=Any similarity smaller than the given percentile will be suppressed during clustering.
-CommandLineArgument.ClusterPreprocessingThreshold=Any similarity smaller than the given threshold value will be suppressed during clustering.
+CommandLineArgument.ClusterPreprocessingThreshold=Any similarity smaller than the given threshold value will be suppressed during clustering.
+SimilarityMetric.Avg.Description = Average of both program coverages. This is the default similarity which works in most cases: Matches with a high average similarity indicate that the programs work in a very similar way.
+SimilarityMetric.Max.Description = Maximum of both program coverages. This ranking is especially useful if the programs are very different in size. This can happen when dead code was inserted to disguise the origin of the plagiarized program.

+ 3 - 0
jplag/src/main/resources/de/jplag/messages_de.properties

@@ -57,3 +57,6 @@ AllMatches.Distribution=Verteilung
 AllMatches.Length=L&auml;nge
 AllMatches.Length=L&auml;nge
 AllMatches.Number_of_matches=Anzahl der &Uuml;bereinstimmungen
 AllMatches.Number_of_matches=Anzahl der &Uuml;bereinstimmungen
 AllMatches.Basecode=Referenz&uuml;bereinstimmung
 AllMatches.Basecode=Referenz&uuml;bereinstimmung
+
+SimilarityMetric.Avg.Description = Average of both program coverages. This is the default similarity which works in most cases: Matches with a high average similarity indicate that the programs work in a very similar way.
+SimilarityMetric.Max.Description = Maximum of both program coverages. This ranking is especially useful if the programs are very different in size. This can happen when dead code was inserted to disguise the origin of the plagiarized program.

+ 4 - 0
jplag/src/main/resources/de/jplag/messages_en.properties

@@ -35,6 +35,7 @@ Report.HELP=HELP
 Report.Distribution=Distribution
 Report.Distribution=Distribution
 Report.Token_Distribution=Token Distribution
 Report.Token_Distribution=Token Distribution
 
 
+
 Clusters.MIN_single_link=MIN, single link
 Clusters.MIN_single_link=MIN, single link
 Clusters.AVR_group_average=AVR, group average
 Clusters.AVR_group_average=AVR, group average
 Clusters.MAX_complete_link=MAX, complete link
 Clusters.MAX_complete_link=MAX, complete link
@@ -57,3 +58,6 @@ AllMatches.Distribution=Distribution
 AllMatches.Length=Length
 AllMatches.Length=Length
 AllMatches.Number_of_matches=No of matches
 AllMatches.Number_of_matches=No of matches
 AllMatches.Basecode=Basecode
 AllMatches.Basecode=Basecode
+
+SimilarityMetric.Avg.Description = Average of both program coverages. This is the default similarity which works in most cases: Matches with a high average similarity indicate that the programs work in a very similar way.
+SimilarityMetric.Max.Description = Maximum of both program coverages. This ranking is especially useful if the programs are very different in size. This can happen when dead code was inserted to disguise the origin of the plagiarized program.

+ 3 - 0
jplag/src/main/resources/de/jplag/messages_es.properties

@@ -57,3 +57,6 @@ AllMatches.Distribution=Distribuci
 AllMatches.Length=Longitud
 AllMatches.Length=Longitud
 AllMatches.Number_of_matches=No de coincidencias
 AllMatches.Number_of_matches=No de coincidencias
 AllMatches.Basecode=Código Base
 AllMatches.Basecode=Código Base
+
+SimilarityMetric.Avg.Description = Average of both program coverages. This is the default similarity which works in most cases: Matches with a high average similarity indicate that the programs work in a very similar way.
+SimilarityMetric.Max.Description = Maximum of both program coverages. This ranking is especially useful if the programs are very different in size. This can happen when dead code was inserted to disguise the origin of the plagiarized program.

+ 3 - 0
jplag/src/main/resources/de/jplag/messages_fr.properties

@@ -57,3 +57,6 @@ AllMatches.Distribution=Distribution
 AllMatches.Length=Length
 AllMatches.Length=Length
 AllMatches.Number_of_matches=No of matches
 AllMatches.Number_of_matches=No of matches
 AllMatches.Basecode=Code source de reference
 AllMatches.Basecode=Code source de reference
+
+SimilarityMetric.Avg.Description = Average of both program coverages. This is the default similarity which works in most cases: Matches with a high average similarity indicate that the programs work in a very similar way.
+SimilarityMetric.Max.Description = Maximum of both program coverages. This ranking is especially useful if the programs are very different in size. This can happen when dead code was inserted to disguise the origin of the plagiarized program.

+ 3 - 0
jplag/src/main/resources/de/jplag/messages_pt.properties

@@ -57,3 +57,6 @@ AllMatches.Distribution=Distribution
 AllMatches.Length=Length
 AllMatches.Length=Length
 AllMatches.Number_of_matches=No of matches
 AllMatches.Number_of_matches=No of matches
 AllMatches.Basecode=Basecode
 AllMatches.Basecode=Basecode
+
+SimilarityMetric.Avg.Description = Average of both program coverages. This is the default similarity which works in most cases: Matches with a high average similarity indicate that the programs work in a very similar way.
+SimilarityMetric.Max.Description = Maximum of both program coverages. This ranking is especially useful if the programs are very different in size. This can happen when dead code was inserted to disguise the origin of the plagiarized program.

+ 5 - 5
jplag/src/test/java/de/jplag/BaseCodeTest.java

@@ -39,10 +39,10 @@ public class BaseCodeTest extends TestBase {
 
 
     private void verifyResults(JPlagResult result) {
     private void verifyResults(JPlagResult result) {
         assertEquals(2, result.getNumberOfSubmissions());
         assertEquals(2, result.getNumberOfSubmissions());
-        assertEquals(1, result.getComparisons().size());
-        assertEquals(1, result.getComparisons().get(0).getMatches().size());
-        assertEquals(1, result.getSimilarityDistribution()[8]);
-        assertEquals(85f, result.getComparisons().get(0).similarity(), DELTA);
+        assertEquals(1, result.getAllComparisons().size());
+        assertEquals(1, result.getAllComparisons().get(0).getMatches().size());
+        assertEquals(1, result.getSimilarityDistribution()[1]);
+        assertEquals(85f, result.getAllComparisons().get(0).similarity(), DELTA);
     }
     }
 
 
     @Test
     @Test
@@ -52,7 +52,7 @@ public class BaseCodeTest extends TestBase {
     }
     }
 
 
     @Test
     @Test
-    void testInvalidRoot() throws ExitException {
+    void testInvalidRoot() {
         assertThrows(RootDirectoryException.class, () -> runJPlag("basecode", it -> it.setSubmissionDirectories(List.of("WrongRoot"))));
         assertThrows(RootDirectoryException.class, () -> runJPlag("basecode", it -> it.setSubmissionDirectories(List.of("WrongRoot"))));
     }
     }
 
 

+ 2 - 2
jplag/src/test/java/de/jplag/NewJavaFeaturesTest.java

@@ -23,10 +23,10 @@ public class NewJavaFeaturesTest extends TestBase {
         for (Submission submission : result.getSubmissions().getSubmissions()) {
         for (Submission submission : result.getSubmissions().getSubmissions()) {
             assertEquals(6, submission.getFiles().size(), String.format(CHANGE_MESSAGE, "Files"));
             assertEquals(6, submission.getFiles().size(), String.format(CHANGE_MESSAGE, "Files"));
         }
         }
-        assertEquals(1, result.getComparisons().size(), String.format(CHANGE_MESSAGE, "Comparisons"));
+        assertEquals(1, result.getAllComparisons().size(), String.format(CHANGE_MESSAGE, "Comparisons"));
 
 
         // Check similarity and number of matches:
         // Check similarity and number of matches:
-        var comparison = result.getComparisons().get(0);
+        var comparison = result.getAllComparisons().get(0);
         assertEquals(EXPECTED_SIMILARITY, comparison.similarity(), DELTA);
         assertEquals(EXPECTED_SIMILARITY, comparison.similarity(), DELTA);
         assertEquals(EXPECTED_MATCHES, comparison.getMatches().size());
         assertEquals(EXPECTED_MATCHES, comparison.getMatches().size());
     }
     }

+ 13 - 15
jplag/src/test/java/de/jplag/NormalComparisonTest.java

@@ -21,10 +21,10 @@ class NormalComparisonTest extends TestBase {
         JPlagResult result = runJPlagWithDefaultOptions("SimpleDuplicate");
         JPlagResult result = runJPlagWithDefaultOptions("SimpleDuplicate");
 
 
         assertEquals(2, result.getNumberOfSubmissions());
         assertEquals(2, result.getNumberOfSubmissions());
-        assertEquals(1, result.getComparisons().size());
-        assertEquals(1, result.getComparisons().get(0).getMatches().size());
-        assertEquals(1, result.getSimilarityDistribution()[6]);
-        assertEquals(62.07f, result.getComparisons().get(0).similarity(), 0.1f);
+        assertEquals(1, result.getAllComparisons().size());
+        assertEquals(1, result.getAllComparisons().get(0).getMatches().size());
+        assertEquals(1, result.getSimilarityDistribution()[3]);
+        assertEquals(62.07f, result.getAllComparisons().get(0).similarity(), 0.1f);
     }
     }
 
 
     /**
     /**
@@ -35,11 +35,9 @@ class NormalComparisonTest extends TestBase {
         JPlagResult result = runJPlagWithDefaultOptions("NoDuplicate");
         JPlagResult result = runJPlagWithDefaultOptions("NoDuplicate");
 
 
         assertEquals(3, result.getNumberOfSubmissions());
         assertEquals(3, result.getNumberOfSubmissions());
-        assertEquals(3, result.getComparisons().size());
+        assertEquals(3, result.getAllComparisons().size());
 
 
-        result.getComparisons().forEach(comparison -> {
-            assertEquals(0f, comparison.similarity(), 0.1f);
-        });
+        result.getAllComparisons().forEach(comparison -> assertEquals(0f, comparison.similarity(), 0.1f));
     }
     }
 
 
     /**
     /**
@@ -52,10 +50,10 @@ class NormalComparisonTest extends TestBase {
         JPlagResult result = runJPlagWithDefaultOptions("PartialPlagiarism");
         JPlagResult result = runJPlagWithDefaultOptions("PartialPlagiarism");
 
 
         assertEquals(5, result.getNumberOfSubmissions());
         assertEquals(5, result.getNumberOfSubmissions());
-        assertEquals(10, result.getComparisons().size());
+        assertEquals(10, result.getAllComparisons().size());
 
 
         // All comparisons with E shall have no matches
         // All comparisons with E shall have no matches
-        result.getComparisons().stream()
+        result.getAllComparisons().stream()
                 .filter(comparison -> comparison.getSecondSubmission().getName().equals("E") || comparison.getFirstSubmission().getName().equals("E"))
                 .filter(comparison -> comparison.getSecondSubmission().getName().equals("E") || comparison.getFirstSubmission().getName().equals("E"))
                 .forEach(comparison -> assertEquals(0f, comparison.similarity(), DELTA));
                 .forEach(comparison -> assertEquals(0f, comparison.similarity(), DELTA));
 
 
@@ -81,7 +79,7 @@ class NormalComparisonTest extends TestBase {
     }
     }
 
 
     private Optional<JPlagComparison> getSelectedComparison(JPlagResult result, String nameA, String nameB) {
     private Optional<JPlagComparison> getSelectedComparison(JPlagResult result, String nameA, String nameB) {
-        return result.getComparisons().stream().filter(
+        return result.getAllComparisons().stream().filter(
                 comparison -> comparison.getFirstSubmission().getName().equals(nameA) && comparison.getSecondSubmission().getName().equals(nameB)
                 comparison -> comparison.getFirstSubmission().getName().equals(nameA) && comparison.getSecondSubmission().getName().equals(nameB)
                         || comparison.getFirstSubmission().getName().equals(nameB) && comparison.getSecondSubmission().getName().equals(nameA))
                         || comparison.getFirstSubmission().getName().equals(nameB) && comparison.getSecondSubmission().getName().equals(nameA))
                 .findFirst();
                 .findFirst();
@@ -112,7 +110,7 @@ class NormalComparisonTest extends TestBase {
     }
     }
 
 
     @Test
     @Test
-    public void testMultiRootDirBasecodeName() throws ExitException {
+    public void testMultiRootDirBasecodeName() {
         List<String> paths = List.of(getBasePath("basecode"), getBasePath("SimpleDuplicate"));
         List<String> paths = List.of(getBasePath("basecode"), getBasePath("SimpleDuplicate"));
         String basecodePath = "base"; // Should *not* find basecode/base
         String basecodePath = "base"; // Should *not* find basecode/base
         assertThrows(BasecodeException.class, () -> runJPlag(paths, it -> it.setBaseCodeSubmissionName(basecodePath)));
         assertThrows(BasecodeException.class, () -> runJPlag(paths, it -> it.setBaseCodeSubmissionName(basecodePath)));
@@ -125,7 +123,7 @@ class NormalComparisonTest extends TestBase {
         JPlagResult result = runJPlag(newDirectories, oldDirectories, it -> {
         JPlagResult result = runJPlag(newDirectories, oldDirectories, it -> {
         });
         });
         int numberOfExpectedComparison = 1 + 3 * 2;
         int numberOfExpectedComparison = 1 + 3 * 2;
-        assertEquals(numberOfExpectedComparison, result.getComparisons().size());
+        assertEquals(numberOfExpectedComparison, result.getAllComparisons().size());
     }
     }
 
 
     @Test
     @Test
@@ -135,7 +133,7 @@ class NormalComparisonTest extends TestBase {
         JPlagResult result = runJPlag(newDirectories, oldDirectories, it -> {
         JPlagResult result = runJPlag(newDirectories, oldDirectories, it -> {
         });
         });
         int numberOfExpectedComparison = 1;
         int numberOfExpectedComparison = 1;
-        assertEquals(numberOfExpectedComparison, result.getComparisons().size());
+        assertEquals(numberOfExpectedComparison, result.getAllComparisons().size());
     }
     }
 
 
     @Test
     @Test
@@ -145,6 +143,6 @@ class NormalComparisonTest extends TestBase {
         List<String> oldDirectories = List.of(getBasePath("basecode")); // 3 - 1 submissions
         List<String> oldDirectories = List.of(getBasePath("basecode")); // 3 - 1 submissions
         JPlagResult result = runJPlag(newDirectories, oldDirectories, it -> it.setBaseCodeSubmissionName(basecodePath));
         JPlagResult result = runJPlag(newDirectories, oldDirectories, it -> it.setBaseCodeSubmissionName(basecodePath));
         int numberOfExpectedComparison = 1 + 2 * 2;
         int numberOfExpectedComparison = 1 + 2 * 2;
-        assertEquals(numberOfExpectedComparison, result.getComparisons().size());
+        assertEquals(numberOfExpectedComparison, result.getAllComparisons().size());
     }
     }
 }
 }

+ 9 - 11
jplag/src/test/java/de/jplag/ParallelComparisonTest.java

@@ -25,10 +25,10 @@ public class ParallelComparisonTest extends TestBase {
         JPlagResult result = runJPlag("SimpleDuplicate", it -> it.setComparisonMode(PARALLEL));
         JPlagResult result = runJPlag("SimpleDuplicate", it -> it.setComparisonMode(PARALLEL));
 
 
         assertEquals(2, result.getNumberOfSubmissions());
         assertEquals(2, result.getNumberOfSubmissions());
-        assertEquals(1, result.getComparisons().size());
-        assertEquals(1, result.getComparisons().get(0).getMatches().size());
-        assertEquals(1, result.getSimilarityDistribution()[6]);
-        assertEquals(62.07f, result.getComparisons().get(0).similarity(), DELTA);
+        assertEquals(1, result.getAllComparisons().size());
+        assertEquals(1, result.getAllComparisons().get(0).getMatches().size());
+        assertEquals(1, result.getSimilarityDistribution()[3]);
+        assertEquals(62.07f, result.getAllComparisons().get(0).similarity(), DELTA);
     }
     }
 
 
     /**
     /**
@@ -39,11 +39,9 @@ public class ParallelComparisonTest extends TestBase {
         JPlagResult result = runJPlag("NoDuplicate", it -> it.setComparisonMode(PARALLEL));
         JPlagResult result = runJPlag("NoDuplicate", it -> it.setComparisonMode(PARALLEL));
 
 
         assertEquals(3, result.getNumberOfSubmissions());
         assertEquals(3, result.getNumberOfSubmissions());
-        assertEquals(3, result.getComparisons().size());
+        assertEquals(3, result.getAllComparisons().size());
 
 
-        result.getComparisons().forEach(comparison -> {
-            assertEquals(0f, comparison.similarity(), DELTA);
-        });
+        result.getAllComparisons().forEach(comparison -> assertEquals(0f, comparison.similarity(), DELTA));
     }
     }
 
 
     /**
     /**
@@ -56,10 +54,10 @@ public class ParallelComparisonTest extends TestBase {
         JPlagResult result = runJPlag("PartialPlagiarism", it -> it.setComparisonMode(PARALLEL));
         JPlagResult result = runJPlag("PartialPlagiarism", it -> it.setComparisonMode(PARALLEL));
 
 
         assertEquals(5, result.getNumberOfSubmissions());
         assertEquals(5, result.getNumberOfSubmissions());
-        assertEquals(10, result.getComparisons().size());
+        assertEquals(10, result.getAllComparisons().size());
 
 
         // All comparisons with E shall have no matches
         // All comparisons with E shall have no matches
-        result.getComparisons().stream()
+        result.getAllComparisons().stream()
                 .filter(comparison -> comparison.getSecondSubmission().getName().equals("E") || comparison.getFirstSubmission().getName().equals("E"))
                 .filter(comparison -> comparison.getSecondSubmission().getName().equals("E") || comparison.getFirstSubmission().getName().equals("E"))
                 .forEach(comparison -> assertEquals(0f, comparison.similarity(), DELTA));
                 .forEach(comparison -> assertEquals(0f, comparison.similarity(), DELTA));
 
 
@@ -85,7 +83,7 @@ public class ParallelComparisonTest extends TestBase {
     }
     }
 
 
     private Optional<JPlagComparison> getSelectedComparison(JPlagResult result, String nameA, String nameB) {
     private Optional<JPlagComparison> getSelectedComparison(JPlagResult result, String nameA, String nameB) {
-        return result.getComparisons().stream().filter(
+        return result.getAllComparisons().stream().filter(
                 comparison -> comparison.getFirstSubmission().getName().equals(nameA) && comparison.getSecondSubmission().getName().equals(nameB)
                 comparison -> comparison.getFirstSubmission().getName().equals(nameA) && comparison.getSecondSubmission().getName().equals(nameB)
                         || comparison.getFirstSubmission().getName().equals(nameB) && comparison.getSecondSubmission().getName().equals(nameA))
                         || comparison.getFirstSubmission().getName().equals(nameB) && comparison.getSecondSubmission().getName().equals(nameA))
                 .findFirst();
                 .findFirst();

+ 2 - 3
jplag/src/test/java/de/jplag/clustering/ClusteringAdapterTest.java

@@ -23,7 +23,7 @@ public class ClusteringAdapterTest {
 
 
     @Test
     @Test
     public void testClustering() {
     public void testClustering() {
-        List<Submission> submissions = IntStream.range(0, 4).mapToObj(x -> mock(Submission.class)).collect(Collectors.toList());
+        List<Submission> submissions = IntStream.range(0, 4).mapToObj(x -> mock(Submission.class)).toList();
         List<JPlagComparison> comparisons = new ArrayList<>(6);
         List<JPlagComparison> comparisons = new ArrayList<>(6);
         for (int i = 0; i < submissions.size(); i++) {
         for (int i = 0; i < submissions.size(); i++) {
             for (int j = i + 1; j < submissions.size(); j++) {
             for (int j = i + 1; j < submissions.size(); j++) {
@@ -38,8 +38,7 @@ public class ClusteringAdapterTest {
         GenericClusteringAlgorithm algorithm = mock(GenericClusteringAlgorithm.class);
         GenericClusteringAlgorithm algorithm = mock(GenericClusteringAlgorithm.class);
         when(algorithm.cluster(any(RealMatrix.class))).then((InvocationOnMock invocation) -> {
         when(algorithm.cluster(any(RealMatrix.class))).then((InvocationOnMock invocation) -> {
             RealMatrix arg = invocation.getArgument(0);
             RealMatrix arg = invocation.getArgument(0);
-            Collection<Collection<Integer>> result = List.of(IntStream.range(0, arg.getRowDimension()).boxed().collect(Collectors.toList()));
-            return result;
+            return List.of(IntStream.range(0, arg.getRowDimension()).boxed().collect(Collectors.toList()));
         });
         });
 
 
         ClusteringAdapter clustering = new ClusteringAdapter(comparisons, x -> 0.f);
         ClusteringAdapter clustering = new ClusteringAdapter(comparisons, x -> 0.f);

+ 115 - 0
jplag/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java

@@ -0,0 +1,115 @@
+package de.jplag.reporting.reportobject.mapper;
+
+import static org.mockito.Mockito.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import de.jplag.JPlagComparison;
+import de.jplag.JPlagResult;
+import de.jplag.Submission;
+import de.jplag.options.JPlagOptions;
+import de.jplag.reporting.reportobject.model.TopComparison;
+
+public class MetricMapperTest {
+    private final MetricMapper metricMapper = new MetricMapper();
+
+    @Test
+    public void test_getAverageMetric() {
+        // given
+        JPlagResult jPlagResult = createJPlagResult(MockMetric.AVG, distribution(2, 3, 5, 7, 11, 13, 17, 19, 23, 29),
+                comparison(submission("1"), submission("2"), .7f), comparison(submission("3"), submission("4"), .3f));
+        // when
+        var result = metricMapper.getAverageMetric(jPlagResult);
+
+        // then
+        Assertions.assertEquals("AVG", result.name());
+        Assertions.assertIterableEquals(List.of(2, 3, 5, 7, 11, 13, 17, 19, 23, 29), result.distribution());
+        Assertions.assertEquals(List.of(new TopComparison("1", "2", .7f), new TopComparison("3", "4", .3f)), result.topComparisons());
+        Assertions.assertEquals(
+                "Average of both program coverages. This is the default similarity which"
+                        + " works in most cases: Matches with a high average similarity indicate that the programs work " + "in a very similar way.",
+                result.description());
+    }
+
+    @Test
+    public void test_getMaxMetric() {
+        // given
+        JPlagResult jPlagResult = createJPlagResult(MockMetric.MAX, distribution(2, 3, 5, 7, 11, 13, 17, 19, 23, 29),
+                comparison(submission("00"), submission("01"), .7f), comparison(submission("10"), submission("11"), .3f));
+        // when
+        var result = metricMapper.getMaxMetric(jPlagResult);
+
+        // then
+        Assertions.assertEquals("MAX", result.name());
+        Assertions.assertIterableEquals(List.of(2, 3, 5, 7, 11, 13, 17, 19, 23, 29), result.distribution());
+        Assertions.assertEquals(List.of(new TopComparison("00", "01", .7f), new TopComparison("10", "11", .3f)), result.topComparisons());
+        Assertions.assertEquals(
+                "Maximum of both program coverages. This ranking is especially useful if the programs are very "
+                        + "different in size. This can happen when dead code was inserted to disguise the origin of the plagiarized program.",
+                result.description());
+    }
+
+    private int[] distribution(int... numbers) {
+        return numbers;
+    }
+
+    private CreateSubmission submission(String name) {
+        return new CreateSubmission(name);
+    }
+
+    private Comparison comparison(CreateSubmission submission1, CreateSubmission submission2, float similarity) {
+        return new Comparison(submission1, submission2, similarity);
+    }
+
+    private JPlagResult createJPlagResult(MockMetric metricToMock, int[] distribution, Comparison... createComparisonsDto) {
+        JPlagResult jPlagResult = mock(JPlagResult.class);
+
+        if (metricToMock.equals(MockMetric.AVG)) {
+            doReturn(distribution).when(jPlagResult).getSimilarityDistribution();
+        } else if (metricToMock.equals(MockMetric.MAX)) {
+            doReturn(distribution).when(jPlagResult).getMaxSimilarityDistribution();
+
+        }
+
+        JPlagOptions options = mock(JPlagOptions.class);
+        doReturn(createComparisonsDto.length).when(options).getMaximumNumberOfComparisons();
+        doReturn(options).when(jPlagResult).getOptions();
+
+        List<JPlagComparison> comparisonList = new ArrayList<>();
+        for (Comparison comparisonDto : createComparisonsDto) {
+            Submission submission1 = mock(Submission.class);
+            doReturn(comparisonDto.submission1.name).when(submission1).getName();
+            Submission submission2 = mock(Submission.class);
+            doReturn(comparisonDto.submission2.name).when(submission2).getName();
+
+            JPlagComparison mockedComparison = mock(JPlagComparison.class);
+            doReturn(submission1).when(mockedComparison).getFirstSubmission();
+            doReturn(submission2).when(mockedComparison).getSecondSubmission();
+            if (metricToMock.equals(MockMetric.AVG)) {
+                doReturn(comparisonDto.similarity).when(mockedComparison).similarity();
+            } else if (metricToMock.equals(MockMetric.MAX)) {
+                doReturn(comparisonDto.similarity).when(mockedComparison).maximalSimilarity();
+            }
+            comparisonList.add(mockedComparison);
+        }
+
+        doReturn(comparisonList).when(jPlagResult).getComparisons(anyInt());
+        return jPlagResult;
+    }
+
+    private enum MockMetric {
+        MAX,
+        AVG
+    }
+
+    private record Comparison(CreateSubmission submission1, CreateSubmission submission2, float similarity) {
+    }
+
+    private record CreateSubmission(String name) {
+    }
+
+}

+ 2 - 2
jplag/src/test/java/de/jplag/special/VolumeTest.java

@@ -51,10 +51,10 @@ public class VolumeTest extends TestBase {
 
 
         var csv = readCSVResults(String.format("%s/%s", this.getBasePath(), "matches_avg.csv"));
         var csv = readCSVResults(String.format("%s/%s", this.getBasePath(), "matches_avg.csv"));
 
 
-        assertEquals(csv.size(), results.getComparisons().size());
+        assertEquals(csv.size(), results.getAllComparisons().size());
         System.out.println("Volume test size: " + csv.size());
         System.out.println("Volume test size: " + csv.size());
 
 
-        results.getComparisons().forEach(result -> {
+        results.getAllComparisons().forEach(result -> {
             var key = result.getFirstSubmission().getName() + result.getSecondSubmission().getName();
             var key = result.getFirstSubmission().getName() + result.getSecondSubmission().getName();
 
 
             assertTrue(csv.containsKey(key));
             assertTrue(csv.containsKey(key));

+ 1 - 0
jplag/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker

@@ -0,0 +1 @@
+mock-maker-inline

+ 4 - 6
report-viewer/src/components/DistributionDiagram.vue

@@ -3,11 +3,11 @@
 -->
 -->
 <template>
 <template>
   <div class="wrapper">
   <div class="wrapper">
-    <BarChart :chartData="chartData" :options="options" class="chart"/>
+    <BarChart :chartData="chartData" :options="options" class="chart" />
   </div>
   </div>
 </template>
 </template>
 
 
-<script>
+<script lang="ts">
 import {defineComponent, ref, watch} from "vue";
 import {defineComponent, ref, watch} from "vue";
 import {BarChart} from "vue-chart-3"
 import {BarChart} from "vue-chart-3"
 import {Chart, registerables} from 'chart.js';
 import {Chart, registerables} from 'chart.js';
@@ -21,7 +21,7 @@ export default defineComponent({
   components: {BarChart},
   components: {BarChart},
   props: {
   props: {
     distribution: {
     distribution: {
-      type: Array,
+      type: Array<number>,
       required: true
       required: true
     }
     }
   },
   },
@@ -111,11 +111,9 @@ export default defineComponent({
   box-shadow: #777777 2px 3px 3px;
   box-shadow: #777777 2px 3px 3px;
   display: flex;
   display: flex;
   height: 100%;
   height: 100%;
-
 }
 }
 
 
 .chart {
 .chart {
   width: 100%;
   width: 100%;
-
 }
 }
-</style>
+</style>

+ 39 - 26
report-viewer/src/components/MetricButton.vue

@@ -3,41 +3,37 @@
   metric the distribution and comparisons are displayed.
   metric the distribution and comparisons are displayed.
 -->
 -->
 <template>
 <template>
-  <div class="wrapper" v-bind:class="{ selected : isSelected }">
-    <div class="metric">
-      <p class="metric-name">{{ metricName }}</p>
-      <img alt="?" src="@/assets/help_outline_black_18dp.svg"/>
-    </div>
-    <div class="threshold">
-      <p>Threshold: </p>
-      <p>{{ metricThreshold }}%</p>
+  <div class="wrapper" v-bind:class="{ selected: isSelected }">
+    <div>
+      <p class="metric-name">{{ metric.metricName }}</p>
+      <div class="tooltip">
+        <img alt="?" src="@/assets/help_outline_black_18dp.svg" />
+        <span class="tooltiptext">{{metric.description}}</span>
+      </div>
     </div>
     </div>
   </div>
   </div>
 </template>
 </template>
 
 
-<script>
-import {defineComponent} from "vue";
+<script lang="ts">
+import { defineComponent, PropType } from "vue";
+import { Metric } from "@/model/Metric";
 
 
 export default defineComponent({
 export default defineComponent({
   name: "MetricButton",
   name: "MetricButton",
   props: {
   props: {
-    metricName: {
-      type: String,
-      required: true
-    },
-    metricThreshold: {
-      type: Number,
-      required: true
+    metric: {
+      type: Object as PropType<Metric>,
+      required: true,
     },
     },
     isSelected: {
     isSelected: {
       type: Boolean,
       type: Boolean,
-      default: false
+      default: false,
     },
     },
   },
   },
   setup() {
   setup() {
-    return {}
-  }
-})
+    return {};
+  },
+});
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
@@ -51,7 +47,6 @@ export default defineComponent({
   margin-right: 5%;
   margin-right: 5%;
 }
 }
 
 
-
 .wrapper > div > * {
 .wrapper > div > * {
   margin: 0;
   margin: 0;
   padding: 0;
   padding: 0;
@@ -67,9 +62,7 @@ export default defineComponent({
   color: white;
   color: white;
 }
 }
 
 
-.metric {
-  margin-bottom: 2%;
-}
+
 
 
 .metric-name {
 .metric-name {
   font-size: xx-large;
   font-size: xx-large;
@@ -90,4 +83,24 @@ export default defineComponent({
 .selected p {
 .selected p {
   color: white;
   color: white;
 }
 }
-</style>
+.tooltip {
+  position: relative;
+  display: inline-block;
+}
+
+.tooltip .tooltiptext {
+  visibility: hidden;
+  width: 240px;
+  background-color: black;
+  color: #fff;
+  text-align: center;
+  padding: 10px ;
+  border-radius: 6px;
+  position: absolute;
+  z-index: 1;
+}
+
+.tooltip:hover .tooltiptext {
+  visibility: visible;
+}
+</style>

+ 1 - 0
report-viewer/src/model/Metric.ts

@@ -5,6 +5,7 @@ import {ComparisonListElement} from "./ComparisonListElement";
  */
  */
 export type Metric = {
 export type Metric = {
     metricName: string,
     metricName: string,
+    description: string,
     metricThreshold: number,
     metricThreshold: number,
     distribution: Array<number>,
     distribution: Array<number>,
     comparisons: Array<ComparisonListElement>
     comparisons: Array<ComparisonListElement>

+ 60 - 61
report-viewer/src/model/factories/OverviewFactory.ts

@@ -1,67 +1,66 @@
-import {Overview} from "../Overview";
-import {Metric} from "../Metric";
-import {ComparisonListElement} from "../ComparisonListElement";
-import {Cluster} from "@/model/Cluster";
+import { Overview } from "../Overview";
+import { Metric } from "../Metric";
+import { ComparisonListElement } from "../ComparisonListElement";
+import { Cluster } from "@/model/Cluster";
 
 
 export class OverviewFactory {
 export class OverviewFactory {
+  static getOverview(json: Record<string, unknown>): Overview {
+    const submissionFolder = json.submission_folder_path as Array<string>;
+    const baseCodeFolder = "";
+    const language = json.language as string;
+    const fileExtensions = json.file_extensions as Array<string>;
+    const matchSensitivity = json.match_sensitivity as number;
+    const submissions = json.submission_ids as Array<string>;
+    const dateOfExecution = json.date_of_execution as string;
+    const duration = json.execution_time as number as number;
+    const metrics = [] as Array<Metric>;
+    const clusters = [] as Array<Cluster>;
+    (json.metrics as Array<unknown>).forEach((jsonMetric) => {
+      const metric = jsonMetric as Record<string, unknown>;
+      const comparisons = [] as Array<ComparisonListElement>;
 
 
-    static getOverview(json: Record<string, unknown>): Overview {
-        const submissionFolder = json.submission_folder_path as Array<string>
-        const baseCodeFolder = ""
-        const language = json.language as string
-        const fileExtensions = json.file_extensions as Array<string>
-        const matchSensitivity = json.match_sensitivity as number
-        const submissions = json.submission_ids as Array<string>
-        const dateOfExecution = json.date_of_execution as string
-        const duration = json.execution_time as number as number
-        const metrics = [] as Array<Metric>
-        const clusters = [] as Array<Cluster>
-        (json.metrics as Array<unknown>).forEach((jsonMetric) => {
-            const metric = jsonMetric as Record<string, unknown>
-            const comparisons = [] as Array<ComparisonListElement>
-
-            (metric.topComparisons as Array<Record<string, unknown>>).forEach(jsonComparison => {
-                const comparison: ComparisonListElement = {
-                    firstSubmissionId: jsonComparison.first_submission as string,
-                    secondSubmissionId: jsonComparison.second_submission as string,
-                    matchPercentage: jsonComparison.match_percentage as number
-                }
-                comparisons.push(comparison)
-            })
-
-            const newMetric: Metric = {
-                metricName: metric.name as string,
-                metricThreshold: metric.threshold as number,
-                distribution: metric.distribution as Array<number>,
-                comparisons: comparisons
-            }
-            metrics.push(newMetric)
-        });
-
-        if (json.clusters) {
-            (json.clusters as Array<unknown>).forEach(jsonCluster => {
-                const cluster = jsonCluster as Record<string, unknown>
-                const newCluster: Cluster = {
-                    averageSimilarity: cluster.average_similarity as number,
-                    strength: cluster.strength as number,
-                    members: cluster.members as Array<string>
-                }
-                clusters.push(newCluster)
-            })
+      (metric.topComparisons as Array<Record<string, unknown>>).forEach(
+        (jsonComparison) => {
+          const comparison: ComparisonListElement = {
+            firstSubmissionId: jsonComparison.first_submission as string,
+            secondSubmissionId: jsonComparison.second_submission as string,
+            matchPercentage: jsonComparison.match_percentage as number,
+          };
+          comparisons.push(comparison);
         }
         }
+      );
+      metrics.push({
+        metricName: metric.name as string,
+        metricThreshold: metric.threshold as number,
+        distribution: metric.distribution as Array<number>,
+        comparisons: comparisons,
+        description: metric.description as string
+      });
+    });
 
 
-
-        return new Overview(
-            submissionFolder,
-            baseCodeFolder,
-            language,
-            fileExtensions,
-            matchSensitivity,
-            submissions,
-            dateOfExecution,
-            duration,
-            metrics,
-            clusters
-        )
+    if (json.clusters) {
+      (json.clusters as Array<unknown>).forEach((jsonCluster) => {
+        const cluster = jsonCluster as Record<string, unknown>;
+        const newCluster: Cluster = {
+          averageSimilarity: cluster.average_similarity as number,
+          strength: cluster.strength as number,
+          members: cluster.members as Array<string>,
+        };
+        clusters.push(newCluster);
+      });
     }
     }
-}
+
+    return new Overview(
+      submissionFolder,
+      baseCodeFolder,
+      language,
+      fileExtensions,
+      matchSensitivity,
+      submissions,
+      dateOfExecution,
+      duration,
+      metrics,
+      clusters
+    );
+  }
+}

+ 1 - 2
report-viewer/src/views/OverviewView.vue

@@ -64,8 +64,7 @@
             :id="metric.metricName"
             :id="metric.metricName"
             :key="metric.metricName"
             :key="metric.metricName"
             :is-selected="selectedMetric[index]"
             :is-selected="selectedMetric[index]"
-            :metric-name="metric.metricName"
-            :metric-threshold="metric.metricThreshold"
+            :metric="metric"
             @click="selectMetric(index)"
             @click="selectMetric(index)"
           />
           />
         </div>
         </div>