Ver Fonte

Merge branch 'master-remote' into golang

Robin Maisch há 4 anos atrás
pai
commit
bb77c0fe93
80 ficheiros alterados com 687 adições e 855 exclusões
  1. 6 0
      .gitignore
  2. 0 4
      jplag.cli/pom.xml
  3. 18 5
      jplag.cli/src/main/java/de/jplag/CLI.java
  4. 270 0
      jplag.cli/src/main/java/de/jplag/logger/CollectedLogger.java
  5. 44 0
      jplag.cli/src/main/java/de/jplag/logger/CollectedLoggerFactory.java
  6. 4 0
      jplag.cli/src/main/java/de/jplag/logger/Triple.java
  7. 54 0
      jplag.cli/src/main/java/org/slf4j/impl/StaticLoggerBinder.java
  8. 1 1
      jplag.cli/src/test/java/de/jplag/cli/CommandLineInterfaceTest.java
  9. 0 28
      jplag.frontend-testutils/src/test/java/de/jplag/testutils/TestErrorConsumer.java
  10. 6 16
      jplag.frontend-utils/src/main/java/de/jplag/AbstractParser.java
  11. 0 20
      jplag.frontend-utils/src/main/java/de/jplag/ErrorConsumer.java
  12. 10 15
      jplag.frontend-utils/src/main/java/de/jplag/Language.java
  13. 2 13
      jplag.frontend.chars/src/main/java/de/jplag/chars/Language.java
  14. 7 14
      jplag.frontend.chars/src/main/java/de/jplag/chars/Parser.java
  15. 2 23
      jplag.frontend.cpp/src/main/java/de/jplag/cpp/Language.java
  16. 3 5
      jplag.frontend.cpp/src/main/java/de/jplag/cpp/Scanner.java
  17. 2 2
      jplag.frontend.cpp/src/main/javacc/CPP.jj
  18. 1 1
      jplag.frontend.csharp-6/src/main/java/de/jplag/csharp/CSharpListener.java
  19. 4 6
      jplag.frontend.csharp-6/src/main/java/de/jplag/csharp/CSharpParserAdapter.java
  20. 2 23
      jplag.frontend.csharp-6/src/main/java/de/jplag/csharp/Language.java
  21. 11 7
      jplag.frontend.csharp-6/src/main/java/de/jplag/csharp/grammar/CSharpPreprocessorParserBase.java
  22. 1 3
      jplag.frontend.csharp-6/src/test/java/de/jplag/csharp/MinimalCSharpFrontendTest.java
  23. 1 1
      jplag.frontend.java/src/main/java/de/jplag/java/JavaTokenConstants.java
  24. 6 9
      jplag.frontend.java/src/main/java/de/jplag/java/JavacAdapter.java
  25. 2 23
      jplag.frontend.java/src/main/java/de/jplag/java/Language.java
  26. 3 7
      jplag.frontend.java/src/main/java/de/jplag/java/Parser.java
  27. 3 5
      jplag.frontend.kotlin/src/main/java/de/jplag/kotlin/KotlinParserAdapter.java
  28. 2 23
      jplag.frontend.kotlin/src/main/java/de/jplag/kotlin/Language.java
  29. 3 5
      jplag.frontend.kotlin/src/test/java/de/jplag/kotlin/KotlinFrontendTest.java
  30. 2 23
      jplag.frontend.python-3/src/main/java/de/jplag/python3/Language.java
  31. 7 9
      jplag.frontend.python-3/src/main/java/de/jplag/python3/Parser.java
  32. 2 23
      jplag.frontend.rlang/src/main/java/de/jplag/rlang/Language.java
  33. 3 5
      jplag.frontend.rlang/src/main/java/de/jplag/rlang/RParserAdapter.java
  34. 2 4
      jplag.frontend.rlang/src/test/java/de/jplag/rlang/RFrontendTest.java
  35. 2 23
      jplag.frontend.scheme/src/main/java/de/jplag/scheme/Language.java
  36. 6 8
      jplag.frontend.scheme/src/main/java/de/jplag/scheme/Parser.java
  37. 2 2
      jplag.frontend.scheme/src/main/javacc/Scheme.jj
  38. 1 0
      jplag.frontend.text/src/main/java/de/jplag/text/InputState.java
  39. 2 19
      jplag.frontend.text/src/main/java/de/jplag/text/Language.java
  40. 6 14
      jplag.frontend.text/src/main/java/de/jplag/text/Parser.java
  41. 0 79
      jplag/src/main/java/de/jplag/ErrorCollector.java
  42. 7 6
      jplag/src/main/java/de/jplag/GreedyStringTiling.java
  43. 10 14
      jplag/src/main/java/de/jplag/JPlag.java
  44. 2 1
      jplag/src/main/java/de/jplag/JPlagComparison.java
  45. 1 1
      jplag/src/main/java/de/jplag/JPlagResult.java
  46. 7 42
      jplag/src/main/java/de/jplag/Match.java
  47. 13 10
      jplag/src/main/java/de/jplag/Submission.java
  48. 29 36
      jplag/src/main/java/de/jplag/SubmissionSet.java
  49. 20 11
      jplag/src/main/java/de/jplag/SubmissionSetBuilder.java
  50. 1 3
      jplag/src/main/java/de/jplag/clustering/ClusteringFactory.java
  51. 1 1
      jplag/src/main/java/de/jplag/clustering/ClusteringResult.java
  52. 3 2
      jplag/src/main/java/de/jplag/clustering/IntegerMapping.java
  53. 1 1
      jplag/src/main/java/de/jplag/clustering/PreprocessorHelper.java
  54. 23 51
      jplag/src/main/java/de/jplag/clustering/algorithm/AgglomerativeClustering.java
  55. 2 4
      jplag/src/main/java/de/jplag/clustering/algorithm/BayesianOptimization.java
  56. 1 1
      jplag/src/main/java/de/jplag/clustering/algorithm/GaussianProcess.java
  57. 3 3
      jplag/src/main/java/de/jplag/clustering/algorithm/InterClusterSimilarity.java
  58. 2 2
      jplag/src/main/java/de/jplag/clustering/algorithm/SpectralClustering.java
  59. 2 2
      jplag/src/main/java/de/jplag/exceptions/ExitException.java
  60. 0 21
      jplag/src/main/java/de/jplag/exceptions/ReportGenerationException.java
  61. 2 2
      jplag/src/main/java/de/jplag/options/JPlagOptions.java
  62. 2 2
      jplag/src/main/java/de/jplag/reporting/jsonfactory/JsonFactory.java
  63. 14 13
      jplag/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java
  64. 4 44
      jplag/src/main/java/de/jplag/reporting/reportobject/model/ComparisonReport.java
  65. 2 2
      jplag/src/main/java/de/jplag/reporting/reportobject/model/FilesOfSubmission.java
  66. 3 60
      jplag/src/main/java/de/jplag/reporting/reportobject/model/Match.java
  67. 0 1
      jplag/src/main/java/de/jplag/reporting/reportobject/model/TopComparison.java
  68. 3 3
      jplag/src/main/java/de/jplag/strategy/AbstractComparisonStrategy.java
  69. 2 4
      jplag/src/main/java/de/jplag/strategy/ComparisonMode.java
  70. 1 1
      jplag/src/main/java/de/jplag/strategy/NormalComparisonStrategy.java
  71. 16 17
      jplag/src/main/java/de/jplag/strategy/ParallelComparisonStrategy.java
  72. 2 15
      jplag/src/main/java/de/jplag/strategy/SubmissionTuple.java
  73. 0 1
      jplag/src/main/resources/de/jplag/messages_de.properties
  74. 0 1
      jplag/src/main/resources/de/jplag/messages_en.properties
  75. 0 2
      jplag/src/main/resources/de/jplag/messages_es.properties
  76. 0 1
      jplag/src/main/resources/de/jplag/messages_fr.properties
  77. 0 1
      jplag/src/main/resources/de/jplag/messages_pt.properties
  78. 3 3
      jplag/src/test/java/de/jplag/clustering/algorithm/BayesianOptimizationTest.java
  79. 1 1
      jplag/src/test/java/de/jplag/clustering/algorithm/ClusteringData.java
  80. 1 1
      jplag/src/test/java/de/jplag/clustering/preprocessors/PreprocessingTestBase.java

+ 6 - 0
.gitignore

@@ -22,3 +22,9 @@ hs_err_pid*
 .idea/
 *.iml
 .flattened-pom.xml
+
+# VSCode
+.vscode/
+
+# ANTLR
+.antlr/

+ 0 - 4
jplag.cli/pom.xml

@@ -56,10 +56,6 @@
             <groupId>net.sourceforge.argparse4j</groupId>
             <artifactId>argparse4j</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-simple</artifactId>
-        </dependency>
     </dependencies>
     <build>
         <plugins>

+ 18 - 5
jplag.cli/src/main/java/de/jplag/CLI.java

@@ -2,6 +2,7 @@ package de.jplag;
 
 import static de.jplag.CommandLineArgument.*;
 
+import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
@@ -12,6 +13,7 @@ import net.sourceforge.argparse4j.inf.ArgumentParser;
 import net.sourceforge.argparse4j.inf.ArgumentParserException;
 import net.sourceforge.argparse4j.inf.Namespace;
 
+import org.slf4j.ILoggerFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -20,6 +22,7 @@ import de.jplag.clustering.ClusteringOptions;
 import de.jplag.clustering.Preprocessing;
 import de.jplag.clustering.algorithm.InterClusterSimilarity;
 import de.jplag.exceptions.ExitException;
+import de.jplag.logger.CollectedLoggerFactory;
 import de.jplag.options.JPlagOptions;
 import de.jplag.options.LanguageOption;
 import de.jplag.options.SimilarityMetric;
@@ -32,10 +35,12 @@ 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);
 
+    private static final Random RANDOM = new SecureRandom();
+
     private static final String CREDITS = "Created by IPD Tichy, Guido Malpohl, and others. JPlag logo designed by Sandro Koch. Currently maintained by Sebastian Hahner and Timur Saglam.";
 
     private static final String[] DESCRIPTIONS = {"Detecting Software Plagiarism", "Software-Archaeological Playground", "Since 1996",
@@ -62,12 +67,21 @@ public class CLI {
             JPlagResult result = program.run();
             Report report = new JsonReport();
             report.saveReport(result, arguments.getString(RESULT_FOLDER.flagWithoutDash()));
+            finalizeLogger();
         } catch (ExitException exception) {
             logger.error(exception.getMessage(), exception);
+            finalizeLogger();
             System.exit(1);
         }
     }
 
+    private static void finalizeLogger() {
+        ILoggerFactory factory = LoggerFactory.getILoggerFactory();
+        if (!(factory instanceof CollectedLoggerFactory collectedLoggerFactory))
+            return;
+        collectedLoggerFactory.finalizeInstances();
+    }
+
     /**
      * Creates the command line interface and initializes the argument parser.
      */
@@ -164,15 +178,14 @@ public class CLI {
     }
 
     private String generateDescription() {
-        var randomDescription = DESCRIPTIONS[new Random().nextInt(DESCRIPTIONS.length)];
-        return String.format("JPlag - %s" + System.lineSeparator() + CREDITS, randomDescription);
+        var randomDescription = DESCRIPTIONS[RANDOM.nextInt(DESCRIPTIONS.length)];
+        return String.format("JPlag - %s%n%s", CREDITS, randomDescription);
     }
 
     private void addAllMultiValueArgument(List<List<String>> argumentValues, List<String> destinationRootDirectories) {
         if (argumentValues == null) {
             return;
         }
-
-        argumentValues.stream().forEach(value -> destinationRootDirectories.addAll(value));
+        argumentValues.forEach(destinationRootDirectories::addAll);
     }
 }

