Browse Source

Cleanup some minor code issues

Dominik Fuchß 4 years ago
parent
commit
f6c3bb91a7
34 changed files with 171 additions and 289 deletions
  1. 2 3
      jplag.cli/src/main/java/de/jplag/CLI.java
  2. 7 6
      jplag/src/main/java/de/jplag/GreedyStringTiling.java
  3. 1 1
      jplag/src/main/java/de/jplag/JPlag.java
  4. 2 1
      jplag/src/main/java/de/jplag/JPlagComparison.java
  5. 1 1
      jplag/src/main/java/de/jplag/JPlagResult.java
  6. 9 13
      jplag/src/main/java/de/jplag/Match.java
  7. 2 2
      jplag/src/main/java/de/jplag/Submission.java
  8. 10 13
      jplag/src/main/java/de/jplag/SubmissionSet.java
  9. 4 2
      jplag/src/main/java/de/jplag/SubmissionSetBuilder.java
  10. 1 3
      jplag/src/main/java/de/jplag/clustering/ClusteringFactory.java
  11. 1 1
      jplag/src/main/java/de/jplag/clustering/ClusteringResult.java
  12. 3 2
      jplag/src/main/java/de/jplag/clustering/IntegerMapping.java
  13. 1 1
      jplag/src/main/java/de/jplag/clustering/PreprocessorHelper.java
  14. 23 51
      jplag/src/main/java/de/jplag/clustering/algorithm/AgglomerativeClustering.java
  15. 2 4
      jplag/src/main/java/de/jplag/clustering/algorithm/BayesianOptimization.java
  16. 1 1
      jplag/src/main/java/de/jplag/clustering/algorithm/GaussianProcess.java
  17. 3 3
      jplag/src/main/java/de/jplag/clustering/algorithm/InterClusterSimilarity.java
  18. 2 2
      jplag/src/main/java/de/jplag/clustering/algorithm/SpectralClustering.java
  19. 2 2
      jplag/src/main/java/de/jplag/exceptions/ExitException.java
  20. 0 21
      jplag/src/main/java/de/jplag/exceptions/ReportGenerationException.java
  21. 2 2
      jplag/src/main/java/de/jplag/options/JPlagOptions.java
  22. 6 6
      jplag/src/main/java/de/jplag/reporting/jsonfactory/JsonFactory.java
  23. 10 12
      jplag/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java
  24. 16 25
      jplag/src/main/java/de/jplag/reporting/reportobject/model/ComparisonReport.java
  25. 2 2
      jplag/src/main/java/de/jplag/reporting/reportobject/model/FilesOfSubmission.java
  26. 1 12
      jplag/src/main/java/de/jplag/reporting/reportobject/model/JPlagReport.java
  27. 17 29
      jplag/src/main/java/de/jplag/reporting/reportobject/model/Match.java
  28. 10 17
      jplag/src/main/java/de/jplag/reporting/reportobject/model/Metric.java
  29. 8 13
      jplag/src/main/java/de/jplag/reporting/reportobject/model/TopComparison.java
  30. 2 2
      jplag/src/main/java/de/jplag/strategy/AbstractComparisonStrategy.java
  31. 1 3
      jplag/src/main/java/de/jplag/strategy/ComparisonMode.java
  32. 1 1
      jplag/src/main/java/de/jplag/strategy/NormalComparisonStrategy.java
  33. 16 17
      jplag/src/main/java/de/jplag/strategy/ParallelComparisonStrategy.java
  34. 2 15
      jplag/src/main/java/de/jplag/strategy/SubmissionTuple.java

+ 2 - 3
jplag.cli/src/main/java/de/jplag/CLI.java

@@ -32,7 +32,7 @@ import de.jplag.strategy.ComparisonMode;
  * Command line interface class, allows using via command line.
  * @see CLI#main(String[])
  */
-public class CLI {
+public final class CLI {
 
     private static final Logger logger = LoggerFactory.getLogger(CLI.class);
 
@@ -172,7 +172,6 @@ public class CLI {
         if (argumentValues == null) {
             return;
         }
-
-        argumentValues.stream().forEach(value -> destinationRootDirectories.addAll(value));
+        argumentValues.forEach(destinationRootDirectories::addAll);
     }
 }

+ 7 - 6
jplag/src/main/java/de/jplag/GreedyStringTiling.java

@@ -98,7 +98,8 @@ public class GreedyStringTiling {
     }
 
     private JPlagComparison swapAndCompare(Submission firstSubmission, Submission secondSubmission, boolean isBaseCodeComparison) {
-        Submission smallerSubmission, largerSubmission;
+        Submission smallerSubmission;
+        Submission largerSubmission;
         if (firstSubmission.getTokenList().size() > secondSubmission.getTokenList().size()) {
             smallerSubmission = secondSubmission;
             largerSubmission = firstSubmission;
@@ -187,11 +188,11 @@ public class GreedyStringTiling {
                 }
             }
             for (int i = matches.size() - 1; i >= 0; i--) {
-                int x = matches.get(i).getStartOfFirst();  // Beginning of/in sequence A
-                int y = matches.get(i).getStartOfSecond();  // Beginning of/in sequence B
-                comparison.addMatch(x, y, matches.get(i).getLength());
+                int x = matches.get(i).startOfFirst();  // Beginning of/in sequence A
+                int y = matches.get(i).startOfSecond();  // Beginning of/in sequence B
+                comparison.addMatch(x, y, matches.get(i).length());
                 // in order that "Match" will be newly build (because reusing)
-                for (int j = matches.get(i).getLength(); j > 0; j--) {
+                for (int j = matches.get(i).length(); j > 0; j--) {
                     first.getToken(x).setMarked(true); // mark all Tokens!
                     second.getToken(y).setMarked(true);
                     if (isBaseCodeComparison) {
@@ -220,7 +221,7 @@ public class GreedyStringTiling {
     /**
      * Disable finding a match at separator tokens and basecode matches for non-basecode comparisons.
      * @param tokenList Tokens to mark.
-     * @param isBaseCodeComparison Whether the {@link Token#basecode} matches should be enabled for matching.
+     * @param isBaseCodeComparison Whether the {@link Token#isBasecode()} matches should be enabled for matching.
      */
     private void markTokens(TokenList tokenList, boolean isBaseCodeComparison) {
         for (Token token : tokenList.allTokens()) {

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

@@ -119,7 +119,7 @@ public class JPlag {
 
             this.options.setLanguage(language);
             this.options.setLanguageDefaults(language);
-            logger.info("Initialized language " + language.getName());
+            logger.info("Initialized language {}", language.getName());
             return language;
         } catch (NoSuchMethodException | SecurityException | ClassNotFoundException | InstantiationException | IllegalAccessException
                 | IllegalArgumentException | InvocationTargetException e) {

+ 2 - 1
jplag/src/main/java/de/jplag/JPlagComparison.java

@@ -40,6 +40,7 @@ public class JPlagComparison implements Comparator<JPlagComparison> { // FIXME T
         return Float.compare(comparison2.similarity(), comparison1.similarity()); // comparison2 first!
     }
 
+    // TODO DF: hashCode is not implemented!!
     @Override
     public boolean equals(Object other) {
         if (!(other instanceof JPlagComparison)) {
@@ -76,7 +77,7 @@ public class JPlagComparison implements Comparator<JPlagComparison> { // FIXME T
         int numberOfMatchedTokens = 0;
 
         for (Match match : matches) {
-            numberOfMatchedTokens += match.getLength();
+            numberOfMatchedTokens += match.length();
         }
 
         return numberOfMatchedTokens;

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

@@ -115,7 +115,7 @@ public class JPlagResult {
     /**
      * Note: Before, comparisons with a similarity below the given threshold were also included in the similarity matrix.
      */
-    private int[] calculateSimilarityDistribution(List<JPlagComparison> comparisons) {
+    private static 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)

+ 9 - 13
jplag/src/main/java/de/jplag/Match.java

@@ -3,11 +3,7 @@ package de.jplag;
 /**
  * Represents two sections of two submissions that are similar.
  */
-public class Match {
-
-    private final int startOfFirst;
-    private final int startOfSecond;
-    private final int length;
+public record Match(int startOfFirst, int startOfSecond, int length) {
 
     /**
      * Creates a match.
@@ -15,30 +11,30 @@ public class Match {
      * @param startOfSecond is the starting token index in the second submission.
      * @param length is the length of these similar sections (number of tokens).
      */
-    public Match(int startOfFirst, int startOfSecond, int length) {
-        this.startOfFirst = startOfFirst;
-        this.startOfSecond = startOfSecond;
-        this.length = length;
+    public Match {
     }
 
     /**
      * @return the starting index in the first submission.
      */
-    public int getStartOfFirst() {
+    @Override
+    public int startOfFirst() {
         return startOfFirst;
     }
 
     /**
      * @return the starting index in the second submission.
      */
-    public int getStartOfSecond() {
+    @Override
+    public int startOfSecond() {
         return startOfSecond;
     }
 
     /**
      * @return the length of the similar sections, meaning the number of tokens.
      */
-    public int getLength() {
+    @Override
+    public int length() {
         return length;
     }
 
@@ -46,7 +42,7 @@ public class Match {
      * Checks if two matches overlap.
      * @return true if they do.
      */
-    public final boolean overlap(int otherStartOfFirst, int otherStartOfSecond, int oLength) {
+    public boolean overlap(int otherStartOfFirst, int otherStartOfSecond, int oLength) {
         if (startOfFirst < otherStartOfFirst) {
             if ((otherStartOfFirst - startOfFirst) < length) {
                 return true;

+ 2 - 2
jplag/src/main/java/de/jplag/Submission.java

@@ -184,7 +184,7 @@ public class Submission implements Comparable<Submission> {
      * String representation of the code files contained in this submission, annotated with all tokens.
      * @return the annotated code as string.
      */
-    public String getTokenAnnotatedSourcCode() {
+    public String getTokenAnnotatedSourceCode() {
         return TokenPrinter.printTokens(tokenList, files, submissionRootFile);
     }
 
@@ -246,7 +246,7 @@ public class Submission implements Comparable<Submission> {
      * @return Whether parsing was successful.
      */
     /* package-private */ boolean parse(boolean debugParser) {
-        if (files == null || files.size() == 0) {
+        if (files == null || files.isEmpty()) {
             errorCollector.print("ERROR: nothing to parse for submission \"" + name + "\"", null);
             tokenList = null;
             hasErrors = true; // invalidate submission

+ 10 - 13
jplag/src/main/java/de/jplag/SubmissionSet.java

@@ -1,10 +1,7 @@
 package de.jplag;
 
-import static java.util.stream.Collectors.toList;
-
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 import java.util.stream.Collectors;
 
 import de.jplag.exceptions.BasecodeException;
@@ -26,7 +23,7 @@ public class SubmissionSet {
     /**
      * Base code submission if it exists.
      */
-    private final Optional<Submission> baseCodeSubmission;
+    private final Submission baseCodeSubmission;
 
     private final ErrorCollector errorCollector;
     private final JPlagOptions options;
@@ -35,9 +32,9 @@ public class SubmissionSet {
 
     /**
      * @param submissions Submissions to check for plagiarism.
-     * @param baseCode Base code submission if it exists.
+     * @param baseCode Base code submission if it exists or {@code null}.
      */
-    public SubmissionSet(List<Submission> submissions, Optional<Submission> baseCode, ErrorCollector errorCollector, JPlagOptions options)
+    public SubmissionSet(List<Submission> submissions, Submission baseCode, ErrorCollector errorCollector, JPlagOptions options)
             throws ExitException {
         this.allSubmissions = submissions;
         this.baseCodeSubmission = baseCode;
@@ -52,7 +49,7 @@ public class SubmissionSet {
      * @return Whether a basecode is available for this collection.
      */
     public boolean hasBaseCode() {
-        return baseCodeSubmission.isPresent();
+        return baseCodeSubmission != null;
     }
 
     /**
@@ -62,10 +59,10 @@ public class SubmissionSet {
      * @see #hasBaseCode
      */
     public Submission getBaseCode() {
-        if (baseCodeSubmission.isEmpty()) {
+        if (baseCodeSubmission == null) {
             throw new AssertionError("Querying a non-existing basecode submission.");
         }
-        return baseCodeSubmission.get();
+        return baseCodeSubmission;
     }
 
     /**
@@ -96,14 +93,14 @@ public class SubmissionSet {
     }
 
     private List<Submission> filterInvalidSubmissions() {
-        return allSubmissions.stream().filter(Submission::hasErrors).collect(toList());
+        return allSubmissions.stream().filter(Submission::hasErrors).toList();
     }
 
     private void parseAllSubmissions() throws ExitException {
         try {
             parseSubmissions(allSubmissions);
-            if (baseCodeSubmission.isPresent()) {
-                parseBaseCodeSubmission(baseCodeSubmission.get()); // cannot use ifPresent because of throws declaration
+            if (baseCodeSubmission != null) {
+                parseBaseCodeSubmission(baseCodeSubmission); // cannot use ifPresent because of throws declaration
             }
         } catch (OutOfMemoryError exception) {
             throw new SubmissionException("Out of memory during parsing of submission \"" + currentSubmissionName + "\"", exception);
@@ -185,7 +182,7 @@ public class SubmissionSet {
         }
 
         long duration = System.currentTimeMillis() - startTime;
-        String timePerSubmission = submissions.size() > 0 ? Long.toString(duration / submissions.size()) : "n/a";
+        String timePerSubmission = !submissions.isEmpty() ? Long.toString(duration / submissions.size()) : "n/a";
         errorCollector.print(null, "Total time for parsing: " + TimeUtil.formatDuration(duration));
         errorCollector.print(null, "Time per parsed submission: " + timePerSubmission + " msec");
     }

+ 4 - 2
jplag/src/main/java/de/jplag/SubmissionSetBuilder.java

@@ -69,7 +69,7 @@ public class SubmissionSetBuilder {
 
         // Merge everything in a submission set.
         List<Submission> submissions = new ArrayList<>(foundSubmissions.values());
-        return new SubmissionSet(submissions, baseCodeSubmission, errorCollector, options);
+        return new SubmissionSet(submissions, baseCodeSubmission.orElse(null), errorCollector, options);
     }
 
     /**
@@ -125,7 +125,7 @@ public class SubmissionSetBuilder {
         // Extract the basecode submission if necessary.
         Optional<Submission> baseCodeSubmission = Optional.empty();
         if (options.hasBaseCode()) {
-            String baseCodeName = options.getBaseCodeSubmissionName().get();
+            String baseCodeName = options.getBaseCodeSubmissionName().orElseThrow();
             Submission baseCode = loadBaseCodeAsPath(baseCodeName);
             if (baseCode == null) {
                 int numberOfRootDirectories = submissionDirectories.size() + oldSubmissionDirectories.size();
@@ -140,7 +140,9 @@ public class SubmissionSetBuilder {
                 // Single root-directory, try the legacy way of specifying basecode.
                 baseCode = loadBaseCodeViaName(baseCodeName, rootDirectory, foundSubmissions);
             }
+            // TODO DF: Here the method assumes that baseCode can be null. later, this is not assumed anymore
             baseCodeSubmission = Optional.ofNullable(baseCode);
+
             logger.info("Basecode directory \"{}\" will be used.", baseCode.getName());
 
             // Basecode may also be registered as a user submission. If so, remove the latter.

+ 1 - 3
jplag/src/main/java/de/jplag/clustering/ClusteringFactory.java

@@ -4,7 +4,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
-import java.util.stream.Collectors;
 
 import de.jplag.JPlagComparison;
 import de.jplag.Submission;
@@ -42,8 +41,7 @@ public class ClusteringFactory {
     }
 
     private static ClusteringResult<Submission> removeBadClusters(final ClusteringResult<Submission> clustering) {
-        List<Cluster<Submission>> filtered = clustering.getClusters().stream().filter(cluster -> !cluster.isBadCluster())
-                .collect(Collectors.toList());
+        List<Cluster<Submission>> filtered = clustering.getClusters().stream().filter(cluster -> !cluster.isBadCluster()).toList();
         return new ClusteringResult<>(filtered, clustering.getCommunityStrength());
     }
 }

+ 1 - 1
jplag/src/main/java/de/jplag/clustering/ClusteringResult.java

@@ -66,7 +66,7 @@ public class ClusteringResult<T> {
         }
         List<Cluster<Integer>> clusters = new ArrayList<>(clustering.size());
         float communityStrength = 0;
-        if (clustering.size() > 0) {
+        if (!clustering.isEmpty()) {
             RealMatrix percentagesOfSimilaritySums = new Array2DRowRealMatrix(clustering.size(), clustering.size());
             percentagesOfSimilaritySums = percentagesOfSimilaritySums.scalarMultiply(0);
             for (int i = 0; i < numberOfSubmissions; i++) {

+ 3 - 2
jplag/src/main/java/de/jplag/clustering/IntegerMapping.java

@@ -3,14 +3,15 @@ package de.jplag.clustering;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Creates a mapping of any set to integers
  */
 public class IntegerMapping<T> {
 
-    private HashMap<T, Integer> mapping;
-    private List<T> backMapping;
+    private final Map<T, Integer> mapping;
+    private final List<T> backMapping;
     private int size = 0;
 
     public IntegerMapping(int initialCapacity) {

+ 1 - 1
jplag/src/main/java/de/jplag/clustering/PreprocessorHelper.java

@@ -24,7 +24,7 @@ public class PreprocessorHelper {
         mapping = new IntegerMapping<>(connections.length);
         RealMatrix similarity = new Array2DRowRealMatrix(connections, true);
         for (int i = 0; i < similarity.getRowDimension(); i++) {
-            if (DoubleStream.of(similarity.getRow(i)).filter(x -> x > 0).findAny().isPresent()) {
+            if (DoubleStream.of(similarity.getRow(i)).anyMatch(x -> x > 0)) {
                 rowList.add(i);
                 mapping.map(i);
             }

+ 23 - 51
jplag/src/main/java/de/jplag/clustering/algorithm/AgglomerativeClustering.java

@@ -1,11 +1,6 @@
 package de.jplag.clustering.algorithm;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.PriorityQueue;
-import java.util.Set;
+import java.util.*;
 import java.util.stream.Collectors;
 
 import org.apache.commons.math3.linear.RealMatrix;
@@ -32,32 +27,29 @@ public class AgglomerativeClustering implements GenericClusteringAlgorithm {
         // clusters, those need to be ignored.
         PriorityQueue<ClusterConnection> similarities;
 
-        {
-            // initialization
+        // initialization
 
-            List<Cluster> initialClusters = new ArrayList<>(size);
-            for (int i = 0; i < size; i++) {
-                List<Integer> members = new ArrayList<>();
-                members.add(i);
-                Cluster cluster = new Cluster(members);
-                initialClusters.add(cluster);
-                clusters.add(cluster);
-            }
+        List<Cluster> initialClusters = new ArrayList<>(size);
+        for (int i = 0; i < size; i++) {
+            List<Integer> members = new ArrayList<>();
+            members.add(i);
+            Cluster cluster = new Cluster(members);
+            initialClusters.add(cluster);
+            clusters.add(cluster);
+        }
 
-            List<ClusterConnection> initialSimilarities = new ArrayList<>(size * (size - 1) / 2);
+        List<ClusterConnection> initialSimilarities = new ArrayList<>(size * (size - 1) / 2);
 
-            for (int leftIndex = 0; leftIndex < initialClusters.size(); leftIndex++) {
-                Cluster leftCluster = initialClusters.get(leftIndex);
-                for (int rightIndex = leftIndex + 1; rightIndex < initialClusters.size(); rightIndex++) {
-                    Cluster rightCluster = initialClusters.get(rightIndex);
-                    initialSimilarities
-                            .add(new ClusterConnection(leftCluster, rightCluster, (float) similarityMatrix.getEntry(leftIndex, rightIndex)));
-                }
+        for (int leftIndex = 0; leftIndex < initialClusters.size(); leftIndex++) {
+            Cluster leftCluster = initialClusters.get(leftIndex);
+            for (int rightIndex = leftIndex + 1; rightIndex < initialClusters.size(); rightIndex++) {
+                Cluster rightCluster = initialClusters.get(rightIndex);
+                initialSimilarities.add(new ClusterConnection(leftCluster, rightCluster, (float) similarityMatrix.getEntry(leftIndex, rightIndex)));
             }
-
-            similarities = new PriorityQueue<>(initialSimilarities);
         }
 
+        similarities = new PriorityQueue<>(initialSimilarities);
+
         while (clusters.size() > 1) {
             ClusterConnection nearest = similarities.poll();
             // TODO Check that nearest is never null
@@ -70,8 +62,8 @@ public class AgglomerativeClustering implements GenericClusteringAlgorithm {
             }
             clusters.remove(nearest.left);
             clusters.remove(nearest.right);
-            nearest.left.getSubmissions().addAll(nearest.right.getSubmissions());
-            Cluster combined = new Cluster(nearest.left.getSubmissions());
+            nearest.left.submissions().addAll(nearest.right.submissions());
+            Cluster combined = new Cluster(nearest.left.submissions());
             for (Cluster otherCluster : clusters) {
                 float similarity = options.getAgglomerativeInterClusterSimilarity().clusterSimilarity(combined.submissions, otherCluster.submissions,
                         similarityMatrix);
@@ -80,21 +72,10 @@ public class AgglomerativeClustering implements GenericClusteringAlgorithm {
             clusters.add(combined);
         }
 
-        return clusters.stream().map(Cluster::getSubmissions).collect(Collectors.toList());
+        return clusters.stream().map(Cluster::submissions).collect(Collectors.toList());
     }
 
-    private static class ClusterConnection implements Comparable<ClusterConnection> {
-
-        private final Cluster left;
-        private final Cluster right;
-        private final float similarity;
-
-        public ClusterConnection(Cluster left, Cluster right, float similarity) {
-            this.left = left;
-            this.right = right;
-            this.similarity = similarity;
-        }
-
+    private record ClusterConnection(Cluster left, Cluster right, float similarity) implements Comparable<ClusterConnection> {
         @Override
         public int compareTo(ClusterConnection other) {
             return (int) Math.signum(other.similarity - similarity);
@@ -106,16 +87,7 @@ public class AgglomerativeClustering implements GenericClusteringAlgorithm {
      * Encapsulate a list in a class because we do not want hashing based on the members but on identity only. Also a
      * cluster need a different identity than the list because the lists are reused.
      */
-    private static class Cluster {
-        private final List<Integer> submissions;
-
-        public Cluster(List<Integer> submissions) {
-            this.submissions = submissions;
-        }
-
-        public List<Integer> getSubmissions() {
-            return submissions;
-        }
+    private record Cluster(List<Integer> submissions) {
     }
 
 }

+ 2 - 4
jplag/src/main/java/de/jplag/clustering/algorithm/BayesianOptimization.java

@@ -159,7 +159,7 @@ public class BayesianOptimization {
             } else {
                 // GPR
                 GaussianProcess gpr = fit(testedCoordinates, observations);
-                if (debug) {
+                if (debug && logger.isDebugEnabled()) {
                     logger.debug(gpr.toString(minima, maxima, 100, 25, 0));
                 }
                 // TODO Check that best is not null here
@@ -169,9 +169,7 @@ public class BayesianOptimization {
             OptimizationResult<T> result = objectiveFunction.apply(coordinates);
             result.params = coordinates;
             observations.add(result.getScore());
-            if (best == null) {
-                best = result;
-            } else if (result.score > best.score) {
+            if (best == null || result.score > best.score) {
                 best = result;
             }
         }

+ 1 - 1
jplag/src/main/java/de/jplag/clustering/algorithm/GaussianProcess.java

@@ -74,7 +74,7 @@ public class GaussianProcess {
      */
     public static GaussianProcess fit(List<RealVector> observedCoordinates, double[] observations, double noise, boolean normalize,
             double[] lengthScale) {
-        if (observedCoordinates.size() < 1)
+        if (observedCoordinates.isEmpty())
             throw new IllegalArgumentException("Observed coordinates are empty");
         if (observedCoordinates.size() != observations.length) {
             throw new IllegalArgumentException(MessageFormat.format("Observed coordinates and observations are of different dimensions {0} and {1}",

+ 3 - 3
jplag/src/main/java/de/jplag/clustering/algorithm/InterClusterSimilarity.java

@@ -1,7 +1,7 @@
 package de.jplag.clustering.algorithm;
 
 import java.util.List;
-import java.util.function.BiFunction;
+import java.util.function.BinaryOperator;
 
 import org.apache.commons.math3.linear.RealMatrix;
 
@@ -11,9 +11,9 @@ public enum InterClusterSimilarity {
     AVERAGE(0, Float::sum);
 
     private final float neutralElement;
-    private final BiFunction<Float, Float, Float> accumulator;
+    private final BinaryOperator<Float> accumulator;
 
-    InterClusterSimilarity(float neutralElement, BiFunction<Float, Float, Float> accumulator) {
+    InterClusterSimilarity(float neutralElement, BinaryOperator<Float> accumulator) {
         this.neutralElement = neutralElement;
         this.accumulator = accumulator;
     }

+ 2 - 2
jplag/src/main/java/de/jplag/clustering/algorithm/SpectralClustering.java

@@ -105,6 +105,7 @@ public class SpectralClustering implements GenericClusteringAlgorithm {
     private Collection<Collection<Integer>> cluster(int numberOfClusters, int dimension, List<Integer> eigenValueIds, EigenDecomposition ed) {
         RealMatrix concatenatedEigenVectors = new Array2DRowRealMatrix(dimension, numberOfClusters);
         concatenatedEigenVectors.walkInOptimizedOrder(new DefaultRealMatrixChangingVisitor() {
+            @Override
             public double visit(int row, int column, double value) {
                 int eigenVectorId = eigenValueIds.get(column);
                 RealVector eigenVector = ed.getEigenvector(eigenVectorId);
@@ -113,8 +114,7 @@ public class SpectralClustering implements GenericClusteringAlgorithm {
         });
 
         List<ClusterableEigenVector> normRows = IntStream.range(0, dimension).filter(i -> concatenatedEigenVectors.getRowVector(i).getNorm() > 0)
-                .mapToObj(row -> new ClusterableEigenVector(row, concatenatedEigenVectors.getRowVector(row).unitVector()))
-                .collect(Collectors.toList());
+                .mapToObj(row -> new ClusterableEigenVector(row, concatenatedEigenVectors.getRowVector(row).unitVector())).toList();
 
         Clusterer<ClusterableEigenVector> clusterer = new KMeansPlusPlusClusterer<>(numberOfClusters, options.getSpectralMaxKMeansIterationPerRun());
         List<? extends Cluster<ClusterableEigenVector>> clusters = clusterer.cluster(normRows);

+ 2 - 2
jplag/src/main/java/de/jplag/exceptions/ExitException.java

@@ -10,11 +10,11 @@ public abstract class ExitException extends Exception {
     @Serial
     private static final long serialVersionUID = 7091658804288889231L; // generated
 
-    public ExitException(String message) {
+    protected ExitException(String message) {
         super(message);
     }
 
-    public ExitException(String message, Throwable cause) {
+    protected ExitException(String message, Throwable cause) {
         super(message, cause);
     }
 }

+ 0 - 21
jplag/src/main/java/de/jplag/exceptions/ReportGenerationException.java

@@ -1,21 +0,0 @@
-package de.jplag.exceptions;
-
-import java.io.Serial;
-
-/**
- * Exceptions for problems with the report generation that lead to an preemptive exit.
- */
-public class ReportGenerationException extends ExitException {
-
-    @Serial
-    private static final long serialVersionUID = 628097693006117102L; // generated
-
-    public ReportGenerationException(String message) {
-        super(message);
-    }
-
-    public ReportGenerationException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-}

+ 2 - 2
jplag/src/main/java/de/jplag/options/JPlagOptions.java

@@ -289,10 +289,10 @@ public class JPlagOptions {
 
     public void setSimilarityThreshold(float similarityThreshold) {
         if (similarityThreshold > 100) {
-            logger.warn("Maximum threshold of 100 used instead of " + similarityThreshold);
+            logger.warn("Maximum threshold of 100 used instead of {}", similarityThreshold);
             this.similarityThreshold = 100;
         } else if (similarityThreshold < 0) {
-            logger.warn("Minimum threshold of 0 used instead of " + similarityThreshold);
+            logger.warn("Minimum threshold of 0 used instead of {}", similarityThreshold);
             this.similarityThreshold = 0;
         } else {
             this.similarityThreshold = similarityThreshold;

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

@@ -31,12 +31,12 @@ public class JsonFactory {
     public static List<String> getJsonStrings(JPlagReport jPlagReport) {
         List<String> jsonReports = new ArrayList<>();
         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));
             }
         } catch (JsonProcessingException e) {
-            logger.error("Error converting object to json " + e.getMessage());
+            logger.error("Error converting object to json " + e.getMessage(), e);
         }
         return jsonReports;
     }
@@ -48,9 +48,9 @@ public class JsonFactory {
     public static boolean saveJsonFiles(JPlagReport jPlagReport, String folderPath) {
         try {
             ObjectMapper mapper = new ObjectMapper();
-            mapper.writeValue(Path.of(folderPath, "overview.json").toFile(), jPlagReport.getOverviewReport());
-            for (ComparisonReport report : jPlagReport.getComparisons()) {
-                String name = report.getFirstSubmissionId().concat("-").concat(report.getSecondSubmissionId()).concat(".json");
+            mapper.writeValue(Path.of(folderPath, "overview.json").toFile(), jPlagReport.overviewReport());
+            for (ComparisonReport report : jPlagReport.comparisons()) {
+                String name = report.firstSubmissionId().concat("-").concat(report.secondSubmissionId()).concat(".json");
                 mapper.writeValue(Path.of(folderPath, name).toFile(), report);
             }
         } catch (IOException e) {

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

@@ -6,7 +6,6 @@ import java.io.FileReader;
 import java.io.IOException;
 import java.text.SimpleDateFormat;
 import java.util.*;
-import java.util.stream.Collectors;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -85,8 +84,7 @@ public class ReportObjectFactory {
     }
 
     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()))
-                .collect(Collectors.toList());
+        return matches.stream().map(match -> convertMatchToReportMatch(comparison, match, result.getOptions().getLanguage().usesIndex())).toList();
     }
 
     /**
@@ -143,7 +141,7 @@ public class ReportObjectFactory {
      * @return A list containing FilesOfSubmission DTOs.
      */
     private static List<FilesOfSubmission> getFilesForSubmission(Submission submission) {
-        return submission.getFiles().stream().map(file -> new FilesOfSubmission(file.getName(), readFileLines(file))).collect(Collectors.toList());
+        return submission.getFiles().stream().map(file -> new FilesOfSubmission(file.getName(), readFileLines(file))).toList();
     }
 
     /**
@@ -153,19 +151,19 @@ public class ReportObjectFactory {
      * @param usesIndex Indicates whether the language uses indexes.
      * @return A Match DTO.
      */
-    private static Match convertMatchToReportMatch(JPlagComparison comparison, de.jplag.Match match, Boolean usesIndex) {
+    private static Match convertMatchToReportMatch(JPlagComparison comparison, de.jplag.Match match, boolean usesIndex) {
         TokenList tokensFirst = comparison.getFirstSubmission().getTokenList();
         TokenList tokensSecond = comparison.getSecondSubmission().getTokenList();
-        Token startTokenFirst = tokensFirst.getToken(match.getStartOfFirst());
-        Token endTokenFirst = tokensFirst.getToken(match.getStartOfFirst() + match.getLength() - 1);
-        Token startTokenSecond = tokensSecond.getToken(match.getStartOfSecond());
-        Token endTokenSecond = tokensSecond.getToken(match.getStartOfSecond() + match.getLength() - 1);
+        Token startTokenFirst = tokensFirst.getToken(match.startOfFirst());
+        Token endTokenFirst = tokensFirst.getToken(match.startOfFirst() + match.length() - 1);
+        Token startTokenSecond = tokensSecond.getToken(match.startOfSecond());
+        Token endTokenSecond = tokensSecond.getToken(match.startOfSecond() + match.length() - 1);
 
         int startFirst = usesIndex ? startTokenFirst.getIndex() : startTokenFirst.getLine();
         int endFirst = usesIndex ? endTokenFirst.getIndex() : endTokenFirst.getLine();
         int startSecond = usesIndex ? startTokenSecond.getIndex() : startTokenSecond.getLine();
         int endSecond = usesIndex ? endTokenSecond.getIndex() : endTokenSecond.getLine();
-        int tokens = match.getLength();
+        int tokens = match.length();
 
         return new Match(startTokenFirst.getFile(), startTokenSecond.getFile(), startFirst, endFirst, startSecond, endSecond, tokens);
     }
@@ -178,7 +176,7 @@ public class ReportObjectFactory {
                 lines.add(line);
             }
         } catch (IOException exception) {
-            logger.error("Could not read file: " + exception.getMessage());
+            logger.error("Could not read file: " + exception.getMessage(), exception);
         }
         return lines;
     }
@@ -190,6 +188,6 @@ public class ReportObjectFactory {
     }
 
     private static List<Integer> intArrayToList(int[] array) {
-        return Arrays.stream(array).boxed().collect(Collectors.toList());
+        return Arrays.stream(array).boxed().toList();
     }
 }

+ 16 - 25
jplag/src/main/java/de/jplag/reporting/reportobject/model/ComparisonReport.java

@@ -4,25 +4,10 @@ import java.util.List;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 
-public class ComparisonReport {
-
-    @JsonProperty("first_submission_id")
-    private final String firstSubmissionId;
-
-    @JsonProperty("second_submission_id")
-    private final String secondSubmissionId;
-
-    @JsonProperty("match_percentage")
-    private final float matchPercentage;
-
-    @JsonProperty("files_of_first_submission")
-    private final List<FilesOfSubmission> filesOfFirstSubmission;
-
-    @JsonProperty("files_of_second_submission")
-    private final List<FilesOfSubmission> filesOfSecondSubmission;
-
-    @JsonProperty("matches")
-    private final List<Match> matches;
+public record ComparisonReport(@JsonProperty("first_submission_id") String firstSubmissionId,
+        @JsonProperty("second_submission_id") String secondSubmissionId, @JsonProperty("match_percentage") float matchPercentage,
+        @JsonProperty("files_of_first_submission") List<FilesOfSubmission> filesOfFirstSubmission,
+        @JsonProperty("files_of_second_submission") List<FilesOfSubmission> filesOfSecondSubmission, @JsonProperty("matches") List<Match> matches) {
 
     public ComparisonReport(String firstSubmissionId, String secondSubmissionId, float matchPercentage,
             List<FilesOfSubmission> filesOfFirstSubmission, List<FilesOfSubmission> filesOfSecondSubmission, List<Match> matches) {
@@ -35,27 +20,33 @@ public class ComparisonReport {
         this.matches = matches;
     }
 
-    public String getFirstSubmissionId() {
+    @Override
+    public String firstSubmissionId() {
         return firstSubmissionId;
     }
 
-    public String getSecondSubmissionId() {
+    @Override
+    public String secondSubmissionId() {
         return secondSubmissionId;
     }
 
-    public float getMatchPercentage() {
+    @Override
+    public float matchPercentage() {
         return matchPercentage;
     }
 
-    public List<FilesOfSubmission> getFilesOfFirstSubmission() {
+    @Override
+    public List<FilesOfSubmission> filesOfFirstSubmission() {
         return filesOfFirstSubmission;
     }
 
-    public List<FilesOfSubmission> getFilesOfSecondSubmission() {
+    @Override
+    public List<FilesOfSubmission> filesOfSecondSubmission() {
         return filesOfSecondSubmission;
     }
 
-    public List<Match> getMatches() {
+    @Override
+    public List<Match> matches() {
         return matches;
     }
 }

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

@@ -12,9 +12,9 @@ public class FilesOfSubmission {
     @JsonProperty("lines")
     private final List<String> lines;
 
-    public FilesOfSubmission(String fileName, List<String> file_code) {
+    public FilesOfSubmission(String fileName, List<String> fileCode) {
         this.fileName = fileName;
-        this.lines = List.copyOf(file_code);
+        this.lines = List.copyOf(fileCode);
     }
 
     public String getFileName() {

+ 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;
 
-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) {
         this.overviewReport = overviewReport;
         this.comparisons = List.copyOf(comparisons);
     }
-
-    public OverviewReport getOverviewReport() {
-        return overviewReport;
-    }
-
-    public List<ComparisonReport> getComparisons() {
-        return comparisons;
-    }
 }

+ 17 - 29
jplag/src/main/java/de/jplag/reporting/reportobject/model/Match.java

@@ -2,28 +2,9 @@ package de.jplag.reporting.reportobject.model;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 
-public class Match {
-
-    @JsonProperty("first_file_name")
-    private final String firstFileName;
-
-    @JsonProperty("second_file_name")
-    private final String secondFileName;
-
-    @JsonProperty("start_in_first")
-    private final int startInFirst;
-
-    @JsonProperty("end_in_first")
-    private final int endInFirst;
-
-    @JsonProperty("start_in_second")
-    private final int startInSecond;
-
-    @JsonProperty("end_in_second")
-    private final int endInSecond;
-
-    @JsonProperty("tokens")
-    private final int tokens;
+public record Match(@JsonProperty("first_file_name") String firstFileName, @JsonProperty("second_file_name") String secondFileName,
+        @JsonProperty("start_in_first") int startInFirst, @JsonProperty("end_in_first") int endInFirst,
+        @JsonProperty("start_in_second") int startInSecond, @JsonProperty("end_in_second") int endInSecond, @JsonProperty("tokens") int tokens) {
 
     public Match(String firstFileName, String secondFileName, int startInFirst, int endInFirst, int startInSecond, int endInSecond, int tokens) {
         this.firstFileName = firstFileName;
@@ -35,31 +16,38 @@ public class Match {
         this.tokens = tokens;
     }
 
-    public String getFirstFileName() {
+    @Override
+    public String firstFileName() {
         return firstFileName;
     }
 
-    public String getSecondFileName() {
+    @Override
+    public String secondFileName() {
         return secondFileName;
     }
 
-    public int getStartInFirst() {
+    @Override
+    public int startInFirst() {
         return startInFirst;
     }
 
-    public int getEndInFirst() {
+    @Override
+    public int endInFirst() {
         return endInFirst;
     }
 
-    public int getStartInSecond() {
+    @Override
+    public int startInSecond() {
         return startInSecond;
     }
 
-    public int getEndInSecond() {
+    @Override
+    public int endInSecond() {
         return endInSecond;
     }
 
-    public int getTokens() {
+    @Override
+    public int tokens() {
         return tokens;
     }
 }

+ 10 - 17
jplag/src/main/java/de/jplag/reporting/reportobject/model/Metric.java

@@ -4,19 +4,8 @@ import java.util.List;
 
 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 record Metric(@JsonProperty("name") String name, @JsonProperty("threshold") float threshold,
+        @JsonProperty("distribution") List<Integer> distribution, @JsonProperty("topComparisons") List<TopComparison> topComparisons) {
 
     public Metric(String name, float threshold, List<Integer> distribution, List<TopComparison> topComparisons) {
         this.name = name;
@@ -25,19 +14,23 @@ public class Metric {
         this.topComparisons = List.copyOf(topComparisons);
     }
 
-    public String getName() {
+    @Override
+    public String name() {
         return name;
     }
 
-    public float getThreshold() {
+    @Override
+    public float threshold() {
         return threshold;
     }
 
-    public List<Integer> getDistribution() {
+    @Override
+    public List<Integer> distribution() {
         return distribution;
     }
 
-    public List<TopComparison> getTopComparisons() {
+    @Override
+    public List<TopComparison> topComparisons() {
         return topComparisons;
     }
 }

+ 8 - 13
jplag/src/main/java/de/jplag/reporting/reportobject/model/TopComparison.java

@@ -2,16 +2,8 @@ package de.jplag.reporting.reportobject.model;
 
 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 record TopComparison(@JsonProperty("first_submission") String firstSubmission, @JsonProperty("second_submission") String secondSubmission,
+        @JsonProperty("match_percentage") float matchPercentage) {
 
     public TopComparison(String firstSubmission, String secondSubmission, float matchPercentage) {
         this.firstSubmission = firstSubmission;
@@ -19,15 +11,18 @@ public class TopComparison {
         this.matchPercentage = matchPercentage;
     }
 
-    public String getFirstSubmission() {
+    @Override
+    public String firstSubmission() {
         return firstSubmission;
     }
 
-    public String getSecondSubmission() {
+    @Override
+    public String secondSubmission() {
         return secondSubmission;
     }
 
-    public float getMatchPercentage() {
+    @Override
+    public float matchPercentage() {
         return matchPercentage;
     }
 

+ 2 - 2
jplag/src/main/java/de/jplag/strategy/AbstractComparisonStrategy.java

@@ -21,7 +21,7 @@ public abstract class AbstractComparisonStrategy implements ComparisonStrategy {
 
     protected JPlagOptions options;
 
-    public AbstractComparisonStrategy(JPlagOptions options, GreedyStringTiling greedyStringTiling) {
+    protected AbstractComparisonStrategy(JPlagOptions options, GreedyStringTiling greedyStringTiling) {
         this.greedyStringTiling = greedyStringTiling;
         this.options = options;
     }
@@ -47,7 +47,7 @@ public abstract class AbstractComparisonStrategy implements ComparisonStrategy {
      */
     protected Optional<JPlagComparison> compareSubmissions(Submission first, Submission second, boolean withBaseCode) {
         JPlagComparison comparison = greedyStringTiling.compare(first, second);
-        logger.info("Comparing " + first.getName() + "-" + second.getName() + ": " + comparison.similarity());
+        logger.info("Comparing {}-{}: {}", first.getName(), second.getName(), comparison.similarity());
 
         if (options.getSimilarityMetric().isAboveThreshold(comparison, options.getSimilarityThreshold())) {
             return Optional.of(comparison);

+ 1 - 3
jplag/src/main/java/de/jplag/strategy/ComparisonMode.java

@@ -1,7 +1,5 @@
 package de.jplag.strategy;
 
-import static java.util.stream.Collectors.toList;
-
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Optional;
@@ -40,7 +38,7 @@ public enum ComparisonMode {
      * @return a collections of all mode names.
      */
     public static Collection<String> allNames() {
-        return Arrays.stream(values()).map(ComparisonMode::getName).collect(toList());
+        return Arrays.stream(values()).map(ComparisonMode::getName).toList();
     }
 
     /**

+ 1 - 1
jplag/src/main/java/de/jplag/strategy/NormalComparisonStrategy.java

@@ -28,7 +28,7 @@ public class NormalComparisonStrategy extends AbstractComparisonStrategy {
         List<JPlagComparison> comparisons = new ArrayList<>();
 
         for (SubmissionTuple tuple : buildComparisonTuples(submissions)) {
-            compareSubmissions(tuple.getLeft(), tuple.getRight(), withBaseCode).ifPresent(comparisons::add);
+            compareSubmissions(tuple.left(), tuple.right(), withBaseCode).ifPresent(comparisons::add);
         }
 
         long durationInMillis = System.currentTimeMillis() - timeBeforeStartInMillis;

+ 16 - 17
jplag/src/main/java/de/jplag/strategy/ParallelComparisonStrategy.java

@@ -3,19 +3,14 @@ package de.jplag.strategy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
-import de.jplag.GreedyStringTiling;
-import de.jplag.JPlagComparison;
-import de.jplag.JPlagResult;
-import de.jplag.Submission;
-import de.jplag.SubmissionSet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import de.jplag.*;
 import de.jplag.options.JPlagOptions;
 
 /**
@@ -23,6 +18,8 @@ import de.jplag.options.JPlagOptions;
  * @author Timur Saglam
  */
 public class ParallelComparisonStrategy extends AbstractComparisonStrategy {
+    private static final Logger logger = LoggerFactory.getLogger("JPlag");
+
     private static final int TIMEOUT_IN_SECONDS = 5;
     private final ConcurrentMap<String, Lock> submissionLocks;
     private ExecutorService threadPool;
@@ -60,8 +57,9 @@ public class ParallelComparisonStrategy extends AbstractComparisonStrategy {
         while (successfulComparisons < tuples.size()) {
             try {
                 Thread.sleep(5);
-            } catch (InterruptedException exception) {
-                exception.printStackTrace();
+            } catch (InterruptedException e) {
+                logger.error(e.getMessage(), e);
+                Thread.currentThread().interrupt();
             }
         }
 
@@ -81,13 +79,13 @@ public class ParallelComparisonStrategy extends AbstractComparisonStrategy {
         return new Runnable() {
             @Override
             public void run() {
-                Lock leftLock = getOrCreateLock(tuple.getLeft().getName());
-                Lock rightLock = getOrCreateLock(tuple.getRight().getName());
+                Lock leftLock = getOrCreateLock(tuple.left().getName());
+                Lock rightLock = getOrCreateLock(tuple.right().getName());
                 boolean hasLeft = leftLock.tryLock();
                 boolean hasRight = hasLeft && rightLock.tryLock();
                 try {
                     if (hasLeft && hasRight) { // both locks acquired!
-                        compareSubmissions(tuple.getLeft(), tuple.getRight(), withBaseCode).ifPresent(comparisons::add);
+                        compareSubmissions(tuple.left(), tuple.right(), withBaseCode).ifPresent(comparisons::add);
                         synchronized (this) {
                             successfulComparisons++;
                         }
@@ -123,8 +121,9 @@ public class ParallelComparisonStrategy extends AbstractComparisonStrategy {
             if (!threadPool.awaitTermination(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)) {
                 throw new IllegalStateException("Parallel comparison calculation timed out!");
             }
-        } catch (InterruptedException exception) {
-            throw new IllegalStateException("Thread pool interrupted during comparison: " + exception.getMessage());
+        } catch (InterruptedException e) {
+            logger.error(e.getMessage(), e);
+            Thread.currentThread().interrupt();
         }
     }
 

+ 2 - 15
jplag/src/main/java/de/jplag/strategy/SubmissionTuple.java

@@ -5,27 +5,14 @@ import de.jplag.Submission;
 /**
  * Tupel of source code submissions.
  */
-public class SubmissionTuple {
-    private final Submission left;
-    private final Submission right;
-
+public record SubmissionTuple(Submission left, Submission right) {
     /**
      * Creates a tupel from two submissions.
      */
-    public SubmissionTuple(Submission left, Submission right) {
+    public SubmissionTuple {
         if (left == null || right == null) {
             throw new IllegalArgumentException("Submissions cannot be null");
         }
-        this.left = left;
-        this.right = right;
-    }
-
-    public Submission getLeft() {
-        return left;
-    }
-
-    public Submission getRight() {
-        return right;
     }
 
     @Override