Przeglądaj źródła

Merge branch 'master' into feature/versioning

# Conflicts:
#	pom.xml
Dominik Fuchß 4 lat temu
rodzic
commit
b2ee81288b
100 zmienionych plików z 1340 dodań i 3043 usunięć
  1. 4 4
      .github/workflows/maven.yml
  2. 3 3
      .github/workflows/publish.yml
  3. 1 1
      .github/workflows/report-viewer.yml
  4. 3 3
      .github/workflows/spotless.yml
  5. 20 16
      README.md
  6. 1 1
      jplag.frontend-utils/src/main/java/de/jplag/AbstractParser.java
  7. 2 2
      jplag.frontend-utils/src/main/java/de/jplag/ErrorConsumer.java
  8. 11 11
      jplag.frontend-utils/src/main/java/de/jplag/Language.java
  9. 2 2
      jplag.frontend-utils/src/main/java/de/jplag/TokenConstants.java
  10. 1 2
      jplag.frontend-utils/src/main/java/de/jplag/TokenList.java
  11. 2 2
      jplag.frontend.chars/src/main/java/de/jplag/chars/CharToken.java
  12. 2 4
      jplag.frontend.chars/src/main/java/de/jplag/chars/Language.java
  13. 2 2
      jplag.frontend.chars/src/main/java/de/jplag/chars/Parser.java
  14. 1 1
      jplag.frontend.cpp/pom.xml
  15. 65 130
      jplag.frontend.cpp/src/main/java/de/jplag/cpp/CPPToken.java
  16. 62 62
      jplag.frontend.cpp/src/main/java/de/jplag/cpp/CPPTokenConstants.java
  17. 3 3
      jplag.frontend.cpp/src/main/java/de/jplag/cpp/Language.java
  18. 6 8
      jplag.frontend.cpp/src/main/java/de/jplag/cpp/NewlineStream.java
  19. 5 6
      jplag.frontend.cpp/src/main/java/de/jplag/cpp/Scanner.java
  20. 79 84
      jplag.frontend.cpp/src/main/javacc/CPP.jj
  21. 56 111
      jplag.frontend.csharp-1.2/src/main/java/de/jplag/csharp/CSharpToken.java
  22. 53 53
      jplag.frontend.csharp-1.2/src/main/java/de/jplag/csharp/CSharpTokenConstants.java
  23. 2 2
      jplag.frontend.csharp-1.2/src/main/java/de/jplag/csharp/Language.java
  24. 1 1
      jplag.frontend.csharp-1.2/src/main/java/de/jplag/csharp/Parser.java
  25. 8 9
      jplag.frontend.csharp-1.2/src/main/java/de/jplag/csharp/UnicodeReader.java
  26. 11 3
      jplag.frontend.java/src/main/java/de/jplag/java/JavaToken.java
  27. 73 66
      jplag.frontend.java/src/main/java/de/jplag/java/JavaTokenConstants.java
  28. 36 22
      jplag.frontend.java/src/main/java/de/jplag/java/JavacAdapter.java
  29. 6 6
      jplag.frontend.java/src/main/java/de/jplag/java/Language.java
  30. 9 10
      jplag.frontend.java/src/main/java/de/jplag/java/Parser.java
  31. 180 149
      jplag.frontend.java/src/main/java/de/jplag/java/TokenGeneratingTreeScanner.java
  32. 39 617
      jplag.frontend.python-3/src/main/java/de/jplag/python3/JplagPython3Listener.java
  33. 2 2
      jplag.frontend.python-3/src/main/java/de/jplag/python3/Language.java
  34. 1 2
      jplag.frontend.python-3/src/main/java/de/jplag/python3/Parser.java
  35. 31 31
      jplag.frontend.python-3/src/main/java/de/jplag/python3/Python3TokenConstants.java
  36. 3 3
      jplag.frontend.scheme/src/main/java/de/jplag/scheme/Language.java
  37. 1 1
      jplag.frontend.scheme/src/main/java/de/jplag/scheme/Parser.java
  38. 45 89
      jplag.frontend.scheme/src/main/java/de/jplag/scheme/SchemeToken.java
  39. 42 42
      jplag.frontend.scheme/src/main/java/de/jplag/scheme/SchemeTokenConstants.java
  40. 2 5
      jplag.frontend.text/src/main/java/de/jplag/text/Language.java
  41. 1 1
      jplag.frontend.text/src/main/java/de/jplag/text/Parser.java
  42. 5 2
      jplag.frontend.text/src/main/java/de/jplag/text/TextToken.java
  43. 1 1
      jplag/pom.xml
  44. 52 6
      jplag/src/main/java/de/jplag/CLI.java
  45. 2 2
      jplag/src/main/java/de/jplag/CliGroupHelper.java
  46. 9 3
      jplag/src/main/java/de/jplag/CommandLineArgument.java
  47. 1 1
      jplag/src/main/java/de/jplag/ErrorCollector.java
  48. 2 2
      jplag/src/main/java/de/jplag/GreedyStringTiling.java
  49. 7 12
      jplag/src/main/java/de/jplag/JPlag.java
  50. 3 82
      jplag/src/main/java/de/jplag/JPlagComparison.java
  51. 0 9
      jplag/src/main/java/de/jplag/Match.java
  52. 3 2
      jplag/src/main/java/de/jplag/NumberOfArgumentValues.java
  53. 79 150
      jplag/src/main/java/de/jplag/Submission.java
  54. 2 2
      jplag/src/main/java/de/jplag/SubmissionSet.java
  55. 57 23
      jplag/src/main/java/de/jplag/SubmissionSetBuilder.java
  56. 4 4
      jplag/src/main/java/de/jplag/TimeUtil.java
  57. 3 2
      jplag/src/main/java/de/jplag/clustering/Cluster.java
  58. 6 6
      jplag/src/main/java/de/jplag/clustering/ClusteringAdapter.java
  59. 32 4
      jplag/src/main/java/de/jplag/clustering/ClusteringAlgorithm.java
  60. 1 8
      jplag/src/main/java/de/jplag/clustering/ClusteringFactory.java
  61. 3 3
      jplag/src/main/java/de/jplag/clustering/ClusteringResult.java
  62. 2 2
      jplag/src/main/java/de/jplag/clustering/IntegerMapping.java
  63. 2 4
      jplag/src/main/java/de/jplag/clustering/Preprocessing.java
  64. 8 7
      jplag/src/main/java/de/jplag/clustering/algorithm/AgglomerativeClustering.java
  65. 10 11
      jplag/src/main/java/de/jplag/clustering/algorithm/BFGS.java
  66. 10 8
      jplag/src/main/java/de/jplag/clustering/algorithm/BayesianOptimization.java
  67. 7 7
      jplag/src/main/java/de/jplag/clustering/algorithm/GaussianProcess.java
  68. 8 9
      jplag/src/main/java/de/jplag/clustering/algorithm/InterClusterSimilarity.java
  69. 5 6
      jplag/src/main/java/de/jplag/clustering/algorithm/SpectralClustering.java
  70. 1 1
      jplag/src/main/java/de/jplag/clustering/preprocessors/CumulativeDistributionFunctionPreprocessor.java
  71. 1 1
      jplag/src/main/java/de/jplag/clustering/preprocessors/PercentileThresholdProcessor.java
  72. 2 2
      jplag/src/main/java/de/jplag/clustering/preprocessors/ThresholdPreprocessor.java
  73. 3 0
      jplag/src/main/java/de/jplag/exceptions/BasecodeException.java
  74. 3 0
      jplag/src/main/java/de/jplag/exceptions/ExitException.java
  75. 3 0
      jplag/src/main/java/de/jplag/exceptions/ReportGenerationException.java
  76. 3 0
      jplag/src/main/java/de/jplag/exceptions/RootDirectoryException.java
  77. 3 0
      jplag/src/main/java/de/jplag/exceptions/SubmissionException.java
  78. 30 20
      jplag/src/main/java/de/jplag/options/JPlagOptions.java
  79. 4 4
      jplag/src/main/java/de/jplag/options/SimilarityMetric.java
  80. 4 8
      jplag/src/main/java/de/jplag/options/Verbosity.java
  81. 0 46
      jplag/src/main/java/de/jplag/reporting/BufferedCounter.java
  82. 0 75
      jplag/src/main/java/de/jplag/reporting/Color.java
  83. 0 31
      jplag/src/main/java/de/jplag/reporting/HTMLFile.java
  84. 4 4
      jplag/src/main/java/de/jplag/reporting/JsonReport.java
  85. 0 24
      jplag/src/main/java/de/jplag/reporting/MarkupText.java
  86. 0 26
      jplag/src/main/java/de/jplag/reporting/Messages.java
  87. 14 769
      jplag/src/main/java/de/jplag/reporting/Report.java
  88. 0 35
      jplag/src/main/java/de/jplag/reporting/TagParser.java
  89. 3 3
      jplag/src/main/java/de/jplag/reporting/jsonfactory/JsonFactory.java
  90. 24 6
      jplag/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java
  91. 1 1
      jplag/src/main/java/de/jplag/reporting/reportobject/model/Cluster.java
  92. 1 1
      jplag/src/main/java/de/jplag/reporting/reportobject/model/ComparisonReport.java
  93. 1 1
      jplag/src/main/java/de/jplag/reporting/reportobject/model/FilesOfSubmission.java
  94. 1 1
      jplag/src/main/java/de/jplag/reporting/reportobject/model/JPlagReport.java
  95. 1 1
      jplag/src/main/java/de/jplag/reporting/reportobject/model/Match.java
  96. 1 1
      jplag/src/main/java/de/jplag/reporting/reportobject/model/Metric.java
  97. 1 1
      jplag/src/main/java/de/jplag/reporting/reportobject/model/OverviewReport.java
  98. 1 1
      jplag/src/main/java/de/jplag/reporting/reportobject/model/TopComparison.java
  99. 0 29
      jplag/src/main/java/de/jplag/reporting2/Report.java
  100. 29 1
      jplag/src/main/java/de/jplag/strategy/AbstractComparisonStrategy.java

+ 4 - 4
.github/workflows/maven.yml

@@ -16,10 +16,10 @@ jobs:
     - uses: actions/checkout@v3
 
     - name: Set up JDK
-      uses: actions/setup-java@v2
+      uses: actions/setup-java@v3
       with:
-        java-version: 14
-        distribution: 'zulu'
+        java-version: 17
+        distribution: 'temurin'
             
     - name: Run Tests
       run: mvn test -B
@@ -28,7 +28,7 @@ jobs:
       run: mvn clean package assembly:single
       
     - name: Upload Assembly
-      uses: actions/upload-artifact@v2
+      uses: actions/upload-artifact@v3
       with:
         name: "JPlag"
         path: "jplag/target/jplag-*-jar-with-dependencies.jar"

+ 3 - 3
.github/workflows/publish.yml

@@ -9,10 +9,10 @@ jobs:
     steps:
       - uses: actions/checkout@v3
       - name: Set up Maven Central Repository
-        uses: actions/setup-java@v2
+        uses: actions/setup-java@v3
         with:
-          java-version: '14'
-          distribution: 'zulu'
+          java-version: '17'
+          distribution: 'temurin'
       - name: Set maven settings.xml
         uses: whelk-io/maven-settings-xml-action@v20
         with:

+ 1 - 1
.github/workflows/report-viewer.yml

@@ -24,7 +24,7 @@ jobs:
           npm run build
 
       - name: Deploy 🚀
-        uses: JamesIves/github-pages-deploy-action@v4.2.5
+        uses: JamesIves/github-pages-deploy-action@v4.3.0
         with:
           branch: gh-pages
           folder: report-viewer/dist

+ 3 - 3
.github/workflows/spotless.yml

@@ -16,10 +16,10 @@ jobs:
     - uses: actions/checkout@v3
 
     - name: Set up JDK
-      uses: actions/setup-java@v2
+      uses: actions/setup-java@v3
       with:
-        java-version: 14
-        distribution: 'zulu'
+        java-version: 17
+        distribution: 'temurin'
       
     - name: Check with Spotless
       run: mvn clean spotless:check

+ 20 - 16
README.md

@@ -39,23 +39,27 @@ JPlag can either be used via the CLI or directly via its Java API. For more info
 ```
 JPlag - Detecting Software Plagiarism
 
-Usage: JPlag [ options ] [<root-dir> ...]
- <root-dir>        Root-directory that contains submissions
+Usage: JPlag [ options ] [ <root-dir> ... ] [ -new <new-dir> ... ] [ -old <old-dir> ... ]
+ <root-dir>        Root-directory with submissions to check for plagiarism
+ <new-dir>         Root-directory with submissions to check for plagiarism
+ <old-dir>         Root-directory with prior submissions to compare against
 
 named arguments:
-  -h, --help             show this help message and exit
-  -l                     {java,python3,cpp,csharp,char,text,scheme} Select the language to parse the submissions (default: java)
-  -bc BC                 Path of the directory containing the base code (common framework used in all submissions)
-  -v                     {quiet,long} Verbosity of the logging (default: quiet)
-  -d                     Debug parser. Non-parsable files will be stored (default false)
-  -S S                   Look in directories <root-dir>/*/<dir> for programs
-  -p P                   comma-separated list of all filename suffixes that are included
-  -x X                   All files named in this file will be ignored in the comparison (line-separated list)
-  -t T                   Tunes the comparison sensitivity by adjusting the minimum token required to be counted as a matching section. A smaller <n> increases the sensitivity but might lead to more false-positives
-  -m M                   Comparison similarity threshold [0-100]: All comparisons above this threshold will be saved (default: 0.0)
-  -n N                   The maximum number of comparisons that will be shown in the generated report, if set to -1 all comparisons will be shown (default: 30)
-  -r R                   Name of the directory in which the comparison results will be stored (default: result)
-  -c                     {normal,parallel} Comparison mode used to compare the programs (default: normal)
+  -h, --help       show this help message and exit
+  -l               {java,python3,cpp,csharp,char,text,scheme} Select the language to parse the submissions (default: java)
+  -bc BC           Path of the directory containing the base code (common framework used in all submissions)
+  -v               {quiet,long} Verbosity of the logging (default: quiet)
+  -d               Debug parser. Non-parsable files will be stored (default: false)
+  -S S             Look in directories <root-dir>/*/<dir> for programs
+  -p P             comma-separated list of all filename suffixes that are included
+  -x X             All files named in this file will be ignored in the comparison (line-separated list)
+  -t T             Tunes the comparison sensitivity by adjusting the minimum token  required  to be counted as a matching section. A smaller
+                        <n> increases the sensitivity but might lead to more false-positives
+  -m M             Comparison similarity threshold [0-100]: All comparisons above this threshold will be saved (default: 0.0)
+  -n N             The maximum number of comparisons that will be shown in the  generated report, if set to -1 all comparisons will be shown
+                        (default: 30)
+  -r R             Name of the directory in which the comparison results will be stored (default: result)
+  -c               {normal,parallel} Comparison mode used to compare the programs (default: normal)
 ```
 
 ### Java API