+ 270 - 0
jplag.cli/src/main/java/de/jplag/logger/CollectedLogger.java

@@ -0,0 +1,270 @@
+package de.jplag.logger;
+
+import java.io.PrintStream;
+import java.io.Serial;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.concurrent.ConcurrentLinkedDeque;
+
+import org.slf4j.helpers.FormattingTuple;
+import org.slf4j.helpers.MarkerIgnoringBase;
+import org.slf4j.helpers.MessageFormatter;
+import org.slf4j.spi.LocationAwareLogger;
+
+/**
+ * This logger is able to collect errors and print them at the end. Mainly adopted from org.slf4j.impl.SimpleLogger
+ * @author Dominik Fuchss
+ */
+public final class CollectedLogger extends MarkerIgnoringBase {
+
+    @Serial
+    private static final long serialVersionUID = -1278670638921140275L;
+
+    private static final int LOG_LEVEL_TRACE = LocationAwareLogger.TRACE_INT;
+    private static final int LOG_LEVEL_DEBUG = LocationAwareLogger.DEBUG_INT;
+    private static final int LOG_LEVEL_INFO = LocationAwareLogger.INFO_INT;
+    private static final int LOG_LEVEL_WARN = LocationAwareLogger.WARN_INT;
+    private static final int LOG_LEVEL_ERROR = LocationAwareLogger.ERROR_INT;
+
+    private final int currentLogLevel = LOG_LEVEL_INFO;
+
+    /**
+     * The short name of this simple log instance
+     */
+    private transient String shortLogName = null;
+
+    /**
+     * Indicator whether finalization is in progress.
+     * @see #printAllErrorsForLogger()
+     */
+    private transient boolean isFinalizing = false;
+
+    private final transient SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-hh:mm:ss_SSS");
+
+    private final ConcurrentLinkedDeque<Triple<String, Throwable, Date>> allErrors = new ConcurrentLinkedDeque<>();
+
+    CollectedLogger(String name) {
+        this.name = name;
+    }
+
+    private void log(int level, String message, Throwable throwable) {
+        log(level, message, throwable, null);
+    }
+
+    private void log(int level, String message, Throwable throwable, Date timeOfError) {
+        if (!isLevelEnabled(level)) {
+            return;
+        }
+
+        if (level == LOG_LEVEL_ERROR && !isFinalizing) {
+            // Buffer errors for the final output
+            allErrors.add(new Triple<>(message, throwable, new Date()));
+            return;
+        }
+
+        StringBuilder builder = new StringBuilder(32);
+
+        // Append date-time
+        builder.append(dateFormat.format(timeOfError == null ? new Date() : timeOfError)).append(' ');
+
+        // Append current thread name
+        builder.append('[').append(Thread.currentThread().getName()).append("] ");
+        // Append current Level
+        builder.append('[').append(renderLevel(level)).append(']').append(' ');
+
+        // Append the name of the log instance
+        if (shortLogName == null)
+            shortLogName = computeShortName();
+        builder.append(shortLogName).append(" - ");
+        // Append the message
+        builder.append(message);
+
+        write(builder, throwable);
+    }
+
+    void printAllErrorsForLogger() {
+        this.isFinalizing = true;
+        // Copy errors to prevent infinite recursion
+        var errors = new ArrayList<>(this.allErrors);
+        if (errors.isEmpty())
+            return;
+
+        this.allErrors.removeAll(errors);
+
+        info("Summary of all Errors:");
+        errors.forEach(error -> log(LOG_LEVEL_ERROR, error.first(), error.second(), error.third()));
+        isFinalizing = false;
+    }
+
+    @SuppressWarnings("java:S106")
+    void write(StringBuilder buf, Throwable throwable) {
+        PrintStream targetStream = System.out;
+
+        targetStream.println(buf.toString());
+        writeThrowable(throwable, targetStream);
+        targetStream.flush();
+    }
+
+    private void writeThrowable(Throwable throwable, PrintStream targetStream) {
+        if (throwable != null) {
+            throwable.printStackTrace(targetStream);
+        }
+    }
+
+    private String computeShortName() {
+        return name.substring(name.lastIndexOf(".") + 1);
+    }
+
+    private boolean isLevelEnabled(int logLevel) {
+        return logLevel >= currentLogLevel;
+    }
+
+    private String renderLevel(int level) {
+        return switch (level) {
+            case LOG_LEVEL_TRACE -> "TRACE";
+            case LOG_LEVEL_DEBUG -> "DEBUG";
+            case LOG_LEVEL_INFO -> "INFO";
+            case LOG_LEVEL_WARN -> "WARN";
+            case LOG_LEVEL_ERROR -> "ERROR";
+            default -> throw new IllegalStateException("Unrecognized level [" + level + "]");
+        };
+    }
+
+    public boolean isTraceEnabled() {
+        return isLevelEnabled(LOG_LEVEL_TRACE);
+    }
+
+    @Override
+    public void trace(String message) {
+        log(LOG_LEVEL_TRACE, message, null);
+    }
+
+    public void trace(String format, Object param1) {
+        formatAndLog(LOG_LEVEL_TRACE, format, param1, null);
+    }
+
+    public void trace(String format, Object param1, Object param2) {
+        formatAndLog(LOG_LEVEL_TRACE, format, param1, param2);
+    }
+
+    public void trace(String format, Object... argArray) {
+        formatAndLog(LOG_LEVEL_TRACE, format, argArray);
+    }
+
+    public void trace(String message, Throwable t) {
+        log(LOG_LEVEL_TRACE, message, t);
+    }
+
+    public boolean isDebugEnabled() {
+        return isLevelEnabled(LOG_LEVEL_DEBUG);
+    }
+
+    public void debug(String message) {
+        log(LOG_LEVEL_DEBUG, message, null);
+    }
+
+    public void debug(String format, Object param1) {
+        formatAndLog(LOG_LEVEL_DEBUG, format, param1, null);
+    }
+
+    public void debug(String format, Object param1, Object param2) {
+        formatAndLog(LOG_LEVEL_DEBUG, format, param1, param2);
+    }
+
+    public void debug(String format, Object... argArray) {
+        formatAndLog(LOG_LEVEL_DEBUG, format, argArray);
+    }
+
+    public void debug(String message, Throwable throwable) {
+        log(LOG_LEVEL_DEBUG, message, throwable);
+    }
+
+    public boolean isInfoEnabled() {
+        return isLevelEnabled(LOG_LEVEL_INFO);
+    }
+
+    public void info(String message) {
+        log(LOG_LEVEL_INFO, message, null);
+    }
+
+    public void info(String format, Object arg) {
+        formatAndLog(LOG_LEVEL_INFO, format, arg, null);
+    }
+
+    public void info(String format, Object arg1, Object arg2) {
+        formatAndLog(LOG_LEVEL_INFO, format, arg1, arg2);
+    }
+
+    public void info(String format, Object... argArray) {
+        formatAndLog(LOG_LEVEL_INFO, format, argArray);
+    }
+
+    public void info(String message, Throwable throwable) {
+        log(LOG_LEVEL_INFO, message, throwable);
+    }
+
+    public boolean isWarnEnabled() {
+        return isLevelEnabled(LOG_LEVEL_WARN);
+    }
+
+    public void warn(String message) {
+        log(LOG_LEVEL_WARN, message, null);
+    }
+
+    public void warn(String format, Object arg) {
+        formatAndLog(LOG_LEVEL_WARN, format, arg, null);
+    }
+
+    public void warn(String format, Object arg1, Object arg2) {
+        formatAndLog(LOG_LEVEL_WARN, format, arg1, arg2);
+    }
+
+    public void warn(String format, Object... argArray) {
+        formatAndLog(LOG_LEVEL_WARN, format, argArray);
+    }
+
+    public void warn(String message, Throwable throwable) {
+        log(LOG_LEVEL_WARN, message, throwable);
+    }
+
+    public boolean isErrorEnabled() {
+        return isLevelEnabled(LOG_LEVEL_ERROR);
+    }
+
+    public void error(String message) {
+        log(LOG_LEVEL_ERROR, message, null);
+    }
+
+    public void error(String format, Object arg) {
+        formatAndLog(LOG_LEVEL_ERROR, format, arg, null);
+    }
+
+    public void error(String format, Object arg1, Object arg2) {
+        formatAndLog(LOG_LEVEL_ERROR, format, arg1, arg2);
+    }
+
+    public void error(String format, Object... argArray) {
+        formatAndLog(LOG_LEVEL_ERROR, format, argArray);
+    }
+
+    public void error(String message, Throwable throwable) {
+        log(LOG_LEVEL_ERROR, message, throwable);
+    }
+
+    private void formatAndLog(int level, String format, Object arg1, Object arg2) {
+        if (!isLevelEnabled(level)) {
+            return;
+        }
+        FormattingTuple formattingTuple = MessageFormatter.format(format, arg1, arg2);
+        log(level, formattingTuple.getMessage(), formattingTuple.getThrowable());
+    }
+
+    private void formatAndLog(int level, String format, Object... arguments) {
+        if (!isLevelEnabled(level)) {
+            return;
+        }
+        FormattingTuple formattingTuple = MessageFormatter.arrayFormat(format, arguments);
+        log(level, formattingTuple.getMessage(), formattingTuple.getThrowable());
+    }
+}

+ 44 - 0
jplag.cli/src/main/java/de/jplag/logger/CollectedLoggerFactory.java

@@ -0,0 +1,44 @@
+package de.jplag.logger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.slf4j.ILoggerFactory;
+import org.slf4j.Logger;
+
+/**
+ * The factory for {@link CollectedLogger}.
+ * @author Dominik Fuchss
+ */
+public final class CollectedLoggerFactory implements ILoggerFactory {
+
+    private final ConcurrentMap<String, CollectedLogger> loggerMap;
+
+    public CollectedLoggerFactory() {
+        loggerMap = new ConcurrentHashMap<>();
+    }
+
+    /**
+     * Return an appropriate {@link CollectedLogger} instance by name.
+     */
+    public Logger getLogger(String name) {
+        CollectedLogger simpleLogger = loggerMap.get(name);
+        if (simpleLogger != null) {
+            return simpleLogger;
+        } else {
+            CollectedLogger newInstance = new CollectedLogger(name);
+            Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);
+            return oldInstance == null ? newInstance : oldInstance;
+        }
+    }
+
+    /**
+     * Print all errors of existing instances of {@link CollectedLogger}.
+     */
+    public void finalizeInstances() {
+        List<CollectedLogger> copy = new ArrayList<>(loggerMap.values());
+        copy.forEach(CollectedLogger::printAllErrorsForLogger);
+    }
+}

+ 4 - 0
jplag.cli/src/main/java/de/jplag/logger/Triple.java

@@ -0,0 +1,4 @@
+package de.jplag.logger;
+
+public record Triple<A, B, C> (A first, B second, C third) {
+}

+ 54 - 0
jplag.cli/src/main/java/org/slf4j/impl/StaticLoggerBinder.java

@@ -0,0 +1,54 @@
+package org.slf4j.impl;
+
+import org.slf4j.ILoggerFactory;
+import org.slf4j.LoggerFactory;
+import org.slf4j.spi.LoggerFactoryBinder;
+
+import de.jplag.logger.CollectedLoggerFactory;
+
+/**
+ * The binding of {@link LoggerFactory} class with an actual instance of {@link ILoggerFactory} is performed using
+ * information returned by this class. The class has been adopted from SimpleLogger.
+ * @author Dominik Fuchss
+ */
+public class StaticLoggerBinder implements LoggerFactoryBinder {
+
+    /**
+     * The unique instance of this class.
+     */
+    private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
+
+    /**
+     * Return the singleton of this class.
+     * @return the StaticLoggerBinder singleton
+     */
+    public static StaticLoggerBinder getSingleton() {
+        return SINGLETON;
+    }
+
+    /**
+     * Declare the version of the SLF4J API this implementation is compiled against. The value of this field is modified
+     * with each major release.
+     */
+    // to avoid constant folding by the compiler, this field must *not* be final
+    public static String REQUESTED_API_VERSION = "1.6.99"; // !final
+
+    private static final String loggerFactoryClassStr = CollectedLoggerFactory.class.getName();
+
+    /**
+     * The ILoggerFactory instance returned by the {@link #getLoggerFactory} method should always be the same object
+     */
+    private final ILoggerFactory loggerFactory;
+
+    private StaticLoggerBinder() {
+        loggerFactory = new CollectedLoggerFactory();
+    }
+
+    public ILoggerFactory getLoggerFactory() {
+        return loggerFactory;
+    }
+
+    public String getLoggerFactoryClassStr() {
+        return loggerFactoryClassStr;
+    }
+}

+ 1 - 1
jplag.cli/src/test/java/de/jplag/cli/CommandLineInterfaceTest.java

@@ -17,7 +17,7 @@ import de.jplag.options.JPlagOptions;
  * line arguments, thus these tests do not test the functionality of the options during the comparison.
  * @author Timur Saglam
  */
-public class CommandLineInterfaceTest {
+public abstract class CommandLineInterfaceTest {
     protected static final String CURRENT_DIRECTORY = ".";
     protected static final float DELTA = 0.0001f;
 

+ 0 - 28
jplag.frontend-testutils/src/test/java/de/jplag/testutils/TestErrorConsumer.java

@@ -1,28 +0,0 @@
-package de.jplag.testutils;
-
-import static org.junit.jupiter.api.Assertions.fail;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import de.jplag.ErrorConsumer;
-
-/**
- * Mock error consumer that fails the test case on error occurrence.
- * @author Timur Saglam
- */
-public class TestErrorConsumer implements ErrorConsumer {
-    private final Logger logger = LoggerFactory.getLogger("JPlag-Test");
-
-    @Override
-    public void addError(String errorMessage) {
-        logger.error(errorMessage);
-        fail(errorMessage);
-    }
-
-    @Override
-    public void print(String message, String longMessage) {
-        logger.info(message);
-    }
-
-}

+ 6 - 16
jplag.frontend-utils/src/main/java/de/jplag/AbstractParser.java

@@ -1,15 +1,18 @@
 package de.jplag;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 /**
  * Abstract parser class. Counts errors and manages an error consumer.
  * @author Emeric Kwemou
  */
 public abstract class AbstractParser {
-    protected ErrorConsumer errorConsumer;
     protected int errors = 0;
+    public final Logger logger;
 
-    public AbstractParser(ErrorConsumer errorConsumer) {
-        this.errorConsumer = errorConsumer;
+    protected AbstractParser() {
+        this.logger = LoggerFactory.getLogger(this.getClass());
     }
 
     /**
@@ -18,17 +21,4 @@ public abstract class AbstractParser {
     public boolean hasErrors() {
         return errors != 0;
     }
-
-    /**
-     * @return the error consumer that collects and prints errors.
-     */
-    public ErrorConsumer getErrorConsumer() {
-        return errorConsumer;
-    }
-
-    /**
-     * Setter for the error consumer that collects and prints errors.
-     * @param errorConsumer is the consumer to set.
-     */
-
 }

+ 0 - 20
jplag.frontend-utils/src/main/java/de/jplag/ErrorConsumer.java

@@ -1,20 +0,0 @@
-package de.jplag;
-
-/**
- * Consumes errors from a language parser.
- */
-public interface ErrorConsumer {
-
-    /**
-     * Adds an error to the error stack. The error is only printed directly when using the corresponding verbosity option.
-     * @param errorMessage is the error message.
-     */
-    void addError(String errorMessage);
-
-    /**
-     * Prints a message depending on the verbosity.
-     * @param message is the message for the normal mode.
-     * @param longMessage is the message for verbose mode.
-     */
-    void print(String message, String longMessage);
-}

+ 10 - 15
jplag.frontend-utils/src/main/java/de/jplag/Language.java

@@ -8,7 +8,7 @@ import java.io.File;
 public interface Language {
 
     /**
-     * Suffixes for the files containing code of the language.
+     * Suffixes for the files containing code of the language. An empty array means all suffixes are valid.
      */
     String[] suffixes();
 
@@ -41,22 +41,17 @@ public interface Language {
     boolean hasErrors();
 
     /**
-     * Determines whether the parser provide column information.
+     * Determines whether the parser provide column information. If that is the case, line and column indices are used
+     * instead of a single token index.
      */
-    boolean supportsColumns();
+    default boolean supportsColumns() {
+        return true;
+    }
 
     /**
-     * Determines whether JPlag should use a fixed-width font in its reports.
+     * Determines whether a fixed-width font should be used to display that language.
      */
-    boolean isPreformatted();
-
-    /**
-     * Determines whether tokens from the scanner are indexed.
-     */
-    boolean usesIndex();
-
-    /**
-     * Number of defined tokens in the scanner of the language.
-     */
-    int numberOfTokens();
+    default boolean isPreformatted() {
+        return true;
+    }
 }

+ 2 - 13
jplag.frontend.chars/src/main/java/de/jplag/chars/Language.java

@@ -2,7 +2,6 @@ package de.jplag.chars;
 
 import java.io.File;
 
-import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
 /*
@@ -12,8 +11,8 @@ public class Language implements de.jplag.Language {
 
     private final Parser parser;
 
-    public Language(ErrorConsumer program) {
-        parser = new Parser(program);
+    public Language() {
+        parser = new Parser();
     }
 
     @Override
@@ -55,14 +54,4 @@ public class Language implements de.jplag.Language {
     public boolean isPreformatted() {
         return false;
     }
-
-    @Override
-    public boolean usesIndex() {
-        return true;
-    }
-
-    @Override
-    public int numberOfTokens() {
-        return 36;
-    }
 }

+ 7 - 14
jplag.frontend.chars/src/main/java/de/jplag/chars/Parser.java

@@ -5,7 +5,6 @@ import java.io.FileReader;
 import java.nio.charset.StandardCharsets;
 
 import de.jplag.AbstractParser;
-import de.jplag.ErrorConsumer;
 import de.jplag.TokenConstants;
 import de.jplag.TokenList;
 
@@ -15,25 +14,24 @@ public class Parser extends AbstractParser {
 
     /**
      * Creates the parser.
-     * @param errorConsumer is the consumer for any occurring errors.
      */
-    public Parser(ErrorConsumer errorConsumer) {
-        super(errorConsumer);
+    public Parser() {
+        super();
     }
 
     public TokenList parse(File directory, String[] files) {
         tokens = new TokenList();
         errors = 0;
         for (String file : files) {
-            getErrorConsumer().print(null, "Parsing file " + file);
+            logger.trace("Parsing file {}", file);
             if (!parseFile(directory, file))
                 errors++;
             tokens.addToken(new CharToken(TokenConstants.FILE_END, file, this));
         }
         if (errors == 0)
-            errorConsumer.print(null, "OK");
+            logger.trace("OK");
         else
-            errorConsumer.print(null, errors + " ERROR" + (errors > 1 ? "S" : ""));
+            logger.error(errors + " ERROR" + (errors > 1 ? "S" : ""));
 
         return tokens;
     }
@@ -44,9 +42,7 @@ public class Parser extends AbstractParser {
         int length;
         int offset = 0;
 
-        try {
-            FileReader reader = new FileReader(new File(dir, file), StandardCharsets.UTF_8);
-
+        try (FileReader reader = new FileReader(new File(dir, file), StandardCharsets.UTF_8)) {
             do {
                 length = reader.read(buffer);
 
@@ -57,11 +53,8 @@ public class Parser extends AbstractParser {
                 }
                 offset += length;
             } while (length != -1);
-
-            // close file
-            reader.close();
         } catch (Exception e) {
-            e.printStackTrace();
+            logger.error(e.getMessage(), e);
             return false;
         }
         return true;

+ 2 - 23
jplag.frontend.cpp/src/main/java/de/jplag/cpp/Language.java

@@ -2,14 +2,13 @@ package de.jplag.cpp;
 
 import java.io.File;
 
-import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
 public class Language implements de.jplag.Language {
     private final Scanner scanner; // cpp code is scanned not parsed
 
-    public Language(ErrorConsumer errorConsumer) {
-        scanner = new Scanner(errorConsumer);
+    public Language() {
+        scanner = new Scanner();
     }
 
     @Override
@@ -32,21 +31,6 @@ public class Language implements de.jplag.Language {
         return 12;
     }
 
-    @Override
-    public boolean supportsColumns() {
-        return true;
-    }
-
-    @Override
-    public boolean isPreformatted() {
-        return true;
-    }
-
-    @Override
-    public boolean usesIndex() {
-        return false;
-    }
-
     @Override
     public TokenList parse(File dir, String[] files) {
         return this.scanner.scan(dir, files);
@@ -56,9 +40,4 @@ public class Language implements de.jplag.Language {
     public boolean hasErrors() {
         return this.scanner.hasErrors();
     }
-
-    @Override
-    public int numberOfTokens() {
-        return CPPTokenConstants.NUM_DIFF_TOKENS;
-    }
 }

+ 3 - 5
jplag.frontend.cpp/src/main/java/de/jplag/cpp/Scanner.java

@@ -3,7 +3,6 @@ package de.jplag.cpp;
 import java.io.File;
 
 import de.jplag.AbstractParser;
-import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
 public class Scanner extends AbstractParser {
@@ -13,10 +12,9 @@ public class Scanner extends AbstractParser {
 
     /**
      * Creates the parser.
-     * @param errorConsumer is the consumer for any occurring errors.
      */
-    public Scanner(ErrorConsumer errorConsumer) {
-        super(errorConsumer);
+    public Scanner() {
+        super();
     }
 
     public TokenList scan(File directory, String[] files) {
@@ -24,7 +22,7 @@ public class Scanner extends AbstractParser {
         errors = 0;
         for (String currentFile : files) {
             this.currentFile = currentFile;
-            getErrorConsumer().print(null, "Scanning file " + currentFile);
+            logger.trace("Scanning file {}", currentFile);
             if (!CPPScanner.scanFile(directory, currentFile, this)) {
                 errors++;
             }

+ 2 - 2
jplag.frontend.cpp/src/main/javacc/CPP.jj

@@ -37,10 +37,10 @@ public class CPPScanner implements CPPTokenConstants {
         try {
             scanner.scan();
         } catch (ParseException e) {
-            delegatingScanner.getErrorConsumer().addError("  Parsing Error in '" + fileName + "':\n  " + e.getMessage());
+            delegatingScanner.logger.error("Parsing Error in '" + fileName + "': " + e.getMessage());
             return false;
         } catch (TokenMgrException e) {
-            delegatingScanner.getErrorConsumer().addError("  Scanning Error in '" + fileName + "':\n  " + e.getMessage());
+            delegatingScanner.logger.error("Scanning Error in '" + fileName + "': " + e.getMessage());
             return false;
         }
         return true;

+ 1 - 1
jplag.frontend.csharp-6/src/main/java/de/jplag/csharp/CSharpListener.java

@@ -215,7 +215,7 @@ public class CSharpListener extends CSharpParserBaseListener implements CSharpTo
 
     @Override
     public void enterUsingStatement(UsingStatementContext context) {
-        transformToken(USING, null);
+        transformToken(USING, context.getStart());
         super.enterUsingStatement(context);
     }
 

+ 4 - 6
jplag.frontend.csharp-6/src/main/java/de/jplag/csharp/CSharpParserAdapter.java

@@ -12,7 +12,6 @@ import org.antlr.v4.runtime.tree.ParseTree;
 import org.antlr.v4.runtime.tree.ParseTreeWalker;
 
 import de.jplag.AbstractParser;
-import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 import de.jplag.csharp.grammar.CSharpLexer;
 import de.jplag.csharp.grammar.CSharpParser;
@@ -28,10 +27,9 @@ public class CSharpParserAdapter extends AbstractParser {
 
     /**
      * Creates the parser adapter.
-     * @param errorConsumer is the consumer for any occurring errors.
      */
-    public CSharpParserAdapter(ErrorConsumer errorConsumer) {
-        super(errorConsumer);
+    public CSharpParserAdapter() {
+        super();
     }
 
     /**
@@ -54,7 +52,7 @@ public class CSharpParserAdapter extends AbstractParser {
 
     private boolean parseFile(File directory, String fileName) {
         File file = new File(directory, fileName);
-        try (FileInputStream inputStream = new FileInputStream(file);) {
+        try (FileInputStream inputStream = new FileInputStream(file)) {
             currentFile = fileName;
 
             // create a lexer, a parser and a buffer between them.
@@ -72,7 +70,7 @@ public class CSharpParserAdapter extends AbstractParser {
                 treeWalker.walk(new CSharpListener(this), parseTree);
             }
         } catch (IOException exception) {
-            getErrorConsumer().addError("Parsing Error in '" + fileName + "':" + File.separator + exception.toString());
+            logger.error("Parsing Error in '" + fileName + "':" + File.separator + exception, exception);
             return false;
         }
         return true;

+ 2 - 23
jplag.frontend.csharp-6/src/main/java/de/jplag/csharp/Language.java

@@ -3,7 +3,6 @@ package de.jplag.csharp;
 import java.io.File;
 import java.util.Arrays;
 
-import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
 /**
@@ -18,8 +17,8 @@ public class Language implements de.jplag.Language {
 
     private final CSharpParserAdapter parser;
 
-    public Language(ErrorConsumer program) {
-        parser = new CSharpParserAdapter(program);
+    public Language() {
+        parser = new CSharpParserAdapter();
     }
 
     @Override
@@ -51,24 +50,4 @@ public class Language implements de.jplag.Language {
     public boolean hasErrors() {
         return parser.hasErrors();
     }
-
-    @Override
-    public boolean supportsColumns() {
-        return true;
-    }
-
-    @Override
-    public boolean isPreformatted() {
-        return true;
-    }
-
-    @Override
-    public boolean usesIndex() {
-        return false;
-    }
-
-    @Override
-    public int numberOfTokens() {
-        return CSharpTokenConstants.NUM_DIFF_TOKENS;
-    }
 }

+ 11 - 7
jplag.frontend.csharp-6/src/main/java/de/jplag/csharp/grammar/CSharpPreprocessorParserBase.java

@@ -1,14 +1,18 @@
 package de.jplag.csharp.grammar;
 
 import java.util.HashSet;
+import java.util.Objects;
 import java.util.Stack;
 
-import org.antlr.v4.runtime.*;
+import org.antlr.v4.runtime.Parser;
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.TokenStream;
 
 /**
  * This class was taken from <a href="https://github.com/antlr/grammars-v4/tree/master/csharp">antlr/grammars-v4</a>. It
  * was originally written by Ken Domino. Note that this class is licensed under Eclipse Public License - v 1.0.
  */
+@SuppressWarnings("java:S100")
 abstract class CSharpPreprocessorParserBase extends Parser {
     protected CSharpPreprocessorParserBase(TokenStream input) {
         super(input);
@@ -16,7 +20,7 @@ abstract class CSharpPreprocessorParserBase extends Parser {
         ConditionalSymbols.add("DEBUG");
     }
 
-    Stack<Boolean> conditions = new Stack<Boolean>();
+    Stack<Boolean> conditions = new Stack<>();
     public HashSet<String> ConditionalSymbols = new HashSet<String>();
 
     protected Boolean AllConditions() {
@@ -51,7 +55,7 @@ abstract class CSharpPreprocessorParserBase extends Parser {
     protected void OnPreprocessorDirectiveElif() {
         ParserRuleContext c = this._ctx;
         CSharpPreprocessorParser.PreprocessorConditionalContext d = (CSharpPreprocessorParser.PreprocessorConditionalContext) c;
-        if (!conditions.peek()) {
+        if (Boolean.FALSE.equals(conditions.peek())) {
             conditions.pop();
             d.value = d.expr.value.equals("true") && AllConditions();
             conditions.push(d.expr.value.equals("true"));
@@ -63,9 +67,9 @@ abstract class CSharpPreprocessorParserBase extends Parser {
     protected void OnPreprocessorDirectiveElse() {
         ParserRuleContext c = this._ctx;
         CSharpPreprocessorParser.PreprocessorConditionalContext d = (CSharpPreprocessorParser.PreprocessorConditionalContext) c;
-        if (!conditions.peek()) {
+        if (Boolean.FALSE.equals(conditions.peek())) {
             conditions.pop();
-            d.value = true && AllConditions();
+            d.value = AllConditions();
             conditions.push(true);
         } else {
             d.value = false;
@@ -154,13 +158,13 @@ abstract class CSharpPreprocessorParserBase extends Parser {
     protected void OnPreprocessorExpressionConditionalEq() {
         ParserRuleContext c = this._ctx;
         CSharpPreprocessorParser.Preprocessor_expressionContext d = (CSharpPreprocessorParser.Preprocessor_expressionContext) c;
-        d.value = (d.expr1.value == d.expr2.value ? "true" : "false");
+        d.value = (Objects.equals(d.expr1.value, d.expr2.value) ? "true" : "false");
     }
 
     protected void OnPreprocessorExpressionConditionalNe() {
         ParserRuleContext c = this._ctx;
         CSharpPreprocessorParser.Preprocessor_expressionContext d = (CSharpPreprocessorParser.Preprocessor_expressionContext) c;
-        d.value = (d.expr1.value != d.expr2.value ? "true" : "false");
+        d.value = (!Objects.equals(d.expr1.value, d.expr2.value) ? "true" : "false");
     }
 
     protected void OnPreprocessorExpressionConditionalAnd() {

+ 1 - 3
jplag.frontend.csharp-6/src/test/java/de/jplag/csharp/MinimalCSharpFrontendTest.java

@@ -20,7 +20,6 @@ import de.jplag.Token;
 import de.jplag.TokenConstants;
 import de.jplag.TokenList;
 import de.jplag.TokenPrinter;
-import de.jplag.testutils.TestErrorConsumer;
 
 class MinimalCSharpFrontendTest {
     private final Logger logger = LoggerFactory.getLogger("JPlag-Test");
@@ -33,8 +32,7 @@ class MinimalCSharpFrontendTest {
 
     @BeforeEach
     public void setUp() {
-        TestErrorConsumer consumer = new TestErrorConsumer();
-        frontend = new Language(consumer);
+        frontend = new Language();
         baseDirectory = BASE_PATH.toFile();
         assertTrue(baseDirectory.exists(), "Could not find base directory!");
     }

+ 1 - 1
jplag.frontend.java/src/main/java/de/jplag/java/JavaTokenConstants.java

@@ -84,5 +84,5 @@ public interface JavaTokenConstants extends TokenConstants {
     int J_RECORD_BEGIN = 68;
     int J_RECORD_END = 69;
 
-    final static int NUM_DIFF_TOKENS = 70;	    // @formatter:on
+    int NUM_DIFF_TOKENS = 70;	    // @formatter:on
 }

+ 6 - 9
jplag.frontend.java/src/main/java/de/jplag/java/JavacAdapter.java

@@ -6,12 +6,10 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Paths;
 import java.util.Collections;
 
-import javax.tools.Diagnostic;
-import javax.tools.DiagnosticCollector;
-import javax.tools.JavaCompiler;
+import javax.tools.*;
 import javax.tools.JavaCompiler.CompilationTask;
-import javax.tools.StandardJavaFileManager;
-import javax.tools.ToolProvider;
+
+import org.slf4j.Logger;
 
 import com.sun.source.tree.CompilationUnitTree;
 import com.sun.source.tree.LineMap;
@@ -19,7 +17,6 @@ import com.sun.source.util.JavacTask;
 import com.sun.source.util.SourcePositions;
 import com.sun.source.util.Trees;
 
-import de.jplag.ErrorConsumer;
 import de.jplag.TokenConstants;
 
 public class JavacAdapter {
@@ -39,7 +36,7 @@ public class JavacAdapter {
             ast.accept(new TokenGeneratingTreeScanner(filename, parser, map, positions, ast), null);
             parser.add(TokenConstants.FILE_END, filename, 1, -1, -1);
         }
-        return processErrors(parser.getErrorConsumer(), listener);
+        return processErrors(parser.logger, listener);
     }
 
     private Iterable<? extends CompilationUnitTree> executeCompilationTask(final CompilationTask task) {
@@ -60,11 +57,11 @@ public class JavacAdapter {
         }
     }
 
-    private int processErrors(final ErrorConsumer consumer, DiagnosticCollector<Object> listener) {
+    private int processErrors(Logger logger, DiagnosticCollector<Object> listener) {
         int errors = 0;
         for (Diagnostic<?> diagnosticItem : listener.getDiagnostics()) {
             if (diagnosticItem.getKind() == javax.tools.Diagnostic.Kind.ERROR) {
-                consumer.addError(diagnosticItem.toString());
+                logger.error("{}", diagnosticItem);
                 errors++;
             }
         }

+ 2 - 23
jplag.frontend.java/src/main/java/de/jplag/java/Language.java

@@ -2,7 +2,6 @@ package de.jplag.java;
 
 import java.io.File;
 
-import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
 /**
@@ -11,8 +10,8 @@ import de.jplag.TokenList;
 public class Language implements de.jplag.Language {
     private final Parser parser;
 
-    public Language(ErrorConsumer errorConsumer) {
-        parser = new Parser(errorConsumer);
+    public Language() {
+        parser = new Parser();
     }
 
     @Override
@@ -35,26 +34,6 @@ public class Language implements de.jplag.Language {
         return 9;
     }
 
-    @Override
-    public boolean supportsColumns() {
-        return true;
-    }
-
-    @Override
-    public boolean isPreformatted() {
-        return true;
-    }
-
-    @Override
-    public boolean usesIndex() {
-        return false;
-    }
-
-    @Override
-    public int numberOfTokens() {
-        return JavaTokenConstants.NUM_DIFF_TOKENS;
-    }
-
     @Override
     public TokenList parse(File directory, String[] files) {
         return this.parser.parse(directory, files);

+ 3 - 7
jplag.frontend.java/src/main/java/de/jplag/java/Parser.java

@@ -1,12 +1,9 @@
 package de.jplag.java;
 
-import static java.util.stream.Collectors.toList;
-
 import java.io.File;
 import java.util.Arrays;
 
 import de.jplag.AbstractParser;
-import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
 public class Parser extends AbstractParser {
@@ -14,16 +11,15 @@ public class Parser extends AbstractParser {
 
     /**
      * Creates the parser.
-     * @param errorConsumer is the consumer for any occurring errors.
      */
-    public Parser(ErrorConsumer errorConsumer) {
-        super(errorConsumer);
+    public Parser() {
+        super();
     }
 
     public TokenList parse(File directory, String[] files) {
         tokens = new TokenList();
         errors = 0;
-        var pathedFiles = Arrays.stream(files).map(it -> new File(directory, it)).collect(toList());
+        var pathedFiles = Arrays.stream(files).map(it -> new File(directory, it)).toList();
         errors += new JavacAdapter().parseFiles(directory, pathedFiles, this);
         return tokens;
     }

+ 3 - 5
jplag.frontend.kotlin/src/main/java/de/jplag/kotlin/KotlinParserAdapter.java

@@ -11,7 +11,6 @@ import org.antlr.v4.runtime.tree.ParseTree;
 import org.antlr.v4.runtime.tree.ParseTreeWalker;
 
 import de.jplag.AbstractParser;
-import de.jplag.ErrorConsumer;
 import de.jplag.TokenConstants;
 import de.jplag.TokenList;
 import de.jplag.kotlin.grammar.KotlinLexer;
@@ -25,10 +24,9 @@ public class KotlinParserAdapter extends AbstractParser {
 
     /**
      * Creates the KotlinParserAdapter
-     * @param consumer the ErrorConsumer that parser errors are passed on to.
      */
-    public KotlinParserAdapter(ErrorConsumer consumer) {
-        super(consumer);
+    public KotlinParserAdapter() {
+        super();
     }
 
     /**
@@ -66,7 +64,7 @@ public class KotlinParserAdapter extends AbstractParser {
                 treeWalker.walk(listener, parseTree);
             }
         } catch (IOException exception) {
-            getErrorConsumer().addError("Parsing Error in '%s': %s%s".formatted(fileName, File.separator, exception));
+            logger.error("Parsing Error in '{}': {}{}", fileName, File.separator, exception);
             return false;
         }
         return true;

+ 2 - 23
jplag.frontend.kotlin/src/main/java/de/jplag/kotlin/Language.java

@@ -2,7 +2,6 @@ package de.jplag.kotlin;
 
 import java.io.File;
 
-import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
 /**
@@ -16,8 +15,8 @@ public class Language implements de.jplag.Language {
     private static final String[] FILE_EXTENSIONS = {".kt"};
     private final KotlinParserAdapter parserAdapter;
 
-    public Language(ErrorConsumer consumer) {
-        this.parserAdapter = new KotlinParserAdapter(consumer);
+    public Language() {
+        this.parserAdapter = new KotlinParserAdapter();
     }
 
     @Override
@@ -49,24 +48,4 @@ public class Language implements de.jplag.Language {
     public boolean hasErrors() {
         return parserAdapter.hasErrors();
     }
-
-    @Override
-    public boolean supportsColumns() {
-        return true;
-    }
-
-    @Override
-    public boolean isPreformatted() {
-        return true;
-    }
-
-    @Override
-    public boolean usesIndex() {
-        return false;
-    }
-
-    @Override
-    public int numberOfTokens() {
-        return KotlinTokenConstants.NUMBER_DIFF_TOKENS;
-    }
 }

+ 3 - 5
jplag.frontend.kotlin/src/test/java/de/jplag/kotlin/KotlinFrontendTest.java

@@ -21,7 +21,6 @@ import de.jplag.Token;
 import de.jplag.TokenConstants;
 import de.jplag.TokenList;
 import de.jplag.TokenPrinter;
-import de.jplag.testutils.TestErrorConsumer;
 
 class KotlinFrontendTest {
 
@@ -54,8 +53,7 @@ class KotlinFrontendTest {
 
     @BeforeEach
     void setup() {
-        TestErrorConsumer consumer = new TestErrorConsumer();
-        language = new Language(consumer);
+        language = new Language();
     }
 
     @Test
@@ -78,7 +76,7 @@ class KotlinFrontendTest {
      */
     @Test
     void testTokenToString() {
-        var missingTokens = IntStream.range(0, language.numberOfTokens())
+        var missingTokens = IntStream.range(0, KotlinTokenConstants.NUMBER_DIFF_TOKENS)
                 .mapToObj(type -> new KotlinToken(type, NOT_SET_STRING, NOT_SET, NOT_SET, NOT_SET))
                 .filter(token -> token.type2string().contains("UNKNOWN")).toList();
 
@@ -158,7 +156,7 @@ class KotlinFrontendTest {
     private void testTokenCoverage(TokenList tokens, String fileName) {
         var foundTokens = StreamSupport.stream(tokens.allTokens().spliterator(), true).mapToInt(Token::getType).sorted().distinct().toArray();
         // Exclude SEPARATOR_TOKEN, as it does not occur
-        var allTokens = IntStream.range(0, language.numberOfTokens()).filter(i -> i != TokenConstants.SEPARATOR_TOKEN).toArray();
+        var allTokens = IntStream.range(0, KotlinTokenConstants.NUMBER_DIFF_TOKENS).filter(i -> i != TokenConstants.SEPARATOR_TOKEN).toArray();
 
         if (allTokens.length > foundTokens.length) {
             var diffLine = IntStream.range(0, allTokens.length)

+ 2 - 23
jplag.frontend.python-3/src/main/java/de/jplag/python3/Language.java

@@ -2,15 +2,14 @@ package de.jplag.python3;
 
 import java.io.File;
 
-import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
 public class Language implements de.jplag.Language {
 
     private final Parser parser;
 
-    public Language(ErrorConsumer errorConsumer) {
-        parser = new Parser(errorConsumer);
+    public Language() {
+        parser = new Parser();
     }
 
     @Override
@@ -42,24 +41,4 @@ public class Language implements de.jplag.Language {
     public boolean hasErrors() {
         return this.parser.hasErrors();
     }
-
-    @Override
-    public boolean supportsColumns() {
-        return true;
-    }
-
-    @Override
-    public boolean isPreformatted() {
-        return true;
-    }
-
-    @Override
-    public boolean usesIndex() {
-        return false;
-    }
-
-    @Override
-    public int numberOfTokens() {
-        return Python3TokenConstants.NUM_DIFF_TOKENS;
-    }
 }

+ 7 - 9
jplag.frontend.python-3/src/main/java/de/jplag/python3/Parser.java

@@ -13,7 +13,6 @@ import org.antlr.v4.runtime.tree.ParseTree;
 import org.antlr.v4.runtime.tree.ParseTreeWalker;
 
 import de.jplag.AbstractParser;
-import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 import de.jplag.python3.grammar.Python3Lexer;
 import de.jplag.python3.grammar.Python3Parser;
@@ -26,21 +25,20 @@ public class Parser extends AbstractParser {
 
     /**
      * Creates the parser.
-     * @param errorConsumer is the consumer for any occurring errors.
      */
-    public Parser(ErrorConsumer errorConsumer) {
-        super(errorConsumer);
+    public Parser() {
+        super();
     }
 
     public TokenList parse(File directory, String[] files) {
         tokens = new TokenList();
         errors = 0;
-        for (int i = 0; i < files.length; i++) {
-            getErrorConsumer().print(null, "Parsing file " + files[i]);
-            if (!parseFile(directory, files[i])) {
+        for (String file : files) {
+            logger.trace("Parsing file {}", file);
+            if (!parseFile(directory, file)) {
                 errors++;
             }
-            tokens.addToken(new Python3Token(Python3TokenConstants.FILE_END, files[i], -1, -1, -1));
+            tokens.addToken(new Python3Token(Python3TokenConstants.FILE_END, file, -1, -1, -1));
         }
         return tokens;
     }
@@ -71,7 +69,7 @@ public class Parser extends AbstractParser {
             }
 
         } catch (IOException e) {
-            getErrorConsumer().addError("Parsing Error in '" + file + "':\n" + e.getMessage());
+            logger.error("Parsing Error in '" + file + "': " + e.getMessage(), e);
             return false;
         }
 

+ 2 - 23
jplag.frontend.rlang/src/main/java/de/jplag/rlang/Language.java

@@ -2,7 +2,6 @@ package de.jplag.rlang;
 
 import java.io.File;
 
-import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
 /**
@@ -16,8 +15,8 @@ public class Language implements de.jplag.Language {
     private static final String[] FILE_EXTENSION = {".R", ".r"};
     private final RParserAdapter parserAdapter;
 
-    public Language(ErrorConsumer consumer) {
-        this.parserAdapter = new RParserAdapter(consumer);
+    public Language() {
+        this.parserAdapter = new RParserAdapter();
     }
 
     @Override
@@ -49,24 +48,4 @@ public class Language implements de.jplag.Language {
     public boolean hasErrors() {
         return parserAdapter.hasErrors();
     }
-
-    @Override
-    public boolean supportsColumns() {
-        return true;
-    }
-
-    @Override
-    public boolean isPreformatted() {
-        return true;
-    }
-
-    @Override
-    public boolean usesIndex() {
-        return false;
-    }
-
-    @Override
-    public int numberOfTokens() {
-        return RTokenConstants.NUM_DIFF_TOKENS;
-    }
 }

+ 3 - 5
jplag.frontend.rlang/src/main/java/de/jplag/rlang/RParserAdapter.java

@@ -11,7 +11,6 @@ import org.antlr.v4.runtime.tree.ParseTree;
 import org.antlr.v4.runtime.tree.ParseTreeWalker;
 
 import de.jplag.AbstractParser;
-import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 import de.jplag.rlang.grammar.RFilter;
 import de.jplag.rlang.grammar.RLexer;
@@ -28,10 +27,9 @@ public class RParserAdapter extends AbstractParser implements RTokenConstants {
 
     /**
      * Creates the RParserAdapter
-     * @param errorConsumer the ErrorConsumer that parser errors are passed on to.
      */
-    public RParserAdapter(ErrorConsumer errorConsumer) {
-        super(errorConsumer);
+    public RParserAdapter() {
+        super();
     }
 
     /**
@@ -77,7 +75,7 @@ public class RParserAdapter extends AbstractParser implements RTokenConstants {
                 treeWalker.walk(new JplagRListener(this), parseTree);
             }
         } catch (IOException exception) {
-            getErrorConsumer().addError("Parsing Error in '" + fileName + "':" + File.separator + exception);
+            logger.error("Parsing Error in '" + fileName + "': " + File.separator + exception.getMessage(), exception);
             return false;
         }
         return true;

+ 2 - 4
jplag.frontend.rlang/src/test/java/de/jplag/rlang/RFrontendTest.java

@@ -19,9 +19,8 @@ import de.jplag.Token;
 import de.jplag.TokenConstants;
 import de.jplag.TokenList;
 import de.jplag.TokenPrinter;
-import de.jplag.testutils.TestErrorConsumer;
 
-public class RFrontendTest {
+class RFrontendTest {
 
     /**
      * Regular expression for lines that contain no code.
@@ -41,8 +40,7 @@ public class RFrontendTest {
 
     @BeforeEach
     void setup() {
-        TestErrorConsumer consumer = new TestErrorConsumer();
-        language = new Language(consumer);
+        language = new Language();
     }
 
     @Test

+ 2 - 23
jplag.frontend.scheme/src/main/java/de/jplag/scheme/Language.java

@@ -2,14 +2,13 @@ package de.jplag.scheme;
 
 import java.io.File;
 
-import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
 public class Language implements de.jplag.Language {
     private final de.jplag.scheme.Parser parser;
 
-    public Language(ErrorConsumer program) {
-        parser = new Parser(program);
+    public Language() {
+        parser = new Parser();
     }
 
     @Override
@@ -32,21 +31,6 @@ public class Language implements de.jplag.Language {
         return 13;
     }
 
-    @Override
-    public boolean supportsColumns() {
-        return true;
-    }
-
-    @Override
-    public boolean isPreformatted() {
-        return true;
-    }
-
-    @Override
-    public boolean usesIndex() {
-        return false;
-    }
-
     @Override
     public TokenList parse(File dir, String[] files) {
         return this.parser.parse(dir, files);
@@ -56,9 +40,4 @@ public class Language implements de.jplag.Language {
     public boolean hasErrors() {
         return this.parser.hasErrors();
     }
-
-    @Override
-    public int numberOfTokens() {
-        return SchemeTokenConstants.NUM_DIFF_TOKENS;
-    }
 }

+ 6 - 8
jplag.frontend.scheme/src/main/java/de/jplag/scheme/Parser.java

@@ -3,7 +3,6 @@ package de.jplag.scheme;
 import java.io.File;
 
 import de.jplag.AbstractParser;
-import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
 public class Parser extends AbstractParser {
@@ -13,19 +12,18 @@ public class Parser extends AbstractParser {
 
     /**
      * Creates the parser.
-     * @param errorConsumer is the consumer for any occurring errors.
      */
-    public Parser(ErrorConsumer errorConsumer) {
-        super(errorConsumer);
+    public Parser() {
+        super();
     }
 
     public TokenList parse(File directory, String[] files) {
         tokens = new TokenList();
         errors = 0;
-        for (int i = 0; i < files.length; i++) {
-            currentFile = files[i];
-            getErrorConsumer().print(null, "Parsing file " + files[i]);
-            if (!SchemeParser.parseFile(directory, files[i], null, this))
+        for (String file : files) {
+            currentFile = file;
+            logger.trace("Parsing file {}", file);
+            if (!SchemeParser.parseFile(directory, file, null, this))
                 errors++;
             tokens.addToken(new SchemeToken(SchemeTokenConstants.FILE_END, currentFile));
         }

+ 2 - 2
jplag.frontend.scheme/src/main/javacc/Scheme.jj

@@ -78,10 +78,10 @@ public class SchemeParser implements SchemeTokenConstants {
         try {
             parser.Program();
         } catch (ParseException e) {
-            parserX.getErrorConsumer().addError("  Parsing Error in '" + fileName + "':\n  " + e.getMessage() + "\n");
+            parserX.logger.error("Parsing Error in '" + fileName + "': " + e.getMessage());
             return false;
         } catch (TokenMgrException e) {
-            parserX.getErrorConsumer().addError("  Scanning Error in '" + fileName + "':\n  " + e.getMessage() + "\n");
+            parserX.logger.error("Scanning Error in '" + fileName + "': " + e.getMessage());
             return false;
         }
         return true;

+ 1 - 0
jplag.frontend.text/src/main/java/de/jplag/text/InputState.java

@@ -10,6 +10,7 @@ import antlr.LexerSharedInputState;
  * This object contains the data associated with an input stream of characters. Multiple lexers share a single
  * LexerSharedInputState to lex the same input stream.
  */
+@SuppressWarnings({"java:S1104", "java:S2387"})
 public class InputState extends LexerSharedInputState {
     public int column = 0;
     public int tokenColumn = 0;

+ 2 - 19
jplag.frontend.text/src/main/java/de/jplag/text/Language.java

@@ -1,17 +1,15 @@
-
 package de.jplag.text;
 
 import java.io.File;
 
-import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
 public class Language implements de.jplag.Language {
 
     private final Parser parser;
 
-    public Language(ErrorConsumer errorConsumer) {
-        parser = new Parser(errorConsumer);
+    public Language() {
+        parser = new Parser();
     }
 
     @Override
@@ -44,23 +42,8 @@ public class Language implements de.jplag.Language {
         return this.parser.hasErrors();
     }
 
-    @Override
-    public boolean supportsColumns() {
-        return true;
-    }
-
     @Override
     public boolean isPreformatted() {
         return false;
     }
-
-    @Override
-    public boolean usesIndex() {
-        return false;
-    }
-
-    @Override
-    public int numberOfTokens() {
-        return parser.serial;
-    }
 }

+ 6 - 14
jplag.frontend.text/src/main/java/de/jplag/text/Parser.java

@@ -4,20 +4,14 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.util.Hashtable;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import antlr.Token;
 
 import de.jplag.AbstractParser;
-import de.jplag.ErrorConsumer;
 import de.jplag.TokenConstants;
 import de.jplag.TokenList;
 
 public class Parser extends AbstractParser {
 
-    private static final Logger logger = LoggerFactory.getLogger(Parser.class);
-
     protected Hashtable<String, Integer> table = new Hashtable<>();
     protected int serial = 1; // 0 is FILE_END token
 
@@ -29,17 +23,16 @@ public class Parser extends AbstractParser {
 
     /**
      * Creates the parser.
-     * @param errorConsumer is the consumer for any occurring errors.
      */
-    public Parser(ErrorConsumer errorConsumer) {
-        super(errorConsumer);
+    public Parser() {
+        super();
     }
 
     public TokenList parse(File directory, String[] files) {
         tokens = new TokenList();
         errors = 0;
         for (String file : files) {
-            getErrorConsumer().print("", "Parsing file " + file);
+            logger.trace("Parsing file {}", file);
             if (!parseFile(directory, file))
                 errors++;
             tokens.addToken(new TextToken(TokenConstants.FILE_END, file, this));
@@ -60,8 +53,7 @@ public class Parser extends AbstractParser {
             return;
         runOut = true;
         errors++;
-        errorConsumer.print("ERROR: Out of serials!", null);
-        logger.error("de.jplag.text.Parser: ERROR: Out of serials!");
+        logger.error("Out of serials!");
     }
 
     private boolean parseFile(File directory, String file) {
@@ -86,8 +78,8 @@ public class Parser extends AbstractParser {
             // close file
             inputStream.close();
         } catch (Exception e) {
-            getErrorConsumer().addError(
-                    "  Parsing Error in '" + file + "' (line " + (inputState != null ? "" + inputState.getLine() : "") + "):\n  " + e.getMessage());
+            logger.error("Parsing Error in '" + file + "' (line " + (inputState != null ? "" + inputState.getLine() : "") + "):\n  " + e.getMessage(),
+                    e);
             return false;
         }
         return true;

+ 0 - 79
jplag/src/main/java/de/jplag/ErrorCollector.java

@@ -1,79 +0,0 @@
-package de.jplag;
-
-import static de.jplag.options.Verbosity.LONG;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import de.jplag.options.JPlagOptions;
-import de.jplag.options.Verbosity;
-
-/**
- * Error collector class that collects errors but also allows printing the collected errors.
- * @author Timur Saglam
- */
-public class ErrorCollector implements ErrorConsumer {
-    // TODO DF We should replace *all* usages of the ErrorConsumer by a suitable logger
-    private final Logger logger = LoggerFactory.getLogger("JPlag");
-    private final List<String> collectedErrors; // List of errors that occurred during the execution of the errorConsumer.
-    private final JPlagOptions options;
-    private String currentSubmissionName;
-
-    public ErrorCollector(JPlagOptions options) {
-        this.options = options;
-        collectedErrors = new ArrayList<>();
-        currentSubmissionName = "<Unknown submission>";
-    }
-
-    @Override
-    public void addError(String errorMessage) {
-        collectedErrors.add("[" + currentSubmissionName + "] " + errorMessage);
-        print(null, "\t" + errorMessage);
-    }
-
-    @Override
-    public void print(String message, String longMessage) {
-        if (message == null && longMessage == null) {
-            throw new IllegalArgumentException("At least one message parameter needs to be non-null!");
-        }
-        Verbosity verbosity = options.getVerbosity();
-        if (message != null) {
-            logger.info(message);
-        }
-        if (longMessage != null && verbosity == LONG) {
-            logger.info(longMessage);
-        }
-    }
-
-    /**
-     * Print all collected errors messages in a list-like fashion.
-     */
-    public void printCollectedErrors() {
-        StringBuilder errorReport = new StringBuilder();
-        logger.error("The following errors occured: ");
-        for (String message : collectedErrors) {
-            errorReport.append(message);
-            errorReport.append('\n');
-        }
-
-        logger.error(errorReport.toString());
-    }
-
-    /**
-     * Updates the name of the currently processed submission.
-     * @param currentSubmissionName is the name.
-     */
-    public void setCurrentSubmissionName(String currentSubmissionName) {
-        this.currentSubmissionName = currentSubmissionName;
-    }
-
-    /**
-     * @return true if there is at least one error.
-     */
-    public boolean hasErrors() {
-        return !collectedErrors.isEmpty();
-    }
-}

+ 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()) {

+ 10 - 14
jplag/src/main/java/de/jplag/JPlag.java

@@ -29,14 +29,13 @@ import de.jplag.strategy.ParallelComparisonStrategy;
  * This class coordinates the whole errorConsumer flow.
  */
 public class JPlag {
-    private static final Logger logger = LoggerFactory.getLogger("JPlag");
+    private static final Logger logger = LoggerFactory.getLogger(JPlag.class);
 
     private final JPlagOptions options;
 
     private final Language language;
     private final ComparisonStrategy comparisonStrategy;
     private final GreedyStringTiling coreAlgorithm; // Contains the comparison logic.
-    private final ErrorCollector errorCollector;
     private final Set<String> excludedFileNames;
 
     /**
@@ -45,7 +44,6 @@ public class JPlag {
      */
     public JPlag(JPlagOptions options) {
         this.options = options;
-        errorCollector = new ErrorCollector(options);
         coreAlgorithm = new GreedyStringTiling(options);
         language = initializeLanguage();
         comparisonStrategy = initializeComparisonStrategy(options.getComparisonMode());
@@ -61,9 +59,9 @@ public class JPlag {
         try (BufferedReader reader = new BufferedReader(new FileReader(exclusionFileName, JPlagOptions.CHARSET))) {
             final var excludedFileNames = reader.lines().collect(Collectors.toSet());
             if (options.getVerbosity() == LONG) {
-                errorCollector.print(null, "Excluded files:");
+                logger.info("Excluded files:");
                 for (var excludedFilename : excludedFileNames) {
-                    errorCollector.print(null, " " + excludedFilename);
+                    logger.info(excludedFilename);
                 }
             }
             return excludedFileNames;
@@ -80,7 +78,7 @@ public class JPlag {
      */
     public JPlagResult run() throws ExitException {
         // Parse and validate submissions.
-        SubmissionSetBuilder builder = new SubmissionSetBuilder(language, options, errorCollector, excludedFileNames);
+        SubmissionSetBuilder builder = new SubmissionSetBuilder(language, options, excludedFileNames);
         SubmissionSet submissionSet = builder.buildSubmissionSet();
 
         if (submissionSet.hasBaseCode()) {
@@ -94,7 +92,7 @@ public class JPlag {
 
         // Compare valid submissions.
         JPlagResult result = comparisonStrategy.compareSubmissions(submissionSet);
-        errorCollector.print("\nTotal time for comparing submissions: " + TimeUtil.formatDuration(result.getDuration()), null);
+        logger.info("\nTotal time for comparing submissions: " + TimeUtil.formatDuration(result.getDuration()));
 
         result.setClusteringResult(ClusteringFactory.getClusterings(result.getAllComparisons(), options.getClusteringOptions()));
 
@@ -112,19 +110,17 @@ public class JPlag {
         LanguageOption languageOption = this.options.getLanguageOption();
 
         try {
-            Constructor<?> constructor = Class.forName(languageOption.getClassPath()).getConstructor(ErrorConsumer.class);
-            Object[] constructorParams = {errorCollector};
-
-            Language language = (Language) constructor.newInstance(constructorParams);
+            Constructor<?> constructor = Class.forName(languageOption.getClassPath()).getConstructor();
+            Language language = (Language) constructor.newInstance();
 
             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) {
-            e.printStackTrace();
-            throw new IllegalStateException("Language instantiation failed:" + e.getMessage());
+            logger.error(e.getMessage(), e);
+            throw new IllegalStateException("Language instantiation failed:" + e.getMessage(), 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

@@ -131,7 +131,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) {
-        return calculateDistributionFor(comparisons, (JPlagComparison::similarity));
+        return calculateDistributionFor(comparisons, JPlagComparison::similarity);
     }
 
     private int[] calculateDistributionFor(List<JPlagComparison> comparisons, Function<JPlagComparison, Float> similarityExtractor) {

+ 7 - 42
jplag/src/main/java/de/jplag/Match.java

@@ -2,57 +2,22 @@ package de.jplag;
 
 /**
  * Represents two sections of two submissions that are similar.
+ * @param startOfFirst is the starting token index in the first submission.
+ * @param startOfSecond is the starting token index in the second submission.
+ * @param length is the length of these similar sections (number of tokens).
  */
-public class Match {
-
-    private final int startOfFirst;
-    private final int startOfSecond;
-    private final int length;
-
-    /**
-     * Creates a match.
-     * @param startOfFirst is the starting token index in the first submission.
-     * @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;
-    }
-
-    /**
-     * @return the starting index in the first submission.
-     */
-    public int getStartOfFirst() {
-        return startOfFirst;
-    }
-
-    /**
-     * @return the starting index in the second submission.
-     */
-    public int getStartOfSecond() {
-        return startOfSecond;
-    }
-
-    /**
-     * @return the length of the similar sections, meaning the number of tokens.
-     */
-    public int getLength() {
-        return length;
-    }
-
+public record Match(int startOfFirst, int startOfSecond, int length) {
     /**
      * 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 otherLength) {
         if (startOfFirst < otherStartOfFirst) {
             if ((otherStartOfFirst - startOfFirst) < length) {
                 return true;
             }
         } else {
-            if ((startOfFirst - otherStartOfFirst) < oLength) {
+            if ((startOfFirst - otherStartOfFirst) < otherLength) {
                 return true;
             }
         }
@@ -60,7 +25,7 @@ public class Match {
         if (startOfSecond < otherStartOfSecond) {
             return (otherStartOfSecond - startOfSecond) < length;
         } else {
-            return (startOfSecond - otherStartOfSecond) < oLength;
+            return (startOfSecond - otherStartOfSecond) < otherLength;
         }
     }
 }

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

@@ -6,10 +6,15 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Collection;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 /**
  * Represents a single submission. A submission can contain multiple files.
  */
 public class Submission implements Comparable<Submission> {
+    private static final Logger logger = LoggerFactory.getLogger(Submission.class);
+
     /**
      * Directory name for storing submission files with parse errors if so requested.
      */
@@ -51,7 +56,6 @@ public class Submission implements Comparable<Submission> {
     private JPlagComparison baseCodeComparison;
 
     private final Language language;
-    private final ErrorCollector errorCollector;
 
     /**
      * Creates a submission.
@@ -60,19 +64,18 @@ public class Submission implements Comparable<Submission> {
      * @param isNew states whether the submission must be checked for plagiarism.
      * @param files are the files of the submissions, if the root is a single file it should just contain one file.
      * @param language is the language of the submission.
-     * @param errorCollector is the interface for error reporting.
      */
-    public Submission(String name, File submissionRootFile, boolean isNew, Collection<File> files, Language language, ErrorCollector errorCollector) {
+    public Submission(String name, File submissionRootFile, boolean isNew, Collection<File> files, Language language) {
         this.name = name;
         this.submissionRootFile = submissionRootFile;
         this.isNew = isNew;
         this.files = files;
         this.language = language;
-        this.errorCollector = errorCollector;
     }
 
     @Override
     public int compareTo(Submission other) {
+        // TODO DF: "equals(Object obj)" should be overridden along with the "compareTo(T obj)" method
         return name.compareTo(other.name);
     }
 
@@ -184,7 +187,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);
     }
 
@@ -204,12 +207,12 @@ public class Submission implements Comparable<Submission> {
             try {
                 Files.copy(file.toPath(), new File(submissionDirectory, file.getName()).toPath());
             } catch (IOException exception) {
-                errorCollector.print("Error copying file: " + exception + "\n", null);
+                logger.error("Error copying file: " + exception.getMessage(), exception);
             }
         }
     }
 
-    private File createSubdirectory(File parent, String... subdirectoryNames) {
+    private static File createSubdirectory(File parent, String... subdirectoryNames) {
         File subdirectory = parent;
         for (String name : subdirectoryNames) {
             subdirectory = new File(subdirectory, name);
@@ -246,8 +249,8 @@ public class Submission implements Comparable<Submission> {
      * @return Whether parsing was successful.
      */
     /* package-private */ boolean parse(boolean debugParser) {
-        if (files == null || files.size() == 0) {
-            errorCollector.print("ERROR: nothing to parse for submission \"" + name + "\"", null);
+        if (files == null || files.isEmpty()) {
+            logger.error("ERROR: nothing to parse for submission \"{}\"", name);
             tokenList = null;
             hasErrors = true; // invalidate submission
             return false;
@@ -258,7 +261,7 @@ public class Submission implements Comparable<Submission> {
         tokenList = language.parse(submissionRootFile, relativeFilePaths);
         if (!language.hasErrors()) {
             if (tokenList.size() < 3) {
-                errorCollector.print("Submission \"" + name + "\" is too short!", null);
+                logger.error("Submission \"{}\" is too short!", name);
                 tokenList = null;
                 hasErrors = true; // invalidate submission
                 return false;

+ 29 - 36
jplag/src/main/java/de/jplag/SubmissionSet.java

@@ -1,12 +1,12 @@
 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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import de.jplag.exceptions.BasecodeException;
 import de.jplag.exceptions.ExitException;
 import de.jplag.exceptions.SubmissionException;
@@ -16,6 +16,8 @@ import de.jplag.options.JPlagOptions;
  * Collection of all submissions and their basecode if it exists. Parses all submissions upon creation.
  */
 public class SubmissionSet {
+    private static final Logger logger = LoggerFactory.getLogger(SubmissionSet.class);
+
     /**
      * Submissions to check for plagiarism.
      */
@@ -26,22 +28,19 @@ 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;
     private int errors = 0;
     private String currentSubmissionName;
 
     /**
      * @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)
-            throws ExitException {
+    public SubmissionSet(List<Submission> submissions, Submission baseCode, JPlagOptions options) throws ExitException {
         this.allSubmissions = submissions;
         this.baseCodeSubmission = baseCode;
-        this.errorCollector = errorCollector;
         this.options = options;
         parseAllSubmissions();
         this.submissions = filterValidSubmissions();
@@ -52,7 +51,7 @@ public class SubmissionSet {
      * @return Whether a basecode is available for this collection.
      */
     public boolean hasBaseCode() {
-        return baseCodeSubmission.isPresent();
+        return baseCodeSubmission != null;
     }
 
     /**
@@ -62,10 +61,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,21 +95,18 @@ 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);
             }
         } catch (OutOfMemoryError exception) {
             throw new SubmissionException("Out of memory during parsing of submission \"" + currentSubmissionName + "\"", exception);
         }
-        if (errorCollector.hasErrors()) {
-            errorCollector.printCollectedErrors();
-        }
     }
 
     /**
@@ -118,16 +114,15 @@ public class SubmissionSet {
      */
     private void parseBaseCodeSubmission(Submission baseCode) throws BasecodeException {
         long startTime = System.currentTimeMillis();
-        errorCollector.print("----- Parsing basecode submission: " + baseCode.getName(), null);
+        logger.info("----- Parsing basecode submission: " + baseCode.getName());
         if (!baseCode.parse(options.isDebugParser())) {
-            errorCollector.printCollectedErrors();
             throw new BasecodeException("Could not successfully parse basecode submission!");
         } else if (baseCode.getNumberOfTokens() < options.getMinimumTokenMatch()) {
             throw new BasecodeException("Basecode submission contains fewer tokens than minimum match length allows!");
         }
-        errorCollector.print("Basecode submission parsed!", null);
+        logger.info("Basecode submission parsed!");
         long duration = System.currentTimeMillis() - startTime;
-        errorCollector.print(null, "Time for parsing Basecode: " + TimeUtil.formatDuration(duration));
+        logger.trace("Time for parsing Basecode: " + TimeUtil.formatDuration(duration));
 
     }
 
@@ -136,7 +131,7 @@ public class SubmissionSet {
      */
     private void parseSubmissions(List<Submission> submissions) {
         if (submissions.isEmpty()) {
-            errorCollector.print("No submissions to parse!", null);
+            logger.warn("No submissions to parse!");
             return;
         }
 
@@ -146,16 +141,15 @@ public class SubmissionSet {
         for (Submission submission : submissions) {
             boolean ok;
 
-            errorCollector.print(null, "------ Parsing submission: " + submission.getName());
+            logger.trace("------ Parsing submission: " + submission.getName());
             currentSubmissionName = submission.getName();
-            errorCollector.setCurrentSubmissionName(currentSubmissionName);
 
             if (!(ok = submission.parse(options.isDebugParser()))) {
                 errors++;
             }
 
             if (submission.getTokenList() != null && submission.getNumberOfTokens() < options.getMinimumTokenMatch()) {
-                errorCollector.addError("Submission contains fewer tokens than minimum match length allows!");
+                logger.error("Submission {} contains fewer tokens than minimum match length allows!", currentSubmissionName);
                 submission.setTokenList(null);
                 tooShort++;
                 ok = false;
@@ -163,31 +157,30 @@ public class SubmissionSet {
             }
 
             if (ok) {
-                errorCollector.print(null, "OK");
+                logger.trace("OK");
             } else {
-                errorCollector.print(null, "ERROR -> Submission removed");
+                logger.error("ERROR -> Submission {} removed", currentSubmissionName);
             }
         }
 
         int validSubmissions = submissions.size() - errors - tooShort;
-        errorCollector.print(validSubmissions + " submissions parsed successfully!", null);
-        errorCollector.print(errors + " parser error" + (errors != 1 ? "s!" : "!") + "", null);
-        errorCollector.print(tooShort + " too short submission" + (tooShort != 1 ? "s!" : "!") + "", null);
+        logger.trace(validSubmissions + " submissions parsed successfully!");
+        logger.trace(errors + " parser error" + (errors != 1 ? "s!" : "!"));
+        logger.trace(tooShort + " too short submission" + (tooShort != 1 ? "s!" : "!"));
         printDetails(submissions, startTime, tooShort);
-        errorCollector.print("", null); // new line
     }
 
     private void printDetails(List<Submission> submissions, long startTime, int tooShort) {
         if (tooShort == 1) {
-            errorCollector.print(null, tooShort + " submission is not valid because it contains fewer tokens than minimum match length allows.");
+            logger.trace(tooShort + " submission is not valid because it contains fewer tokens than minimum match length allows.");
         } else if (tooShort > 1) {
-            errorCollector.print(null, tooShort + " submissions are not valid because they contain fewer tokens than minimum match length allows.");
+            logger.trace(tooShort + " submissions are not valid because they contain fewer tokens than minimum match length allows.");
         }
 
         long duration = System.currentTimeMillis() - startTime;
         String timePerSubmission = submissions.size() > 0 ? 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");
+        logger.trace("Total time for parsing: " + TimeUtil.formatDuration(duration));
+        logger.trace("Time per parsed submission: " + timePerSubmission + " msec");
     }
 
 }

+ 20 - 11
jplag/src/main/java/de/jplag/SubmissionSetBuilder.java

@@ -2,7 +2,16 @@ package de.jplag;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
 import java.util.function.Function;
 
 import org.slf4j.Logger;
@@ -24,20 +33,17 @@ public class SubmissionSetBuilder {
 
     private final Language language;
     private final JPlagOptions options;
-    private final ErrorCollector errorCollector;
     private final Set<String> excludedFileNames; // Set of file names to be excluded in comparison.
 
     /**
      * Creates a builder for submission sets.
      * @param language is the language of the submissions.
      * @param options are the configured options.
-     * @param errorCollector is the interface for error reporting.
      * @param excludedFileNames a list of file names to be excluded
      */
-    public SubmissionSetBuilder(Language language, JPlagOptions options, ErrorCollector errorCollector, Set<String> excludedFileNames) {
+    public SubmissionSetBuilder(Language language, JPlagOptions options, Set<String> excludedFileNames) {
         this.language = language;
         this.options = options;
-        this.errorCollector = errorCollector;
         this.excludedFileNames = excludedFileNames;
     }
 
@@ -69,7 +75,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), options);
     }
 
     /**
@@ -115,8 +121,9 @@ public class SubmissionSetBuilder {
         // former use can be removed without affecting the result of the checks.
         oldSubmissionDirectories.removeAll(commonRootdirectories);
         for (File rootDirectory : commonRootdirectories) {
-            logger.warn("Root directory \"" + rootDirectory.toString()
-                    + "\" is specified both for plagiarism checking and for prior submissions, will perform plagiarism checking only.");
+            logger.warn(
+                    "Root directory \"{}\" is specified both for plagiarism checking and for prior submissions, will perform plagiarism checking only.",
+                    rootDirectory);
         }
     }
 
@@ -125,7 +132,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();
@@ -134,13 +141,15 @@ public class SubmissionSetBuilder {
                 }
 
                 // There is one root directory, and the submissionDirectories variable has been checked to be non-empty.
-                // That set thus contains the the one and only root directory.
+                // That set thus contains the one and only root directory.
                 File rootDirectory = submissionDirectories.iterator().next();
 
                 // 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.
@@ -280,7 +289,7 @@ public class SubmissionSetBuilder {
         }
 
         submissionFile = makeCanonical(submissionFile, it -> new SubmissionException("Cannot create submission: " + submissionName, it));
-        return new Submission(submissionName, submissionFile, isNew, parseFilesRecursively(submissionFile), language, errorCollector);
+        return new Submission(submissionName, submissionFile, isNew, parseFilesRecursively(submissionFile), language);
     }
 
     /**

+ 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;

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

@@ -36,7 +36,7 @@ public class JsonFactory {
                 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;
     }
@@ -50,7 +50,7 @@ public class JsonFactory {
             ObjectMapper mapper = new ObjectMapper();
             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.firstSubmissionId().concat("-").concat(report.secondSubmissionId()).concat(".json");
                 mapper.writeValue(Path.of(folderPath, name).toFile(), report);
             }
         } catch (IOException e) {

+ 14 - 13
jplag/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java

@@ -5,8 +5,10 @@ import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
 import java.text.SimpleDateFormat;
-import java.util.*;
-import java.util.stream.Collectors;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -90,8 +92,8 @@ 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().supportsColumns()))
+                .toList();
     }
 
     /**
@@ -132,7 +134,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();
     }
 
     /**
@@ -142,19 +144,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);
     }
@@ -167,7 +169,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;
     }
@@ -177,5 +179,4 @@ public class ReportObjectFactory {
         Date date = new Date();
         return dateFormat.format(date);
     }
-
 }

+ 4 - 44
jplag/src/main/java/de/jplag/reporting/reportobject/model/ComparisonReport.java

@@ -4,26 +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) {
 
@@ -34,28 +18,4 @@ public class ComparisonReport {
         this.filesOfSecondSubmission = List.copyOf(filesOfSecondSubmission);
         this.matches = matches;
     }
-
-    public String getFirstSubmissionId() {
-        return firstSubmissionId;
-    }
-
-    public String getSecondSubmissionId() {
-        return secondSubmissionId;
-    }
-
-    public float getMatchPercentage() {
-        return matchPercentage;
-    }
-
-    public List<FilesOfSubmission> getFilesOfFirstSubmission() {
-        return filesOfFirstSubmission;
-    }
-
-    public List<FilesOfSubmission> getFilesOfSecondSubmission() {
-        return filesOfSecondSubmission;
-    }
-
-    public List<Match> getMatches() {
-        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() {

+ 3 - 60
jplag/src/main/java/de/jplag/reporting/reportobject/model/Match.java

@@ -2,64 +2,7 @@ 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 Match(String firstFileName, String secondFileName, int startInFirst, int endInFirst, int startInSecond, int endInSecond, int tokens) {
-        this.firstFileName = firstFileName;
-        this.secondFileName = secondFileName;
-        this.startInFirst = startInFirst;
-        this.endInFirst = endInFirst;
-        this.startInSecond = startInSecond;
-        this.endInSecond = endInSecond;
-        this.tokens = tokens;
-    }
-
-    public String getFirstFileName() {
-        return firstFileName;
-    }
-
-    public String getSecondFileName() {
-        return secondFileName;
-    }
-
-    public int getStartInFirst() {
-        return startInFirst;
-    }
-
-    public int getEndInFirst() {
-        return endInFirst;
-    }
-
-    public int getStartInSecond() {
-        return startInSecond;
-    }
-
-    public int getEndInSecond() {
-        return endInSecond;
-    }
-
-    public int getTokens() {
-        return 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) {
 }

+ 0 - 1
jplag/src/main/java/de/jplag/reporting/reportobject/model/TopComparison.java

@@ -4,5 +4,4 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 
 public record TopComparison(@JsonProperty("first_submission") String firstSubmission, @JsonProperty("second_submission") String secondSubmission,
         @JsonProperty("match_percentage") float matchPercentage) {
-
 }

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

@@ -19,9 +19,9 @@ public abstract class AbstractComparisonStrategy implements ComparisonStrategy {
 
     private final GreedyStringTiling greedyStringTiling;
 
-    protected JPlagOptions options;
+    protected final 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);

+ 2 - 4
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;
@@ -16,7 +14,7 @@ public enum ComparisonMode {
     private final String name;
     private final String description;
 
-    private ComparisonMode(String description) {
+    ComparisonMode(String description) {
         this.description = description;
         name = toString().toLowerCase();
     }
@@ -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

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

@@ -32,7 +32,6 @@ Report.Matches_for_X1_AND_X2=&Uuml;bereinstimmungen f&uuml;r {1_NAME} & {2_NAME}
 Report.Links=Links
 Report.INDEX=INDEX
 Report.HELP=HILFE
-Report.Distribution=Verteilung
 Report.Token_Distribution=Verteilung der Token
 
 Clusters.MIN_single_link=MIN, einfache Verkn&uuml;pfung

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

@@ -32,7 +32,6 @@ Report.Matches_for_X1_AND_X2=Matches for {1_NAME} & {2_NAME}
 Report.Links=Links
 Report.INDEX=INDEX
 Report.HELP=HELP
-Report.Distribution=Distribution
 Report.Token_Distribution=Token Distribution
 
 

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

@@ -3,7 +3,6 @@ Report.MatchesAvg=Coincidencias clasificadas por la semejanza media
 Report.MatchesMax=Coincidencias clasificadas por semejanza máxima
 Report.MatchesMin=Coincidencias clasificadas por semejanza mínima
 Report.average_similarity=semejanza media
-Report.maximum_similarity=semejanza máxima
 Report.maximum_similarity=semejanza mínima
 Report.WhatIsThis=¿Qué es esto?
 Report.Search_Results=Resultados de la Búsqueda
@@ -32,7 +31,6 @@ Report.Matches_for_X1_AND_X2=Coinicdencias para {1_NAME} & {2_NAME}
 Report.Links=Links
 Report.INDEX=INDICE
 Report.HELP=AYUDA
-Report.Distribution=Distribución
 Report.Token_Distribution=Distribución de símbolos
 
 Clusters.MIN_single_link=MIN, link único

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

@@ -32,7 +32,6 @@ Report.Matches_for_X1_AND_X2=Ressemblance pour {1_NAME} & {2_NAME}
 Report.Links=Liens
 Report.INDEX=INDEX
 Report.HELP=AIDE
-Report.Distribution=Repartition
 Report.Token_Distribution=Repartition des tokens
 
 Clusters.MIN_single_link=MIN, single link

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

@@ -32,7 +32,6 @@ Report.Matches_for_X1_AND_X2=Matches for {1_NAME} & {2_NAME}
 Report.Links=Links
 Report.INDEX=INDEX
 Report.HELP=HELP
-Report.Distribution=Distribution
 Report.Token_Distribution=Token Distribution
 
 Clusters.MIN_single_link=MIN, single link

+ 3 - 3
jplag/src/test/java/de/jplag/clustering/algorithm/BayesianOptimizationTest.java

@@ -6,10 +6,10 @@ import org.apache.commons.math3.linear.ArrayRealVector;
 import org.apache.commons.math3.linear.RealVector;
 import org.junit.jupiter.api.Test;
 
-public class BayesianOptimizationTest {
+class BayesianOptimizationTest {
 
     @Test
-    public void findParabolaMax() {
+    void findParabolaMax() {
         RealVector minima = new ArrayRealVector(new double[] {-20});
         RealVector maxima = new ArrayRealVector(new double[] {10});
         RealVector lengthScale = new ArrayRealVector(new double[] {5});
@@ -19,7 +19,7 @@ public class BayesianOptimizationTest {
             double val = v.getEntry(0);
             double result = -(val - maximumAt) * (val - maximumAt);
             result += 0.2 * (Math.random() - 0.5);
-            return new BayesianOptimization.OptimizationResult<Double>(result, result);
+            return new BayesianOptimization.OptimizationResult<>(result, result);
         }).getParams().getEntry(0);
         assertEquals(maximumAt, maximumPoint, 1);
     }

+ 1 - 1
jplag/src/test/java/de/jplag/clustering/algorithm/ClusteringData.java

@@ -38,7 +38,7 @@ public enum ClusteringData {
     private final Set<Set<Integer>> expected;
     private final ClusteringOptions options;
 
-    private ClusteringData(Supplier<RealMatrix> similarity, int[][] expected, ClusteringOptions.Builder options) {
+    ClusteringData(Supplier<RealMatrix> similarity, int[][] expected, ClusteringOptions.Builder options) {
         this.similarity = similarity.get();
         this.expected = makeSets(
                 Arrays.stream(expected).map(intArray -> Arrays.stream(intArray).boxed().collect(Collectors.toList())).collect(Collectors.toList()));

+ 1 - 1
jplag/src/test/java/de/jplag/clustering/preprocessors/PreprocessingTestBase.java

@@ -50,7 +50,7 @@ public class PreprocessingTestBase {
 
         assertTrue(usedOriginalIndices.stream().allMatch(index -> index >= 0 && index < original.getColumnDimension()), "original indices valid");
 
-        double[] columnNorms = IntStream.range(0, result.getColumnDimension()).mapToObj(index -> result.getColumn(index))
+        double[] columnNorms = IntStream.range(0, result.getColumnDimension()).mapToObj(result::getColumn)
                 .mapToDouble(array -> new ArrayRealVector(array, false).getNorm()).toArray();
 
         for (int i = 0; i < columnNorms.length; i++) {