@@ -63,7 +67,7 @@ named arguments:
 The new API makes it easy to integrate JPlag's plagiarism detection into external Java projects:
 
 ```java
-JPlagOptions options = new JPlagOptions(List.of("/path/to/rootDir"), LanguageOption.JAVA);
+JPlagOptions options = new JPlagOptions(List.of("/path/to/rootDir"), List.of(), LanguageOption.JAVA);
 options.setBaseCodeSubmissionName("template");
 
 JPlag jplag = new JPlag(options);

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

@@ -24,7 +24,7 @@ public abstract class AbstractParser {
         return errorConsumer;
     }
 
-    public void setProgram(ErrorConsumer errorConsumer) {
+    public void setErrorConsumer(ErrorConsumer errorConsumer) {
         this.errorConsumer = errorConsumer;
     }
 }

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

@@ -9,12 +9,12 @@ 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.
      */
-    public void addError(String errorMessage);
+    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.
      */
-    public void print(String message, String longMessage);
+    void print(String message, String longMessage);
 }

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

@@ -10,55 +10,55 @@ public interface Language {
     /**
      * Suffixes for the files containing code of the language.
      */
-    public String[] suffixes();
+    String[] suffixes();
 
     /**
      * Descriptive name of the language.
      */
-    public String getName();
+    String getName();
 
     /**
      * Short name of the language used for CLI options.
      */
-    public String getShortName();
+    String getShortName();
 
     /**
      * Minimum number of tokens required for a match.
      */
-    public int minimumTokenMatch();
+    int minimumTokenMatch();
 
     /**
      * Parses a set files in a directory.
      */
-    public TokenList parse(File dir, String[] files);
+    TokenList parse(File dir, String[] files);
 
     /**
      * Whether errors were found during the last {@link #parse}.
      */
-    public boolean hasErrors();
+    boolean hasErrors();
 
     /**
      * Number of errors found during the last {@link #parse}.
      */
-    public int errorCount();
+    int errorCount();
 
     /**
      * Determines whether the parser provide column information.
      */
-    public boolean supportsColumns();
+    boolean supportsColumns();
 
     /**
      * Determines whether JPlag should use a fixed-width font in its reports.
      */
-    public boolean isPreformatted();
+    boolean isPreformatted();
 
     /**
      * Determines whether tokens from the scanner are indexed.
      */
-    public boolean usesIndex();
+    boolean usesIndex();
 
     /**
      * Number of defined tokens in the scanner of the language.
      */
-    public int numberOfTokens();
+    int numberOfTokens();
 }

+ 2 - 2
jplag.frontend-utils/src/main/java/de/jplag/TokenConstants.java

@@ -7,10 +7,10 @@ public interface TokenConstants {
     /**
      * Marks the end of the file, has a special purpose in the comparison algorithm.
      */
-    final static int FILE_END = 0;
+    int FILE_END = 0;
 
     /**
      * Used to optionally separate methods from each other with an always marked token
      */
-    final static int SEPARATOR_TOKEN = 1;
+    int SEPARATOR_TOKEN = 1;
 }

+ 1 - 2
jplag.frontend-utils/src/main/java/de/jplag/TokenList.java

@@ -55,8 +55,7 @@ public class TokenList {
 
     @Override
     public final String toString() {
-        StringBuffer buffer = new StringBuffer();
-
+        StringBuilder buffer = new StringBuilder();
         try {
             for (int i = 0; i < tokens.size(); i++) {
                 buffer.append(i);

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

@@ -4,8 +4,8 @@ import de.jplag.Token;
 
 public class CharToken extends Token {
 
-    private int index;
-    private Character character;
+    private final int index;
+    private final Character character;
 
     public CharToken(int type, String file, int index, Character character, Parser parser) {
         super(type, file, -1);

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

@@ -9,13 +9,11 @@ import de.jplag.TokenList;
  * read in text files as characters
  */
 public class Language implements de.jplag.Language {
-    private ErrorConsumer program;
 
-    private de.jplag.chars.Parser parser = new Parser();
+    private final de.jplag.chars.Parser parser = new Parser();
 
     public Language(ErrorConsumer program) {
-        this.program = program;
-        this.parser.setProgram(this.program);
+        this.parser.setErrorConsumer(program);
     }
 
     @Override

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

@@ -12,7 +12,7 @@ public class Parser extends AbstractParser {
 
     private TokenList tokens;
 
-    public TokenList parse(File directory, String files[]) {
+    public TokenList parse(File directory, String[] files) {
         tokens = new TokenList();
         errors = 0;
         for (String file : files) {
@@ -59,7 +59,7 @@ public class Parser extends AbstractParser {
         return true;
     }
 
-    private int[] mapping = {-1, // 0 (nul)
+    private final int[] mapping = {-1, // 0 (nul)
             -1, // 1 (soh)
             -1, // 2 (stx)
             -1, // 3 (etx)

+ 1 - 1
jplag.frontend.cpp/pom.xml

@@ -28,7 +28,7 @@
                         </goals>
                         <phase>generate-sources</phase>
                         <configuration>
-                            <jdkVersion>1.5</jdkVersion>
+                            <jdkVersion>17</jdkVersion>
                             <javadocFriendlyComments>true</javadocFriendlyComments>
                             <packageName>de.jplag.cpp</packageName>
                             <sourceDirectory>src/main/javacc</sourceDirectory>

+ 65 - 130
jplag.frontend.cpp/src/main/java/de/jplag/cpp/CPPToken.java

@@ -13,135 +13,70 @@ public class CPPToken extends Token implements CPPTokenConstants {
     }
 
     protected String type2string() {
-        switch (type) {
-            case CPPTokenConstants.FILE_END:
-                return "EOF";
-
-            case C_BLOCK_BEGIN:
-                return "BLOCK{";
-            case C_BLOCK_END:
-                return "}BLOCK";
-            case C_SCOPE:
-                return "SCOPE";
-            case C_QUESTIONMARK:
-                return "COND";
-            case C_ELLIPSIS:
-                return "...";
-            case C_ASSIGN:
-                return "ASSIGN";
-            case C_DOT:
-                return "DOT";
-            case C_ARROW:
-                return "ARROW";
-            case C_DOTSTAR:
-                return "DOTSTAR";
-            case C_ARROWSTAR:
-                return "ARROWSTAR";
-            case C_AUTO:
-                return "AUTO";
-            case C_BREAK:
-                return "BREAK";
-            case C_CASE:
-                return "CASE";
-            case C_CATCH:
-                return "CATCH";
-            case C_CHAR:
-                return "CHAR";
-            case C_CONST:
-                return "CONST";
-            case C_CONTINUE:
-                return "CONTINUE";
-            case C_DEFAULT:
-                return "DEFAULT";
-            case C_DELETE:
-                return "DELETE";
-            case C_DO:
-                return "DO";
-            case C_DOUBLE:
-                return "DOUBLE";
-            case C_ELSE:
-                return "ELSE";
-            case C_ENUM:
-                return "ENUM";
-            case C_EXTERN:
-                return "EXTERN";
-            case C_FLOAT:
-                return "FLOAT";
-            case C_FOR:
-                return "FOR";
-            case C_FRIEND:
-                return "FRIEND";
-            case C_GOTO:
-                return "GOTO";
-            case C_IF:
-                return "IF";
-            case C_INLINE:
-                return "INLINE";
-            case C_INT:
-                return "INT";
-            case C_LONG:
-                return "LONG";
-            case C_NEW:
-                return "NEW";
-            case C_PRIVATE:
-                return "PRIVATE";
-            case C_PROTECTED:
-                return "PROTECTED";
-            case C_PUBLIC:
-                return "PUBLIC";
-            case C_REDECLARED:
-                return "REDECLARED";
-            case C_REGISTER:
-                return "REGISTER";
-            case C_RETURN:
-                return "RETURN";
-            case C_SHORT:
-                return "SHORT";
-            case C_SIGNED:
-                return "SIGNED";
-            case C_SIZEOF:
-                return "SIZEOF";
-            case C_STATIC:
-                return "STATIC";
-            case C_STRUCT:
-                return "STRUCT";
-            case C_CLASS:
-                return "CLASS";
-            case C_SWITCH:
-                return "SWITCH";
-            case C_TEMPLATE:
-                return "TEMPLATE";
-            case C_THIS:
-                return "THIS";
-            case C_TRY:
-                return "TRY";
-            case C_TYPEDEF:
-                return "TYPEDEF";
-            case C_UNION:
-                return "UNION";
-            case C_UNSIGNED:
-                return "UNSIGNED";
-            case C_VIRTUAL:
-                return "VIRTUAL";
-            case C_VOID:
-                return "VOID";
-            case C_VOLANTILE:
-                return "VOLANTILE";
-            case C_WHILE:
-                return "WHILE";
-            case C_OPERATOR:
-                return "OPERATOR";
-            case C_THROW:
-                return "THROW";
-            case C_ID:
-                return "ID";
-            case C_FUN:
-                return "FUN";
-            case C_NULL:
-                return "NULL";
-
-            default:
-                return "<UNKNOWN" + type + ">";
-        }
+        return switch (type) {
+            case CPPTokenConstants.FILE_END -> "EOF";
+            case C_BLOCK_BEGIN -> "BLOCK{";
+            case C_BLOCK_END -> "}BLOCK";
+            case C_SCOPE -> "SCOPE";
+            case C_QUESTIONMARK -> "COND";
+            case C_ELLIPSIS -> "...";
+            case C_ASSIGN -> "ASSIGN";
+            case C_DOT -> "DOT";
+            case C_ARROW -> "ARROW";
+            case C_DOTSTAR -> "DOTSTAR";
+            case C_ARROWSTAR -> "ARROWSTAR";
+            case C_AUTO -> "AUTO";
+            case C_BREAK -> "BREAK";
+            case C_CASE -> "CASE";
+            case C_CATCH -> "CATCH";
+            case C_CHAR -> "CHAR";
+            case C_CONST -> "CONST";
+            case C_CONTINUE -> "CONTINUE";
+            case C_DEFAULT -> "DEFAULT";
+            case C_DELETE -> "DELETE";
+            case C_DO -> "DO";
+            case C_DOUBLE -> "DOUBLE";
+            case C_ELSE -> "ELSE";
+            case C_ENUM -> "ENUM";
+            case C_EXTERN -> "EXTERN";
+            case C_FLOAT -> "FLOAT";
+            case C_FOR -> "FOR";
+            case C_FRIEND -> "FRIEND";
+            case C_GOTO -> "GOTO";
+            case C_IF -> "IF";
+            case C_INLINE -> "INLINE";
+            case C_INT -> "INT";
+            case C_LONG -> "LONG";
+            case C_NEW -> "NEW";
+            case C_PRIVATE -> "PRIVATE";
+            case C_PROTECTED -> "PROTECTED";
+            case C_PUBLIC -> "PUBLIC";
+            case C_REDECLARED -> "REDECLARED";
+            case C_REGISTER -> "REGISTER";
+            case C_RETURN -> "RETURN";
+            case C_SHORT -> "SHORT";
+            case C_SIGNED -> "SIGNED";
+            case C_SIZEOF -> "SIZEOF";
+            case C_STATIC -> "STATIC";
+            case C_STRUCT -> "STRUCT";
+            case C_CLASS -> "CLASS";
+            case C_SWITCH -> "SWITCH";
+            case C_TEMPLATE -> "TEMPLATE";
+            case C_THIS -> "THIS";
+            case C_TRY -> "TRY";
+            case C_TYPEDEF -> "TYPEDEF";
+            case C_UNION -> "UNION";
+            case C_UNSIGNED -> "UNSIGNED";
+            case C_VIRTUAL -> "VIRTUAL";
+            case C_VOID -> "VOID";
+            case C_VOLANTILE -> "VOLANTILE";
+            case C_WHILE -> "WHILE";
+            case C_OPERATOR -> "OPERATOR";
+            case C_THROW -> "THROW";
+            case C_ID -> "ID";
+            case C_FUN -> "FUN";
+            case C_NULL -> "NULL";
+            default -> "<UNKNOWN" + type + ">";
+        };
     }
 }

+ 62 - 62
jplag.frontend.cpp/src/main/java/de/jplag/cpp/CPPTokenConstants.java

@@ -3,67 +3,67 @@ package de.jplag.cpp;
 import de.jplag.TokenConstants;
 
 public interface CPPTokenConstants extends TokenConstants {
-    final static int C_BLOCK_BEGIN = 2;
-    final static int C_BLOCK_END = 3;
-    final static int C_SCOPE = 4;
-    final static int C_QUESTIONMARK = 5;
-    final static int C_ELLIPSIS = 6;
-    final static int C_ASSIGN = 7;
-    final static int C_DOT = 8;
-    final static int C_ARROW = 9;
-    final static int C_ARROWSTAR = 10;
-    final static int C_AUTO = 11;
-    final static int C_BREAK = 12;
-    final static int C_CASE = 13;
-    final static int C_CATCH = 14;
-    final static int C_CHAR = 15;
-    final static int C_CONST = 16;
-    final static int C_CONTINUE = 17;
-    final static int C_DEFAULT = 18;
-    final static int C_DELETE = 19;
-    final static int C_DO = 20;
-    final static int C_DOUBLE = 21;
-    final static int C_ELSE = 22;
-    final static int C_ENUM = 23;
-    final static int C_EXTERN = 24;
-    final static int C_FLOAT = 25;
-    final static int C_FOR = 26;
-    final static int C_FRIEND = 27;
-    final static int C_GOTO = 28;
-    final static int C_IF = 29;
-    final static int C_INLINE = 30;
-    final static int C_INT = 31;
-    final static int C_LONG = 32;
-    final static int C_NEW = 33;
-    final static int C_PRIVATE = 34;
-    final static int C_PROTECTED = 35;
-    final static int C_PUBLIC = 36;
-    final static int C_REDECLARED = 37;
-    final static int C_REGISTER = 38;
-    final static int C_RETURN = 39;
-    final static int C_SHORT = 40;
-    final static int C_SIGNED = 41;
-    final static int C_SIZEOF = 42;
-    final static int C_STATIC = 43;
-    final static int C_STRUCT = 44;
-    final static int C_CLASS = 45;
-    final static int C_SWITCH = 46;
-    final static int C_TEMPLATE = 47;
-    final static int C_THIS = 48;
-    final static int C_TRY = 49;
-    final static int C_TYPEDEF = 50;
-    final static int C_UNION = 51;
-    final static int C_UNSIGNED = 52;
-    final static int C_VIRTUAL = 53;
-    final static int C_VOID = 54;
-    final static int C_VOLANTILE = 55;
-    final static int C_WHILE = 56;
-    final static int C_OPERATOR = 57;
-    final static int C_THROW = 58;
-    final static int C_ID = 59;
-    final static int C_FUN = 60;
-    final static int C_DOTSTAR = 61;
-    final static int C_NULL = 62;
+    int C_BLOCK_BEGIN = 2;
+    int C_BLOCK_END = 3;
+    int C_SCOPE = 4;
+    int C_QUESTIONMARK = 5;
+    int C_ELLIPSIS = 6;
+    int C_ASSIGN = 7;
+    int C_DOT = 8;
+    int C_ARROW = 9;
+    int C_ARROWSTAR = 10;
+    int C_AUTO = 11;
+    int C_BREAK = 12;
+    int C_CASE = 13;
+    int C_CATCH = 14;
+    int C_CHAR = 15;
+    int C_CONST = 16;
+    int C_CONTINUE = 17;
+    int C_DEFAULT = 18;
+    int C_DELETE = 19;
+    int C_DO = 20;
+    int C_DOUBLE = 21;
+    int C_ELSE = 22;
+    int C_ENUM = 23;
+    int C_EXTERN = 24;
+    int C_FLOAT = 25;
+    int C_FOR = 26;
+    int C_FRIEND = 27;
+    int C_GOTO = 28;
+    int C_IF = 29;
+    int C_INLINE = 30;
+    int C_INT = 31;
+    int C_LONG = 32;
+    int C_NEW = 33;
+    int C_PRIVATE = 34;
+    int C_PROTECTED = 35;
+    int C_PUBLIC = 36;
+    int C_REDECLARED = 37;
+    int C_REGISTER = 38;
+    int C_RETURN = 39;
+    int C_SHORT = 40;
+    int C_SIGNED = 41;
+    int C_SIZEOF = 42;
+    int C_STATIC = 43;
+    int C_STRUCT = 44;
+    int C_CLASS = 45;
+    int C_SWITCH = 46;
+    int C_TEMPLATE = 47;
+    int C_THIS = 48;
+    int C_TRY = 49;
+    int C_TYPEDEF = 50;
+    int C_UNION = 51;
+    int C_UNSIGNED = 52;
+    int C_VIRTUAL = 53;
+    int C_VOID = 54;
+    int C_VOLANTILE = 55;
+    int C_WHILE = 56;
+    int C_OPERATOR = 57;
+    int C_THROW = 58;
+    int C_ID = 59;
+    int C_FUN = 60;
+    int C_DOTSTAR = 61;
+    int C_NULL = 62;
 
-    final static int NUM_DIFF_TOKENS = 63;
+    int NUM_DIFF_TOKENS = 63;
 }

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

@@ -6,11 +6,11 @@ import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
 public class Language implements de.jplag.Language {
-    private Scanner scanner; // cpp code is scanned not parsed
+    private final Scanner scanner; // cpp code is scanned not parsed
 
     public Language(ErrorConsumer program) {
         this.scanner = new Scanner();
-        this.scanner.setProgram(program);
+        this.scanner.setErrorConsumer(program);
     }
 
     @Override
@@ -40,7 +40,7 @@ public class Language implements de.jplag.Language {
 
     @Override
     public boolean supportsColumns() {
-        return false;
+        return true;
     }
 
     @Override

+ 6 - 8
jplag.frontend.cpp/src/main/java/de/jplag/cpp/NewlineStream.java

@@ -8,7 +8,7 @@ import java.io.InputStream;
  */
 public class NewlineStream extends InputStream {
     private int endOfFile = 0;
-    private InputStream stream;
+    private final InputStream stream;
 
     public NewlineStream(InputStream stream) {
         super();
@@ -19,20 +19,18 @@ public class NewlineStream extends InputStream {
     public int read() throws IOException {
         int result;
         switch (endOfFile) {
-            case 0:
+            case 0 -> {
                 result = stream.read();
                 if (result == -1) {
                     result = 13;
                     endOfFile = 1;
                 }
-                break;
-            case 1:
+            }
+            case 1 -> {
                 result = 10;
                 endOfFile = 2;
-                break;
-            default:
-                result = -1;
-                break;
+            }
+            default -> result = -1;
         }
         return result;
     }

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

@@ -10,14 +10,13 @@ public class Scanner extends AbstractParser {
 
     private TokenList tokens;
 
-    public TokenList scan(File directory, String files[]) {
+    public TokenList scan(File directory, String[] files) {
         tokens = new TokenList();
         errors = 0;
-        CPPScanner scanner = null;// will be initialized in Method scanFile
-        for (int i = 0; i < files.length; i++) {
-            currentFile = files[i];
-            getErrorConsumer().print(null, "Scanning file " + files[i]);
-            if (!CPPScanner.scanFile(directory, files[i], scanner, this)) {
+        for (String currentFile : files) {
+            this.currentFile = currentFile;
+            getErrorConsumer().print(null, "Scanning file " + currentFile);
+            if (!CPPScanner.scanFile(directory, currentFile, this)) {
                 errors++;
             }
             tokens.addToken(new CPPToken(CPPTokenConstants.FILE_END, currentFile));

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

@@ -1,7 +1,5 @@
-/* Hey Emacs, this is -*- mode: java; page-delimiter: "^%%$"; -*- */
-
-/* Ein C/C++ Scanner.
- * ...weil Parsen zu schwierig ist.
+/*
+ * A C/C++ scanner.
  */
 
 options
@@ -24,28 +22,25 @@ import java.io.InputStream;
 import de.jplag.cpp.NewlineStream;
 
 public class CPPScanner implements CPPTokenConstants {
-    private static Scanner scanner2;
+    private static Scanner delegatingScanner;
 
-    public static boolean scanFile(File dir, String fileName, CPPScanner scanner, Scanner scannerX) {
+    public static boolean scanFile(File dir, String fileName, Scanner delegatingScanner) {
+        CPPScanner scanner;
         try {
-            InputStream in = new NewlineStream(new FileInputStream(new File(dir, fileName)));
-            if (scanner == null) {
-                scanner = new CPPScanner(in, "UTF-8");
-            } else {
-                scanner.ReInit(in, "UTF-8");
-            }
-            scanner.scanner2 = scannerX;
+            InputStream input = new NewlineStream(new FileInputStream(new File(dir, fileName)));
+            scanner = new CPPScanner(input, "UTF-8");
+            scanner.delegatingScanner = delegatingScanner;
         } catch (FileNotFoundException e) {
-            System.out.println("C/C++ Scanner:  File " + fileName + " not found.");
+            System.out.println("C/C++ Scanner: File " + fileName + " not found.");
             return false;
         }
         try {
             scanner.scan();
         } catch (ParseException e) {
-            scannerX.getErrorConsumer().addError("  Parsing Error in '" + fileName + "':\n  " + e.getMessage());
+            delegatingScanner.getErrorConsumer().addError("  Parsing Error in '" + fileName + "':\n  " + e.getMessage());
             return false;
         } catch (TokenMgrException e) {
-            scannerX.getErrorConsumer().addError("  Scanning Error in '" + fileName + "':\n  " + e.getMessage());
+            delegatingScanner.getErrorConsumer().addError("  Scanning Error in '" + fileName + "':\n  " + e.getMessage());
             return false;
         }
         return true;
@@ -281,29 +276,29 @@ void scan(): {}
 
 void token(): {}
 {
-  "{"            { scanner2.add(C_BLOCK_BEGIN,token); }
-| "}"            { scanner2.add(C_BLOCK_END,token); }
+  "{"            { delegatingScanner.add(C_BLOCK_BEGIN,token); }
+| "}"            { delegatingScanner.add(C_BLOCK_END,token); }
 | "["
 | "]"
 | "("
 | ")"
-| "::"           { scanner2.add(C_SCOPE,token); }
+| "::"           { delegatingScanner.add(C_SCOPE,token); }
 | ":"
 | ";"
 | ","
-| "?"            { scanner2.add(C_QUESTIONMARK,token); }
-| "..."          { scanner2.add(C_ELLIPSIS,token); }
-| "="            { scanner2.add(C_ASSIGN,token); }
-| "*="           { scanner2.add(C_ASSIGN,token); }
-| "/="           { scanner2.add(C_ASSIGN,token); }
-| "%="           { scanner2.add(C_ASSIGN,token); }
-| "+="           { scanner2.add(C_ASSIGN,token); }
-| "-="           { scanner2.add(C_ASSIGN,token); }
-| "<<="          { scanner2.add(C_ASSIGN,token); }
-| ">>="          { scanner2.add(C_ASSIGN,token); }
-| "&="           { scanner2.add(C_ASSIGN,token); }
-| "^="           { scanner2.add(C_ASSIGN,token); }
-| "|="           { scanner2.add(C_ASSIGN,token); }
+| "?"            { delegatingScanner.add(C_QUESTIONMARK,token); }
+| "..."          { delegatingScanner.add(C_ELLIPSIS,token); }
+| "="            { delegatingScanner.add(C_ASSIGN,token); }
+| "*="           { delegatingScanner.add(C_ASSIGN,token); }
+| "/="           { delegatingScanner.add(C_ASSIGN,token); }
+| "%="           { delegatingScanner.add(C_ASSIGN,token); }
+| "+="           { delegatingScanner.add(C_ASSIGN,token); }
+| "-="           { delegatingScanner.add(C_ASSIGN,token); }
+| "<<="          { delegatingScanner.add(C_ASSIGN,token); }
+| ">>="          { delegatingScanner.add(C_ASSIGN,token); }
+| "&="           { delegatingScanner.add(C_ASSIGN,token); }
+| "^="           { delegatingScanner.add(C_ASSIGN,token); }
+| "|="           { delegatingScanner.add(C_ASSIGN,token); }
 | "||"
 | "&&"
 | "|"
@@ -322,61 +317,61 @@ void token(): {}
 | "*"
 | "/"
 | "%"
-| "++"           { scanner2.add(C_ASSIGN,token); }
-| "--"           { scanner2.add(C_ASSIGN,token); }
+| "++"           { delegatingScanner.add(C_ASSIGN,token); }
+| "--"           { delegatingScanner.add(C_ASSIGN,token); }
 | "~"
 | "!"
-| "auto"         { scanner2.add(C_AUTO,token); }
-| "break"        { scanner2.add(C_BREAK,token); }
-| "case"         { scanner2.add(C_CASE,token); }
-| "catch"        { scanner2.add(C_CATCH,token); }
-| "char"         { scanner2.add(C_CHAR,token); }
-| "const"        { scanner2.add(C_CONST,token); }
-| "continue"     { scanner2.add(C_CONTINUE,token); }
-| "default"      { scanner2.add(C_DEFAULT,token); }
-| "delete"       { scanner2.add(C_DELETE,token); }
-| "do"           { scanner2.add(C_DO,token); }
-| "double"       { scanner2.add(C_DOUBLE,token); }
-| "else"         { scanner2.add(C_ELSE,token); }
-| "enum"         { scanner2.add(C_ENUM,token); }
-| "extern"       { scanner2.add(C_EXTERN,token); }
-| "float"        { scanner2.add(C_FLOAT,token); }
-| "for"          { scanner2.add(C_FOR,token); }
-| "friend"       { scanner2.add(C_FRIEND,token); }
-| "goto"         { scanner2.add(C_GOTO,token); }
-| "if"           { scanner2.add(C_IF,token); }
-| "inline"       { scanner2.add(C_INLINE,token); }
-| "int"          { scanner2.add(C_INT,token); }
-| "long"         { scanner2.add(C_LONG,token); }
-| "new"          { scanner2.add(C_NEW,token); }
-| "private"      { scanner2.add(C_PRIVATE,token); }
-| "protected"    { scanner2.add(C_PROTECTED,token); }
-| "public"       { scanner2.add(C_PUBLIC,token); }
-| "redeclared"   { scanner2.add(C_REDECLARED,token); }
-| "register"     { scanner2.add(C_REGISTER,token); }
-| "return"       { scanner2.add(C_RETURN,token); }
-| "short"        { scanner2.add(C_SHORT,token); }
-| "signed"       { scanner2.add(C_SIGNED,token); }
-| "sizeof"       { scanner2.add(C_SIZEOF,token); }
-| "static"       { scanner2.add(C_STATIC,token); }
-| "struct"       { scanner2.add(C_STRUCT,token); }
-| "class"        { scanner2.add(C_CLASS,token); }
-| "switch"       { scanner2.add(C_SWITCH,token); }
-| "template"     { scanner2.add(C_TEMPLATE,token); }
-| "this"         { scanner2.add(C_THIS,token); }
-| "try"          { scanner2.add(C_TRY,token); }
-| "typedef"      { scanner2.add(C_TYPEDEF,token); }
-| "union"        { scanner2.add(C_UNION,token); }
-| "unsigned"     { scanner2.add(C_UNSIGNED,token); }
-| "virtual"      { scanner2.add(C_VIRTUAL,token); }
-| "void"         { scanner2.add(C_VOID,token); }
-| "volatile"     { scanner2.add(C_VOLANTILE,token); }
-| "while"        { scanner2.add(C_WHILE,token); }
-| "operator"     { scanner2.add(C_OPERATOR,token); }
+| "auto"         { delegatingScanner.add(C_AUTO,token); }
+| "break"        { delegatingScanner.add(C_BREAK,token); }
+| "case"         { delegatingScanner.add(C_CASE,token); }
+| "catch"        { delegatingScanner.add(C_CATCH,token); }
+| "char"         { delegatingScanner.add(C_CHAR,token); }
+| "const"        { delegatingScanner.add(C_CONST,token); }
+| "continue"     { delegatingScanner.add(C_CONTINUE,token); }
+| "default"      { delegatingScanner.add(C_DEFAULT,token); }
+| "delete"       { delegatingScanner.add(C_DELETE,token); }
+| "do"           { delegatingScanner.add(C_DO,token); }
+| "double"       { delegatingScanner.add(C_DOUBLE,token); }
+| "else"         { delegatingScanner.add(C_ELSE,token); }
+| "enum"         { delegatingScanner.add(C_ENUM,token); }
+| "extern"       { delegatingScanner.add(C_EXTERN,token); }
+| "float"        { delegatingScanner.add(C_FLOAT,token); }
+| "for"          { delegatingScanner.add(C_FOR,token); }
+| "friend"       { delegatingScanner.add(C_FRIEND,token); }
+| "goto"         { delegatingScanner.add(C_GOTO,token); }
+| "if"           { delegatingScanner.add(C_IF,token); }
+| "inline"       { delegatingScanner.add(C_INLINE,token); }
+| "int"          { delegatingScanner.add(C_INT,token); }
+| "long"         { delegatingScanner.add(C_LONG,token); }
+| "new"          { delegatingScanner.add(C_NEW,token); }
+| "private"      { delegatingScanner.add(C_PRIVATE,token); }
+| "protected"    { delegatingScanner.add(C_PROTECTED,token); }
+| "public"       { delegatingScanner.add(C_PUBLIC,token); }
+| "redeclared"   { delegatingScanner.add(C_REDECLARED,token); }
+| "register"     { delegatingScanner.add(C_REGISTER,token); }
+| "return"       { delegatingScanner.add(C_RETURN,token); }
+| "short"        { delegatingScanner.add(C_SHORT,token); }
+| "signed"       { delegatingScanner.add(C_SIGNED,token); }
+| "sizeof"       { delegatingScanner.add(C_SIZEOF,token); }
+| "static"       { delegatingScanner.add(C_STATIC,token); }
+| "struct"       { delegatingScanner.add(C_STRUCT,token); }
+| "class"        { delegatingScanner.add(C_CLASS,token); }
+| "switch"       { delegatingScanner.add(C_SWITCH,token); }
+| "template"     { delegatingScanner.add(C_TEMPLATE,token); }
+| "this"         { delegatingScanner.add(C_THIS,token); }
+| "try"          { delegatingScanner.add(C_TRY,token); }
+| "typedef"      { delegatingScanner.add(C_TYPEDEF,token); }
+| "union"        { delegatingScanner.add(C_UNION,token); }
+| "unsigned"     { delegatingScanner.add(C_UNSIGNED,token); }
+| "virtual"      { delegatingScanner.add(C_VIRTUAL,token); }
+| "void"         { delegatingScanner.add(C_VOID,token); }
+| "volatile"     { delegatingScanner.add(C_VOLANTILE,token); }
+| "while"        { delegatingScanner.add(C_WHILE,token); }
+| "operator"     { delegatingScanner.add(C_OPERATOR,token); }
 | "true"
 | "false"
-| "throw"        { scanner2.add(C_THROW,token); }
-| "NULL"         { scanner2.add(C_NULL,token); }
+| "throw"        { delegatingScanner.add(C_THROW,token); }
+| "NULL"         { delegatingScanner.add(C_NULL,token); }
 
 | <OCTALINT>
 | <OCTALLONG>
@@ -400,7 +395,7 @@ void token(): {}
 
 | <BACKSLASH>
 
-       //| var() [LOOKAHEAD(2) "("        { scanner2.add(C_FUN,token); } ]
+       //| var() [LOOKAHEAD(2) "("        { delegatingScanner.add(C_FUN,token); } ]
 | "."
 | "->"
 | ".*"

+ 56 - 111
jplag.frontend.csharp-1.2/src/main/java/de/jplag/csharp/CSharpToken.java

@@ -9,116 +9,61 @@ public class CSharpToken extends Token implements CSharpTokenConstants {
     }
 
     protected String type2string() {
-        switch (type) {
-            case CSharpTokenConstants.FILE_END:
-                return "EOF";
-            case _INVOCATION:
-                return "INVOCATION";
-            case _OBJECT_CREATION:
-                return "OBJECT_CRE";
-            case _ARRAY_CREATION:
-                return "ARRAY_CREA";
-            case _ASSIGNMENT:
-                return "ASSIGNMENT";
-            case _L_BRACE:
-                return "L_BRACE { ";
-            case _R_BRACE:
-                return "R_BRACE } ";
-            case _DECLARE_VAR:
-                return "DECLAREVAR";
-            case _DECLARE_CONST:
-                return "DECLARE_CO";
-            case _IF:
-                return "IF {      ";
-            case _ELSE:
-                return "ELSE      ";
-            case _END_IF:
-                return "IF }      ";
-            case _SWITCH_BEGIN:
-                return "SWITCH {  ";
-            case _SWITCH_END:
-                return "SWITCH }  ";
-            case _CASE:
-                return "CASE      ";
-            case _DO:
-                return "DO        ";
-            case _WHILE:
-                return "WHILE     ";
-            case _FOR:
-                return "FOR       ";
-            case _FOREACH:
-                return "FOREACH   ";
-            case _BREAK:
-                return "BREAK     ";
-            case _CONTINUE:
-                return "CONTINUE  ";
-            case _GOTO:
-                return "GOTO      ";
-            case _RETURN:
-                return "RETURN    ";
-            case _THROW:
-                return "THROW     ";
-            case _CHECKED:
-                return "CHECKED   ";
-            case _UNCHECKED:
-                return "UNCHECKED ";
-            case _LOCK:
-                return "LOCK      ";
-            case _USING:
-                return "USING     ";
-            case _TRY:
-                return "TRY       ";
-            case _CATCH:
-                return "CATCH     ";
-            case _FINALLY:
-                return "FINALLY   ";
-            case _NAMESPACE_BEGIN:
-                return "NAMESPACE{";
-            case _NAMESPACE_END:
-                return "NAMESPACE}";
-            case _USING_DIRECTIVE:
-                return "USING_DIR ";
-            case _CLASS_BEGIN:
-                return "CLASS {   ";
-            case _CLASS_END:
-                return "CLASS }   ";
-            case _METHOD:
-                return "METHOD    ";
-            case _PROPERTY:
-                return "PROPERTY  ";
-            case _EVENT:
-                return "EVENT     ";
-            case _INDEXER:
-                return "INDEXER   ";
-            case _OPERATOR:
-                return "OPERATOR  ";
-            case _CONSTRUCTOR:
-                return "CONSTR    ";
-            case _STATIC_CONSTR:
-                return "ST_CONSTR ";
-            case _DESTRUCTOR:
-                return "DESTRUCTOR";
-            case _STRUCT_BEGIN:
-                return "STRUCT {  ";
-            case _STRUCT_END:
-                return "STRUCT }  ";
-            case _INTERFACE_BEGIN:
-                return "INTERFACE{";
-            case _INTERFACE_END:
-                return "INTERFACE}";
-            case _ENUM:
-                return "ENUM      ";
-            case _DELEGATE:
-                return "DELEGATE  ";
-            case _ATTRIBUTE:
-                return "ATTRIBUTE ";
-            case _UNSAFE:
-                return "UNSAFE    ";
-            case _FIXED:
-                return "FIXED     ";
-
-            default:
-                return "<UNKNOWN> ";
-        }
+        return switch (type) {
+            case CSharpTokenConstants.FILE_END -> "EOF";
+            case _INVOCATION -> "INVOCATION";
+            case _OBJECT_CREATION -> "OBJECT_CRE";
+            case _ARRAY_CREATION -> "ARRAY_CREA";
+            case _ASSIGNMENT -> "ASSIGNMENT";
+            case _L_BRACE -> "L_BRACE { ";
+            case _R_BRACE -> "R_BRACE } ";
+            case _DECLARE_VAR -> "DECLAREVAR";
+            case _DECLARE_CONST -> "DECLARE_CO";
+            case _IF -> "IF {      ";
+            case _ELSE -> "ELSE      ";
+            case _END_IF -> "IF }      ";
+            case _SWITCH_BEGIN -> "SWITCH {  ";
+            case _SWITCH_END -> "SWITCH }  ";
+            case _CASE -> "CASE      ";
+            case _DO -> "DO        ";
+            case _WHILE -> "WHILE     ";
+            case _FOR -> "FOR       ";
+            case _FOREACH -> "FOREACH   ";
+            case _BREAK -> "BREAK     ";
+            case _CONTINUE -> "CONTINUE  ";
+            case _GOTO -> "GOTO      ";
+            case _RETURN -> "RETURN    ";
+            case _THROW -> "THROW     ";
+            case _CHECKED -> "CHECKED   ";
+            case _UNCHECKED -> "UNCHECKED ";
+            case _LOCK -> "LOCK      ";
+            case _USING -> "USING     ";
+            case _TRY -> "TRY       ";
+            case _CATCH -> "CATCH     ";
+            case _FINALLY -> "FINALLY   ";
+            case _NAMESPACE_BEGIN -> "NAMESPACE{";
+            case _NAMESPACE_END -> "NAMESPACE}";
+            case _USING_DIRECTIVE -> "USING_DIR ";
+            case _CLASS_BEGIN -> "CLASS {   ";
+            case _CLASS_END -> "CLASS }   ";
+            case _METHOD -> "METHOD    ";
+            case _PROPERTY -> "PROPERTY  ";
+            case _EVENT -> "EVENT     ";
+            case _INDEXER -> "INDEXER   ";
+            case _OPERATOR -> "OPERATOR  ";
+            case _CONSTRUCTOR -> "CONSTR    ";
+            case _STATIC_CONSTR -> "ST_CONSTR ";
+            case _DESTRUCTOR -> "DESTRUCTOR";
+            case _STRUCT_BEGIN -> "STRUCT {  ";
+            case _STRUCT_END -> "STRUCT }  ";
+            case _INTERFACE_BEGIN -> "INTERFACE{";
+            case _INTERFACE_END -> "INTERFACE}";
+            case _ENUM -> "ENUM      ";
+            case _DELEGATE -> "DELEGATE  ";
+            case _ATTRIBUTE -> "ATTRIBUTE ";
+            case _UNSAFE -> "UNSAFE    ";
+            case _FIXED -> "FIXED     ";
+            default -> "<UNKNOWN> ";
+        };
     }
 }

+ 53 - 53
jplag.frontend.csharp-1.2/src/main/java/de/jplag/csharp/CSharpTokenConstants.java

@@ -3,58 +3,58 @@ package de.jplag.csharp;
 import de.jplag.TokenConstants;
 
 public interface CSharpTokenConstants extends TokenConstants {
-    final static int _INVOCATION = 2;
-    final static int _OBJECT_CREATION = 3;
-    final static int _ARRAY_CREATION = 4;
-    final static int _ASSIGNMENT = 5;
-    final static int _L_BRACE = 6;
-    final static int _R_BRACE = 7;
-    final static int _DECLARE_VAR = 8;
-    final static int _DECLARE_CONST = 9;
-    final static int _IF = 10;
-    final static int _ELSE = 11;
-    final static int _SWITCH_BEGIN = 12;
-    final static int _SWITCH_END = 13;
-    final static int _CASE = 14;
-    final static int _DO = 15;
-    final static int _WHILE = 16;
-    final static int _FOR = 17;
-    final static int _FOREACH = 18;
-    final static int _BREAK = 19;
-    final static int _CONTINUE = 20;
-    final static int _GOTO = 21;
-    final static int _RETURN = 22;
-    final static int _THROW = 23;
-    final static int _CHECKED = 24;
-    final static int _UNCHECKED = 25;
-    final static int _LOCK = 26;
-    final static int _USING = 27;
-    final static int _TRY = 28;
-    final static int _CATCH = 29;
-    final static int _FINALLY = 30;
-    final static int _NAMESPACE_BEGIN = 31;
-    final static int _NAMESPACE_END = 32;
-    final static int _USING_DIRECTIVE = 33;
-    final static int _CLASS_BEGIN = 34;
-    final static int _CLASS_END = 35;
-    final static int _METHOD = 36;
-    final static int _PROPERTY = 37;
-    final static int _EVENT = 38;
-    final static int _INDEXER = 39;
-    final static int _OPERATOR = 40;
-    final static int _CONSTRUCTOR = 41;
-    final static int _STATIC_CONSTR = 42;
-    final static int _DESTRUCTOR = 43;
-    final static int _STRUCT_BEGIN = 44;
-    final static int _STRUCT_END = 45;
-    final static int _INTERFACE_BEGIN = 46;
-    final static int _INTERFACE_END = 47;
-    final static int _ENUM = 48;
-    final static int _DELEGATE = 49;
-    final static int _ATTRIBUTE = 50;
-    final static int _END_IF = 51;
-    final static int _UNSAFE = 52;
-    final static int _FIXED = 53;
+    int _INVOCATION = 2;
+    int _OBJECT_CREATION = 3;
+    int _ARRAY_CREATION = 4;
+    int _ASSIGNMENT = 5;
+    int _L_BRACE = 6;
+    int _R_BRACE = 7;
+    int _DECLARE_VAR = 8;
+    int _DECLARE_CONST = 9;
+    int _IF = 10;
+    int _ELSE = 11;
+    int _SWITCH_BEGIN = 12;
+    int _SWITCH_END = 13;
+    int _CASE = 14;
+    int _DO = 15;
+    int _WHILE = 16;
+    int _FOR = 17;
+    int _FOREACH = 18;
+    int _BREAK = 19;
+    int _CONTINUE = 20;
+    int _GOTO = 21;
+    int _RETURN = 22;
+    int _THROW = 23;
+    int _CHECKED = 24;
+    int _UNCHECKED = 25;
+    int _LOCK = 26;
+    int _USING = 27;
+    int _TRY = 28;
+    int _CATCH = 29;
+    int _FINALLY = 30;
+    int _NAMESPACE_BEGIN = 31;
+    int _NAMESPACE_END = 32;
+    int _USING_DIRECTIVE = 33;
+    int _CLASS_BEGIN = 34;
+    int _CLASS_END = 35;
+    int _METHOD = 36;
+    int _PROPERTY = 37;
+    int _EVENT = 38;
+    int _INDEXER = 39;
+    int _OPERATOR = 40;
+    int _CONSTRUCTOR = 41;
+    int _STATIC_CONSTR = 42;
+    int _DESTRUCTOR = 43;
+    int _STRUCT_BEGIN = 44;
+    int _STRUCT_END = 45;
+    int _INTERFACE_BEGIN = 46;
+    int _INTERFACE_END = 47;
+    int _ENUM = 48;
+    int _DELEGATE = 49;
+    int _ATTRIBUTE = 50;
+    int _END_IF = 51;
+    int _UNSAFE = 52;
+    int _FIXED = 53;
 
-    final static int NUM_DIFF_TOKENS = 54;
+    int NUM_DIFF_TOKENS = 54;
 }

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

@@ -6,11 +6,11 @@ import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
 public class Language implements de.jplag.Language {
-    private Parser parser;
+    private final Parser parser;
 
     public Language(ErrorConsumer program) {
         this.parser = new Parser();
-        this.parser.setProgram(program);
+        this.parser.setErrorConsumer(program);
 
     }
 

+ 1 - 1
jplag.frontend.csharp-1.2/src/main/java/de/jplag/csharp/Parser.java

@@ -15,7 +15,7 @@ public class Parser extends AbstractParser {
     private TokenList tokens;
     private String currentFile;
 
-    public TokenList parse(File directory, String files[]) {
+    public TokenList parse(File directory, String[] files) {
         tokens = new TokenList();
         errors = 0;
         for (int i = 0; i < files.length; i++) {

+ 8 - 9
jplag.frontend.csharp-1.2/src/main/java/de/jplag/csharp/UnicodeReader.java

@@ -7,6 +7,7 @@ import java.io.PushbackInputStream;
 import java.io.Reader;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
+import java.util.Objects;
 
 /**
  * Generic unicode text reader, which will use BOM mark to identify the encoding to be used. If BOM is not found then
@@ -33,11 +34,11 @@ import java.nio.charset.StandardCharsets;
  * </pre>
  */
 public class UnicodeReader extends Reader {
-    private PushbackInputStream internalIn;
+    private final PushbackInputStream internalIn;
 
     private InputStreamReader internalIn2 = null;
 
-    private Charset defaultEnc;
+    private final Charset defaultEnc;
 
     private static final int BOM_SIZE = 4;
 
@@ -64,7 +65,7 @@ public class UnicodeReader extends Reader {
             return;
 
         Charset encoding;
-        byte bom[] = new byte[BOM_SIZE];
+        byte[] bom = new byte[BOM_SIZE];
         int n, unread;
         n = internalIn.read(bom, 0, bom.length);
 
@@ -80,7 +81,9 @@ public class UnicodeReader extends Reader {
         } else if ((bom[0] == (byte) 0x00) && (bom[1] == (byte) 0x00) && (bom[2] == (byte) 0xFE) && (bom[3] == (byte) 0xFF)) {
             encoding = Charset.forName("UTF-32BE");
             unread = n - 4;
-        } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE) && (bom[2] == (byte) 0x00) && (bom[3] == (byte) 0x00)) {
+        }
+        // TODO WARNING: This line will evaluate always to false. Maybe that's an issue
+        else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE) && (bom[2] == (byte) 0x00) && (bom[3] == (byte) 0x00)) {
             encoding = Charset.forName("UTF-32LE");
             unread = n - 4;
         } else {
@@ -93,11 +96,7 @@ public class UnicodeReader extends Reader {
             internalIn.unread(bom, (n - unread), unread);
 
         // Use given encoding
-        if (encoding == null) {
-            internalIn2 = new InputStreamReader(internalIn, StandardCharsets.UTF_8);
-        } else {
-            internalIn2 = new InputStreamReader(internalIn, encoding);
-        }
+        internalIn2 = new InputStreamReader(internalIn, Objects.requireNonNullElse(encoding, StandardCharsets.UTF_8));
     }
 
     @Override

+ 11 - 3
jplag.frontend.java/src/main/java/de/jplag/java/JavaToken.java

@@ -1,6 +1,7 @@
 package de.jplag.java;
 
 import de.jplag.Token;
+import de.jplag.TokenConstants;
 
 public class JavaToken extends Token implements JavaTokenConstants {
 
@@ -11,9 +12,9 @@ public class JavaToken extends Token implements JavaTokenConstants {
     @Override
     protected String type2string() {
         switch (type) {
-            case JavaTokenConstants.FILE_END:
+            case TokenConstants.FILE_END:
                 return "EOF";
-            case JavaTokenConstants.SEPARATOR_TOKEN:
+            case TokenConstants.SEPARATOR_TOKEN:
                 return "METHOD_SEPARATOR";
             case J_PACKAGE:
                 return "PACKAGE";
@@ -141,9 +142,16 @@ public class JavaToken extends Token implements JavaTokenConstants {
                 return "PROVIDES";
             case J_REQUIRES:
                 return "REQUIRES";
-
             case J_TRY_WITH_RESOURCE:
                 return "TRY_RES";
+            case J_YIELD:
+                return "YIELD";
+            case J_DEFAULT:
+                return "DEFAULT";
+            case J_RECORD_BEGIN:
+                return "RECORD{";
+            case J_RECORD_END:
+                return "}RECORD";
 
             default:
                 System.err.println("UNKNOWN: " + type);

+ 73 - 66
jplag.frontend.java/src/main/java/de/jplag/java/JavaTokenConstants.java

@@ -4,78 +4,85 @@ import de.jplag.TokenConstants;
 
 public interface JavaTokenConstants extends TokenConstants {
 
-    // @formatter:off
-    final static int J_PACKAGE = 2;				// check
-    final static int J_IMPORT = 3;				// check
-    final static int J_CLASS_BEGIN = 4;			// check
-    final static int J_CLASS_END = 5;			// check
-    final static int J_METHOD_BEGIN = 6;		// check
-    final static int J_METHOD_END = 7;			// check
-    final static int J_VARDEF = 8;				// check
-    final static int J_SYNC_BEGIN = 9;			// check
-    final static int J_SYNC_END = 10;			// check
-    final static int J_DO_BEGIN = 11;			// check
-    final static int J_DO_END = 12;				// check
-    final static int J_WHILE_BEGIN = 13;		// check
-    final static int J_WHILE_END = 14;			// check
-    final static int J_FOR_BEGIN = 15;			// check
-    final static int J_FOR_END = 16;			// check
-    final static int J_SWITCH_BEGIN = 17;		// check
-    final static int J_SWITCH_END = 18;			// check
-    final static int J_CASE = 19;				// check
-    final static int J_TRY_BEGIN = 20;			// check
-    final static int J_CATCH_BEGIN = 21;    	// check
-    final static int J_CATCH_END = 22;      	// check
-    final static int J_FINALLY = 23;			// check
-    final static int J_IF_BEGIN = 24;			// check
-    final static int J_ELSE = 25;				// check
-    final static int J_IF_END = 26;				// check
-    final static int J_COND = 27;				// check
-    final static int J_BREAK = 28;				// check
-    final static int J_CONTINUE = 29;			// check
-    final static int J_RETURN = 30;				// check
-    final static int J_THROW = 31;				// check
-    final static int J_IN_CLASS_BEGIN = 32;		//
-    final static int J_IN_CLASS_END = 33;		//
-    final static int J_APPLY = 34;				// check
-    final static int J_NEWCLASS = 35;			// check
-    final static int J_NEWARRAY = 36;			// check
-    final static int J_ASSIGN = 37;				// check
-    final static int J_INTERFACE_BEGIN = 38;	// check
-    final static int J_INTERFACE_END = 39;		// check
-    final static int J_CONSTR_BEGIN = 40;		//
-    final static int J_CONSTR_END = 41;			//
-    final static int J_INIT_BEGIN = 42;			// check
-    final static int J_INIT_END = 43;			// check
-    final static int J_VOID = 44;				//
-    final static int J_ARRAY_INIT_BEGIN = 45;	// check
-    final static int J_ARRAY_INIT_END = 46; 	// check
+    int J_PACKAGE = 2;                // check
+    int J_IMPORT = 3;                // check
+    int J_CLASS_BEGIN = 4;            // check
+    int J_CLASS_END = 5;            // check
+    int J_METHOD_BEGIN = 6;        // check
+    int J_METHOD_END = 7;            // check
+    int J_VARDEF = 8;                // check
+    int J_SYNC_BEGIN = 9;            // check
+    int J_SYNC_END = 10;            // check
+    int J_DO_BEGIN = 11;            // check
+    int J_DO_END = 12;                // check
+    int J_WHILE_BEGIN = 13;        // check
+    int J_WHILE_END = 14;            // check
+    int J_FOR_BEGIN = 15;            // check
+    int J_FOR_END = 16;            // check
+    int J_SWITCH_BEGIN = 17;        // check
+    int J_SWITCH_END = 18;            // check
+    int J_CASE = 19;                // check
+    int J_TRY_BEGIN = 20;            // check
+    int J_CATCH_BEGIN = 21;        // check
+    int J_CATCH_END = 22;        // check
+    int J_FINALLY = 23;            // check
+    int J_IF_BEGIN = 24;            // check
+    int J_ELSE = 25;                // check
+    int J_IF_END = 26;                // check
+    int J_COND = 27;                // check
+    int J_BREAK = 28;                // check
+    int J_CONTINUE = 29;            // check
+    int J_RETURN = 30;                // check
+    int J_THROW = 31;                // check
+    int J_IN_CLASS_BEGIN = 32;        //
+    int J_IN_CLASS_END = 33;        //
+    int J_APPLY = 34;                // check
+    int J_NEWCLASS = 35;            // check
+    int J_NEWARRAY = 36;            // check
+    int J_ASSIGN = 37;                // check
+    int J_INTERFACE_BEGIN = 38;    // check
+    int J_INTERFACE_END = 39;        // check
+    int J_CONSTR_BEGIN = 40;        //
+    int J_CONSTR_END = 41;            //
+    int J_INIT_BEGIN = 42;            // check
+    int J_INIT_END = 43;            // check
+    int J_VOID = 44;                //
+    int J_ARRAY_INIT_BEGIN = 45;    // check
+    int J_ARRAY_INIT_END = 46;    // check
 
     // new in 1.5:
-    final static int J_ENUM_BEGIN = 47;			// check
-    final static int J_ENUM_CLASS_BEGIN = 48;	// ?? doesn't exist in JAVAC
-    final static int J_ENUM_END = 49;			// check
-    final static int J_GENERIC = 50;			// check
-    final static int J_ASSERT = 51;				// check
+    int J_ENUM_BEGIN = 47;            // check
+    int J_ENUM_CLASS_BEGIN = 48;    // ?? doesn't exist in JAVAC
+    int J_ENUM_END = 49;            // check
+    int J_GENERIC = 50;            // check
+    int J_ASSERT = 51;                // check
 
-    final static int J_ANNO = 52;				// check
-    final static int J_ANNO_MARKER = 53;		// ??
-    final static int J_ANNO_M_BEGIN = 54;		// ??
-    final static int J_ANNO_M_END = 55;			// ??
-    final static int J_ANNO_T_BEGIN = 56;		// check
-    final static int J_ANNO_T_END = 57;			// check
-    final static int J_ANNO_C_BEGIN = 58;		// ??
-    final static int J_ANNO_C_END = 59;			// ??
+    int J_ANNO = 52;                // check
+    int J_ANNO_MARKER = 53;        // ??
+    int J_ANNO_M_BEGIN = 54;        // ??
+    int J_ANNO_M_END = 55;            // ??
+    int J_ANNO_T_BEGIN = 56;        // check
+    int J_ANNO_T_END = 57;            // check
+    int J_ANNO_C_BEGIN = 58;        // ??
+    int J_ANNO_C_END = 59;            // ??
 
     // new in 1.7
-    final static int J_TRY_WITH_RESOURCE = 60; 	// check
+    int J_TRY_WITH_RESOURCE = 60;    // check
 
     // new in 1.9
-    final static int J_REQUIRES = 61; 	        // check
-    final static int J_PROVIDES = 62; 	        // check
-    final static int J_EXPORTS = 63; 	        // check
-    final static int J_MODULE_BEGIN = 64;       // check
-    final static int J_MODULE_END = 65; 	    // check
+    int J_REQUIRES = 61;            // check
+    int J_PROVIDES = 62;            // check
+    int J_EXPORTS = 63;            // check
+    int J_MODULE_BEGIN = 64;       // check
+    int J_MODULE_END = 65;        // check
 
-    final static int NUM_DIFF_TOKENS = 66;	    // @formatter:on
+    // new in 13
+    final static int J_YIELD = 66;
+
+    // new in 17
+    final static int J_DEFAULT = 67;
+    final static int J_RECORD_BEGIN = 68;
+    final static int J_RECORD_END = 69;
+
+    final static int NUM_DIFF_TOKENS = 70;	    // @formatter:on
 }

+ 36 - 22
jplag.frontend.java/src/main/java/de/jplag/java/JavacAdapter.java

@@ -9,7 +9,7 @@ import java.util.Collections;
 import javax.tools.Diagnostic;
 import javax.tools.DiagnosticCollector;
 import javax.tools.JavaCompiler;
-import javax.tools.JavaFileObject;
+import javax.tools.JavaCompiler.CompilationTask;
 import javax.tools.StandardJavaFileManager;
 import javax.tools.ToolProvider;
 
@@ -19,38 +19,52 @@ 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 {
 
     private static final JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
 
-    public int parseFiles(File directory, File[] pathedFiles, final Parser parser) {
+    public int parseFiles(File directory, Iterable<File> pathedFiles, final Parser parser) {
         final StandardJavaFileManager fileManager = javac.getStandardFileManager(null, null, StandardCharsets.UTF_8);
-        DiagnosticCollector<? super JavaFileObject> diagListen = new DiagnosticCollector<>();
-        final JavaCompiler.CompilationTask task = javac.getTask(null, fileManager, diagListen, null, null,
-                fileManager.getJavaFileObjects(pathedFiles));
-        Iterable<? extends CompilationUnitTree> asts = Collections.emptyList();
-        try {
-            asts = ((JavacTask) task).parse();
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
+        var listener = new DiagnosticCollector<>();
+        var javaFiles = fileManager.getJavaFileObjectsFromFiles(pathedFiles);
+        final CompilationTask task = javac.getTask(null, fileManager, listener, null, null, javaFiles);
         final Trees trees = Trees.instance(task);
         final SourcePositions positions = trees.getSourcePositions();
-        for (final CompilationUnitTree ast : asts) {
-            final String filename;
-            if (directory == null)
-                filename = ast.getSourceFile().getName();
-            else {
-                filename = Paths.get(directory.toURI()).relativize(Paths.get(ast.getSourceFile().toUri())).toString();
-            }
+        for (final CompilationUnitTree ast : executeCompilationTask(task)) {
+            final String filename = fileNameOf(directory, ast);
             final LineMap map = ast.getLineMap();
             ast.accept(new TokenGeneratingTreeScanner(filename, parser, map, positions, ast), null);
-            parser.add(JavaTokenConstants.FILE_END, filename, 1, -1, -1);
+            parser.add(TokenConstants.FILE_END, filename, 1, -1, -1);
         }
+        return processErrors(parser.getErrorConsumer(), listener);
+    }
+
+    private Iterable<? extends CompilationUnitTree> executeCompilationTask(final CompilationTask task) {
+        Iterable<? extends CompilationUnitTree> abstractSyntaxTrees = Collections.emptyList();
+        try {
+            abstractSyntaxTrees = ((JavacTask) task).parse();
+        } catch (IOException exception) {
+            exception.printStackTrace();
+        }
+        return abstractSyntaxTrees;
+    }
+
+    private String fileNameOf(File directory, final CompilationUnitTree ast) {
+        if (directory == null)
+            return ast.getSourceFile().getName();
+        else {
+            return Paths.get(directory.toURI()).relativize(Paths.get(ast.getSourceFile().toUri())).toString();
+        }
+    }
+
+    private int processErrors(final ErrorConsumer consumer, DiagnosticCollector<Object> listener) {
         int errors = 0;
-        for (Diagnostic<?> diagItem : diagListen.getDiagnostics()) {
-            if (diagItem.getKind() == javax.tools.Diagnostic.Kind.ERROR) {
-                parser.getErrorConsumer().addError(diagItem.toString());
+        for (Diagnostic<?> diagnosticItem : listener.getDiagnostics()) {
+            if (diagnosticItem.getKind() == javax.tools.Diagnostic.Kind.ERROR) {
+                consumer.addError(diagnosticItem.toString());
                 errors++;
             }
         }

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

@@ -9,11 +9,11 @@ import de.jplag.TokenList;
  * Language for Java 9 and newer.
  */
 public class Language implements de.jplag.Language {
-    private Parser parser;
+    private final Parser parser;
 
-    public Language(ErrorConsumer program) {
+    public Language(ErrorConsumer errorConsumer) {
         this.parser = new Parser();
-        this.parser.setProgram(program);
+        this.parser.setErrorConsumer(errorConsumer);
     }
 
     @Override
@@ -23,7 +23,7 @@ public class Language implements de.jplag.Language {
 
     @Override
     public String getName() {
-        return "Javac 1.9+ based AST plugin";
+        return "Javac based AST plugin";
     }
 
     @Override
@@ -57,8 +57,8 @@ public class Language implements de.jplag.Language {
     }
 
     @Override
-    public TokenList parse(File dir, String[] files) {
-        return this.parser.parse(dir, files);
+    public TokenList parse(File directory, String[] files) {
+        return this.parser.parse(directory, files);
     }
 
     @Override

+ 9 - 10
jplag.frontend.java/src/main/java/de/jplag/java/Parser.java

@@ -1,6 +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.TokenList;
@@ -8,24 +11,20 @@ import de.jplag.TokenList;
 public class Parser extends AbstractParser {
     private TokenList tokens;
 
-    public TokenList parse(File directory, String files[]) {
+    public TokenList parse(File directory, String[] files) {
         tokens = new TokenList();
         errors = 0;
-        File pathedFiles[] = new File[files.length];
-        for (int i = 0; i < files.length; i++) {
-            pathedFiles[i] = new File(directory, files[i]);
-        }
-        JavacAdapter javac = new JavacAdapter();
-        errors += javac.parseFiles(directory, pathedFiles, this);
+        var pathedFiles = Arrays.stream(files).map(it -> new File(directory, it)).collect(toList());
+        errors += new JavacAdapter().parseFiles(directory, pathedFiles, this);
         this.parseEnd();
         return tokens;
     }
 
-    public void add(int type, String filename, long line, long col, long length) {
-        tokens.addToken(new JavaToken(type, filename, (int) line, (int) col, (int) length));
+    public void add(int type, String filename, long line, long column, long length) {
+        tokens.addToken(new JavaToken(type, filename, (int) line, (int) column, (int) length));
     }
 
-    public void errorsInc() {
+    public void increaseErrors() {
         errors++;
     }
 }

+ 180 - 149
jplag.frontend.java/src/main/java/de/jplag/java/TokenGeneratingTreeScanner.java

@@ -11,6 +11,7 @@ import com.sun.source.tree.ClassTree;
 import com.sun.source.tree.CompilationUnitTree;
 import com.sun.source.tree.ConditionalExpressionTree;
 import com.sun.source.tree.ContinueTree;
+import com.sun.source.tree.DefaultCaseLabelTree;
 import com.sun.source.tree.DoWhileLoopTree;
 import com.sun.source.tree.EnhancedForLoopTree;
 import com.sun.source.tree.ErroneousTree;
@@ -37,6 +38,7 @@ import com.sun.source.tree.TryTree;
 import com.sun.source.tree.TypeParameterTree;
 import com.sun.source.tree.VariableTree;
 import com.sun.source.tree.WhileLoopTree;
+import com.sun.source.tree.YieldTree;
 import com.sun.source.util.SourcePositions;
 import com.sun.source.util.TreeScanner;
 
@@ -55,328 +57,357 @@ final class TokenGeneratingTreeScanner extends TreeScanner<Object, Object> {
         this.ast = ast;
     }
 
+    /**
+     * Convenience method that adds a specific token.
+     * @param tokenType is the type from {@link JavaTokenConstants}.
+     * @param position is the start position of the token.
+     * @param length is the length of the token.
+     */
+    private void addToken(int tokenType, long position, int length) {
+        parser.add(tokenType, filename, map.getLineNumber(position), map.getColumnNumber(position), length);
+    }
+
+    /**
+     * Convenience method that adds a specific token.
+     * @param tokenType is the type from {@link JavaTokenConstants}.
+     * @param start is the start position of the token.
+     * @param end is the end position of the token for the calculation of the length.
+     */
+    private void addToken(int tokenType, long start, long end) {
+        parser.add(tokenType, filename, map.getLineNumber(start), map.getColumnNumber(start), (end - start));
+    }
+
     @Override
     public Object visitBlock(BlockTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        long m = positions.getEndPosition(ast, node);
-        parser.add(JavaTokenConstants.J_INIT_BEGIN, filename, map.getLineNumber(n), map.getColumnNumber(n), 1);
-        Object ret = super.visitBlock(node, p);
-        parser.add(JavaTokenConstants.J_INIT_END, filename, map.getLineNumber(m - 1), map.getColumnNumber(m - 1), 1);
-        return ret;
+        long start = positions.getStartPosition(ast, node);
+        long end = positions.getEndPosition(ast, node) - 1;
+        addToken(JavaTokenConstants.J_INIT_BEGIN, start, 1);
+        Object result = super.visitBlock(node, p);
+        addToken(JavaTokenConstants.J_INIT_END, end, 1);
+        return result;
     }
 
     @Override
     public Object visitClass(ClassTree node, Object p) {
-        boolean isEnum = false;
-        boolean isInterface = false;
-        boolean isAnnotation = false;
-        long n = positions.getStartPosition(ast, node);
-        long m = positions.getEndPosition(ast, node);
-
-        if (node.getKind() == Tree.Kind.ENUM)
-            isEnum = true;
-        if (node.getKind() == Tree.Kind.INTERFACE)
-            isInterface = true;
-        if (node.getKind() == Tree.Kind.ANNOTATION_TYPE)
-            isAnnotation = true;
-        if (isEnum)
-            parser.add(JavaTokenConstants.J_ENUM_BEGIN, filename, map.getLineNumber(n), map.getColumnNumber(n), 4);
-        if (isInterface)
-            parser.add(JavaTokenConstants.J_INTERFACE_BEGIN, filename, map.getLineNumber(n), map.getColumnNumber(n), 9);
-        if (isAnnotation)
-            parser.add(JavaTokenConstants.J_ANNO_T_BEGIN, filename, map.getLineNumber(n), map.getColumnNumber(n), 10);
-        if (!isEnum && !isInterface && !isAnnotation)
-            parser.add(JavaTokenConstants.J_CLASS_BEGIN, filename, map.getLineNumber(n), map.getColumnNumber(n), 5);
+        long start = positions.getStartPosition(ast, node);
+        long end = positions.getEndPosition(ast, node) - 1;
+
+        if (node.getKind() == Tree.Kind.ENUM) {
+            addToken(JavaTokenConstants.J_ENUM_BEGIN, start, 4);
+        } else if (node.getKind() == Tree.Kind.INTERFACE) {
+            addToken(JavaTokenConstants.J_INTERFACE_BEGIN, start, 9);
+        } else if (node.getKind() == Tree.Kind.RECORD) {
+            addToken(JavaTokenConstants.J_RECORD_BEGIN, start, 1);
+        } else if (node.getKind() == Tree.Kind.ANNOTATION_TYPE) {
+            addToken(JavaTokenConstants.J_ANNO_T_BEGIN, start, 10);
+        } else if (node.getKind() == Tree.Kind.CLASS) {
+            addToken(JavaTokenConstants.J_CLASS_BEGIN, start, 5);
+        }
         Object result = super.visitClass(node, p);
-        if (!isEnum && !isInterface && !isAnnotation)
-            parser.add(JavaTokenConstants.J_CLASS_END, filename, map.getLineNumber(m - 1), map.getColumnNumber(m - 1), 1);
-        if (isAnnotation)
-            parser.add(JavaTokenConstants.J_ANNO_T_END, filename, map.getLineNumber(m - 1), map.getColumnNumber(m - 1), 1);
-        if (isEnum)
-            parser.add(JavaTokenConstants.J_ENUM_END, filename, map.getLineNumber(m - 1), map.getColumnNumber(m - 1), 1);
-        if (isInterface)
-            parser.add(JavaTokenConstants.J_INTERFACE_END, filename, map.getLineNumber(m - 1), map.getColumnNumber(m - 1), 1);
+        if (node.getKind() == Tree.Kind.ENUM) {
+            addToken(JavaTokenConstants.J_ENUM_END, end, 1);
+        } else if (node.getKind() == Tree.Kind.INTERFACE) {
+            addToken(JavaTokenConstants.J_INTERFACE_END, end, 1);
+        } else if (node.getKind() == Tree.Kind.RECORD) {
+            addToken(JavaTokenConstants.J_RECORD_END, end, 1);
+        } else if (node.getKind() == Tree.Kind.ANNOTATION_TYPE) {
+            addToken(JavaTokenConstants.J_ANNO_T_END, end, 1);
+        } else if (node.getKind() == Tree.Kind.CLASS) {
+            addToken(JavaTokenConstants.J_CLASS_END, end, 1);
+        }
         return result;
     }
 
     @Override
     public Object visitImport(ImportTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        parser.add(JavaTokenConstants.J_IMPORT, filename, map.getLineNumber(n), map.getColumnNumber(n), 6);
+        long start = positions.getStartPosition(ast, node);
+        addToken(JavaTokenConstants.J_IMPORT, start, 6);
         return super.visitImport(node, p);
     }
 
     @Override
     public Object visitPackage(PackageTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        parser.add(JavaTokenConstants.J_PACKAGE, filename, map.getLineNumber(n), map.getColumnNumber(n), 7);
+        long start = positions.getStartPosition(ast, node);
+        addToken(JavaTokenConstants.J_PACKAGE, start, 7);
         return super.visitPackage(node, p);
     }
 
     @Override
     public Object visitMethod(MethodTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        long m = positions.getEndPosition(ast, node);
-        parser.add(JavaTokenConstants.J_METHOD_BEGIN, filename, map.getLineNumber(n), map.getColumnNumber(n), node.getName().length());
-        Object ret = super.visitMethod(node, p);
-        parser.add(JavaTokenConstants.J_METHOD_END, filename, map.getLineNumber(m - 1), map.getColumnNumber(m - 1), 1);
-        return ret;
+        long start = positions.getStartPosition(ast, node);
+        long end = positions.getEndPosition(ast, node) - 1;
+        addToken(JavaTokenConstants.J_METHOD_BEGIN, start, node.getName().length());
+        Object result = super.visitMethod(node, p);
+        addToken(JavaTokenConstants.J_METHOD_END, end, 1);
+        return result;
     }
 
     @Override
     public Object visitSynchronized(SynchronizedTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        long m = positions.getEndPosition(ast, node);
-        parser.add(JavaTokenConstants.J_SYNC_BEGIN, filename, map.getLineNumber(n), map.getColumnNumber(n), 12);
-        Object ret = super.visitSynchronized(node, p);
-        parser.add(JavaTokenConstants.J_SYNC_END, filename, map.getLineNumber(m - 1), map.getColumnNumber(m - 1), 1);
-        return ret;
+        long start = positions.getStartPosition(ast, node);
+        long end = positions.getEndPosition(ast, node) - 1;
+        addToken(JavaTokenConstants.J_SYNC_BEGIN, start, 12);
+        Object result = super.visitSynchronized(node, p);
+        addToken(JavaTokenConstants.J_SYNC_END, end, 1);
+        return result;
     }
 
     @Override
     public Object visitDoWhileLoop(DoWhileLoopTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        long m = positions.getEndPosition(ast, node);
-        parser.add(JavaTokenConstants.J_DO_BEGIN, filename, map.getLineNumber(n), map.getColumnNumber(n), 2);
-        Object ret = super.visitDoWhileLoop(node, p);
-        parser.add(JavaTokenConstants.J_DO_END, filename, map.getLineNumber(m - 1), map.getColumnNumber(m - 1), 1);
-        return ret;
+        long start = positions.getStartPosition(ast, node);
+        long end = positions.getEndPosition(ast, node) - 1;
+        addToken(JavaTokenConstants.J_DO_BEGIN, start, 2);
+        Object result = super.visitDoWhileLoop(node, p);
+        addToken(JavaTokenConstants.J_DO_END, end, 1);
+        return result;
     }
 
     @Override
     public Object visitWhileLoop(WhileLoopTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        long m = positions.getEndPosition(ast, node);
-        parser.add(JavaTokenConstants.J_WHILE_BEGIN, filename, map.getLineNumber(n), map.getColumnNumber(n), 5);
-        Object ret = super.visitWhileLoop(node, p);
-        parser.add(JavaTokenConstants.J_WHILE_END, filename, map.getLineNumber(m - 1), map.getColumnNumber(m - 1), 1);
-        return ret;
+        long start = positions.getStartPosition(ast, node);
+        long end = positions.getEndPosition(ast, node) - 1;
+        addToken(JavaTokenConstants.J_WHILE_BEGIN, start, 5);
+        Object result = super.visitWhileLoop(node, p);
+        addToken(JavaTokenConstants.J_WHILE_END, end, 1);
+        return result;
     }
 
     @Override
     public Object visitForLoop(ForLoopTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        long m = positions.getEndPosition(ast, node);
-        parser.add(JavaTokenConstants.J_FOR_BEGIN, filename, map.getLineNumber(n), map.getColumnNumber(n), 3);
-        Object ret = super.visitForLoop(node, p);
-        parser.add(JavaTokenConstants.J_FOR_END, filename, map.getLineNumber(m - 1), map.getColumnNumber(m - 1), 1);
-        return ret;
+        long start = positions.getStartPosition(ast, node);
+        long end = positions.getEndPosition(ast, node) - 1;
+        addToken(JavaTokenConstants.J_FOR_BEGIN, start, 3);
+        Object result = super.visitForLoop(node, p);
+        addToken(JavaTokenConstants.J_FOR_END, end, 1);
+        return result;
     }
 
     @Override
     public Object visitEnhancedForLoop(EnhancedForLoopTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        long m = positions.getEndPosition(ast, node);
-        parser.add(JavaTokenConstants.J_FOR_BEGIN, filename, map.getLineNumber(n), map.getColumnNumber(n), 3);
-        Object ret = super.visitEnhancedForLoop(node, p);
-        parser.add(JavaTokenConstants.J_FOR_END, filename, map.getLineNumber(m - 1), map.getColumnNumber(m - 1), 1);
-        return ret;
+        long start = positions.getStartPosition(ast, node);
+        long end = positions.getEndPosition(ast, node) - 1;
+        addToken(JavaTokenConstants.J_FOR_BEGIN, start, 3);
+        Object result = super.visitEnhancedForLoop(node, p);
+        addToken(JavaTokenConstants.J_FOR_END, end, 1);
+        return result;
     }
 
     @Override
     public Object visitSwitch(SwitchTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        long m = positions.getEndPosition(ast, node);
-        parser.add(JavaTokenConstants.J_SWITCH_BEGIN, filename, map.getLineNumber(n), map.getColumnNumber(n), 6);
-        Object ret = super.visitSwitch(node, p);
-        parser.add(JavaTokenConstants.J_SWITCH_END, filename, map.getLineNumber(m - 1), map.getColumnNumber(m - 1), 1);
-        return ret;
+        long start = positions.getStartPosition(ast, node);
+        long end = positions.getEndPosition(ast, node) - 1;
+        addToken(JavaTokenConstants.J_SWITCH_BEGIN, start, 6);
+        Object result = super.visitSwitch(node, p);
+        addToken(JavaTokenConstants.J_SWITCH_END, end, 1);
+        return result;
     }
 
     @Override
     public Object visitSwitchExpression(SwitchExpressionTree node, Object parameterValue) {
         long start = positions.getStartPosition(ast, node);
-        long end = positions.getEndPosition(ast, node);
-        parser.add(JavaTokenConstants.J_SWITCH_BEGIN, filename, map.getLineNumber(start), map.getColumnNumber(start), 6);
+        long end = positions.getEndPosition(ast, node) - 1;
+        addToken(JavaTokenConstants.J_SWITCH_BEGIN, start, 6);
         Object result = super.visitSwitchExpression(node, parameterValue);
-        parser.add(JavaTokenConstants.J_SWITCH_END, filename, map.getLineNumber(end - 1), map.getColumnNumber(end - 1), 1);
+        addToken(JavaTokenConstants.J_SWITCH_END, end, 1);
         return result;
     }
 
     @Override
     public Object visitCase(CaseTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        parser.add(JavaTokenConstants.J_CASE, filename, map.getLineNumber(n), map.getColumnNumber(n), 4);
+        long start = positions.getStartPosition(ast, node);
+        addToken(JavaTokenConstants.J_CASE, start, 4);
         return super.visitCase(node, p);
     }
 
     @Override
     public Object visitTry(TryTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
+        long start = positions.getStartPosition(ast, node);
         if (node.getResources().isEmpty())
-            parser.add(JavaTokenConstants.J_TRY_BEGIN, filename, map.getLineNumber(n), map.getColumnNumber(n), 3);
+            addToken(JavaTokenConstants.J_TRY_BEGIN, start, 3);
         else
-            parser.add(JavaTokenConstants.J_TRY_WITH_RESOURCE, filename, map.getLineNumber(n), map.getColumnNumber(n), 3);
+            addToken(JavaTokenConstants.J_TRY_WITH_RESOURCE, start, 3);
         if (node.getFinallyBlock() != null)
-            parser.add(JavaTokenConstants.J_FINALLY, filename, map.getLineNumber(n), map.getColumnNumber(n), 3);
+            addToken(JavaTokenConstants.J_FINALLY, start, 3);
         return super.visitTry(node, p);
     }
 
     @Override
     public Object visitCatch(CatchTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        long m = positions.getEndPosition(ast, node);
-        parser.add(JavaTokenConstants.J_CATCH_BEGIN, filename, map.getLineNumber(n), map.getColumnNumber(n), 5);
-        Object ret = super.visitCatch(node, p);
-        parser.add(JavaTokenConstants.J_CATCH_END, filename, map.getLineNumber(m - 1), map.getColumnNumber(m - 1), 1);
-        return ret;
+        long start = positions.getStartPosition(ast, node);
+        long end = positions.getEndPosition(ast, node) - 1;
+        addToken(JavaTokenConstants.J_CATCH_BEGIN, start, 5);
+        Object result = super.visitCatch(node, p);
+        addToken(JavaTokenConstants.J_CATCH_END, end, 1);
+        return result;
     }
 
     @Override
     public Object visitIf(IfTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        long m = positions.getEndPosition(ast, node);
-        parser.add(JavaTokenConstants.J_IF_BEGIN, filename, map.getLineNumber(n), map.getColumnNumber(n), 2);
+        long start = positions.getStartPosition(ast, node);
+        long end = positions.getEndPosition(ast, node) - 1;
+        addToken(JavaTokenConstants.J_IF_BEGIN, start, 2);
         node.getCondition().accept(this, p);
         node.getThenStatement().accept(this, p);
         if (node.getElseStatement() != null) {
-            n = positions.getStartPosition(ast, node.getElseStatement());
-            parser.add(JavaTokenConstants.J_ELSE, filename, map.getLineNumber(n), map.getColumnNumber(n), 4);
+            start = positions.getStartPosition(ast, node.getElseStatement());
+            addToken(JavaTokenConstants.J_ELSE, start, 4);
             node.getElseStatement().accept(this, p);
         }
-        parser.add(JavaTokenConstants.J_IF_END, filename, map.getLineNumber(m - 1), map.getColumnNumber(m - 1), 1);
+        addToken(JavaTokenConstants.J_IF_END, end, 1);
         return null;
     }
 
     @Override
     public Object visitBreak(BreakTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        parser.add(JavaTokenConstants.J_BREAK, filename, map.getLineNumber(n), map.getColumnNumber(n), 5);
+        long start = positions.getStartPosition(ast, node);
+        addToken(JavaTokenConstants.J_BREAK, start, 5);
         return super.visitBreak(node, p);
     }
 
     @Override
     public Object visitContinue(ContinueTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        parser.add(JavaTokenConstants.J_CONTINUE, filename, map.getLineNumber(n), map.getColumnNumber(n), 8);
+        long start = positions.getStartPosition(ast, node);
+        addToken(JavaTokenConstants.J_CONTINUE, start, 8);
         return super.visitContinue(node, p);
     }
 
     @Override
     public Object visitReturn(ReturnTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        parser.add(JavaTokenConstants.J_RETURN, filename, map.getLineNumber(n), map.getColumnNumber(n), 6);
+        long start = positions.getStartPosition(ast, node);
+        addToken(JavaTokenConstants.J_RETURN, start, 6);
         return super.visitReturn(node, p);
     }
 
     @Override
     public Object visitThrow(ThrowTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        parser.add(JavaTokenConstants.J_THROW, filename, map.getLineNumber(n), map.getColumnNumber(n), 5);
+        long start = positions.getStartPosition(ast, node);
+        addToken(JavaTokenConstants.J_THROW, start, 5);
         return super.visitThrow(node, p);
     }
 
     @Override
     public Object visitNewClass(NewClassTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
+        long start = positions.getStartPosition(ast, node);
         if (node.getTypeArguments().size() > 0) {
-            parser.add(JavaTokenConstants.J_GENERIC, filename, map.getLineNumber(n), map.getColumnNumber(n),
-                    3 + node.getIdentifier().toString().length());
+            addToken(JavaTokenConstants.J_GENERIC, start, 3 + node.getIdentifier().toString().length());
         }
-        parser.add(JavaTokenConstants.J_NEWCLASS, filename, map.getLineNumber(n), map.getColumnNumber(n), 3);
+        addToken(JavaTokenConstants.J_NEWCLASS, start, 3);
         return super.visitNewClass(node, p);
     }
 
     @Override
     public Object visitTypeParameter(TypeParameterTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
+        long start = positions.getStartPosition(ast, node);
         // This is odd, but also done like this in Java17
-        parser.add(JavaTokenConstants.J_GENERIC, filename, map.getLineNumber(n), map.getColumnNumber(n), 1);
+        addToken(JavaTokenConstants.J_GENERIC, start, 1);
         return super.visitTypeParameter(node, p);
     }
 
     @Override
     public Object visitNewArray(NewArrayTree node, Object arg1) {
-        long n = positions.getStartPosition(ast, node);
-        long m = positions.getEndPosition(ast, node);
-        parser.add(JavaTokenConstants.J_NEWARRAY, filename, map.getLineNumber(n), map.getColumnNumber(n), 3);
+        long start = positions.getStartPosition(ast, node);
+        long end = positions.getEndPosition(ast, node) - 1;
+        addToken(JavaTokenConstants.J_NEWARRAY, start, 3);
         if (node.getInitializers() != null && !node.getInitializers().isEmpty()) {
-            n = positions.getStartPosition(ast, node.getInitializers().get(0));
-            parser.add(JavaTokenConstants.J_ARRAY_INIT_BEGIN, filename, map.getLineNumber(n), map.getColumnNumber(n), 1);
-            ;
-            parser.add(JavaTokenConstants.J_ARRAY_INIT_END, filename, map.getLineNumber(m - 1), map.getColumnNumber(m - 1), 1);
-            ;
+            start = positions.getStartPosition(ast, node.getInitializers().get(0));
+            addToken(JavaTokenConstants.J_ARRAY_INIT_BEGIN, start, 1);
+            addToken(JavaTokenConstants.J_ARRAY_INIT_END, end, 1);
         }
         return super.visitNewArray(node, arg1);
     }
 
     @Override
     public Object visitAssignment(AssignmentTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        parser.add(JavaTokenConstants.J_ASSIGN, filename, map.getLineNumber(n), map.getColumnNumber(n), 1);
+        long start = positions.getStartPosition(ast, node);
+        addToken(JavaTokenConstants.J_ASSIGN, start, 1);
         return super.visitAssignment(node, p);
     }
 
     @Override
     public Object visitAssert(AssertTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        parser.add(JavaTokenConstants.J_ASSERT, filename, map.getLineNumber(n), map.getColumnNumber(n), 6);
+        long start = positions.getStartPosition(ast, node);
+        addToken(JavaTokenConstants.J_ASSERT, start, 6);
         return super.visitAssert(node, p);
     }
 
     @Override
     public Object visitVariable(VariableTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        parser.add(JavaTokenConstants.J_VARDEF, filename, map.getLineNumber(n), map.getColumnNumber(n), node.toString().length());
+        long start = positions.getStartPosition(ast, node);
+        addToken(JavaTokenConstants.J_VARDEF, start, node.toString().length());
         return super.visitVariable(node, p);
     }
 
     @Override
     public Object visitConditionalExpression(ConditionalExpressionTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        parser.add(JavaTokenConstants.J_COND, filename, map.getLineNumber(n), map.getColumnNumber(n), 1);
+        long start = positions.getStartPosition(ast, node);
+        addToken(JavaTokenConstants.J_COND, start, 1);
         return super.visitConditionalExpression(node, p);
     }
 
     @Override
     public Object visitMethodInvocation(MethodInvocationTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        parser.add(JavaTokenConstants.J_APPLY, filename, map.getLineNumber(n), map.getColumnNumber(n),
-                positions.getEndPosition(ast, node.getMethodSelect()) - n);
+        long start = positions.getStartPosition(ast, node);
+        addToken(JavaTokenConstants.J_APPLY, start, positions.getEndPosition(ast, node.getMethodSelect()) - start);
         return super.visitMethodInvocation(node, p);
     }
 
     @Override
     public Object visitAnnotation(AnnotationTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        parser.add(JavaTokenConstants.J_ANNO, filename, map.getLineNumber(n), map.getColumnNumber(n), 1);
+        long start = positions.getStartPosition(ast, node);
+        addToken(JavaTokenConstants.J_ANNO, start, 1);
         return super.visitAnnotation(node, p);
     }
 
     @Override
     public Object visitModule(ModuleTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        long m = positions.getEndPosition(ast, node);
-        parser.add(JavaTokenConstants.J_MODULE_BEGIN, filename, map.getLineNumber(n), map.getColumnNumber(n), 6);
-        Object ret = super.visitModule(node, p);
-        parser.add(JavaTokenConstants.J_MODULE_END, filename, map.getLineNumber(m - 1), map.getColumnNumber(m - 1), 1);
-        return ret;
+        long start = positions.getStartPosition(ast, node);
+        long end = positions.getEndPosition(ast, node) - 1;
+        addToken(JavaTokenConstants.J_MODULE_BEGIN, start, 6);
+        Object result = super.visitModule(node, p);
+        addToken(JavaTokenConstants.J_MODULE_END, end, 1);
+        return result;
     }
 
     @Override
     public Object visitRequires(RequiresTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        parser.add(JavaTokenConstants.J_REQUIRES, filename, map.getLineNumber(n), map.getColumnNumber(n), 8);
+        long start = positions.getStartPosition(ast, node);
+        addToken(JavaTokenConstants.J_REQUIRES, start, 8);
         return super.visitRequires(node, p);
     }
 
     @Override
     public Object visitProvides(ProvidesTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        parser.add(JavaTokenConstants.J_PROVIDES, filename, map.getLineNumber(n), map.getColumnNumber(n), 8);
+        long start = positions.getStartPosition(ast, node);
+        addToken(JavaTokenConstants.J_PROVIDES, start, 8);
         return super.visitProvides(node, p);
     }
 
     @Override
     public Object visitExports(ExportsTree node, Object p) {
-        long n = positions.getStartPosition(ast, node);
-        parser.add(JavaTokenConstants.J_EXPORTS, filename, map.getLineNumber(n), map.getColumnNumber(n), 7);
+        long start = positions.getStartPosition(ast, node);
+        addToken(JavaTokenConstants.J_EXPORTS, start, 7);
         return super.visitExports(node, p);
     }
 
     @Override
     public Object visitErroneous(ErroneousTree node, Object p) {
-        parser.errorsInc();
+        parser.increaseErrors();
         return super.visitErroneous(node, p);
     }
+
+    @Override
+    public Object visitYield(YieldTree node, Object p) {
+        long start = positions.getStartPosition(ast, node);
+        long end = positions.getEndPosition(ast, node);
+        addToken(JavaTokenConstants.J_YIELD, start, end);
+        return super.visitYield(node, p);
+    }
+
+    @Override
+    public Object visitDefaultCaseLabel(DefaultCaseLabelTree node, Object p) {
+        long start = positions.getStartPosition(ast, node);
+        long end = positions.getEndPosition(ast, node);
+        addToken(JavaTokenConstants.J_DEFAULT, start, end);
+        return super.visitDefaultCaseLabel(node, p);
+    }
 }

+ 39 - 617
jplag.frontend.python-3/src/main/java/de/jplag/python3/JplagPython3Listener.java

@@ -1,763 +1,185 @@
 package de.jplag.python3;
 
-import org.antlr.v4.runtime.ParserRuleContext;
-import org.antlr.v4.runtime.tree.ErrorNode;
 import org.antlr.v4.runtime.tree.TerminalNode;
 
-import de.jplag.python3.grammar.Python3Listener;
+import de.jplag.python3.grammar.Python3BaseListener;
 import de.jplag.python3.grammar.Python3Parser;
 
-public class JplagPython3Listener implements Python3Listener, Python3TokenConstants {
+public class JplagPython3Listener extends Python3BaseListener implements Python3TokenConstants {
 
-    private Parser jplagParser;
+    private final Parser parser;
 
-    public JplagPython3Listener(de.jplag.python3.Parser jplag) {
-        jplagParser = jplag;
-    }
-
-    @Override
-    public void enterTestlist(Python3Parser.TestlistContext ctx) {
-    }
-
-    @Override
-    public void exitTestlist(Python3Parser.TestlistContext ctx) {
+    public JplagPython3Listener(Parser parser) {
+        this.parser = parser;
     }
 
     @Override
     public void enterAssert_stmt(Python3Parser.Assert_stmtContext ctx) {
-        jplagParser.add(ASSERT, ctx.getStart());
-    }
-
-    @Override
-    public void exitAssert_stmt(Python3Parser.Assert_stmtContext ctx) {
-    }
-
-    @Override
-    public void enterArgument(Python3Parser.ArgumentContext ctx) {
-    }
-
-    @Override
-    public void exitArgument(Python3Parser.ArgumentContext ctx) {
-    }
-
-    @Override
-    public void enterNot_test(Python3Parser.Not_testContext ctx) {
-    }
-
-    @Override
-    public void exitNot_test(Python3Parser.Not_testContext ctx) {
-    }
-
-    @Override
-    public void enterFile_input(Python3Parser.File_inputContext ctx) {
-    }
-
-    @Override
-    public void exitFile_input(Python3Parser.File_inputContext ctx) {
-    }
-
-    @Override
-    public void enterXor_expr(Python3Parser.Xor_exprContext ctx) {
-    }
-
-    @Override
-    public void exitXor_expr(Python3Parser.Xor_exprContext ctx) {
-    }
-
-    @Override
-    public void enterImport_from(Python3Parser.Import_fromContext ctx) {
-    }
-
-    @Override
-    public void exitImport_from(Python3Parser.Import_fromContext ctx) {
-    }
-
-    @Override
-    public void enterSingle_input(Python3Parser.Single_inputContext ctx) {
-    }
-
-    @Override
-    public void exitSingle_input(Python3Parser.Single_inputContext ctx) {
+        parser.add(ASSERT, ctx.getStart());
     }
 
     @Override
     public void enterDecorated(Python3Parser.DecoratedContext ctx) {
-        jplagParser.add(DEC_BEGIN, ctx.getStart());
+        parser.add(DEC_BEGIN, ctx.getStart());
     }
 
     @Override
     public void exitDecorated(Python3Parser.DecoratedContext ctx) {
-        jplagParser.addEnd(DEC_END, ctx.getStart());
-    }
-
-    @Override
-    public void enterWith_item(Python3Parser.With_itemContext ctx) {
-    }
-
-    @Override
-    public void exitWith_item(Python3Parser.With_itemContext ctx) {
+        parser.addEnd(DEC_END, ctx.getStart());
     }
 
     @Override
     public void enterRaise_stmt(Python3Parser.Raise_stmtContext ctx) {
-        jplagParser.add(RAISE, ctx.getStart());
-    }
-
-    @Override
-    public void exitRaise_stmt(Python3Parser.Raise_stmtContext ctx) {
-    }
-
-    @Override
-    public void enterImport_as_name(Python3Parser.Import_as_nameContext ctx) {
-    }
-
-    @Override
-    public void exitImport_as_name(Python3Parser.Import_as_nameContext ctx) {
+        parser.add(RAISE, ctx.getStart());
     }
 
     @Override
     public void enterExcept_clause(Python3Parser.Except_clauseContext ctx) {
-        jplagParser.add(EXCEPT_BEGIN, ctx.getStart());
+        parser.add(EXCEPT_BEGIN, ctx.getStart());
     }
 
     @Override
     public void exitExcept_clause(Python3Parser.Except_clauseContext ctx) {
-        jplagParser.addEnd(EXCEPT_END, ctx.getStart());
-    }
-
-    @Override
-    public void enterCompound_stmt(Python3Parser.Compound_stmtContext ctx) {
-    }
-
-    @Override
-    public void exitCompound_stmt(Python3Parser.Compound_stmtContext ctx) {
-    }
-
-    @Override
-    public void enterAnd_expr(Python3Parser.And_exprContext ctx) {
-    }
-
-    @Override
-    public void exitAnd_expr(Python3Parser.And_exprContext ctx) {
-    }
-
-    @Override
-    public void enterLambdef_nocond(Python3Parser.Lambdef_nocondContext ctx) {
-    }
-
-    @Override
-    public void exitLambdef_nocond(Python3Parser.Lambdef_nocondContext ctx) {
+        parser.addEnd(EXCEPT_END, ctx.getStart());
     }
 
     @Override
     public void enterDictorsetmaker(Python3Parser.DictorsetmakerContext ctx) {
-        jplagParser.add(ARRAY, ctx.getStart());
-    }
-
-    @Override
-    public void exitDictorsetmaker(Python3Parser.DictorsetmakerContext ctx) {
+        parser.add(ARRAY, ctx.getStart());
     }
 
     @Override
     public void enterReturn_stmt(Python3Parser.Return_stmtContext ctx) {
-        jplagParser.add(RETURN, ctx.getStart());
-    }
-
-    @Override
-    public void exitReturn_stmt(Python3Parser.Return_stmtContext ctx) {
-    }
-
-    @Override
-    public void enterDotted_name(Python3Parser.Dotted_nameContext ctx) {
-    }
-
-    @Override
-    public void exitDotted_name(Python3Parser.Dotted_nameContext ctx) {
-    }
-
-    @Override
-    public void enterFlow_stmt(Python3Parser.Flow_stmtContext ctx) {
-    }
-
-    @Override
-    public void exitFlow_stmt(Python3Parser.Flow_stmtContext ctx) {
+        parser.add(RETURN, ctx.getStart());
     }
 
     @Override
     public void enterWhile_stmt(Python3Parser.While_stmtContext ctx) {
-        jplagParser.add(WHILE_BEGIN, ctx.getStart());
+        parser.add(WHILE_BEGIN, ctx.getStart());
     }
 
     @Override
     public void exitWhile_stmt(Python3Parser.While_stmtContext ctx) {
-        jplagParser.addEnd(WHILE_END, ctx.getStart());
-    }
-
-    @Override
-    public void enterOr_test(Python3Parser.Or_testContext ctx) {
-    }
-
-    @Override
-    public void exitOr_test(Python3Parser.Or_testContext ctx) {
-    }
-
-    @Override
-    public void enterComparison(Python3Parser.ComparisonContext ctx) {
-    }
-
-    @Override
-    public void exitComparison(Python3Parser.ComparisonContext ctx) {
-    }
-
-    @Override
-    public void enterTest(Python3Parser.TestContext ctx) {
-    }
-
-    @Override
-    public void exitTest(Python3Parser.TestContext ctx) {
-    }
-
-    @Override
-    public void enterSubscript(Python3Parser.SubscriptContext ctx) {
-    }
-
-    @Override
-    public void exitSubscript(Python3Parser.SubscriptContext ctx) {
-    }
-
-    @Override
-    public void enterComp_for(Python3Parser.Comp_forContext ctx) {
-    }
-
-    @Override
-    public void exitComp_for(Python3Parser.Comp_forContext ctx) {
+        parser.addEnd(WHILE_END, ctx.getStart());
     }
 
     @Override
     public void enterYield_arg(Python3Parser.Yield_argContext ctx) {
-        jplagParser.add(YIELD, ctx.getStart());
-    }
-
-    @Override
-    public void exitYield_arg(Python3Parser.Yield_argContext ctx) {
-    }
-
-    @Override
-    public void enterYield_expr(Python3Parser.Yield_exprContext ctx) {
-    }
-
-    @Override
-    public void exitYield_expr(Python3Parser.Yield_exprContext ctx) {
+        parser.add(YIELD, ctx.getStart());
     }
 
     @Override
     public void enterImport_stmt(Python3Parser.Import_stmtContext ctx) {
-        jplagParser.add(IMPORT, ctx.getStart());
-    }
-
-    @Override
-    public void exitImport_stmt(Python3Parser.Import_stmtContext ctx) {
-    }
-
-    @Override
-    public void enterShift_expr(Python3Parser.Shift_exprContext ctx) {
-    }
-
-    @Override
-    public void exitShift_expr(Python3Parser.Shift_exprContext ctx) {
+        parser.add(IMPORT, ctx.getStart());
     }
 
     @Override
     public void enterLambdef(Python3Parser.LambdefContext ctx) {
-        jplagParser.add(LAMBDA, ctx.getStart());
-    }
-
-    @Override
-    public void exitLambdef(Python3Parser.LambdefContext ctx) {
-    }
-
-    @Override
-    public void enterAnd_test(Python3Parser.And_testContext ctx) {
-    }
-
-    @Override
-    public void exitAnd_test(Python3Parser.And_testContext ctx) {
-    }
-
-    @Override
-    public void enterGlobal_stmt(Python3Parser.Global_stmtContext ctx) {
-    }
-
-    @Override
-    public void exitGlobal_stmt(Python3Parser.Global_stmtContext ctx) {
-    }
-
-    @Override
-    public void enterImport_as_names(Python3Parser.Import_as_namesContext ctx) {
-    }
-
-    @Override
-    public void exitImport_as_names(Python3Parser.Import_as_namesContext ctx) {
-    }
-
-    @Override
-    public void enterDecorators(Python3Parser.DecoratorsContext ctx) {
-    }
-
-    @Override
-    public void exitDecorators(Python3Parser.DecoratorsContext ctx) {
+        parser.add(LAMBDA, ctx.getStart());
     }
 
     @Override
     public void enterTry_stmt(Python3Parser.Try_stmtContext ctx) {
-        jplagParser.add(TRY_BEGIN, ctx.getStart());
-    }
-
-    @Override
-    public void exitTry_stmt(Python3Parser.Try_stmtContext ctx) {
-    }
-
-    @Override
-    public void enterComp_op(Python3Parser.Comp_opContext ctx) {
-    }
-
-    @Override
-    public void exitComp_op(Python3Parser.Comp_opContext ctx) {
-    }
-
-    @Override
-    public void enterStar_expr(Python3Parser.Star_exprContext ctx) {
-    }
-
-    @Override
-    public void exitStar_expr(Python3Parser.Star_exprContext ctx) {
+        parser.add(TRY_BEGIN, ctx.getStart());
     }
 
     @Override
     public void enterBreak_stmt(Python3Parser.Break_stmtContext ctx) {
-        jplagParser.add(BREAK, ctx.getStart());
-    }
-
-    @Override
-    public void exitBreak_stmt(Python3Parser.Break_stmtContext ctx) {
-    }
-
-    @Override
-    public void enterParameters(Python3Parser.ParametersContext ctx) {
-    }
-
-    @Override
-    public void exitParameters(Python3Parser.ParametersContext ctx) {
-    }
-
-    @Override
-    public void enterDecorator(Python3Parser.DecoratorContext ctx) {
-    }
-
-    @Override
-    public void exitDecorator(Python3Parser.DecoratorContext ctx) {
-    }
-
-    @Override
-    public void enterTfpdef(Python3Parser.TfpdefContext ctx) {
-    }
-
-    @Override
-    public void exitTfpdef(Python3Parser.TfpdefContext ctx) {
+        parser.add(BREAK, ctx.getStart());
     }
 
     @Override
     public void enterTestlist_comp(Python3Parser.Testlist_compContext ctx) {
         if (ctx.getText().contains(",")) {
-            jplagParser.add(ARRAY, ctx.getStart());
+            parser.add(ARRAY, ctx.getStart());
         }
     }
 
-    @Override
-    public void exitTestlist_comp(Python3Parser.Testlist_compContext ctx) {
-    }
-
     @Override
     public void enterIf_stmt(Python3Parser.If_stmtContext ctx) {
-        jplagParser.add(IF_BEGIN, ctx.getStart());
+        parser.add(IF_BEGIN, ctx.getStart());
     }
 
     @Override
     public void exitIf_stmt(Python3Parser.If_stmtContext ctx) {
-        jplagParser.addEnd(IF_END, ctx.getStart());
+        parser.addEnd(IF_END, ctx.getStart());
     }
 
     @Override
     public void enterWith_stmt(Python3Parser.With_stmtContext ctx) {
-        jplagParser.add(WITH_BEGIN, ctx.getStart());
+        parser.add(WITH_BEGIN, ctx.getStart());
     }
 
     @Override
     public void exitWith_stmt(Python3Parser.With_stmtContext ctx) {
-        jplagParser.addEnd(WITH_END, ctx.getStart());
+        parser.addEnd(WITH_END, ctx.getStart());
     }
 
     @Override
     public void enterClassdef(Python3Parser.ClassdefContext ctx) {
-        jplagParser.add(CLASS_BEGIN, ctx.getStart());
+        parser.add(CLASS_BEGIN, ctx.getStart());
     }
 
     @Override
     public void exitClassdef(Python3Parser.ClassdefContext ctx) {
-        jplagParser.addEnd(CLASS_END, ctx.getStart());
-    }
-
-    @Override
-    public void enterExprlist(Python3Parser.ExprlistContext ctx) {
-    }
-
-    @Override
-    public void exitExprlist(Python3Parser.ExprlistContext ctx) {
-    }
-
-    @Override
-    public void enterSmall_stmt(Python3Parser.Small_stmtContext ctx) {
-    }
-
-    @Override
-    public void exitSmall_stmt(Python3Parser.Small_stmtContext ctx) {
+        parser.addEnd(CLASS_END, ctx.getStart());
     }
 
     @Override
     public void enterTrailer(Python3Parser.TrailerContext ctx) {
         if (ctx.getText().charAt(0) == '(') {
-            jplagParser.add(APPLY, ctx.getStart());
+            parser.add(APPLY, ctx.getStart());
         } else {
-            jplagParser.add(ARRAY, ctx.getStart());
+            parser.add(ARRAY, ctx.getStart());
         }
     }
 
-    @Override
-    public void exitTrailer(Python3Parser.TrailerContext ctx) {
-    }
-
-    @Override
-    public void enterDotted_as_names(Python3Parser.Dotted_as_namesContext ctx) {
-    }
-
-    @Override
-    public void exitDotted_as_names(Python3Parser.Dotted_as_namesContext ctx) {
-    }
-
-    @Override
-    public void enterArith_expr(Python3Parser.Arith_exprContext ctx) {
-    }
-
-    @Override
-    public void exitArith_expr(Python3Parser.Arith_exprContext ctx) {
-    }
-
-    @Override
-    public void enterArglist(Python3Parser.ArglistContext ctx) {
-    }
-
-    @Override
-    public void exitArglist(Python3Parser.ArglistContext ctx) {
-    }
-
-    @Override
-    public void enterSimple_stmt(Python3Parser.Simple_stmtContext ctx) {
-    }
-
-    @Override
-    public void exitSimple_stmt(Python3Parser.Simple_stmtContext ctx) {
-    }
-
-    @Override
-    public void enterTypedargslist(Python3Parser.TypedargslistContext ctx) {
-    }
-
-    @Override
-    public void exitTypedargslist(Python3Parser.TypedargslistContext ctx) {
-    }
-
-    @Override
-    public void enterExpr(Python3Parser.ExprContext ctx) {
-    }
-
-    @Override
-    public void exitExpr(Python3Parser.ExprContext ctx) {
-    }
-
-    @Override
-    public void enterTerm(Python3Parser.TermContext ctx) {
-    }
-
-    @Override
-    public void exitTerm(Python3Parser.TermContext ctx) {
-    }
-
-    @Override
-    public void enterPower(Python3Parser.PowerContext ctx) {
-    }
-
-    @Override
-    public void exitPower(Python3Parser.PowerContext ctx) {
-    }
-
-    @Override
-    public void enterDotted_as_name(Python3Parser.Dotted_as_nameContext ctx) {
-    }
-
-    @Override
-    public void exitDotted_as_name(Python3Parser.Dotted_as_nameContext ctx) {
-    }
-
-    @Override
-    public void enterFactor(Python3Parser.FactorContext ctx) {
-    }
-
-    @Override
-    public void exitFactor(Python3Parser.FactorContext ctx) {
-    }
-
-    @Override
-    public void enterSliceop(Python3Parser.SliceopContext ctx) {
-    }
-
-    @Override
-    public void exitSliceop(Python3Parser.SliceopContext ctx) {
-    }
-
     @Override
     public void enterFuncdef(Python3Parser.FuncdefContext ctx) {
-        jplagParser.add(METHOD_BEGIN, ctx.getStart());
+        parser.add(METHOD_BEGIN, ctx.getStart());
     }
 
     @Override
     public void exitFuncdef(Python3Parser.FuncdefContext ctx) {
-        jplagParser.addEnd(METHOD_END, ctx.getStart());
-    }
-
-    @Override
-    public void enterSubscriptlist(Python3Parser.SubscriptlistContext ctx) {
-    }
-
-    @Override
-    public void exitSubscriptlist(Python3Parser.SubscriptlistContext ctx) {
-    }
-
-    @Override
-    public void enterTest_nocond(Python3Parser.Test_nocondContext ctx) {
-    }
-
-    @Override
-    public void exitTest_nocond(Python3Parser.Test_nocondContext ctx) {
-    }
-
-    @Override
-    public void enterComp_iter(Python3Parser.Comp_iterContext ctx) {
-    }
-
-    @Override
-    public void exitComp_iter(Python3Parser.Comp_iterContext ctx) {
-    }
-
-    @Override
-    public void enterNonlocal_stmt(Python3Parser.Nonlocal_stmtContext ctx) {
-    }
-
-    @Override
-    public void exitNonlocal_stmt(Python3Parser.Nonlocal_stmtContext ctx) {
-    }
-
-    @Override
-    public void enterEval_input(Python3Parser.Eval_inputContext ctx) {
-    }
-
-    @Override
-    public void exitEval_input(Python3Parser.Eval_inputContext ctx) {
-    }
-
-    @Override
-    public void enterVfpdef(Python3Parser.VfpdefContext ctx) {
-    }
-
-    @Override
-    public void exitVfpdef(Python3Parser.VfpdefContext ctx) {
-    }
-
-    @Override
-    public void enterImport_name(Python3Parser.Import_nameContext ctx) {
-    }
-
-    @Override
-    public void exitImport_name(Python3Parser.Import_nameContext ctx) {
-    }
-
-    @Override
-    public void enterComp_if(Python3Parser.Comp_ifContext ctx) {
-    }
-
-    @Override
-    public void exitComp_if(Python3Parser.Comp_ifContext ctx) {
+        parser.addEnd(METHOD_END, ctx.getStart());
     }
 
     @Override
     public void enterAugassign(Python3Parser.AugassignContext ctx) {
-        jplagParser.add(ASSIGN, ctx.getStart());
-    }
-
-    @Override
-    public void exitAugassign(Python3Parser.AugassignContext ctx) {
-    }
-
-    @Override
-    public void enterPass_stmt(Python3Parser.Pass_stmtContext ctx) {
-    }
-
-    @Override
-    public void exitPass_stmt(Python3Parser.Pass_stmtContext ctx) {
-    }
-
-    @Override
-    public void enterExpr_stmt(Python3Parser.Expr_stmtContext ctx) {
-    }
-
-    @Override
-    public void exitExpr_stmt(Python3Parser.Expr_stmtContext ctx) {
+        parser.add(ASSIGN, ctx.getStart());
     }
 
     @Override
     public void enterYield_stmt(Python3Parser.Yield_stmtContext ctx) {
-        jplagParser.add(YIELD, ctx.getStart());
-    }
-
-    @Override
-    public void exitYield_stmt(Python3Parser.Yield_stmtContext ctx) {
-    }
-
-    @Override
-    public void enterSuite(Python3Parser.SuiteContext ctx) {
-    }
-
-    @Override
-    public void exitSuite(Python3Parser.SuiteContext ctx) {
+        parser.add(YIELD, ctx.getStart());
     }
 
     @Override
     public void enterContinue_stmt(Python3Parser.Continue_stmtContext ctx) {
-        jplagParser.add(CONTINUE, ctx.getStart());
-    }
-
-    @Override
-    public void exitContinue_stmt(Python3Parser.Continue_stmtContext ctx) {
-    }
-
-    @Override
-    public void enterTestlist_star_expr(Python3Parser.Testlist_star_exprContext ctx) {
-    }
-
-    @Override
-    public void exitTestlist_star_expr(Python3Parser.Testlist_star_exprContext ctx) {
-    }
-
-    @Override
-    public void enterVarargslist(Python3Parser.VarargslistContext ctx) {
-    }
-
-    @Override
-    public void exitVarargslist(Python3Parser.VarargslistContext ctx) {
+        parser.add(CONTINUE, ctx.getStart());
     }
 
     @Override
     public void enterFor_stmt(Python3Parser.For_stmtContext ctx) {
-        jplagParser.add(FOR_BEGIN, ctx.getStart());
+        parser.add(FOR_BEGIN, ctx.getStart());
     }
 
     @Override
     public void exitFor_stmt(Python3Parser.For_stmtContext ctx) {
-        jplagParser.addEnd(FOR_END, ctx.getStart());
+        parser.addEnd(FOR_END, ctx.getStart());
     }
 
     @Override
     public void enterDel_stmt(Python3Parser.Del_stmtContext ctx) {
-        jplagParser.add(DEL, ctx.getStart());
-    }
-
-    @Override
-    public void exitDel_stmt(Python3Parser.Del_stmtContext ctx) {
-    }
-
-    @Override
-    public void enterAtom(Python3Parser.AtomContext ctx) {
-    }
-
-    @Override
-    public void exitAtom(Python3Parser.AtomContext ctx) {
-    }
-
-    @Override
-    public void enterStmt(Python3Parser.StmtContext ctx) {
-    }
-
-    @Override
-    public void exitStmt(Python3Parser.StmtContext ctx) {
-    }
-
-    @Override
-    public void enterEveryRule(ParserRuleContext ctx) {
-    }
-
-    @Override
-    public void exitEveryRule(ParserRuleContext ctx) {
+        parser.add(DEL, ctx.getStart());
     }
 
     @Override
     public void visitTerminal(TerminalNode node) {
         if (node.getText().equals("=")) {
-            jplagParser.add(ASSIGN, node.getSymbol());
+            parser.add(ASSIGN, node.getSymbol());
         } else if (node.getText().equals("finally")) {
-            jplagParser.add(FINALLY, node.getSymbol());
+            parser.add(FINALLY, node.getSymbol());
         }
     }
-
-    @Override
-    public void visitErrorNode(ErrorNode node) {
-    }
-
-    @Override
-    public void enterAnnassign(Python3Parser.AnnassignContext ctx) {
-    }
-
-    @Override
-    public void exitAnnassign(Python3Parser.AnnassignContext ctx) {
-    }
-
-    @Override
-    public void enterEncoding_decl(Python3Parser.Encoding_declContext ctx) {
-    }
-
-    @Override
-    public void exitEncoding_decl(Python3Parser.Encoding_declContext ctx) {
-    }
-
-    @Override
-    public void enterAtom_expr(Python3Parser.Atom_exprContext ctx) {
-    }
-
-    @Override
-    public void exitAtom_expr(Python3Parser.Atom_exprContext ctx) {
-    }
-
-    @Override
-    public void enterAsync_funcdef(Python3Parser.Async_funcdefContext ctx) {
-    }
-
-    @Override
-    public void exitAsync_funcdef(Python3Parser.Async_funcdefContext ctx) {
-    }
-
-    @Override
-    public void enterAsync_stmt(Python3Parser.Async_stmtContext ctx) {
-    }
-
-    @Override
-    public void exitAsync_stmt(Python3Parser.Async_stmtContext ctx) {
-    }
 }

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

@@ -7,11 +7,11 @@ import de.jplag.TokenList;
 
 public class Language implements de.jplag.Language {
 
-    private Parser parser;
+    private final Parser parser;
 
     public Language(ErrorConsumer program) {
         this.parser = new Parser();
-        this.parser.setProgram(program);
+        this.parser.setErrorConsumer(program);
     }
 
     @Override

+ 1 - 2
jplag.frontend.python-3/src/main/java/de/jplag/python3/Parser.java

@@ -23,7 +23,7 @@ public class Parser extends AbstractParser {
     private TokenList tokens = new TokenList();
     private String currentFile;
 
-    public TokenList parse(File directory, String files[]) {
+    public TokenList parse(File directory, String[] files) {
         tokens = new TokenList();
         errors = 0;
         for (int i = 0; i < files.length; i++) {
@@ -31,7 +31,6 @@ public class Parser extends AbstractParser {
             if (!parseFile(directory, files[i])) {
                 errors++;
             }
-            System.gc();// Emeric
             tokens.addToken(new Python3Token(Python3TokenConstants.FILE_END, files[i], -1, -1, -1));
         }
         this.parseEnd();

+ 31 - 31
jplag.frontend.python-3/src/main/java/de/jplag/python3/Python3TokenConstants.java

@@ -3,36 +3,36 @@ package de.jplag.python3;
 import de.jplag.TokenConstants;
 
 public interface Python3TokenConstants extends TokenConstants {
-    final static int IMPORT = 2;
-    final static int CLASS_BEGIN = 3;
-    final static int CLASS_END = 4;
-    final static int METHOD_BEGIN = 5;
-    final static int METHOD_END = 6;
-    final static int ASSIGN = 7;
-    final static int WHILE_BEGIN = 8;
-    final static int WHILE_END = 9;
-    final static int FOR_BEGIN = 10;
-    final static int FOR_END = 11;
-    final static int TRY_BEGIN = 12;
-    final static int EXCEPT_BEGIN = 13;
-    final static int EXCEPT_END = 14;
-    final static int FINALLY = 15;
-    final static int IF_BEGIN = 16;
-    final static int IF_END = 18;
-    final static int APPLY = 19;
-    final static int BREAK = 20;
-    final static int CONTINUE = 21;
-    final static int RETURN = 22;
-    final static int RAISE = 23;
-    final static int DEC_BEGIN = 24;
-    final static int DEC_END = 25;
-    final static int LAMBDA = 26;
-    final static int ARRAY = 27;
-    final static int ASSERT = 28;
-    final static int YIELD = 29;
-    final static int DEL = 30;
-    final static int WITH_BEGIN = 31;
-    final static int WITH_END = 32;
+    int IMPORT = 2;
+    int CLASS_BEGIN = 3;
+    int CLASS_END = 4;
+    int METHOD_BEGIN = 5;
+    int METHOD_END = 6;
+    int ASSIGN = 7;
+    int WHILE_BEGIN = 8;
+    int WHILE_END = 9;
+    int FOR_BEGIN = 10;
+    int FOR_END = 11;
+    int TRY_BEGIN = 12;
+    int EXCEPT_BEGIN = 13;
+    int EXCEPT_END = 14;
+    int FINALLY = 15;
+    int IF_BEGIN = 16;
+    int IF_END = 18;
+    int APPLY = 19;
+    int BREAK = 20;
+    int CONTINUE = 21;
+    int RETURN = 22;
+    int RAISE = 23;
+    int DEC_BEGIN = 24;
+    int DEC_END = 25;
+    int LAMBDA = 26;
+    int ARRAY = 27;
+    int ASSERT = 28;
+    int YIELD = 29;
+    int DEL = 30;
+    int WITH_BEGIN = 31;
+    int WITH_END = 32;
 
-    final static int NUM_DIFF_TOKENS = 33;
+    int NUM_DIFF_TOKENS = 33;
 }

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

@@ -9,7 +9,7 @@ public class Language implements de.jplag.Language {
 
     public Language(ErrorConsumer program) {
         this.parser = new Parser();
-        this.parser.setProgram(program);
+        this.parser.setErrorConsumer(program);
 
     }
 
@@ -18,7 +18,7 @@ public class Language implements de.jplag.Language {
         return this.parser.errorsCount();
     }
 
-    private de.jplag.scheme.Parser parser; // Not yet instantiated? See constructor!
+    private final de.jplag.scheme.Parser parser; // Not yet instantiated? See constructor!
 
     @Override
     public String[] suffixes() {
@@ -42,7 +42,7 @@ public class Language implements de.jplag.Language {
 
     @Override
     public boolean supportsColumns() {
-        return false;
+        return true;
     }
 
     @Override

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

@@ -10,7 +10,7 @@ public class Parser extends AbstractParser {
 
     private TokenList tokens;
 
-    public TokenList parse(File directory, String files[]) {
+    public TokenList parse(File directory, String[] files) {
         tokens = new TokenList();
         errors = 0;
         for (int i = 0; i < files.length; i++) {

+ 45 - 89
jplag.frontend.scheme/src/main/java/de/jplag/scheme/SchemeToken.java

@@ -13,94 +13,50 @@ public class SchemeToken extends Token implements SchemeTokenConstants {
     }
 
     protected String type2string() {
-        switch (type) {
-            case SchemeTokenConstants.FILE_END:
-                return "EOF";
-            case S_BOOL:
-                return "BOOL    ";
-            case S_NUMBER:
-                return "NUMBER  ";
-            case S_CHAR:
-                return "CHAR    ";
-            case S_STRING:
-                return "STRING  ";
-            case S_ID:
-                return "ID      ";
-            case S_LIST_BEGIN:
-                return "(LIST   ";
-            case S_LIST_END:
-                return "LIST)   ";
-            case S_VECTOR_BEGIN:
-                return "(VECTOR ";
-            case S_VECTOR_END:
-                return "VECTOR) ";
-            case S_LITERAL:
-                return "LITERAL ";
-            case S_QUOT_BEGIN:
-                return "(QUOT   ";
-            case S_QUOT_END:
-                return "QUOT)   ";
-            case S_CALL:
-                return "CALL    ";
-            case S_LAMBDA_BEGIN:
-                return "(LAMBDA ";
-            case S_LAMBDA_END:
-                return "LAMBDA) ";
-            case S_FORMAL_BEGIN:
-                return "(FORMAL ";
-            case S_FORMAL_END:
-                return "FORMAL) ";
-            case S_BODY_BEGIN:
-                return "(BODY   ";
-            case S_BODY_END:
-                return "BODY)   ";
-            case S_IF_BEGIN:
-                return "(IF     ";
-            case S_IF_END:
-                return "IF)     ";
-            case S_ALTERN:
-                return "ALTERN  ";
-            case S_ASSIGN_BEGIN:
-                return "(ASSIGN ";
-            case S_ASSIGN_END:
-                return "ASSIGN) ";
-            case S_COND_BEGIN:
-                return "(COND   ";
-            case S_COND_END:
-                return "COND)   ";
-            case S_ELSE:
-                return "ELSE    ";
-            case S_CASE_BEGIN:
-                return "(CASE   ";
-            case S_CASE_END:
-                return "CASE)   ";
-            case S_DO_BEGIN:
-                return "(DO     ";
-            case S_DO_END:
-                return "DO)     ";
-            case S_COMMAND:
-                return "COMMAND ";
-            case S_DEF_BEGIN:
-                return "(DEF    ";
-            case S_DEF_END:
-                return "DEF)    ";
-            case S_BEGIN:
-                return "(       ";
-            case S_END:
-                return ")       ";
-            case S_AND:
-                return "AND     ";
-            case S_OR:
-                return "OR      ";
-            case S_LET:
-                return "LET     ";
-            case S_DELAY:
-                return "DELAY   ";
-            case S_VAR:
-                return "VAR     ";
-
-            default:
-                return "<?????> ";
-        }
+        return switch (type) {
+            case SchemeTokenConstants.FILE_END -> "EOF";
+            case S_BOOL -> "BOOL    ";
+            case S_NUMBER -> "NUMBER  ";
+            case S_CHAR -> "CHAR    ";
+            case S_STRING -> "STRING  ";
+            case S_ID -> "ID      ";
+            case S_LIST_BEGIN -> "(LIST   ";
+            case S_LIST_END -> "LIST)   ";
+            case S_VECTOR_BEGIN -> "(VECTOR ";
+            case S_VECTOR_END -> "VECTOR) ";
+            case S_LITERAL -> "LITERAL ";
+            case S_QUOT_BEGIN -> "(QUOT   ";
+            case S_QUOT_END -> "QUOT)   ";
+            case S_CALL -> "CALL    ";
+            case S_LAMBDA_BEGIN -> "(LAMBDA ";
+            case S_LAMBDA_END -> "LAMBDA) ";
+            case S_FORMAL_BEGIN -> "(FORMAL ";
+            case S_FORMAL_END -> "FORMAL) ";
+            case S_BODY_BEGIN -> "(BODY   ";
+            case S_BODY_END -> "BODY)   ";
+            case S_IF_BEGIN -> "(IF     ";
+            case S_IF_END -> "IF)     ";
+            case S_ALTERN -> "ALTERN  ";
+            case S_ASSIGN_BEGIN -> "(ASSIGN ";
+            case S_ASSIGN_END -> "ASSIGN) ";
+            case S_COND_BEGIN -> "(COND   ";
+            case S_COND_END -> "COND)   ";
+            case S_ELSE -> "ELSE    ";
+            case S_CASE_BEGIN -> "(CASE   ";
+            case S_CASE_END -> "CASE)   ";
+            case S_DO_BEGIN -> "(DO     ";
+            case S_DO_END -> "DO)     ";
+            case S_COMMAND -> "COMMAND ";
+            case S_DEF_BEGIN -> "(DEF    ";
+            case S_DEF_END -> "DEF)    ";
+            case S_BEGIN -> "(       ";
+            case S_END -> ")       ";
+            case S_AND -> "AND     ";
+            case S_OR -> "OR      ";
+            case S_LET -> "LET     ";
+            case S_DELAY -> "DELAY   ";
+            case S_VAR -> "VAR     ";
+            default -> "<?????> ";
+        };
     }
 }

+ 42 - 42
jplag.frontend.scheme/src/main/java/de/jplag/scheme/SchemeTokenConstants.java

@@ -4,47 +4,47 @@ import de.jplag.TokenConstants;
 
 public interface SchemeTokenConstants extends TokenConstants {
 
-    final static int S_BOOL = 2;
-    final static int S_NUMBER = 3;
-    final static int S_CHAR = 4;
-    final static int S_STRING = 5;
-    final static int S_ID = 6;
-    final static int S_LIST_BEGIN = 7;
-    final static int S_LIST_END = 8;
-    final static int S_VECTOR_BEGIN = 9;
-    final static int S_VECTOR_END = 10;
-    final static int S_LITERAL = 11;
-    final static int S_QUOT_BEGIN = 12;
-    final static int S_QUOT_END = 13;
-    final static int S_CALL = 14;
-    final static int S_LAMBDA_BEGIN = 15;
-    final static int S_LAMBDA_END = 16;
-    final static int S_FORMAL_BEGIN = 17;
-    final static int S_FORMAL_END = 18;
-    final static int S_BODY_BEGIN = 19;
-    final static int S_BODY_END = 20;
-    final static int S_IF_BEGIN = 21;
-    final static int S_IF_END = 22;
-    final static int S_ALTERN = 23;
-    final static int S_ASSIGN_BEGIN = 24;
-    final static int S_ASSIGN_END = 25;
-    final static int S_COND_BEGIN = 26;
-    final static int S_COND_END = 27;
-    final static int S_ELSE = 28;
-    final static int S_CASE_BEGIN = 29;
-    final static int S_CASE_END = 30;
-    final static int S_DO_BEGIN = 31;
-    final static int S_DO_END = 32;
-    final static int S_COMMAND = 33;
-    final static int S_DEF_BEGIN = 34;
-    final static int S_DEF_END = 35;
-    final static int S_BEGIN = 36;
-    final static int S_END = 37;
-    final static int S_AND = 38;
-    final static int S_OR = 39;
-    final static int S_LET = 40;
-    final static int S_DELAY = 41;
-    final static int S_VAR = 42;
+    int S_BOOL = 2;
+    int S_NUMBER = 3;
+    int S_CHAR = 4;
+    int S_STRING = 5;
+    int S_ID = 6;
+    int S_LIST_BEGIN = 7;
+    int S_LIST_END = 8;
+    int S_VECTOR_BEGIN = 9;
+    int S_VECTOR_END = 10;
+    int S_LITERAL = 11;
+    int S_QUOT_BEGIN = 12;
+    int S_QUOT_END = 13;
+    int S_CALL = 14;
+    int S_LAMBDA_BEGIN = 15;
+    int S_LAMBDA_END = 16;
+    int S_FORMAL_BEGIN = 17;
+    int S_FORMAL_END = 18;
+    int S_BODY_BEGIN = 19;
+    int S_BODY_END = 20;
+    int S_IF_BEGIN = 21;
+    int S_IF_END = 22;
+    int S_ALTERN = 23;
+    int S_ASSIGN_BEGIN = 24;
+    int S_ASSIGN_END = 25;
+    int S_COND_BEGIN = 26;
+    int S_COND_END = 27;
+    int S_ELSE = 28;
+    int S_CASE_BEGIN = 29;
+    int S_CASE_END = 30;
+    int S_DO_BEGIN = 31;
+    int S_DO_END = 32;
+    int S_COMMAND = 33;
+    int S_DEF_BEGIN = 34;
+    int S_DEF_END = 35;
+    int S_BEGIN = 36;
+    int S_END = 37;
+    int S_AND = 38;
+    int S_OR = 39;
+    int S_LET = 40;
+    int S_DELAY = 41;
+    int S_VAR = 42;
 
-    final static int NUM_DIFF_TOKENS = 43;
+    int NUM_DIFF_TOKENS = 43;
 }

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

@@ -8,13 +8,10 @@ import de.jplag.TokenList;
 
 public class Language implements de.jplag.Language {
 
-    private ErrorConsumer program;
-
-    private Parser parser = new Parser();
+    private final Parser parser = new Parser();
 
     public Language(ErrorConsumer program) {
-        this.program = program;
-        this.parser.setProgram(this.program);
+        this.parser.setErrorConsumer(program);
     }
 
     @Override

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

@@ -21,7 +21,7 @@ public class Parser extends AbstractParser {
 
     private boolean runOut = false;
 
-    public TokenList parse(File directory, String files[]) {
+    public TokenList parse(File directory, String[] files) {
         tokens = new TokenList();
         errors = 0;
         for (String file : files) {

+ 5 - 2
jplag.frontend.text/src/main/java/de/jplag/text/TextToken.java

@@ -1,22 +1,25 @@
 package de.jplag.text;
 
+import java.io.Serial;
+
 import de.jplag.Token;
 
 public class TextToken extends Token {
+    @Serial
     private static final long serialVersionUID = 4301179216570538972L;
 
     private static int getSerial(String text, Parser parser) {
         text = text.toLowerCase();
         Integer serial = parser.table.get(text);
         if (serial == null) {
-            serial = Integer.valueOf(parser.serial);
+            serial = parser.serial;
             if (parser.serial == Integer.MAX_VALUE)
                 parser.outOfSerials();
             else
                 parser.serial++;
             parser.table.put(text, serial);
         }
-        return serial.intValue();
+        return serial;
     }
 
     private String text;

+ 1 - 1
jplag/pom.xml

@@ -23,7 +23,7 @@
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
-            <version>2.13.0</version>
+            <version>2.13.2.2</version>
         </dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>

+ 52 - 6
jplag/src/main/java/de/jplag/CLI.java

@@ -1,7 +1,37 @@
 package de.jplag;
 
-import static de.jplag.CommandLineArgument.*;
-
+import static de.jplag.CommandLineArgument.BASE_CODE;
+import static de.jplag.CommandLineArgument.CLUSTER_AGGLOMERATIVE_INTER_CLUSTER_SIMILARITY;
+import static de.jplag.CommandLineArgument.CLUSTER_AGGLOMERATIVE_THRESHOLD;
+import static de.jplag.CommandLineArgument.CLUSTER_ALGORITHM;
+import static de.jplag.CommandLineArgument.CLUSTER_ENABLE;
+import static de.jplag.CommandLineArgument.CLUSTER_METRIC;
+import static de.jplag.CommandLineArgument.CLUSTER_PREPROCESSING_CDF;
+import static de.jplag.CommandLineArgument.CLUSTER_PREPROCESSING_NONE;
+import static de.jplag.CommandLineArgument.CLUSTER_PREPROCESSING_PERCENTILE;
+import static de.jplag.CommandLineArgument.CLUSTER_PREPROCESSING_THRESHOLD;
+import static de.jplag.CommandLineArgument.CLUSTER_SPECTRAL_BANDWIDTH;
+import static de.jplag.CommandLineArgument.CLUSTER_SPECTRAL_KMEANS_ITERATIONS;
+import static de.jplag.CommandLineArgument.CLUSTER_SPECTRAL_MAX_RUNS;
+import static de.jplag.CommandLineArgument.CLUSTER_SPECTRAL_MIN_RUNS;
+import static de.jplag.CommandLineArgument.CLUSTER_SPECTRAL_NOISE;
+import static de.jplag.CommandLineArgument.COMPARISON_MODE;
+import static de.jplag.CommandLineArgument.DEBUG;
+import static de.jplag.CommandLineArgument.EXCLUDE_FILE;
+import static de.jplag.CommandLineArgument.LANGUAGE;
+import static de.jplag.CommandLineArgument.MIN_TOKEN_MATCH;
+import static de.jplag.CommandLineArgument.NEW_DIRECTORY;
+import static de.jplag.CommandLineArgument.OLD_DIRECTORY;
+import static de.jplag.CommandLineArgument.RESULT_FOLDER;
+import static de.jplag.CommandLineArgument.ROOT_DIRECTORY;
+import static de.jplag.CommandLineArgument.SHOWN_COMPARISONS;
+import static de.jplag.CommandLineArgument.SIMILARITY_THRESHOLD;
+import static de.jplag.CommandLineArgument.SUBDIRECTORY;
+import static de.jplag.CommandLineArgument.SUFFIXES;
+import static de.jplag.CommandLineArgument.VERBOSITY;
+
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Optional;
 import java.util.Random;
 
@@ -19,8 +49,8 @@ import de.jplag.options.JPlagOptions;
 import de.jplag.options.LanguageOption;
 import de.jplag.options.SimilarityMetric;
 import de.jplag.options.Verbosity;
-import de.jplag.reporting2.JsonReport;
-import de.jplag.reporting2.Report;
+import de.jplag.reporting.JsonReport;
+import de.jplag.reporting.Report;
 import de.jplag.strategy.ComparisonMode;
 
 /**
@@ -98,8 +128,16 @@ public class CLI {
         if (fileSuffixString != null) {
             fileSuffixes = fileSuffixString.replaceAll("\\s+", "").split(",");
         }
+
+        // Collect the root directories.
+        List<String> submissionDirectories = new ArrayList<>();
+        List<String> oldSubmissionDirectories = new ArrayList<>();
+        addAllMultiValueArgument(ROOT_DIRECTORY.getListFrom(namespace), submissionDirectories);
+        addAllMultiValueArgument(NEW_DIRECTORY.getListFrom(namespace), submissionDirectories);
+        addAllMultiValueArgument(OLD_DIRECTORY.getListFrom(namespace), oldSubmissionDirectories);
+
         LanguageOption language = LanguageOption.fromDisplayName(LANGUAGE.getFrom(namespace));
-        JPlagOptions options = new JPlagOptions(ROOT_DIRECTORY.getListFrom(namespace), language);
+        JPlagOptions options = new JPlagOptions(submissionDirectories, oldSubmissionDirectories, language);
         options.setBaseCodeSubmissionName(BASE_CODE.getFrom(namespace));
         options.setVerbosity(Verbosity.fromOption(VERBOSITY.getFrom(namespace)));
         options.setDebugParser(DEBUG.getFrom(namespace));
@@ -109,7 +147,7 @@ public class CLI {
         options.setMinimumTokenMatch(MIN_TOKEN_MATCH.getFrom(namespace));
         options.setSimilarityThreshold(SIMILARITY_THRESHOLD.getFrom(namespace));
         options.setMaximumNumberOfComparisons(SHOWN_COMPARISONS.getFrom(namespace));
-        ComparisonMode.fromName(COMPARISON_MODE.getFrom(namespace)).ifPresentOrElse(it -> options.setComparisonMode(it),
+        ComparisonMode.fromName(COMPARISON_MODE.getFrom(namespace)).ifPresentOrElse(options::setComparisonMode,
                 () -> System.out.println("Unknown comparison mode, using default mode!"));
 
         ClusteringOptions.Builder clusteringBuilder = new ClusteringOptions.Builder();
@@ -152,4 +190,12 @@ public class CLI {
         var randomDescription = DESCRIPTIONS[new Random().nextInt(DESCRIPTIONS.length)];
         return String.format("JPlag - %s" + System.lineSeparator() + CREDITS, randomDescription);
     }
+
+    private void addAllMultiValueArgument(List<List<String>> argumentValues, List<String> destinationRootDirectories) {
+        if (argumentValues == null) {
+            return;
+        }
+
+        argumentValues.stream().forEach(value -> destinationRootDirectories.addAll(value));
+    }
 }

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

@@ -15,8 +15,8 @@ import net.sourceforge.argparse4j.inf.MutuallyExclusiveGroup;
 public class CliGroupHelper {
 
     private final ArgumentParser parser;
-    private Map<String, MutuallyExclusiveGroup> mutuallyExclusiveGroups = new HashMap<>();
-    private Map<String, ArgumentGroup> argumentGroups = new HashMap<>();
+    private final Map<String, MutuallyExclusiveGroup> mutuallyExclusiveGroups = new HashMap<>();
+    private final Map<String, ArgumentGroup> argumentGroups = new HashMap<>();
 
     public CliGroupHelper(ArgumentParser parser) {
         this.parser = parser;

+ 9 - 3
jplag/src/main/java/de/jplag/CommandLineArgument.java

@@ -5,6 +5,7 @@ import static de.jplag.CLI.CLUSTERING_PREPROCESSING_GROUP_NAME;
 import static de.jplag.options.JPlagOptions.DEFAULT_COMPARISON_MODE;
 import static de.jplag.options.JPlagOptions.DEFAULT_SHOWN_COMPARISONS;
 import static de.jplag.options.JPlagOptions.DEFAULT_SIMILARITY_THRESHOLD;
+import static net.sourceforge.argparse4j.impl.Arguments.append;
 import static net.sourceforge.argparse4j.impl.Arguments.storeTrue;
 
 import java.util.Collection;
@@ -30,8 +31,9 @@ import de.jplag.strategy.ComparisonMode;
  * @author Timur Saglam
  */
 public enum CommandLineArgument {
-
-    ROOT_DIRECTORY(new Builder("rootDir", String.class).nargs(NumberOfArgumentValues.ONE_OR_MORE_VALUES)),
+    ROOT_DIRECTORY(new Builder("rootDir", String.class).nargs(NumberOfArgumentValues.ZERO_OR_MORE_VALUES)),
+    NEW_DIRECTORY(new Builder("-new", String.class).nargs(NumberOfArgumentValues.ONE_OR_MORE_VALUES)),
+    OLD_DIRECTORY(new Builder("-old", String.class).nargs(NumberOfArgumentValues.ONE_OR_MORE_VALUES)),
     LANGUAGE(new Builder("-l", String.class).defaultsTo(LanguageOption.getDefault().getDisplayName()).choices(LanguageOption.getAllDisplayNames())),
     BASE_CODE("-bc", String.class),
     VERBOSITY(new Builder("-v", String.class).defaultsTo("quiet").choices(List.of("quiet", "long"))), // TODO SH: Replace verbosity when integrating a
@@ -167,8 +169,12 @@ public enum CommandLineArgument {
         if (type == Boolean.class) {
             argument.action(storeTrue());
         }
-        if (numberOfValues == NumberOfArgumentValues.ONE_OR_MORE_VALUES) {
+        if (!numberOfValues.toString().isEmpty()) {
+            // For multi-value arguments keep all invocations.
+            // This causes the argument value to change its type to 'List<List<String>>'.
+            // Also, when the retrieved value after parsing the CLI is 'null', the argument is not used.
             argument.nargs(numberOfValues.toString());
+            argument.action(append());
         }
     }
 

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

@@ -55,7 +55,7 @@ public class ErrorCollector implements ErrorConsumer { // TODO TS should be even
             errorReport.append('\n');
         }
 
-        System.out.println(errorReport.toString());
+        System.out.println(errorReport);
     }
 
     /**

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

@@ -97,7 +97,7 @@ public class GreedyStringTiling {
         return swapAndCompare(firstSubmission, secondSubmission, true);
     }
 
-    private final JPlagComparison swapAndCompare(Submission firstSubmission, Submission secondSubmission, boolean isBaseCodeComparison) {
+    private JPlagComparison swapAndCompare(Submission firstSubmission, Submission secondSubmission, boolean isBaseCodeComparison) {
         Submission smallerSubmission, largerSubmission;
         if (firstSubmission.getTokenList().size() > secondSubmission.getTokenList().size()) {
             smallerSubmission = secondSubmission;
@@ -122,7 +122,7 @@ public class GreedyStringTiling {
      * @param isBaseCodeComparison specifies whether one of the submissions is the base code.
      * @return the comparison results.
      */
-    private final JPlagComparison compare(Submission firstSubmission, Submission secondSubmission, boolean isBaseCodeComparison) {
+    private JPlagComparison compare(Submission firstSubmission, Submission secondSubmission, boolean isBaseCodeComparison) {
         // first and second refer to the list of tokens of the first and second submission:
         TokenList first = firstSubmission.getTokenList();
         TokenList second = secondSubmission.getTokenList();

+ 7 - 12
jplag/src/main/java/de/jplag/JPlag.java

@@ -37,9 +37,8 @@ public class JPlag {
     /**
      * Creates and initializes a JPlag instance, parameterized by a set of options.
      * @param options determines the parameterization.
-     * @throws ExitException if the initialization fails.
      */
-    public JPlag(JPlagOptions options) throws ExitException {
+    public JPlag(JPlagOptions options) {
         this.options = options;
         errorCollector = new ErrorCollector(options);
         coreAlgorithm = new GreedyStringTiling(options);
@@ -51,9 +50,9 @@ public class JPlag {
 
     /**
      * If an exclusion file is given, it is read in and all strings are saved in the set "excluded".
-     * @param exclusionFileName
+     * @param exclusionFileName the file name or path
      */
-    Set<String> readExclusionFile(final String exclusionFileName) {
+    private Set<String> readExclusionFile(final String exclusionFileName) {
         try (BufferedReader reader = new BufferedReader(new FileReader(exclusionFileName, JPlagOptions.CHARSET))) {
             final var excludedFileNames = reader.lines().collect(Collectors.toSet());
             if (options.getVerbosity() == LONG) {
@@ -98,14 +97,10 @@ public class JPlag {
     }
 
     private ComparisonStrategy initializeComparisonStrategy(final ComparisonMode comparisonMode) {
-        switch (comparisonMode) {
-            case NORMAL:
-                return new NormalComparisonStrategy(options, coreAlgorithm);
-            case PARALLEL:
-                return new ParallelComparisonStrategy(options, coreAlgorithm);
-            default:
-                throw new AssertionError("Unknown comparison strategy found.");
-        }
+        return switch (comparisonMode) {
+            case NORMAL -> new NormalComparisonStrategy(options, coreAlgorithm);
+            case PARALLEL -> new ParallelComparisonStrategy(options, coreAlgorithm);
+        };
     }
 
     private Language initializeLanguage() {

+ 3 - 82
jplag/src/main/java/de/jplag/JPlagComparison.java

@@ -1,13 +1,8 @@
 package de.jplag;
 
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.Comparator;
-import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.Set;
-import java.util.stream.IntStream;
 
 /**
  * This method represents the whole result of a comparison between two submissions.
@@ -31,7 +26,7 @@ public class JPlagComparison implements Comparator<JPlagComparison> { // FIXME T
      * Add a match to the comparison (token indices and number of tokens), if it does not overlap with the existing matches.
      * @see Match#Match(int, int, int)
      */
-    public final void addMatch(int startOfFirst, int startOfSecond, int length) {
+    /* package-private */ final void addMatch(int startOfFirst, int startOfSecond, int length) {
         for (Match match : matches) {
             if (match.overlap(startOfFirst, startOfSecond, length)) {
                 return;
@@ -40,16 +35,6 @@ public class JPlagComparison implements Comparator<JPlagComparison> { // FIXME T
         matches.add(new Match(startOfFirst, startOfSecond, length));
     }
 
-    /**
-     * The bigger a match (length) is relatively to the biggest match the redder is the color returned by this method.
-     */
-    public String color(int length) {
-        int longestMatch = matches.stream().mapToInt(it -> it.getLength()).max().orElse(0);
-        int color = 255 * length / longestMatch;
-        String help = (color < 16 ? "0" : "") + Integer.toHexString(color);
-        return "#" + help + "0000";
-    }
-
     @Override
     public int compare(JPlagComparison comparison1, JPlagComparison comparison2) {
         return Float.compare(comparison2.similarity(), comparison1.similarity()); // comparison2 first!
@@ -63,34 +48,6 @@ public class JPlagComparison implements Comparator<JPlagComparison> { // FIXME T
         return (compare(this, (JPlagComparison) other) == 0);
     }
 
-    /**
-     * This method returns all the files which contributed to a match. Parameter: j == 0 1st submission, j != 0 2nd
-     * submission.
-     */
-    public final String[] files(int j) {
-        if (matches.size() == 0) {
-            return new String[] {};
-        }
-
-        TokenList tokenList = (j == 0 ? firstSubmission : secondSubmission).getTokenList();
-
-        /*
-         * Collect the file names of the first token of each match.
-         */
-        Set<String> collectedFiles = new LinkedHashSet<>();
-        for (Match match : matches) {
-            collectedFiles.add(tokenList.getToken(match.getStart(j == 0)).getFile());
-        }
-
-        /*
-         * sort by file name. (so that equally named files are displayed approximately side by side.)
-         */
-        String[] res = collectedFiles.toArray(new String[0]);
-        Arrays.sort(res);
-
-        return res;
-    }
-
     /**
      * @return the base code matches of the first submission.
      */
@@ -139,22 +96,6 @@ public class JPlagComparison implements Comparator<JPlagComparison> { // FIXME T
         return secondSubmission;
     }
 
-    /**
-     * @param getFirst Whether to return the first submission, else return the second submission.
-     * @return The requested submission.
-     */
-    public Submission getSubmission(boolean getFirst) {
-        return getFirst ? firstSubmission : secondSubmission;
-    }
-
-    /**
-     * @param getFirst Whether to return the first basecode matches, else return the second basecode matches.
-     * @return The requested basecode matches.
-     */
-    public JPlagComparison getBaseCodeMatches(boolean getFirst) {
-        return getSubmission(getFirst).getBaseCodeComparison();
-    }
-
     /**
      * @return Maximum similarity in percent of both submissions.
      */
@@ -218,38 +159,18 @@ public class JPlagComparison implements Comparator<JPlagComparison> { // FIXME T
         return ((int) (secondBasecodeSimilarity() * ROUNDING_FACTOR)) / (float) ROUNDING_FACTOR;
     }
 
-    /**
-     * Creates a permutation for the matches based on the indices of the matched token groups.
-     * @param useFirst determines whether the start of the first or second submission is compared.
-     * @return the permutation indices.
-     */
-    public final List<Integer> sort_permutation(boolean useFirst) {
-        List<Integer> indices = new ArrayList<>(matches.size());
-        IntStream.range(0, matches.size()).forEach(index -> indices.add(index));
-        Comparator<Integer> comparator = (Integer i, Integer j) -> {
-            return Integer.compare(selectStartof(i, useFirst), selectStartof(j, useFirst));
-        };
-        Collections.sort(indices, comparator);
-        return indices;
-    }
-
-    private int selectStartof(Integer index, boolean useFirst) {
-        Match match = matches.get(index);
-        return match.getStart(useFirst);
-    }
-
     @Override
     public String toString() {
         return firstSubmission.getName() + " <-> " + secondSubmission.getName();
     }
 
-    private final float firstBasecodeSimilarity() {
+    private float firstBasecodeSimilarity() {
         float sa = firstSubmission.getSimilarityDivisor(false);
         JPlagComparison firstBaseCodeMatches = firstSubmission.getBaseCodeComparison();
         return firstBaseCodeMatches.getNumberOfMatchedTokens() * 100 / sa;
     }
 
-    private final float secondBasecodeSimilarity() {
+    private float secondBasecodeSimilarity() {
         float sb = secondSubmission.getSimilarityDivisor(false);
         JPlagComparison secondBaseCodeMatches = secondSubmission.getBaseCodeComparison();
         return secondBaseCodeMatches.getNumberOfMatchedTokens() * 100 / sb;

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

@@ -35,15 +35,6 @@ public class Match {
         return startOfSecond;
     }
 
-    /**
-     * @param getFirst Whether to return the starting token index of the first submission, else the starting token index of
-     * the second submission is returned.
-     * @return Start index of the requested submission.
-     */
-    public int getStart(boolean getFirst) {
-        return getFirst ? startOfFirst : startOfSecond;
-    }
-
     /**
      * @return the length of the similar sections, meaning the number of tokens.
      */

+ 3 - 2
jplag/src/main/java/de/jplag/NumberOfArgumentValues.java

@@ -5,11 +5,12 @@ package de.jplag;
  */
 public enum NumberOfArgumentValues {
     SINGLE_VALUE(""),
-    ONE_OR_MORE_VALUES("+"),;
+    ONE_OR_MORE_VALUES("+"),
+    ZERO_OR_MORE_VALUES("*");
 
     private final String representation;
 
-    private NumberOfArgumentValues(String representation) {
+    NumberOfArgumentValues(String representation) {
         this.representation = representation;
     }
 

+ 79 - 150
jplag/src/main/java/de/jplag/Submission.java

@@ -1,20 +1,10 @@
 package de.jplag;
 
-import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
 import java.io.IOException;
-import java.io.InputStreamReader;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
-
-import de.jplag.exceptions.ReportGenerationException;
-import de.jplag.options.JPlagOptions;
 
 /**
  * Represents a single submission. A submission can contain multiple files.
@@ -35,6 +25,11 @@ public class Submission implements Comparable<Submission> {
      */
     private final File submissionRootFile;
 
+    /**
+     * Whether the submission is new. That is, must be checked for plagiarism.
+     */
+    private final boolean isNew;
+
     /**
      * Files of the submission.
      */
@@ -62,18 +57,32 @@ public class Submission implements Comparable<Submission> {
      * Creates a submission.
      * @param name Identification of the submission (directory or filename).
      * @param submissionRootFile is the submission file, or the root of the submission itself.
+     * @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, Collection<File> files, Language language, ErrorCollector errorCollector) {
+    public Submission(String name, File submissionRootFile, boolean isNew, Collection<File> files, Language language, ErrorCollector errorCollector) {
         this.name = name;
         this.submissionRootFile = submissionRootFile;
+        this.isNew = isNew;
         this.files = files;
         this.language = language;
         this.errorCollector = errorCollector;
     }
 
+    @Override
+    public int compareTo(Submission other) {
+        return name.compareTo(other.name);
+    }
+
+    /**
+     * @return base code comparison
+     */
+    public JPlagComparison getBaseCodeComparison() {
+        return baseCodeComparison;
+    }
+
     /**
      * @return a list of files this submission consists of.
      */
@@ -88,14 +97,6 @@ public class Submission implements Comparable<Submission> {
         return name;
     }
 
-    /**
-     * @return the unique file of the submission, which is either in a root folder or a subfolder of root folder when the
-     * subdirectory option is used.
-     */
-    public File getRoot() {
-        return submissionRootFile;
-    }
-
     /**
      * @return Number of tokens in the parse result.
      */
@@ -107,25 +108,11 @@ public class Submission implements Comparable<Submission> {
     }
 
     /**
-     * Sets the base code comparison
-     * @param baseCodeComparison is submissions matches with the base code
-     */
-    public void setBaseCodeComparison(JPlagComparison baseCodeComparison) {
-        this.baseCodeComparison = baseCodeComparison;
-    }
-
-    /**
-     * @return base code comparison
-     */
-    public JPlagComparison getBaseCodeComparison() {
-        return baseCodeComparison;
-    }
-
-    /**
-     * @return Whether a comparison between the submission and the base code is available.
+     * @return the unique file of the submission, which is either in a root folder or a subfolder of root folder when the
+     * subdirectory option is used.
      */
-    public boolean hasBaseCodeMatches() {
-        return baseCodeComparison != null;
+    public File getRoot() {
+        return submissionRootFile;
     }
 
     /**
@@ -148,118 +135,24 @@ public class Submission implements Comparable<Submission> {
     }
 
     /**
-     * @return true if at least one error occurred while parsing this submission; false otherwise.
-     */
-    public boolean hasErrors() {
-        return hasErrors;
-    }
-
-    /**
-     * Parse files of the submission.
-     * @return Whether parsing was successful.
+     * @return Whether a comparison between the submission and the base code is available.
      */
-    public boolean parse(boolean debugParser) {
-        if (files == null || files.size() == 0) {
-            errorCollector.print("ERROR: nothing to parse for submission \"" + name + "\"", null);
-            tokenList = null;
-            hasErrors = true; // invalidate submission
-            return false;
-        }
-
-        String[] relativeFilePaths = getRelativeFilePaths(submissionRootFile, files);
-
-        tokenList = language.parse(submissionRootFile, relativeFilePaths);
-        if (!language.hasErrors()) {
-            if (tokenList.size() < 3) {
-                errorCollector.print("Submission \"" + name + "\" is too short!", null);
-                tokenList = null;
-                hasErrors = true; // invalidate submission
-                return false;
-            }
-            return true;
-        }
-
-        tokenList = null;
-        hasErrors = true; // invalidate submission
-        if (debugParser) {
-            copySubmission();
-        }
-        return false;
+    public boolean hasBaseCodeMatches() {
+        return baseCodeComparison != null;
     }
 
     /**
-     * Used by the "Report" class. All source files are returned as an array of an array of strings.
+     * @return true if at least one error occurred while parsing this submission; false otherwise.
      */
-    public String[][] readFiles(String[] files) throws ReportGenerationException {
-        String[][] result = new String[files.length][];
-        String help;
-        List<String> text = new ArrayList<>();
-
-        for (int i = 0; i < files.length; i++) {
-            text.clear();
-
-            try {
-                FileInputStream fileInputStream = new FileInputStream(new File(submissionRootFile, files[i]));
-                InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, JPlagOptions.CHARSET);
-                BufferedReader in = new BufferedReader(inputStreamReader);
-
-                while ((help = in.readLine()) != null) {
-                    help = help.replaceAll("&", "&amp;");
-                    help = help.replaceAll("<", "&lt;");
-                    help = help.replaceAll(">", "&gt;");
-                    help = help.replaceAll("\"", "&quot;");
-                    text.add(help);
-                }
-
-                in.close();
-                inputStreamReader.close();
-                fileInputStream.close();
-            } catch (FileNotFoundException e) {
-                System.out.println("File not found: " + ((new File(submissionRootFile, files[i])).toString()));
-            } catch (IOException e) {
-                throw new ReportGenerationException("I/O exception!", e);
-            }
-
-            result[i] = new String[text.size()];
-            text.toArray(result[i]);
-        }
-
-        return result;
+    public boolean hasErrors() {
+        return hasErrors;
     }
 
     /**
-     * Used by the "Report" class. All source files are returned as an array of an array of chars.
+     * @return whether the submission is new, That is, must be checked for plagiarism.
      */
-    public char[][] readFilesChar(String[] files) throws ReportGenerationException {
-        char[][] result = new char[files.length][];
-
-        for (int i = 0; i < files.length; i++) {
-            // If the token path is absolute, ignore the provided directory
-            File file = new File(files[i]);
-            if (!file.isAbsolute()) {
-                file = new File(submissionRootFile, files[i]);
-            }
-
-            try {
-                int size = (int) file.length();
-                char[] buffer = new char[size];
-
-                FileReader reader = new FileReader(file, JPlagOptions.CHARSET);
-
-                if (size != reader.read(buffer)) {
-                    System.out.println("Not right size read from the file, but I will still continue...");
-                }
-
-                result[i] = buffer;
-                reader.close();
-            } catch (FileNotFoundException e) {
-                throw new ReportGenerationException("File not found: " + file.getPath(), e);
-            } catch (IOException e) {
-                throw new ReportGenerationException("I/O exception reading file \"" + file.getPath() + "\"!", e);
-            }
-        }
-
-        return result;
+    public boolean isNew() {
+        return isNew;
     }
 
     /**
@@ -271,6 +164,14 @@ public class Submission implements Comparable<Submission> {
         }
     }
 
+    /**
+     * Sets the base code comparison
+     * @param baseCodeComparison is submissions matches with the base code
+     */
+    public void setBaseCodeComparison(JPlagComparison baseCodeComparison) {
+        this.baseCodeComparison = baseCodeComparison;
+    }
+
     /**
      * Sets the tokens that have been parsed from the files this submission consists of.
      * @param tokenList is the list of these tokens.
@@ -279,15 +180,6 @@ public class Submission implements Comparable<Submission> {
         this.tokenList = tokenList;
     }
 
-    public void markAsErroneous() {
-        hasErrors = true;
-    }
-
-    @Override
-    public int compareTo(Submission other) {
-        return name.compareTo(other.name);
-    }
-
     @Override
     public String toString() {
         return name;
@@ -304,7 +196,7 @@ 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.toString() + "\n", null);
+                errorCollector.print("Error copying file: " + exception + "\n", null);
             }
         }
     }
@@ -336,4 +228,41 @@ public class Submission implements Comparable<Submission> {
 
         return files.stream().map(File::toPath).map(baseFilePath::relativize).map(Path::toString).toArray(String[]::new);
     }
+
+    /* package-private */ void markAsErroneous() {
+        hasErrors = true;
+    }
+
+    /**
+     * Parse files of the 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);
+            tokenList = null;
+            hasErrors = true; // invalidate submission
+            return false;
+        }
+
+        String[] relativeFilePaths = getRelativeFilePaths(submissionRootFile, files);
+
+        tokenList = language.parse(submissionRootFile, relativeFilePaths);
+        if (!language.hasErrors()) {
+            if (tokenList.size() < 3) {
+                errorCollector.print("Submission \"" + name + "\" is too short!", null);
+                tokenList = null;
+                hasErrors = true; // invalidate submission
+                return false;
+            }
+            return true;
+        }
+
+        tokenList = null;
+        hasErrors = true; // invalidate submission
+        if (debugParser) {
+            copySubmission();
+        }
+        return false;
+    }
 }

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

@@ -52,7 +52,7 @@ public class SubmissionSet {
      * @return Whether a basecode is available for this collection.
      */
     public boolean hasBaseCode() {
-        return !baseCodeSubmission.isEmpty();
+        return baseCodeSubmission.isPresent();
     }
 
     /**
@@ -96,7 +96,7 @@ public class SubmissionSet {
     }
 
     private List<Submission> filterInvalidSubmissions() {
-        return allSubmissions.stream().filter(submission -> submission.hasErrors()).collect(toList());
+        return allSubmissions.stream().filter(Submission::hasErrors).collect(toList());
     }
 
     private void parseAllSubmissions() throws ExitException {

+ 57 - 23
jplag/src/main/java/de/jplag/SubmissionSetBuilder.java

@@ -36,7 +36,7 @@ public class SubmissionSetBuilder {
      * @param language is the language of the submissions.
      * @param options are the configured options.
      * @param errorCollector is the interface for error reporting.
-     * @param excludedFileNames
+     * @param excludedFileNames a list of file names to be excluded
      */
     public SubmissionSetBuilder(Language language, JPlagOptions options, ErrorCollector errorCollector, Set<String> excludedFileNames) {
         this.language = language;
@@ -51,19 +51,25 @@ public class SubmissionSetBuilder {
      * @throws ExitException if the directory cannot be read.
      */
     public SubmissionSet buildSubmissionSet() throws ExitException {
-        Set<File> rootDirectoryNames = verifyRootDirectories(options.getRootDirectoryNames());
+        Set<File> submissionDirectories = verifyRootDirectories(options.getSubmissionDirectories(), true);
+        Set<File> oldSubmissionDirectories = verifyRootDirectories(options.getOldSubmissionDirectories(), false);
+        checkForNonOverlappingRootDirectories(submissionDirectories, oldSubmissionDirectories);
 
         // For backward compatibility, don't prefix submission names with their root directory
         // if there is only one root directory.
-        boolean multipleRoots = (rootDirectoryNames.size() > 1);
+        int numberOfRootDirectories = submissionDirectories.size() + oldSubmissionDirectories.size();
+        boolean multipleRoots = (numberOfRootDirectories > 1);
 
         // Collect valid looking entries from the root directories.
         Map<File, Submission> foundSubmissions = new HashMap<>();
-        for (File rootDirectory : rootDirectoryNames) {
-            processRootDirectoryEntries(rootDirectory, multipleRoots, foundSubmissions);
+        for (File directory : submissionDirectories) {
+            processRootDirectoryEntries(directory, multipleRoots, foundSubmissions, true);
+        }
+        for (File oldDirectory : oldSubmissionDirectories) {
+            processRootDirectoryEntries(oldDirectory, multipleRoots, foundSubmissions, false);
         }
 
-        Optional<Submission> baseCodeSubmission = loadBaseCode(rootDirectoryNames, foundSubmissions);
+        Optional<Submission> baseCodeSubmission = loadBaseCode(submissionDirectories, oldSubmissionDirectories, foundSubmissions);
 
         // Merge everything in a submission set.
         List<Submission> submissions = new ArrayList<>(foundSubmissions.values());
@@ -73,7 +79,11 @@ public class SubmissionSetBuilder {
     /**
      * Verify that the given root directories exist and have no duplicate entries.
      */
-    private Set<File> verifyRootDirectories(List<String> rootDirectoryNames) throws ExitException {
+    private Set<File> verifyRootDirectories(List<String> rootDirectoryNames, boolean areNewDirectories) throws ExitException {
+        if (areNewDirectories && rootDirectoryNames.isEmpty()) {
+            throw new RootDirectoryException("No root directories specified with submissions to check for plagiarism!");
+        }
+
         Set<File> canonicalRootDirectories = new HashSet<>(rootDirectoryNames.size());
         for (String rootDirectoryName : rootDirectoryNames) {
             File rootDirectory = new File(rootDirectoryName);
@@ -94,28 +104,54 @@ public class SubmissionSetBuilder {
         return canonicalRootDirectories;
     }
 
-    private Optional<Submission> loadBaseCode(Set<File> rootDirectories, Map<File, Submission> foundSubmissions) throws ExitException {
+    /**
+     * Verify that the new and old directory sets are disjunct and modify the old submissions set if necessary.
+     */
+    private void checkForNonOverlappingRootDirectories(Set<File> submissionDirectories, Set<File> oldSubmissionDirectories) {
+
+        Set<File> commonRootdirectories = new HashSet<>(submissionDirectories);
+        commonRootdirectories.retainAll(oldSubmissionDirectories);
+        if (commonRootdirectories.isEmpty()) {
+            return;
+        }
+
+        // As old submission directories are only read while new submission directories are both read and checked, the
+        // former use can be removed without affecting the result of the checks.
+        oldSubmissionDirectories.removeAll(commonRootdirectories);
+        for (File rootDirectory : commonRootdirectories) {
+            System.out.println("Warning: Root directory \"" + rootDirectory.toString()
+                    + "\" is specified both for plagiarism checking and for prior submissions, will perform plagiarism checking only.");
+        }
+    }
+
+    private Optional<Submission> loadBaseCode(Set<File> submissionDirectories, Set<File> oldSubmissionDirectories,
+            Map<File, Submission> foundSubmissions) throws ExitException {
         // Extract the basecode submission if necessary.
         Optional<Submission> baseCodeSubmission = Optional.empty();
         if (options.hasBaseCode()) {
             String baseCodeName = options.getBaseCodeSubmissionName().get();
             Submission baseCode = loadBaseCodeAsPath(baseCodeName);
             if (baseCode == null) {
-                if (rootDirectories.size() > 1) {
+                int numberOfRootDirectories = submissionDirectories.size() + oldSubmissionDirectories.size();
+                if (numberOfRootDirectories > 1) {
                     throw new BasecodeException("The base code submission needs to be specified by path instead of by name!");
                 }
-                File rootDirectory = rootDirectories.iterator().next();
+
+                // 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.
+                File rootDirectory = submissionDirectories.iterator().next();
+
                 // Single root-directory, try the legacy way of specifying basecode.
                 baseCode = loadBaseCodeViaName(baseCodeName, rootDirectory, foundSubmissions);
             }
+            // TODO Optional.of() will cause a NPTR-Exception of baseCode is null. Is this a bug?
             baseCodeSubmission = Optional.of(baseCode);
-            System.out.println(String.format("Basecode directory \"%s\" will be used.", baseCode.getName()));
+            System.out.printf("Basecode directory \"%s\" will be used.%n", baseCode.getName());
 
             // Basecode may also be registered as a user submission. If so, remove the latter.
             Submission removed = foundSubmissions.remove(baseCode.getRoot());
             if (removed != null) {
-                System.out.println(
-                        String.format("Submission \"%s\" is the specified basecode, it will be skipped during comparison.", removed.getName()));
+                System.out.printf("Submission \"%s\" is the specified basecode, it will be skipped during comparison.%n", removed.getName());
             }
         }
         return baseCodeSubmission;
@@ -140,12 +176,9 @@ public class SubmissionSetBuilder {
         try {
             // Use an unlikely short name for the base code. If all is well, this name should not appear
             // in the output since basecode matches are removed from it
-            return processSubmission(basecodeSubmission.getName(), basecodeSubmission);
+            return processSubmission(basecodeSubmission.getName(), basecodeSubmission, false);
         } catch (SubmissionException exception) {
             throw new BasecodeException(exception.getMessage(), exception); // Change thrown exception to basecode exception.
-
-        } catch (ExitException exception) {
-            throw exception;
         }
     }
 
@@ -231,11 +264,11 @@ public class SubmissionSetBuilder {
     /**
      * Process the given directory entry as a submission, the path MUST not be excluded.
      * @param submissionFile the file for the submission.
-     * @param rootDirectoryPrefix Prefix for submission submissions to the root directory, may be empty.
+     * @param isNew states whether submissions found in the root directory must be checked for plagiarism.
      * @return The entry converted to a submission.
      * @throws ExitException when an error has been found with the entry.
      */
-    private Submission processSubmission(String submissionName, File submissionFile) throws ExitException {
+    private Submission processSubmission(String submissionName, File submissionFile, boolean isNew) throws ExitException {
 
         if (submissionFile.isDirectory() && options.getSubdirectoryName() != null) {
             // Use subdirectory instead
@@ -252,16 +285,17 @@ public class SubmissionSetBuilder {
         }
 
         submissionFile = makeCanonical(submissionFile, it -> new SubmissionException("Cannot create submission: " + submissionName, it));
-        return new Submission(submissionName, submissionFile, parseFilesRecursively(submissionFile), language, errorCollector);
+        return new Submission(submissionName, submissionFile, isNew, parseFilesRecursively(submissionFile), language, errorCollector);
     }
 
     /**
      * Process entries in the root directory to check whether they qualify as submissions.
      * @param rootDirectory is the root directory being examined.
-     * @param addRootDirectoryPrefix specifies whether multiple root directories are in use.
      * @param foundSubmissions Submissions found so far, is updated in-place.
+     * @param isNew states whether submissions found in the root directory must be checked for plagiarism.
      */
-    private void processRootDirectoryEntries(File rootDirectory, boolean multipleRoots, Map<File, Submission> foundSubmissions) throws ExitException {
+    private void processRootDirectoryEntries(File rootDirectory, boolean multipleRoots, Map<File, Submission> foundSubmissions, boolean isNew)
+            throws ExitException {
         for (String fileName : listSubmissionFiles(rootDirectory)) {
             File submissionFile = new File(rootDirectory, fileName);
 
@@ -269,7 +303,7 @@ public class SubmissionSetBuilder {
             if (errorMessage == null) {
                 String rootDirectoryPrefix = multipleRoots ? (rootDirectory.getName() + File.separator) : "";
                 String submissionName = rootDirectoryPrefix + fileName;
-                Submission submission = processSubmission(submissionName, submissionFile);
+                Submission submission = processSubmission(submissionName, submissionFile, isNew);
                 foundSubmissions.put(submission.getRoot(), submission);
             } else {
                 System.out.println(errorMessage);

+ 4 - 4
jplag/src/main/java/de/jplag/TimeUtil.java

@@ -7,12 +7,12 @@ public final class TimeUtil {
     }
 
     /**
-     * Convert a duration in milli-seconds to a human-readable representation.
-     * @param durationInMiliseconds Number of milli-seconds to convert.
+     * Convert a duration in milliseconds to a human-readable representation.
+     * @param durationInMilliseconds Number of milliseconds to convert.
      * @return Readable representation of the time interval.
      */
-    public static String formatDuration(long durationInMiliseconds) {
-        int timeInSeconds = (int) (durationInMiliseconds / 1000);
+    public static String formatDuration(long durationInMilliseconds) {
+        int timeInSeconds = (int) (durationInMilliseconds / 1000);
         String hours = (timeInSeconds / 3600 > 0) ? (timeInSeconds / 3600) + " h " : "";
         String minutes = (timeInSeconds / 60 > 0) ? ((timeInSeconds / 60) % 60) + " min " : "";
         String seconds = (timeInSeconds % 60) + " sec";

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

@@ -12,8 +12,8 @@ import java.util.stream.Collectors;
  */
 public class Cluster<T> {
 
-    private float communityStrength;
-    private Collection<T> members;
+    private final float communityStrength;
+    private final Collection<T> members;
     private ClusteringResult<T> clusteringResult = null;
 
     public Cluster(Collection<T> members, float communityStrength) {
@@ -22,6 +22,7 @@ public class Cluster<T> {
     }
 
     public Collection<T> getMembers() {
+        // TODO Check why access to local attribute.
         return members;
     }
 

+ 6 - 6
jplag/src/main/java/de/jplag/clustering/ClusteringAdapter.java

@@ -22,8 +22,8 @@ import de.jplag.clustering.algorithm.GenericClusteringAlgorithm;
  */
 public class ClusteringAdapter {
 
-    private RealMatrix similarityMatrix;
-    private IntegerMapping<Submission> mapping;
+    private final RealMatrix similarityMatrix;
+    private final IntegerMapping<Submission> mapping;
 
     /**
      * Creates the clustering adapter. Only submissions that appear in those similarities might also appear in
@@ -58,10 +58,10 @@ public class ClusteringAdapter {
     public ClusteringResult<Submission> doClustering(GenericClusteringAlgorithm algorithm) {
         Collection<Collection<Integer>> intResult = algorithm.cluster(similarityMatrix);
         ClusteringResult<Integer> modularityClusterResult = ClusteringResult.fromIntegerCollections(new ArrayList<>(intResult), similarityMatrix);
-        List<Cluster<Submission>> mappedClusters = modularityClusterResult.getClusters().stream().map(unmappedCluster -> {
-            return new Cluster<Submission>(unmappedCluster.getMembers().stream().map(mapping::unmap).collect(Collectors.toList()),
-                    unmappedCluster.getCommunityStrength());
-        }).collect(Collectors.toList());
+        List<Cluster<Submission>> mappedClusters = modularityClusterResult.getClusters().stream()
+                .map(unmappedCluster -> new Cluster<>(unmappedCluster.getMembers().stream().map(mapping::unmap).collect(Collectors.toList()),
+                        unmappedCluster.getCommunityStrength()))
+                .collect(Collectors.toList());
         return new ClusteringResult<>(mappedClusters, modularityClusterResult.getCommunityStrength());
     }
 

+ 32 - 4
jplag/src/main/java/de/jplag/clustering/ClusteringAlgorithm.java

@@ -1,14 +1,42 @@
 package de.jplag.clustering;
 
+import java.util.Objects;
+
 import de.jplag.clustering.algorithm.AgglomerativeClustering;
+import de.jplag.clustering.algorithm.GenericClusteringAlgorithm;
 import de.jplag.clustering.algorithm.SpectralClustering;
 
 /**
  * Choosable clustering algorithms
  */
 public enum ClusteringAlgorithm {
-    /** {@link AgglomerativeClustering} */
-    AGGLOMERATIVE,
-    /** {@link SpectralClustering} */
-    SPECTRAL
+    /**
+     * {@link AgglomerativeClustering}
+     */
+    AGGLOMERATIVE(AgglomerativeClustering::new),
+    /**
+     * {@link SpectralClustering}
+     */
+    SPECTRAL(SpectralClustering::new);
+
+    private final ClusteringAlgorithmSupplier constructor;
+
+    ClusteringAlgorithm(ClusteringAlgorithmSupplier constructor) {
+        this.constructor = constructor;
+    }
+
+    /**
+     * Create GenericClusteringAlgorithm by ClusteringOptions.
+     * @param options the options to use for creation
+     * @return a new instance of GenericClusteringAlgorithm
+     */
+    public GenericClusteringAlgorithm create(ClusteringOptions options) {
+        Objects.requireNonNull(options);
+        return this.constructor.create(options);
+    }
+
+    @FunctionalInterface
+    public interface ClusteringAlgorithmSupplier {
+        GenericClusteringAlgorithm create(ClusteringOptions options);
+    }
 }

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

@@ -8,9 +8,7 @@ import java.util.stream.Collectors;
 
 import de.jplag.JPlagComparison;
 import de.jplag.Submission;
-import de.jplag.clustering.algorithm.AgglomerativeClustering;
 import de.jplag.clustering.algorithm.GenericClusteringAlgorithm;
-import de.jplag.clustering.algorithm.SpectralClustering;
 
 /**
  * Runs the clustering according to an options object.
@@ -21,12 +19,7 @@ public class ClusteringFactory {
             return Collections.emptyList();
 
         // init algorithm
-        GenericClusteringAlgorithm clusteringAlgorithm = null;
-        if (options.getAlgorithm() == ClusteringAlgorithm.AGGLOMERATIVE) {
-            clusteringAlgorithm = new AgglomerativeClustering(options);
-        } else if (options.getAlgorithm() == ClusteringAlgorithm.SPECTRAL) {
-            clusteringAlgorithm = new SpectralClustering(options);
-        }
+        GenericClusteringAlgorithm clusteringAlgorithm = options.getAlgorithm().create(options);
 
         // init preprocessor
         Optional<ClusteringPreprocessor> preprocessor = options.getPreprocessor().constructPreprocessor(options);

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

@@ -23,7 +23,7 @@ public class ClusteringResult<T> {
     private float communityStrength = 0;
 
     public ClusteringResult(Collection<Cluster<T>> clusters, float communityStrength) {
-        this.clusters = Collections.unmodifiableList(new ArrayList<>(clusters));
+        this.clusters = List.copyOf(clusters);
         this.communityStrength = communityStrength;
         for (Cluster<T> cluster : clusters) {
             cluster.setClusteringResult(this);
@@ -48,7 +48,7 @@ public class ClusteringResult<T> {
 
     /**
      * How much this clustering result is worth during optimization.
-     * @param similarity
+     * @param similarity TODO DF: JAVADOC
      * @return worth
      */
     public float getWorth(BiFunction<T, T, Float> similarity) {
@@ -92,7 +92,7 @@ public class ClusteringResult<T> {
             for (int i = 0; i < clustering.size(); i++) {
                 double outWeightSum = percentagesOfSimilaritySums.getRowVector(i).getL1Norm();
                 double clusterCommunityStrength = percentagesOfSimilaritySums.getEntry(i, i) - outWeightSum * outWeightSum;
-                clusters.add(new Cluster<Integer>(clustering.get(i), (float) clusterCommunityStrength));
+                clusters.add(new Cluster<>(clustering.get(i), (float) clusterCommunityStrength));
                 communityStrength += clusterCommunityStrength;
             }
         }

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

@@ -9,8 +9,8 @@ import java.util.List;
  */
 public class IntegerMapping<T> {
 
-    private HashMap<T, Integer> mapping = new HashMap<>();
-    private List<T> backMapping = new ArrayList<>();
+    private HashMap<T, Integer> mapping;
+    private List<T> backMapping;
     private int size = 0;
 
     public IntegerMapping(int initialCapacity) {

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

@@ -3,14 +3,12 @@ package de.jplag.clustering;
 import java.util.Optional;
 import java.util.function.Function;
 
-import antlr.preprocessor.Preprocessor;
-
 import de.jplag.clustering.preprocessors.CumulativeDistributionFunctionPreprocessor;
 import de.jplag.clustering.preprocessors.PercentileThresholdProcessor;
 import de.jplag.clustering.preprocessors.ThresholdPreprocessor;
 
 /**
- * List of all usable {@link Preprocessor}s.
+ * List of all usable {@link ClusteringPreprocessor}s.
  */
 public enum Preprocessing {
     NONE(options -> null),
@@ -23,7 +21,7 @@ public enum Preprocessing {
 
     private final Function<ClusteringOptions, ClusteringPreprocessor> constructor;
 
-    private Preprocessing(Function<ClusteringOptions, ClusteringPreprocessor> constructor) {
+    Preprocessing(Function<ClusteringOptions, ClusteringPreprocessor> constructor) {
         this.constructor = constructor;
     }
 

+ 8 - 7
jplag/src/main/java/de/jplag/clustering/algorithm/AgglomerativeClustering.java

@@ -17,7 +17,7 @@ import de.jplag.clustering.ClusteringOptions;
  */
 public class AgglomerativeClustering implements GenericClusteringAlgorithm {
 
-    private ClusteringOptions options;
+    private final ClusteringOptions options;
 
     public AgglomerativeClustering(ClusteringOptions options) {
         this.options = options;
@@ -60,6 +60,7 @@ public class AgglomerativeClustering implements GenericClusteringAlgorithm {
 
         while (clusters.size() > 1) {
             ClusterConnection nearest = similarities.poll();
+            // TODO Check that nearest is never null
             if (!(clusters.contains(nearest.left) && clusters.contains(nearest.right))) {
                 // One cluster already part of another cluster
                 continue;
@@ -82,11 +83,11 @@ public class AgglomerativeClustering implements GenericClusteringAlgorithm {
         return clusters.stream().map(Cluster::getSubmissions).collect(Collectors.toList());
     }
 
-    private class ClusterConnection implements Comparable<ClusterConnection> {
+    private static class ClusterConnection implements Comparable<ClusterConnection> {
 
-        private Cluster left;
-        private Cluster right;
-        private float similarity;
+        private final Cluster left;
+        private final Cluster right;
+        private final float similarity;
 
         public ClusterConnection(Cluster left, Cluster right, float similarity) {
             this.left = left;
@@ -105,8 +106,8 @@ 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 class Cluster {
-        private List<Integer> submissions;
+    private static class Cluster {
+        private final List<Integer> submissions;
 
         public Cluster(List<Integer> submissions) {
             this.submissions = submissions;

+ 10 - 11
jplag/src/main/java/de/jplag/clustering/algorithm/BFGS.java

@@ -70,7 +70,7 @@ import org.apache.commons.math3.util.Pair;
 public class BFGS {
     // private static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(BFGS.class);
     /** A number close to zero, between machine epsilon and its square root. */
-    private static final double EPSILON = Double.valueOf(System.getProperty("smile.bfgs.epsilon", "1E-10"));
+    private static final double EPSILON = Double.parseDouble(System.getProperty("smile.bfgs.epsilon", "1E-10"));
     /** The convergence criterion on function value. */
     private static final double TOLF = 4 * EPSILON;
     /** The scaled maximum step length allowed in line searches. */
@@ -670,14 +670,13 @@ public class BFGS {
      */
     private static class Matrix {
 
-        private RealMatrix d;
-        private int m;
-        private int n;
+        private final RealMatrix d;
+        private final int m;
+        private final int n;
 
         /**
-         * Constructor of zero matrix.
-         * @param m the number of rows.
-         * @param n the number of columns.
+         * Constructor of real matrix.
+         * @param delegate the matrix that shall be wrapped.
          */
         public Matrix(RealMatrix delegate) {
             d = delegate;
@@ -724,10 +723,10 @@ public class BFGS {
          * @return {@code A' * A}.
          */
         public Matrix ata() {
-            Matrix C = new Matrix(d.transpose().multiply(d));
-            // C.mm(TRANSPOSE, 1this, NO_TRANSPOSE, this);
-            // C.uplo(LOWER);
-            return C;
+            Matrix c = new Matrix(d.transpose().multiply(d));
+            // c.mm(TRANSPOSE, 1this, NO_TRANSPOSE, this);
+            // c.uplo(LOWER);
+            return c;
         }
 
         /**

+ 10 - 8
jplag/src/main/java/de/jplag/clustering/algorithm/BayesianOptimization.java

@@ -24,12 +24,12 @@ public class BayesianOptimization {
     private static final int MAX_NON_ZERO_ACQ_FN_EVALS_PER_ITERATION = 50;
     private static final int MAXIMUM_ACQ_FN_EVALS_PER_ITERATION = 1000;
 
-    private RealVector minima;
-    private RealVector maxima;
-    private int maxEvaluations;
-    private int initialPoints;
-    private double noise;
-    private RealVector lengthScale;
+    private final RealVector minima;
+    private final RealVector maxima;
+    private final int maxEvaluations;
+    private final int initialPoints;
+    private final double noise;
+    private final RealVector lengthScale;
     private boolean debug = false;
 
     /**
@@ -55,6 +55,7 @@ public class BayesianOptimization {
         this.lengthScale = lengthScale;
     }
 
+    // TODO This method is not used
     public void setDebugLogging(boolean debug) {
         this.debug = debug;
     }
@@ -157,6 +158,7 @@ public class BayesianOptimization {
                 if (debug) {
                     System.out.println(gpr.toString(minima, maxima, 100, 25, 0));
                 }
+                // TODO Check that best is not null here
                 coordinates = maxAcq(gpr, best.score, poiSampler, zeroAcquisitionsCounter);
                 testedCoordinates.add(coordinates);
             }
@@ -175,8 +177,8 @@ public class BayesianOptimization {
 
     public static final class OptimizationResult<T> {
 
-        private double score;
-        private T value;
+        private final double score;
+        private final T value;
         private RealVector params;
 
         public OptimizationResult(double score, T value) {

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

@@ -21,12 +21,12 @@ import org.apache.commons.math3.stat.StatUtils;
  */
 public class GaussianProcess {
 
-    private List<RealVector> listOfCoordinates;
-    private RealVector weight;
-    private double mean;
-    private double standardDeviation;
-    private CholeskyDecomposition cholesky;
-    private RealVector lengthScale;
+    private final List<RealVector> listOfCoordinates;
+    private final RealVector weight;
+    private final double mean;
+    private final double standardDeviation;
+    private final CholeskyDecomposition cholesky;
+    private final RealVector lengthScale;
 
     private GaussianProcess(List<RealVector> listOfCoordinates, RealVector weight, double mean, double standardDeviation,
             CholeskyDecomposition cholesky, RealVector lengthScale) {
@@ -65,7 +65,7 @@ public class GaussianProcess {
 
     /**
      * Fit Gaussian Process using a matern kernel.
-     * @param observedCoordinates
+     * @param observedCoordinates TODO DOCUMENTATION MISSING
      * @param observations expected to have zero mean, unit variance if normalize is false
      * @param noise variance of noise in Y
      * @param normalize if Y should be normalized

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

@@ -1,19 +1,19 @@
 package de.jplag.clustering.algorithm;
 
 import java.util.List;
-import java.util.function.DoubleBinaryOperator;
+import java.util.function.BiFunction;
 
 import org.apache.commons.math3.linear.RealMatrix;
 
 public enum InterClusterSimilarity {
     MIN(Float.MAX_VALUE, Math::min),
     MAX(Float.MIN_VALUE, Math::max),
-    AVERAGE(0, (a, b) -> a + b);
+    AVERAGE(0, Float::sum);
 
     private final float neutralElement;
-    private final DoubleBinaryOperator accumulator;
+    private final BiFunction<Float, Float, Float> accumulator;
 
-    private InterClusterSimilarity(float neutralElement, DoubleBinaryOperator accumulator) {
+    InterClusterSimilarity(float neutralElement, BiFunction<Float, Float, Float> accumulator) {
         this.neutralElement = neutralElement;
         this.accumulator = accumulator;
     }
@@ -28,11 +28,10 @@ public enum InterClusterSimilarity {
     public float clusterSimilarity(List<Integer> leftCluster, List<Integer> rightCluster, RealMatrix similarityMatrix) {
         float similarity = this.neutralElement;
 
-        for (int leftIndex = 0; leftIndex < leftCluster.size(); leftIndex++) {
-            int leftSubmission = leftCluster.get(leftIndex);
-            for (int rightIndex = 0; rightIndex < rightCluster.size(); rightIndex++) {
-                float submissionSimilarity = (float) similarityMatrix.getEntry(leftSubmission, rightCluster.get(rightIndex));
-                similarity = (float) this.accumulator.applyAsDouble(similarity, submissionSimilarity);
+        for (int leftSubmission : leftCluster) {
+            for (int rightSubmission : rightCluster) {
+                float submissionSimilarity = (float) similarityMatrix.getEntry(leftSubmission, rightSubmission);
+                similarity = this.accumulator.apply(similarity, submissionSimilarity);
             }
         }
 

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

@@ -109,7 +109,7 @@ public class SpectralClustering implements GenericClusteringAlgorithm {
                 int eigenVectorId = eigenValueIds.get(column);
                 RealVector eigenVector = ed.getEigenvector(eigenVectorId);
                 return eigenVector.getEntry(row);
-            };
+            }
         });
 
         List<ClusterableEigenVector> normRows = IntStream.range(0, dimension).filter(i -> concatenatedEigenVectors.getRowVector(i).getNorm() > 0)
@@ -118,14 +118,13 @@ public class SpectralClustering implements GenericClusteringAlgorithm {
 
         Clusterer<ClusterableEigenVector> clusterer = new KMeansPlusPlusClusterer<>(numberOfClusters, options.getSpectralMaxKMeansIterationPerRun());
         List<? extends Cluster<ClusterableEigenVector>> clusters = clusterer.cluster(normRows);
-        return clusters.stream().map(cluster -> {
-            return cluster.getPoints().stream().map(eigenVector -> eigenVector.id).collect(Collectors.toList());
-        }).collect(Collectors.toList());
+        return clusters.stream().map(cluster -> cluster.getPoints().stream().map(eigenVector -> eigenVector.id).collect(Collectors.toList()))
+                .collect(Collectors.toList());
     }
 
     private static class ClusterableEigenVector implements Clusterable {
-        private int id;
-        private double[] eigenVector;
+        private final int id;
+        private final double[] eigenVector;
 
         public ClusterableEigenVector(int id, RealVector eigenVector) {
             this.id = id;

+ 1 - 1
jplag/src/main/java/de/jplag/clustering/preprocessors/CumulativeDistributionFunctionPreprocessor.java

@@ -15,7 +15,7 @@ import de.jplag.clustering.PreprocessorHelper;
  */
 public class CumulativeDistributionFunctionPreprocessor implements ClusteringPreprocessor {
 
-    private PreprocessorHelper helper = new PreprocessorHelper();
+    private final PreprocessorHelper helper = new PreprocessorHelper();
 
     @Override
     public double[][] preprocessSimilarities(double[][] similarityMatrix) {

+ 1 - 1
jplag/src/main/java/de/jplag/clustering/preprocessors/PercentileThresholdProcessor.java

@@ -12,7 +12,7 @@ import de.jplag.clustering.ClusteringPreprocessor;
  */
 public class PercentileThresholdProcessor implements ClusteringPreprocessor {
 
-    private float percentile;
+    private final float percentile;
     private ThresholdPreprocessor thresholdPreprocessor;
 
     public PercentileThresholdProcessor(float percentile) {

+ 2 - 2
jplag/src/main/java/de/jplag/clustering/preprocessors/ThresholdPreprocessor.java

@@ -12,8 +12,8 @@ import de.jplag.clustering.PreprocessorHelper;
  */
 public class ThresholdPreprocessor implements ClusteringPreprocessor {
 
-    private double threshold;
-    private PreprocessorHelper helper = new PreprocessorHelper();
+    private final double threshold;
+    private final PreprocessorHelper helper = new PreprocessorHelper();
 
     public ThresholdPreprocessor(double threshold) {
         this.threshold = threshold;

+ 3 - 0
jplag/src/main/java/de/jplag/exceptions/BasecodeException.java

@@ -1,10 +1,13 @@
 package de.jplag.exceptions;
 
+import java.io.Serial;
+
 /**
  * Exceptions for problems with the basecode submission that lead to an preemptive exit.
  */
 public class BasecodeException extends ExitException {
 
+    @Serial
     private static final long serialVersionUID = -3911476090624995247L; // generated
 
     public BasecodeException(String message) {

+ 3 - 0
jplag/src/main/java/de/jplag/exceptions/ExitException.java

@@ -1,10 +1,13 @@
 package de.jplag.exceptions;
 
+import java.io.Serial;
+
 /**
  * Exceptions for problems during the execution of JPlag that lead to an preemptive exit.
  */
 public abstract class ExitException extends Exception {
 
+    @Serial
     private static final long serialVersionUID = 7091658804288889231L; // generated
 
     public ExitException(String message) {

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

@@ -1,10 +1,13 @@
 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) {

+ 3 - 0
jplag/src/main/java/de/jplag/exceptions/RootDirectoryException.java

@@ -1,10 +1,13 @@
 package de.jplag.exceptions;
 
+import java.io.Serial;
+
 /**
  * Exceptions for problems with the root directory that lead to an preemptive exit.
  */
 public class RootDirectoryException extends ExitException {
 
+    @Serial
     private static final long serialVersionUID = 3134534079325843267L; // generated
 
     public RootDirectoryException(String message) {

+ 3 - 0
jplag/src/main/java/de/jplag/exceptions/SubmissionException.java

@@ -1,10 +1,13 @@
 package de.jplag.exceptions;
 
+import java.io.Serial;
+
 /**
  * Exceptions for problems with the regular submissions that lead to an preemptive exit.
  */
 public class SubmissionException extends ExitException {
 
+    @Serial
     private static final long serialVersionUID = 794916053362767596L; // generated
 
     public SubmissionException(String message) {

+ 30 - 20
jplag/src/main/java/de/jplag/options/JPlagOptions.java

@@ -46,7 +46,7 @@ public class JPlagOptions {
      * Percentage value (must be between 0 and 100). Comparisons (of submissions pairs) with a similarity below this
      * threshold will be ignored. The default value of 0 allows all matches to be stored. This affects which comparisons are
      * stored and thus make it into the result object.
-     * @see JPlagOptions.similarityMetric
+     * @see JPlagOptions#similarityMetric
      */
     private float similarityThreshold = DEFAULT_SIMILARITY_THRESHOLD;
 
@@ -59,7 +59,7 @@ public class JPlagOptions {
     /**
      * The similarity metric determines how the minimum similarity threshold required for a comparison (of two submissions)
      * is calculated. This affects which comparisons are stored and thus make it into the result object.
-     * @see JPlagOptions.similarityThreshold
+     * @see JPlagOptions#similarityThreshold
      */
     private SimilarityMetric similarityMetric = SimilarityMetric.AVG;
 
@@ -80,9 +80,14 @@ public class JPlagOptions {
     private Set<String> excludedFiles = Collections.emptySet();
 
     /**
-     * Directory that contains all submissions.
+     * Directories with new submissions. These must be checked for plagiarism.
      */
-    private List<String> rootDirectoryNames;
+    private List<String> submissionDirectories;
+
+    /**
+     * Directories with old submissions to check against.
+     */
+    private List<String> oldSubmissionDirectories;
 
     /**
      * Path name of the directory containing the base code.
@@ -98,7 +103,7 @@ public class JPlagOptions {
      * of the compatibility fallback listed above.
      * </p>
      */
-    private Optional<String> baseCodeSubmissionName = Optional.empty();
+    private String baseCodeSubmissionName = null;
 
     /**
      * Example: If the subdirectoryName is 'src', only the code inside submissionDir/src of each submission will be used for
@@ -124,13 +129,14 @@ public class JPlagOptions {
     /**
      * Constructor with required attributes.
      */
-    public JPlagOptions(List<String> rootDirectoryNames, LanguageOption languageOption) {
-        this.rootDirectoryNames = rootDirectoryNames;
+    public JPlagOptions(List<String> submissionDirectories, List<String> oldSubmissionDirectories, LanguageOption languageOption) {
+        this.submissionDirectories = submissionDirectories;
+        this.oldSubmissionDirectories = oldSubmissionDirectories;
         this.languageOption = languageOption;
     }
 
     public Optional<String> getBaseCodeSubmissionName() {
-        return baseCodeSubmissionName;
+        return Optional.ofNullable(baseCodeSubmissionName);
     }
 
     public ComparisonMode getComparisonMode() {
@@ -165,8 +171,12 @@ public class JPlagOptions {
         return minimumTokenMatch;
     }
 
-    public List<String> getRootDirectoryNames() {
-        return rootDirectoryNames;
+    public List<String> getSubmissionDirectories() {
+        return submissionDirectories;
+    }
+
+    public List<String> getOldSubmissionDirectories() {
+        return oldSubmissionDirectories;
     }
 
     public SimilarityMetric getSimilarityMetric() {
@@ -186,7 +196,7 @@ public class JPlagOptions {
     }
 
     public boolean hasBaseCode() {
-        return this.baseCodeSubmissionName.isPresent();
+        return this.baseCodeSubmissionName != null;
     }
 
     public boolean isDebugParser() {
@@ -199,9 +209,9 @@ public class JPlagOptions {
 
     public void setBaseCodeSubmissionName(String baseCodeSubmissionName) {
         if (baseCodeSubmissionName == null || baseCodeSubmissionName.isEmpty()) {
-            this.baseCodeSubmissionName = Optional.empty();
+            this.baseCodeSubmissionName = null;
         } else {
-            this.baseCodeSubmissionName = Optional.of(baseCodeSubmissionName);
+            this.baseCodeSubmissionName = baseCodeSubmissionName;
         }
     }
 
@@ -249,11 +259,7 @@ public class JPlagOptions {
     }
 
     public void setMaximumNumberOfComparisons(int maximumNumberOfComparisons) {
-        if (maximumNumberOfComparisons < -1) {
-            this.maximumNumberOfComparisons = -1;
-        } else {
-            this.maximumNumberOfComparisons = maximumNumberOfComparisons;
-        }
+        this.maximumNumberOfComparisons = Math.max(maximumNumberOfComparisons, -1);
     }
 
     public void setMinimumTokenMatch(Integer minimumTokenMatch) {
@@ -264,8 +270,12 @@ public class JPlagOptions {
         }
     }
 
-    public void setRootDirectoryNames(List<String> rootDirectoryNames) {
-        this.rootDirectoryNames = rootDirectoryNames;
+    public void setSubmissionDirectories(List<String> submissionDirectories) {
+        this.submissionDirectories = submissionDirectories;
+    }
+
+    public void setOldSubmissionDirectories(List<String> oldSubmissionDirectories) {
+        this.oldSubmissionDirectories = oldSubmissionDirectories;
     }
 
     public void setSimilarityMetric(SimilarityMetric similarityMetric) {

+ 4 - 4
jplag/src/main/java/de/jplag/options/SimilarityMetric.java

@@ -5,14 +5,14 @@ import java.util.function.Function;
 import de.jplag.JPlagComparison;
 
 public enum SimilarityMetric implements Function<JPlagComparison, Float> {
-    AVG(it -> it.similarity()),
-    MIN(it -> it.minimalSimilarity()),
-    MAX(it -> it.maximalSimilarity()),
+    AVG(JPlagComparison::similarity),
+    MIN(JPlagComparison::minimalSimilarity),
+    MAX(JPlagComparison::maximalSimilarity),
     INTERSECTION(it -> (float) it.getNumberOfMatchedTokens());
 
     private final Function<JPlagComparison, Float> similarityFunction;
 
-    private SimilarityMetric(Function<JPlagComparison, Float> determinePercentage) {
+    SimilarityMetric(Function<JPlagComparison, Float> determinePercentage) {
         this.similarityFunction = determinePercentage;
     }
 

+ 4 - 8
jplag/src/main/java/de/jplag/options/Verbosity.java

@@ -5,13 +5,9 @@ public enum Verbosity {
     LONG;
 
     public static Verbosity fromOption(String optionValue) {
-        switch (optionValue) {
-            case "quiet":
-                return QUIET;
-            case "long":
-                return LONG;
-            default:
-                return QUIET;
-        }
+        for (Verbosity verbosity : Verbosity.values())
+            if (verbosity.name().equalsIgnoreCase(optionValue))
+                return verbosity;
+        return QUIET;
     }
 }

+ 0 - 46
jplag/src/main/java/de/jplag/reporting/BufferedCounter.java

@@ -1,46 +0,0 @@
-package de.jplag.reporting;
-
-import java.io.BufferedWriter;
-import java.io.IOException;
-import java.io.Writer;
-
-/**
- * A buffered writer that counts the number of printed characters.
- */
-public class BufferedCounter extends BufferedWriter {
-
-    private int count;
-
-    public BufferedCounter(Writer out) {
-        super(out);
-        count = 0;
-    }
-
-    @Override
-    public void write(int c) throws IOException {
-        super.write(c);
-        count++;
-    }
-
-    @Override
-    public void write(char[] cbuf, int off, int len) throws IOException {
-        super.write(cbuf, off, len);
-        count += len;
-    }
-
-    @Override
-    public void write(String s, int off, int len) throws IOException {
-        super.write(s, off, len);
-        count += len;
-    }
-
-    @Override
-    public void newLine() throws IOException {
-        super.newLine();
-        count++;
-    }
-
-    public int bytesWritten() {
-        return count;
-    }
-}

+ 0 - 75
jplag/src/main/java/de/jplag/reporting/Color.java

@@ -1,75 +0,0 @@
-package de.jplag.reporting;
-
-public enum Color {
-    BLUE("#0000ff"),
-    BROWN("#f63526"),
-    BROWN_2("#980517"),
-    CADET_BLUE("#53858b"),
-    CHARTREUSE("#6cc417"),
-    CORNFLOWER_BLUE("#151b8d"),
-    CORNSILK("#8c8774"),
-    CYAN("#38a4a5"),
-    DARK_GOLDENROD("#c58917"),
-    DARK_OLIVE_GREEN("#83a33a"),
-    DARK_ORANGE("#ad5910"),
-    DARK_ORCHID("#b041ff"),
-    DARK_ORCHID_2("#571b7e"),
-    DARK_TURQUOISE("#3b9c9c"),
-    DARK_VIOLET("#842dce"),
-    DEEP_PINK("#f52887"),
-    DEEP_SKY_BLUE("#2981b2"),
-    DEEP_SKY_BLUE_2("#3090c7"),
-    FIRE_BRICK("#800517"),
-    FIRE_BRICK_2("#f62817"),
-    FOREST_GREEN("#4e9258"),
-    GOLD("#947010"),
-    GREEN("#4cc417"),
-    HOT_PINK("#f660ab"),
-    KHAKI("#79764d"),
-    LAWN_GREEN("#5eac10"),
-    LIGHT_BLUE("#68818b"),
-    LIGHT_CORAL("#e77471"),
-    LIGHT_CYAN("#717d7d"),
-    LIGHT_PINK("#af7a82"),
-    LIGHT_SALMON("#ae694a"),
-    LIGHT_SEA_GREEN("#3ea99f"),
-    LIGHT_SKY_BLUE("#5b8daf"),
-    LIGHT_SLATE_BLUE("#736aff"),
-    LIGHT_YELLOW("#827d6b"),
-    LIME_GREEN("#41a317"),
-    MAGENTA("#ff00ff"),
-    MAROON("#810541"),
-    MEDIUM_AQUAMARINE("#348781"),
-    MEDIUM_BLUE("#152dc6"),
-    MEDIUM_FOREST_GREEN("#347235"),
-    ORANGE("#f87a17"),
-    ORANGE_2("#c57717"),
-    ORANGE_RED("#c22817"),
-    ORCHID("#a057a5"),
-    PALE_GREEN("#549748"),
-    PALE_TURQUOISE("#668b8b"),
-    PALE_VIOLET_RED("#d16587"),
-    PERU("#c57726"),
-    PURPLE("#8e35ef"),
-    RED("#ff0000"),
-    ROSY_BROWN("#b38481"),
-    ROYAL_BLUE("#2b60de"),
-    SALMON("#ad5a3d"),
-    SEA_GREEN("#4e8975"),
-    STEEL_BLUE("#4863a0"),
-    YELLOW_GREEN("#52d017");
-
-    private final String hexadecimalValue;
-
-    private Color(String hexadecimalValue) {
-        this.hexadecimalValue = hexadecimalValue;
-    }
-
-    public String getHexadecimalValue() {
-        return hexadecimalValue;
-    }
-
-    public static String getHexadecimalValue(int index) {
-        return values()[index % values().length].getHexadecimalValue();
-    }
-}

+ 0 - 31
jplag/src/main/java/de/jplag/reporting/HTMLFile.java

@@ -1,31 +0,0 @@
-package de.jplag.reporting;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-
-import de.jplag.options.JPlagOptions;
-
-public class HTMLFile extends PrintWriter {
-
-    private BufferedCounter counter;
-
-    /**
-     * Static factory method to instantiate an HTMLFile objects.
-     */
-    public static HTMLFile fromFile(File file) throws IOException {
-        BufferedCounter counter = new BufferedCounter(new OutputStreamWriter(new FileOutputStream(file), JPlagOptions.CHARSET));
-        return new HTMLFile(counter);
-    }
-
-    private HTMLFile(BufferedCounter counter) {
-        super(counter);
-        this.counter = counter;
-    }
-
-    public int bytesWritten() {
-        return counter.bytesWritten();
-    }
-}

+ 4 - 4
jplag/src/main/java/de/jplag/reporting2/JsonReport.java → jplag/src/main/java/de/jplag/reporting/JsonReport.java

@@ -1,12 +1,12 @@
-package de.jplag.reporting2;
+package de.jplag.reporting;
 
 import java.io.File;
 import java.util.List;
 
 import de.jplag.JPlagResult;
-import de.jplag.reporting2.jsonfactory.JsonFactory;
-import de.jplag.reporting2.reportobject.ReportObjectFactory;
-import de.jplag.reporting2.reportobject.model.JPlagReport;
+import de.jplag.reporting.jsonfactory.JsonFactory;
+import de.jplag.reporting.reportobject.ReportObjectFactory;
+import de.jplag.reporting.reportobject.model.JPlagReport;
 
 // ReportImplementation -> JsonReport
 

+ 0 - 24
jplag/src/main/java/de/jplag/reporting/MarkupText.java

@@ -1,24 +0,0 @@
-package de.jplag.reporting;
-
-/**
- * This class represents one markup tag that will be included in the text. It is necessary to sort the objects before
- * they are included into the text, so that the original position can be found.
- */
-public class MarkupText {
-    public int fileIndex, lineIndex, column;
-    public String text;
-    public boolean frontMarkup = false;
-
-    public MarkupText(int fileIndex, int lineIndex, int column, String text, boolean frontMarkup) {
-        this.fileIndex = fileIndex;
-        this.lineIndex = lineIndex;
-        this.column = column;
-        this.text = text;
-        this.frontMarkup = frontMarkup;
-    }
-
-    @Override
-    public String toString() {
-        return "MarkUp - file: " + fileIndex + " line: " + lineIndex + " column: " + column + " text: " + text;
-    }
-}

+ 0 - 26
jplag/src/main/java/de/jplag/reporting/Messages.java

@@ -1,26 +0,0 @@
-package de.jplag.reporting;
-
-import java.util.Locale;
-import java.util.MissingResourceException;
-import java.util.ResourceBundle;
-
-public class Messages {
-
-    private final ResourceBundle resourceBundle;
-
-    /**
-     * @param countryTag may be "de", "en", "fr", "es", "pt" or "ptbr"
-     */
-    public Messages(String countryTag) {
-        String bundleName = "de.jplag.messages";
-        resourceBundle = ResourceBundle.getBundle(bundleName, new Locale(countryTag));
-    }
-
-    public String getString(String key) {
-        try {
-            return resourceBundle.getString(key);
-        } catch (MissingResourceException e) {
-            return '!' + key + '!';
-        }
-    }
-}

+ 14 - 769
jplag/src/main/java/de/jplag/reporting/Report.java

@@ -1,784 +1,29 @@
 package de.jplag.reporting;
 
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.net.URL;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
 
-import de.jplag.JPlagComparison;
 import de.jplag.JPlagResult;
-import de.jplag.Match;
-import de.jplag.Submission;
-import de.jplag.Token;
-import de.jplag.TokenList;
-import de.jplag.exceptions.ExitException;
-import de.jplag.exceptions.ReportGenerationException;
-import de.jplag.options.JPlagOptions;
+
+// ReportStrategy -> Report
 
 /**
- * This class writes all the HTML pages.
+ * Strategy interface for reporting. A report generator should implement this interface.
  */
-public class Report { // Mostly legacy code with some minor improvements.
-
-    private static final String CSV_FILE = "matches_avg.csv";
-    private static final String[] PICS = {"forward.gif", "back.gif"};
-
-    private final Map<JPlagComparison, Integer> comparisonToIndex = new HashMap<>();
-    private final Messages msg;
-    private final File reportDir;
-    private final JPlagOptions options;
-
-    private JPlagResult result;
-
-    private int currentComparisonIndex = 0;
-    private int matchWritingProgess = 0;
-    private int matchesWritten = 0;
-
-    public Report(File reportDir, JPlagOptions options) throws ReportGenerationException {
-        this.reportDir = reportDir;
-        this.options = options;
-        msg = new Messages("en");
-
-        validateReportDir();
-    }
-
-    public void writeResult(JPlagResult result) throws ReportGenerationException {
-        this.result = result;
-        System.out.println("\nWriting report...");
-        writeIndex();
-        copyStaticFiles();
-        writeMatches(result.getComparisons(options.getMaximumNumberOfComparisons()));
-        System.out.println("Report exported to " + reportDir.getAbsolutePath());
-    }
-
-    /*
-     * Two colors, represented by Rl,Gl,Bl and Rh,Gh,Bh respectively are mixed according to the percentage "percent"
-     */
-    private String color(float percent, int Rl, int Rh, int Gl, int Gh, int Bl, int Bh) {
-        int redValue = (int) (Rl + (Rh - Rl) * percent / 100);
-        int greenValue = (int) (Gl + (Gh - Gl) * percent / 100);
-        int blueValue = (int) (Bl + (Bh - Bl) * percent / 100);
-
-        String red = (redValue < 16 ? "0" : "") + Integer.toHexString(redValue);
-        String green = (greenValue < 16 ? "0" : "") + Integer.toHexString(greenValue);
-        String blue = (blueValue < 16 ? "0" : "") + Integer.toHexString(blueValue);
-
-        return "#" + red + green + blue;
-    }
-
-    /*
-     * This procedure copies all the data from "data/" into the result-directory.
-     */
-    private void copyStaticFiles() {
-        final String[] fileList = {"back.gif", "forward.gif", "help-en.html", "help-sim-en.html", "logo.png", "fields.js"};
-
-        for (int i = fileList.length - 1; i >= 0; i--) {
-            try {
-                URL url = Report.class.getResource("data/" + fileList[i]);
-                DataInputStream dis = new DataInputStream(url.openStream());
-
-                File dest = new File(reportDir, fileList[i]);
-                DataOutputStream dos = new DataOutputStream(new FileOutputStream(dest));
-
-                byte[] buffer = new byte[1024];
-                int count;
-                do {
-                    count = dis.read(buffer);
-                    if (count != -1) {
-                        dos.write(buffer, 0, count);
-                    }
-                } while (count != -1);
-                dis.close();
-                dos.close();
-            } catch (IOException | NullPointerException e) {
-                e.printStackTrace();
-            }
-        }
-    }
-
-    /**
-     * Create a new HTML file.
-     */
-    private HTMLFile createHTMLFile(String name) throws ReportGenerationException {
-        File file = new File(reportDir, name);
-
-        try {
-            return HTMLFile.fromFile(file);
-        } catch (IOException exception) {
-            throw new ReportGenerationException("Error opening file: " + file, exception);
-        }
-    }
-
-    private int getComparisonIndex(JPlagComparison comparison) {
-        Integer index = comparisonToIndex.get(comparison);
-
-        if (index != null) {
-            return index;
-        }
-
-        comparisonToIndex.put(comparison, currentComparisonIndex++);
-
-        return currentComparisonIndex - 1;
-    }
-
-    /**
-     * This method generates an table entry in the list of all comparisons.
-     */
-    private void reportComparison(HTMLFile htmlFile, JPlagComparison comparison, int index) {
-        Match match;
-        TokenList tokensA = comparison.getFirstSubmission().getTokenList();
-        TokenList tokensB = comparison.getSecondSubmission().getTokenList();
-        // sort();
-
-        htmlFile.println("<TABLE BORDER=\"1\" CELLSPACING=\"0\" BGCOLOR=\"#d0d0d0\">");
-        htmlFile.println("<TR><TH><TH>" + comparison.getFirstSubmission().getName() + " (" + comparison.similarityOfFirst() + "%)<TH>"
-                + comparison.getSecondSubmission().getName() + " (" + comparison.similarityOfSecond() + "%)<TH>"
-                + msg.getString("AllMatches.Tokens"));
-
-        for (int i = 0; i < comparison.getMatches().size(); i++) {
-            match = comparison.getMatches().get(i);
-
-            Token startA = tokensA.getToken(match.getStartOfFirst());
-            Token endA = tokensA.getToken(match.getStartOfFirst() + match.getLength() - 1);
-            Token startB = tokensB.getToken(match.getStartOfSecond());
-            Token endB = tokensB.getToken(match.getStartOfSecond() + match.getLength() - 1);
-
-            String col = Color.getHexadecimalValue(i);
-
-            htmlFile.print("<TR><TD BGCOLOR=\"" + col + "\"><FONT COLOR=\"" + col + "\">-</FONT>");
-            htmlFile.print("<TD><A HREF=\"javascript:ZweiFrames('match" + index + "-0.html#" + i + "',2,'match" + index + "-1.html#" + i
-                    + "',3)\" NAME=\"" + i + "\">");
-            htmlFile.print(new String(startA.getFile().getBytes()));
-
-            if (options.getLanguage().usesIndex()) {
-                htmlFile.print("(" + startA.getIndex() + "-" + endA.getIndex() + ")");
-            } else {
-                htmlFile.print("(" + startA.getLine() + "-" + endA.getLine() + ")");
-            }
-
-            htmlFile.print("<TD><A HREF=\"javascript:ZweiFrames('match" + index + "-0.html#" + i + "',2,'match" + index + "-1.html#" + i
-                    + "',3)\" NAME=\"" + i + "\">");
-            htmlFile.print(startB.getFile());
-
-            if (options.getLanguage().usesIndex()) {
-                htmlFile.print("(" + startB.getIndex() + "-" + endB.getIndex());
-            } else {
-                htmlFile.print("(" + startB.getLine() + "-" + endB.getLine());
-            }
-
-            htmlFile.println(
-                    ")</A><TD ALIGN=center>" + "<FONT COLOR=\"" + comparison.color(match.getLength()) + "\">" + match.getLength() + "</FONT>");
-        }
-
-        if (options.hasBaseCode()) {
-            htmlFile.print(
-                    "<TR><TD BGCOLOR=\"#C0C0C0\"><TD>" + msg.getString("AllMatches.Basecode") + " " + comparison.basecodeSimilarityOfFirst() + "%");
-            htmlFile.println("<TD>" + msg.getString("AllMatches.Basecode") + " " + comparison.basecodeSimilarityOfSecond() + "%<TD>&nbsp;");
-        }
-
-        htmlFile.println("</TABLE>");
-    }
-
-    private synchronized void reportMatchWritingProgress(List<JPlagComparison> comparisons) {
-        matchesWritten++;
-        int currentProgress = 20 * matchesWritten / comparisons.size() * 5; // report every 5% step
-        if (currentProgress > matchWritingProgess) {
-            System.out.println("Writing matches: " + currentProgress + "%");
-            matchWritingProgess = currentProgress;
-        }
-    }
-
-    /**
-     * Make sure the report directory exists, is a directory and has write access.
-     */
-    private void validateReportDir() throws ReportGenerationException {
-        if (!reportDir.exists() && !reportDir.mkdirs()) {
-            throw new ReportGenerationException("Cannot create report directory!");
-        }
-
-        if (!reportDir.isDirectory()) {
-            throw new ReportGenerationException(reportDir + " is not a directory!");
-        }
-
-        if (!reportDir.canWrite()) {
-            throw new ReportGenerationException("Cannot write directory: " + reportDir);
-        }
-    }
-
-    private void writeDistribution(HTMLFile htmlFile) {
-        int barLength = 75;
-        int[] similarityDistribution = result.getSimilarityDistribution();
-
-        int max = 1;
-
-        for (int i = 0; i < 10; i++) {
-            if (similarityDistribution[i] > max) {
-                max = similarityDistribution[i];
-            }
-        }
-
-        htmlFile.println("<H4>" + this.msg.getString("Report.Distribution") + ":</H4>\n<CENTER>");
-        htmlFile.println("<TABLE CELLPADDING=1 CELLSPACING=1>");
-
-        for (int i = 9; i >= 0; i--) {
-            htmlFile.print("<TR BGCOLOR=" + color(i * 10 + 10, 128, 192, 128, 192, 255, 255) + "><TD ALIGN=center>" + (i * 10) + "% - "
-                    + (i * 10 + 10) + "%" + "</TD><TD ALIGN=right>" + similarityDistribution[i] + "</TD><TD>");
-
-            for (int j = (similarityDistribution[i] * barLength / max); j > 0; j--) {
-                htmlFile.print("#");
-            }
-
-            if (similarityDistribution[i] * barLength / max == 0) {
-                if (similarityDistribution[i] == 0) {
-                    htmlFile.print(".");
-                } else {
-                    htmlFile.print("#");
-                }
-            }
-
-            htmlFile.println("</TD></TR>");
-        }
-
-        htmlFile.println("</TABLE></CENTER>\n<P>\n<HR>");
-    }
-
-    private void writeHTMLHeader(HTMLFile file, String title) {
-        file.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
-        file.println("<HTML><HEAD><TITLE>" + title + "</TITLE>");
-        file.println("<META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">");
-        file.println("</HEAD>");
-    }
-
-    /*
-     * i is the number of the match j == 0 if subA is considered, otherwise (j must then be 1) it is subB This procedure
-     * makes use of the column and length information!
-     */
-    private int writeImprovedSubmission(HTMLFile f, int i, JPlagComparison comparison, int j) throws ReportGenerationException {
-        Submission sub = comparison.getSubmission(j == 0);
-        String[] files = comparison.files(j);
-        String[][] text = sub.readFiles(files);
-        TokenList tokens = comparison.getSubmission(j == 0).getTokenList();
-
-        // Markup list:
-        Comparator<MarkupText> comp = (mo1, mo2) -> {
-            int col1 = mo1.column;
-            int col2 = mo2.column;
-            if (col1 > col2) {
-                return -1;
-            } else if (col1 < col2) {
-                return 1;
-            }
-            return (mo1.frontMarkup ? -1 : 1);
-        };
-        TreeMap<MarkupText, Object> markupList = new TreeMap<>(comp);
-
-        for (int x = 0; x < comparison.getMatches().size(); x++) {
-            Match onematch = comparison.getMatches().get(x);
-
-            Token start = tokens.getToken(onematch.getStart(j == 0));
-            Token end = tokens.getToken((onematch.getStart(j == 0)) + onematch.getLength() - 1);
-            for (int fileIndex = 0; fileIndex < files.length; fileIndex++) {
-                if (start.getFile().equals(files[fileIndex]) && text[fileIndex] != null) {
-                    String tmp = "<FONT color=\"" + Color.getHexadecimalValue(x) + "\">" + (j == 1 ? "<div style=\"position:absolute;left:0\">" : "")
-                            + "<A HREF=\"javascript:ZweiFrames('match" + i + "-" + (1 - j) + ".html#" + x + "'," + (3 - j) + ",'match" + i
-                            + "-top.html#" + x + "',1)\"><IMG SRC=\"" + PICS[j] + "\" ALT=\"other\" " + "BORDER=\"0\" ALIGN=\""
-                            + (j == 0 ? "right" : "left") + "\"></A>" + (j == 1 ? "</div>" : "") + "<B>";
-                    // position the icon and the beginning of the colorblock
-                    markupList.put(new MarkupText(fileIndex, start.getLine() - 1, start.getColumn() - 1, tmp, true), null);
-                    // mark the end
-                    markupList.put(new MarkupText(fileIndex, end.getLine() - 1, end.getColumn() + end.getLength() - 1, "</B></FONT>", false), null);
-
-                    // the link location is placed 3 lines before the start of a block
-                    int linkLine = (Math.max(start.getLine() - 4, 0));
-                    markupList.put(new MarkupText(fileIndex, linkLine, 0, "<A NAME=\"" + x + "\"></A>", false), null);
-                }
-            }
-        }
-
-        if (options.hasBaseCode() && comparison.getFirstBaseCodeMatches() != null && comparison.getSecondBaseCodeMatches() != null) {
-            JPlagComparison baseCodeComparison = comparison.getBaseCodeMatches(j == 0);
-
-            for (Match match : baseCodeComparison.getMatches()) {
-                Token start = tokens.getToken(match.getStartOfFirst());
-                Token end = tokens.getToken(match.getStartOfFirst() + match.getLength() - 1);
-
-                for (int fileIndex = 0; fileIndex < files.length; fileIndex++) {
-                    if (start.getFile().equals(files[fileIndex]) && text[fileIndex] != null) {
-                        String tmp = "<font color=\"#C0C0C0\"><EM>";
-                        // beginning of the colorblock
-                        markupList.put(new MarkupText(fileIndex, start.getLine() - 1, start.getColumn() - 1, tmp, false), null);
-                        // mark the end
-                        markupList.put(new MarkupText(fileIndex, end.getLine() - 1, end.getColumn() + end.getLength() - 1, "</EM></font>", true),
-                                null);
-                    }
-                }
-            }
-        }
-
-        // Apply changes:
-        for (MarkupText markup : markupList.keySet()) {
-            // System.out.println(markup);
-            String tmp = text[markup.fileIndex][markup.lineIndex];
-            // is there any &quot;, &amp;, &gt; or &lt; in the String?
-            if (tmp.indexOf('&') >= 0) {
-                List<String> list = new ArrayList<>();
-                // convert the string into a vector
-                int strLength = tmp.length();
-                for (int k = 0; k < strLength; k++) {
-                    if (tmp.charAt(k) != '&') {
-                        list.add(tmp.charAt(k) + "");
-                    } else { // put &quot;, &amp;, &gt; and &lt; into one element
-                        String tmpSub = tmp.substring(k);
-                        if (tmpSub.startsWith("&quot;")) {
-                            list.add("&quot;");
-                            k = k + 5;
-                        } else if (tmpSub.startsWith("&amp;")) {
-                            list.add("&amp;");
-                            k = k + 4;
-                        } else if (tmpSub.startsWith("&lt;")) {
-                            list.add("&lt;");
-                            k = k + 3;
-                        } else if (tmpSub.startsWith("&gt;")) {
-                            list.add("&gt;");
-                            k = k + 3;
-                        } else {
-                            list.add(tmp.charAt(k) + "");
-                        }
-                    }
-                }
-                if (markup.column <= list.size()) {
-                    list.add(markup.column, markup.text);
-                } else {
-                    list.add(markup.text);
-                }
-
-                StringBuilder builder = new StringBuilder();
-                // reconvert the Vector into a String
-                for (String element : list) {
-                    builder.append(element);
-                }
-                text[markup.fileIndex][markup.lineIndex] = builder.toString();
-            } else {
-                text[markup.fileIndex][markup.lineIndex] = tmp.substring(0, (Math.min(tmp.length(), markup.column))) + markup.text
-                        + tmp.substring((Math.min(tmp.length(), markup.column)));
-            }
-        }
-
-        f.println("<div style=\"flex-grow: 1;\">");
-
-        for (int x = 0; x < text.length; x++) {
-            f.println("<h3>");
-            f.println("<center>");
-            f.println("<span>" + sub.getName() + "</span>");
-            f.println("<span> - </span>");
-            f.println("<span>" + files[x] + "</span>");
-            f.println("</center>");
-            f.println("</h3>");
-            f.println("<HR>");
-            if (options.getLanguage().isPreformatted()) {
-                f.println("<PRE>");
-            }
-            for (int y = 0; y < text[x].length; y++) {
-                f.print(text[x][y]);
-                if (!options.getLanguage().isPreformatted()) {
-                    f.println("<BR>");
-                } else {
-                    f.println();
-                }
-            }
-            if (options.getLanguage().isPreformatted()) {
-                f.println("</PRE>");
-            }
-        }
-
-        f.println("</div>");
-
-        return f.bytesWritten();
-    }
+public interface Report {
 
     /**
-     * Write the index.html file.
-     */
-    private void writeIndex() throws ReportGenerationException {
-        HTMLFile htmlFile = createHTMLFile("index.html");
-
-        writeIndexBegin(htmlFile, msg.getString("Report.Search_Results"));
-        writeDistribution(htmlFile);
-
-        writeLinksToComparisons(htmlFile, "<H4>" + msg.getString("Report.MatchesAvg"), CSV_FILE);
-
-        writeMatchesCSV(CSV_FILE);
-
-        writeIndexEnd(htmlFile);
-
-        htmlFile.close();
-    }
-
-    /**
-     * Write the beginning of the index.html file.
-     */
-    private void writeIndexBegin(HTMLFile htmlFile, String title) {
-        writeHTMLHeader(htmlFile, title);
-
-        htmlFile.println("<BODY BGCOLOR=#ffffff LINK=#000088 VLINK=#000000 TEXT=#000000>");
-        htmlFile.println("<TABLE ALIGN=center CELLPADDING=2 CELLSPACING=1>");
-        htmlFile.println("<TR VALIGN=middle ALIGN=center BGCOLOR=#ffffff><TD>" + "<IMG SRC=\"logo.png\" ALT=\"JPlag\" BORDER=0></TD>");
-        htmlFile.println("<TD><H1><BIG>" + title + "</BIG></H1></TD></TR>");
-
-        htmlFile.println("<TR BGCOLOR=#aaaaff VALIGN=top><TD>" + msg.getString("Report.Language") + ":</TD><TD>" + options.getLanguageOption().name()
-                + "</TD></TR>");
-        htmlFile.print("<TR BGCOLOR=#aaaaff VALIGN=top><TD>" + msg.getString("Report.Submissions") + ":</TD><TD>" + result.getNumberOfSubmissions());
-
-        htmlFile.println("</TD></TR>");
-
-        if (options.hasBaseCode()) {
-            htmlFile.print("<TR BGCOLOR=#aaaaff VALIGN=top><TD>" + msg.getString("Report.Basecode_submission") + ":</TD>" + "<TD>"
-                    + options.getBaseCodeSubmissionName().get() + "</TD></TR>");
-        }
-
-        if (options.getMaximumNumberOfComparisons() > 0) {
-            htmlFile.println("<TR BGCOLOR=#aaaaff VALIGN=top><TD>" + msg.getString("Report.Matches_displayed") + ":</TD>" + "<TD>");
-            htmlFile.println(options.getMaximumNumberOfComparisons() + " of " + result.getComparisons().size() + " ("
-                    + msg.getString("Report.Treshold") + ": " + options.getSimilarityThreshold() + "%)<br>");
-
-            htmlFile.println("</TD></TR>");
-        }
-
-        SimpleDateFormat dateFormat = new SimpleDateFormat();
-
-        htmlFile.println(
-                "<TR BGCOLOR=#aaaaff VALIGN=top><TD>" + msg.getString("Report.Date") + ":</TD><TD>" + dateFormat.format(new Date()) + "</TD></TR>");
-        htmlFile.println("<TR BGCOLOR=#aaaaff>" + "<TD><EM>" + msg.getString("Report.Minimum_Match_Length") + "</EM> ("
-                + msg.getString("Report.sensitivity") + "):</TD><TD>" + options.getMinimumTokenMatch() + "</TD></TR>");
-        htmlFile.println("<TR BGCOLOR=#aaaaff VALIGN=top><TD>" + msg.getString("Report.Suffixes") + ":</TD><TD>");
-
-        String[] fileSuffixes = options.getFileSuffixes();
-
-        for (int i = 0; i < fileSuffixes.length; i++) {
-            htmlFile.print(fileSuffixes[i] + (i < fileSuffixes.length - 1 ? ", " : "</TD></TR>\n"));
-        }
-
-        htmlFile.println("</TABLE>\n<HR>");
-    }
-
-    /*
-     * i is the number of the match j == 0 if subA is considered, otherwise it is subB This procedure uses only the
-     * getIndex() method of the token. It is meant to be used with the Character front end
+     * This function returns a list containing the report objects as simple strings. The first element is the string of the
+     * overview object. Each following element is a string of a comparison report object.
+     * @param result The result of a JPlag comparison
+     * @return A list containing report objects to string. First element is Overview. Other elements are comparisons.
      */
-    private void writeIndexedSubmission(HTMLFile f, int i, JPlagComparison comparison, int j) throws ReportGenerationException {
-        boolean useFirst = j == 0;
-        Submission sub = comparison.getSubmission(useFirst);
-        String[] files = comparison.files(j);
-        char[][] text = sub.readFilesChar(files);
-        TokenList tokens = comparison.getSubmission(useFirst).getTokenList();
-
-        // get index array with matches sorted in ascending order.
-        List<Integer> perm = comparison.sort_permutation(useFirst);
-
-        int index = 0; // match index
-        Match onematch = null;
-        Token start = null;
-        Token end = null;
-
-        f.println("<div style=\"flex-grow: 1;\">");
-
-        for (int fileIndex = 0; fileIndex < files.length; fileIndex++) {
-            f.println("<h3>");
-            f.println("<center>");
-            f.println("<span>" + sub.getName() + "</span>");
-            f.println("<span> - </span>");
-            f.println("<span>" + files[fileIndex] + "</span>");
-            f.println("</center>");
-            f.println("</h3>");
-            f.println("<HR>");
-            char[] buffer = text[fileIndex];
-
-            for (int charNr = 0; charNr < buffer.length; charNr++) {
-                if (onematch == null) {
-                    if (index < comparison.getMatches().size()) {
-                        onematch = comparison.getMatches().get(perm.get(index));
-                        start = tokens.getToken(onematch.getStart(j == 0));
-                        end = tokens.getToken((onematch.getStart(j == 0)) + onematch.getLength() - 1);
-                        index++;
-                    } else {
-                        start = end = null;
-                    }
-                }
-                // begin markup
-                if (start != null && start.getIndex() == charNr) {
-                    f.print("<A NAME=\"" + perm.get(index - 1) + "\"></A>");
-                    f.print("<FONT color=\"" + Color.getHexadecimalValue(perm.get(index - 1)) + "\"><B>");
-                    // "<A HREF=\"javascript:ZweiFrames('match"+i+"-"+(1-j)+
-                    // ".html#"+index+"',"+(3-j)+",'match"+i+"-top.html#"+index+
-                    // "',1)\">"+"<IMG SRC=\""+pics[j]+
-                    // "\" ALT=\"other\" BORDER=\"0\" "+"ALIGN="+
-                    // (j==0 ? "right" : "left")+"></A>");
-                }
-                // text
-                if (buffer[charNr] == '<') {
-                    f.print("&lt;");
-                } else if (buffer[charNr] == '>') {
-                    f.print("&gt;");
-                } else if (buffer[charNr] == '\n') {
-                    f.print("<br>\n");
-                } else {
-                    f.print(buffer[charNr]);
-                }
-                // end markup
-                if (end != null && end.getIndex() == charNr) {
-                    f.print("</B></FONT>");
-                    onematch = null; // switch to next match
-                }
-            }
-        }
-
-        f.println("</div>");
-    }
+    List<String> getReportStrings(JPlagResult result);
 
     /**
-     * Write the end of the index.html file.
+     * Creates and saves JPlag report files to the disk.
+     * @param result The result of a JPlag comparison.
+     * @param path Path to the directory where the report should be saved.
+     * @return True if the process is successful, otherwise false.
      */
-    private void writeIndexEnd(HTMLFile htmlFile) {
-        htmlFile.println("<HR>\n<P ALIGN=right><FONT SIZE=\"1\" FACE=\"helvetica\">JPlag</FONT></P>");
-        htmlFile.println("</BODY>\n</HTML>");
-    }
-
-    private void writeLinksToComparisons(HTMLFile htmlFile, String headerStr, String csvFile) {
-        List<JPlagComparison> comparisons = result.getComparisons(options.getMaximumNumberOfComparisons()); // should be already sorted!
-
-        htmlFile.println(headerStr + " (<a href=\"help-sim-" + "en" // Country tag
-                + ".html\"><small><font color=\"#000088\">" + msg.getString("Report.WhatIsThis") + "</font></small></a>):</H4>");
-        htmlFile.println("<p><a href=\"" + csvFile + "\">download csv</a></p>");
-        htmlFile.println("<TABLE CELLPADDING=3 CELLSPACING=2>");
-
-        for (JPlagComparison comparison : comparisons) {
-            String submissionNameA = comparison.getFirstSubmission().getName();
-            String submissionNameB = comparison.getSecondSubmission().getName();
-
-            htmlFile.print("<TR><TD BGCOLOR=" + color(comparison.similarityOfFirst(), 128, 192, 128, 192, 255, 255) + ">" + submissionNameA
-                    + "</TD><TD><nobr>-&gt;</nobr>");
-
-            htmlFile.print("</TD><TD BGCOLOR=" + color(comparison.similarityOfSecond(), 128, 192, 128, 192, 255, 255)
-                    + " ALIGN=center><A HREF=\"match" + getComparisonIndex(comparison) + ".html\">" + submissionNameB + "</A><BR><FONT COLOR=\""
-                    + color(comparison.similarity(), 0, 255, 0, 0, 0, 0) + "\">(" + (((int) (comparison.similarity() * 10)) / (float) 10)
-                    + "%)</FONT>");
-
-            htmlFile.println("</TD></TR>");
-        }
-
-        htmlFile.println("</TABLE><P>\n");
-        htmlFile.println("<!---->");
-    }
-
-    private void writeMatch(JPlagComparison comparison, int i) throws ReportGenerationException {
-        HTMLFile htmlFile = createHTMLFile("match" + i + ".html");
-
-        writeHTMLHeader(htmlFile, TagParser.parse(msg.getString("Report.Matches_for_X1_AND_X2"),
-                new String[] {comparison.getFirstSubmission().getName(), comparison.getSecondSubmission().getName()}));
-
-        htmlFile.println("<body>");
-        htmlFile.println("  <div style=\"align-items: center; display: flex; justify-content: space-around;\">");
-
-        htmlFile.println("    <div>");
-        htmlFile.println("      <h3 align=\"center\">");
-        htmlFile.println(TagParser.parse(msg.getString("Report.Matches_for_X1_AND_X2"),
-                new String[] {comparison.getFirstSubmission().getName(), comparison.getSecondSubmission().getName()}));
-        htmlFile.println("      </h3>");
-        htmlFile.println("      <h1 align=\"center\">");
-        htmlFile.println("        " + comparison.roundedSimilarity() + "%");
-        htmlFile.println("      </h1>");
-        htmlFile.println("      <center>");
-        htmlFile.println("        <a href=\"index.html\" target=\"_top\">");
-        htmlFile.println("          " + msg.getString("Report.INDEX"));
-        htmlFile.println("        </a>");
-        htmlFile.println("        <span>-</span>");
-        htmlFile.println("        <a href=\"help-en.html\" target=\"_top\">");
-        htmlFile.println("          " + msg.getString("Report.HELP"));
-        htmlFile.println("        </a>");
-        htmlFile.println("      </center>");
-        htmlFile.println("    </div>");
-
-        htmlFile.println("    <div>");
-        reportComparison(htmlFile, comparison, i);
-        htmlFile.println("    </div>");
-
-        htmlFile.println("  </div>");
-
-        htmlFile.println("  <hr>");
-
-        htmlFile.println("  <div style=\"display: flex;\">");
-
-        if (options.getLanguage().usesIndex()) {
-            writeIndexedSubmission(htmlFile, i, comparison, 0);
-            writeIndexedSubmission(htmlFile, i, comparison, 1);
-        } else if (options.getLanguage().supportsColumns()) {
-            writeImprovedSubmission(htmlFile, i, comparison, 0);
-            writeImprovedSubmission(htmlFile, i, comparison, 1);
-        } else {
-            writeNormalSubmission(htmlFile, i, comparison, 0);
-            writeNormalSubmission(htmlFile, i, comparison, 1);
-        }
-
-        htmlFile.println("  </div>");
-
-        htmlFile.println("</body>");
-        htmlFile.println("</html>");
-        htmlFile.close();
-    }
-
-    private void writeMatches(List<JPlagComparison> comparisons) {
-        comparisons.parallelStream().forEach(comparison -> {
-            try {
-                writeMatch(comparison, getComparisonIndex(comparison));
-            } catch (ExitException exception) {
-                exception.printStackTrace();
-            }
-            reportMatchWritingProgress(comparisons);
-        });
-    }
-
-    private void writeMatchesCSV(String fileName) {
-        FileWriter writer = null;
-        File csvFile = new File(reportDir, fileName);
-        List<JPlagComparison> comparisons = result.getComparisons(options.getMaximumNumberOfComparisons());
-
-        try {
-            csvFile.createNewFile();
-            writer = new FileWriter(csvFile, JPlagOptions.CHARSET);
-
-            for (JPlagComparison comparison : comparisons) {
-                String submissionNameA = comparison.getFirstSubmission().getName();
-                String submissionNameB = comparison.getSecondSubmission().getName();
-
-                writer.write(getComparisonIndex(comparison) + ";");
-                writer.write(submissionNameA + ";");
-                writer.write(submissionNameB + ";");
-                writer.write((((int) (comparison.similarity() * 10)) / (float) 10) + ";");
-                writer.write("\n");
-            }
-
-            writer.flush();
-        } catch (Exception e) {
-            e.printStackTrace();
-        } finally {
-            try {
-                writer.close();
-            } catch (Exception ignored) {
-            }
-        }
-    }
-
-    /*
-     * i is the number of the match j == 0 if subA is considered, otherwise (j must then be 1) it is subB
-     */
-    private void writeNormalSubmission(HTMLFile f, int i, JPlagComparison comparison, int j) throws ReportGenerationException {
-        Submission sub = comparison.getSubmission(j == 0);
-        String[] files = comparison.files(j);
-
-        String[][] text = sub.readFiles(files);
-
-        TokenList tokens = comparison.getSubmission(j == 0).getTokenList();
-        String hilf;
-        int h;
-        for (int x = 0; x < comparison.getMatches().size(); x++) {
-            Match currentMatch = comparison.getMatches().get(x);
-
-            Token start = tokens.getToken(currentMatch.getStart(j == 0));
-            Token end = tokens.getToken((currentMatch.getStart(j == 0)) + currentMatch.getLength() - 1);
-
-            for (int y = 0; y < files.length; y++) {
-                if (start.getFile().equals(files[y]) && text[y] != null) {
-                    hilf = "<FONT color=\"" + Color.getHexadecimalValue(x) + "\">" + (j == 1 ? "<div style=\"position:absolute;left:0\">" : "")
-                            + "<A HREF=\"javascript:ZweiFrames('match" + i + "-" + (1 - j) + ".html#" + x + "'," + (3 - j) + ",'match" + i
-                            + "-top.html#" + x + "',1)\"><IMG SRC=\"" + PICS[j] + "\" ALT=\"other\" " + "BORDER=\"0\" ALIGN=\""
-                            + (j == 0 ? "right" : "left") + "\"></A>" + (j == 1 ? "</div>" : "") + "<B>";
-                    // position the icon and the beginning of the colorblock
-                    if (text[y][start.getLine() - 1].endsWith("</FONT>")) {
-                        text[y][start.getLine() - 1] += hilf;
-                    } else {
-                        text[y][start.getLine() - 1] = hilf + text[y][start.getLine() - 1];
-                    }
-                    // the link location is placed 3 lines before the start of a block
-                    h = (Math.max(start.getLine() - 4, 0));
-                    text[y][h] = "<A NAME=\"" + x + "\"></A>" + text[y][h];
-                    // mark the end
-                    if (start.getLine() != end.getLine() && // if match is only one line
-                            text[y][end.getLine() - 1].startsWith("<FONT ")) {
-                        text[y][end.getLine() - 1] = "</B></FONT>" + text[y][end.getLine() - 1];
-                    } else {
-                        text[y][end.getLine() - 1] += "</B></FONT>";
-                    }
-                }
-            }
-        }
-
-        if (options.hasBaseCode() && comparison.getFirstBaseCodeMatches() != null && comparison.getSecondBaseCodeMatches() != null) {
-            JPlagComparison baseCodeComparison = comparison.getBaseCodeMatches(j == 0);
-
-            for (Match currentMatch : baseCodeComparison.getMatches()) {
-                Token start = tokens.getToken(currentMatch.getStartOfFirst());
-                Token end = tokens.getToken(currentMatch.getStartOfFirst() + currentMatch.getLength() - 1);
-
-                for (int y = 0; y < files.length; y++) {
-                    if (start.getFile().equals(files[y]) && text[y] != null) {
-                        hilf = ("<font color=\"#C0C0C0\"><EM>");
-                        // position the icon and the beginning of the colorblock
-                        if (text[y][start.getLine() - 1].endsWith("<font color=\"#000000\">")) {
-                            text[y][start.getLine() - 1] += hilf;
-                        } else {
-                            text[y][start.getLine() - 1] = hilf + text[y][start.getLine() - 1];
-                        }
-
-                        // mark the end
-                        if (start.getLine() != end.getLine() && // match is only one line
-                                text[y][end.getLine() - 1].startsWith("<font color=\"#C0C0C0\">")) {
-                            text[y][end.getLine() - 1] = "</EM><font color=\"#000000\">" + text[y][end.getLine() - 1];
-                        } else {
-                            text[y][end.getLine() - 1] += "</EM><font color=\"#000000\">";
-                        }
-                    }
-                }
-            }
-        }
-
-        f.println("<div style=\"flex-grow: 1;\">");
-
-        for (int x = 0; x < text.length; x++) {
-            f.println("<h3>");
-            f.println("<center>");
-            f.println("<span>" + sub.getName() + "</span>");
-            f.println("<span> - </span>");
-            f.println("<span>" + files[x] + "</span>");
-            f.println("</center>");
-            f.println("</h3>");
-            f.println("<HR>");
-            if (options.getLanguage().isPreformatted()) {
-                f.println("<PRE>");
-            }
-            for (int y = 0; y < text[x].length; y++) {
-                f.print(text[x][y]);
-                if (!options.getLanguage().isPreformatted()) {
-                    f.println("<BR>");
-                } else {
-                    f.println();
-                }
-            }
-            if (options.getLanguage().isPreformatted()) {
-                f.println("</PRE>");
-            }
-        }
-
-        f.println("</div>");
-    }
+    boolean saveReport(JPlagResult result, String path);
 }

+ 0 - 35
jplag/src/main/java/de/jplag/reporting/TagParser.java

@@ -1,35 +0,0 @@
-package de.jplag.reporting;
-
-public class TagParser {
-
-    /**
-     * Replaces all "{&lt;index&gt;_description}" tags inside the message string with params[&lt;index&gt;-1].
-     * <p>
-     * Example: parse("blb {1_n/a} d {3_desc} sf {2_bla} d", new String[] {"#", blabla(), null}); returns "blb # d null sf
-     * blab d", if blabla() returns "blab"
-     */
-    public static String parse(String message, String[] params) {
-        String[] tokens = message.split("[{}]", -1);
-        String result = tokens[0];
-
-        for (int i = 1; i < tokens.length; i += 2) { // Go to next tag position
-            try {
-                int ind = tokens[i].indexOf('_');
-                String num = (ind == -1) ? tokens[i] : tokens[i].substring(0, ind);
-                result += params[Integer.parseInt(num) - 1];
-            } catch (Exception ex) {
-                if (ex instanceof NumberFormatException || ex instanceof IndexOutOfBoundsException) {
-                    ex.printStackTrace();
-                    result += "{ILLEGAL PARAMETER INDEX \"" + tokens[i] + "\"}";
-                } else {
-                    throw (RuntimeException) ex;
-                }
-            }
-            if (i + 1 < tokens.length) {
-                result += tokens[i + 1];
-            }
-        }
-
-        return result;
-    }
-}

+ 3 - 3
jplag/src/main/java/de/jplag/reporting2/jsonfactory/JsonFactory.java → jplag/src/main/java/de/jplag/reporting/jsonfactory/JsonFactory.java

@@ -1,4 +1,4 @@
-package de.jplag.reporting2.jsonfactory;
+package de.jplag.reporting.jsonfactory;
 
 import java.io.IOException;
 import java.nio.file.Path;
@@ -8,8 +8,8 @@ import java.util.List;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
-import de.jplag.reporting2.reportobject.model.ComparisonReport;
-import de.jplag.reporting2.reportobject.model.JPlagReport;
+import de.jplag.reporting.reportobject.model.ComparisonReport;
+import de.jplag.reporting.reportobject.model.JPlagReport;
 
 /**
  * Factory class, responsible for creating Json strings and writing them to files.

+ 24 - 6
jplag/src/main/java/de/jplag/reporting2/reportobject/ReportObjectFactory.java → jplag/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java

@@ -1,16 +1,30 @@
-package de.jplag.reporting2.reportobject;
+package de.jplag.reporting.reportobject;
 
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
 import java.text.SimpleDateFormat;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
 import java.util.stream.Collectors;
 
-import de.jplag.*;
-import de.jplag.reporting2.reportobject.model.*;
-import de.jplag.reporting2.reportobject.model.Match;
+import de.jplag.JPlagComparison;
+import de.jplag.JPlagResult;
+import de.jplag.Submission;
+import de.jplag.Token;
+import de.jplag.TokenList;
+import de.jplag.reporting.reportobject.model.Cluster;
+import de.jplag.reporting.reportobject.model.ComparisonReport;
+import de.jplag.reporting.reportobject.model.FilesOfSubmission;
+import de.jplag.reporting.reportobject.model.JPlagReport;
+import de.jplag.reporting.reportobject.model.Match;
+import de.jplag.reporting.reportobject.model.Metric;
+import de.jplag.reporting.reportobject.model.OverviewReport;
+import de.jplag.reporting.reportobject.model.TopComparison;
 
 /**
  * Factory class, responsible for converting a JPlagResult object to Overview and Comparison DTO classes.
@@ -34,7 +48,11 @@ public class ReportObjectFactory {
         List<JPlagComparison> comparisons = result.getComparisons();
         OverviewReport overviewReport = new OverviewReport();
 
-        overviewReport.setSubmissionFolderPath(result.getOptions().getRootDirectoryNames());
+        // TODO: Consider to treat entries that were checked differently from old entries with prior work.
+        List<String> folders = new ArrayList<>();
+        folders.addAll(result.getOptions().getSubmissionDirectories());
+        folders.addAll(result.getOptions().getOldSubmissionDirectories());
+        overviewReport.setSubmissionFolderPath(folders);
 
         String baseCodePath = result.getOptions().hasBaseCode() ? result.getOptions().getBaseCodeSubmissionName().orElse("") : "";
         overviewReport.setBaseCodeFolderPath(baseCodePath);

+ 1 - 1
jplag/src/main/java/de/jplag/reporting2/reportobject/model/Cluster.java → jplag/src/main/java/de/jplag/reporting/reportobject/model/Cluster.java

@@ -1,4 +1,4 @@
-package de.jplag.reporting2.reportobject.model;
+package de.jplag.reporting.reportobject.model;
 
 import java.util.List;
 

+ 1 - 1
jplag/src/main/java/de/jplag/reporting2/reportobject/model/ComparisonReport.java → jplag/src/main/java/de/jplag/reporting/reportobject/model/ComparisonReport.java

@@ -1,4 +1,4 @@
-package de.jplag.reporting2.reportobject.model;
+package de.jplag.reporting.reportobject.model;
 
 import java.util.List;
 

+ 1 - 1
jplag/src/main/java/de/jplag/reporting2/reportobject/model/FilesOfSubmission.java → jplag/src/main/java/de/jplag/reporting/reportobject/model/FilesOfSubmission.java

@@ -1,4 +1,4 @@
-package de.jplag.reporting2.reportobject.model;
+package de.jplag.reporting.reportobject.model;
 
 import java.util.List;
 

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

@@ -1,4 +1,4 @@
-package de.jplag.reporting2.reportobject.model;
+package de.jplag.reporting.reportobject.model;
 
 import java.util.List;
 

+ 1 - 1
jplag/src/main/java/de/jplag/reporting2/reportobject/model/Match.java → jplag/src/main/java/de/jplag/reporting/reportobject/model/Match.java

@@ -1,4 +1,4 @@
-package de.jplag.reporting2.reportobject.model;
+package de.jplag.reporting.reportobject.model;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 

+ 1 - 1
jplag/src/main/java/de/jplag/reporting2/reportobject/model/Metric.java → jplag/src/main/java/de/jplag/reporting/reportobject/model/Metric.java

@@ -1,4 +1,4 @@
-package de.jplag.reporting2.reportobject.model;
+package de.jplag.reporting.reportobject.model;
 
 import java.util.List;
 

+ 1 - 1
jplag/src/main/java/de/jplag/reporting2/reportobject/model/OverviewReport.java → jplag/src/main/java/de/jplag/reporting/reportobject/model/OverviewReport.java

@@ -1,4 +1,4 @@
-package de.jplag.reporting2.reportobject.model;
+package de.jplag.reporting.reportobject.model;
 
 import java.util.List;
 import java.util.Set;

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

@@ -1,4 +1,4 @@
-package de.jplag.reporting2.reportobject.model;
+package de.jplag.reporting.reportobject.model;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 

+ 0 - 29
jplag/src/main/java/de/jplag/reporting2/Report.java

@@ -1,29 +0,0 @@
-package de.jplag.reporting2;
-
-import java.util.List;
-
-import de.jplag.JPlagResult;
-
-// ReportStrategy -> Report
-
-/**
- * Strategy interface for reporting. A report generator should implement this interface.
- */
-public interface Report {
-
-    /**
-     * This function returns a list containing the report objects as simple strings. The first element is the string of the
-     * overview object. Each following element is a string of a comparison report object.
-     * @param result The result of a JPlag comparison
-     * @return A list containing report objects to string. First element is Overview. Other elements are comparisons.
-     */
-    List<String> getReportStrings(JPlagResult result);
-
-    /**
-     * Creates and saves JPlag report files to the disk.
-     * @param result The result of a JPlag comparison.
-     * @param path Path to the directory where the report should be saved.
-     * @return True if the process is successful, otherwise false.
-     */
-    boolean saveReport(JPlagResult result, String path);
-}

+ 29 - 1
jplag/src/main/java/de/jplag/strategy/AbstractComparisonStrategy.java

@@ -1,5 +1,7 @@
 package de.jplag.strategy;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Optional;
 
 import de.jplag.GreedyStringTiling;
@@ -10,7 +12,7 @@ import de.jplag.options.JPlagOptions;
 
 public abstract class AbstractComparisonStrategy implements ComparisonStrategy {
 
-    private GreedyStringTiling greedyStringTiling;
+    private final GreedyStringTiling greedyStringTiling;
 
     protected JPlagOptions options;
 
@@ -47,4 +49,30 @@ public abstract class AbstractComparisonStrategy implements ComparisonStrategy {
         }
         return Optional.empty();
     }
+
+    /**
+     * @return a list of all submission tuples to be processed.
+     */
+    protected static List<SubmissionTuple> buildComparisonTuples(List<Submission> submissions) {
+        List<SubmissionTuple> tuples = new ArrayList<>();
+
+        for (int i = 0; i < (submissions.size() - 1); i++) {
+            Submission first = submissions.get(i);
+            if (first.getTokenList() == null) {
+                continue;
+            }
+
+            for (int j = (i + 1); j < submissions.size(); j++) {
+                Submission second = submissions.get(j);
+                if (second.getTokenList() == null) {
+                    continue;
+                }
+
+                if (first.isNew() || second.isNew()) {
+                    tuples.add(new SubmissionTuple(first, second));
+                }
+            }
+        }
+        return tuples;
+    }
 }

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików