Просмотр исходного кода

Merge branch 'master' into feature/java-17

Timur Sağlam 4 лет назад
Родитель
Сommit
0928fbcad9
48 измененных файлов с 544 добавлено и 2132 удалено
  1. 1 1
      .github/workflows/report-viewer.yml
  2. 20 16
      README.md
  3. 1 1
      jplag.frontend.cpp/pom.xml
  4. 1 1
      jplag.frontend.cpp/src/main/java/de/jplag/cpp/Language.java
  5. 4 5
      jplag.frontend.cpp/src/main/java/de/jplag/cpp/Scanner.java
  6. 79 84
      jplag.frontend.cpp/src/main/javacc/CPP.jj
  7. 39 617
      jplag.frontend.python-3/src/main/java/de/jplag/python3/JplagPython3Listener.java
  8. 0 1
      jplag.frontend.python-3/src/main/java/de/jplag/python3/Parser.java
  9. 1 1
      jplag.frontend.scheme/src/main/java/de/jplag/scheme/Language.java
  10. 1 1
      jplag/pom.xml
  11. 51 5
      jplag/src/main/java/de/jplag/CLI.java
  12. 9 3
      jplag/src/main/java/de/jplag/CommandLineArgument.java
  13. 1 1
      jplag/src/main/java/de/jplag/JPlag.java
  14. 1 77
      jplag/src/main/java/de/jplag/JPlagComparison.java
  15. 0 9
      jplag/src/main/java/de/jplag/Match.java
  16. 2 1
      jplag/src/main/java/de/jplag/NumberOfArgumentValues.java
  17. 78 149
      jplag/src/main/java/de/jplag/Submission.java
  18. 53 14
      jplag/src/main/java/de/jplag/SubmissionSetBuilder.java
  19. 22 8
      jplag/src/main/java/de/jplag/options/JPlagOptions.java
  20. 0 46
      jplag/src/main/java/de/jplag/reporting/BufferedCounter.java
  21. 0 75
      jplag/src/main/java/de/jplag/reporting/Color.java
  22. 0 31
      jplag/src/main/java/de/jplag/reporting/HTMLFile.java
  23. 4 4
      jplag/src/main/java/de/jplag/reporting/JsonReport.java
  24. 0 24
      jplag/src/main/java/de/jplag/reporting/MarkupText.java
  25. 0 26
      jplag/src/main/java/de/jplag/reporting/Messages.java
  26. 14 769
      jplag/src/main/java/de/jplag/reporting/Report.java
  27. 0 35
      jplag/src/main/java/de/jplag/reporting/TagParser.java
  28. 3 3
      jplag/src/main/java/de/jplag/reporting/jsonfactory/JsonFactory.java
  29. 24 6
      jplag/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java
  30. 1 1
      jplag/src/main/java/de/jplag/reporting/reportobject/model/Cluster.java
  31. 1 1
      jplag/src/main/java/de/jplag/reporting/reportobject/model/ComparisonReport.java
  32. 1 1
      jplag/src/main/java/de/jplag/reporting/reportobject/model/FilesOfSubmission.java
  33. 1 1
      jplag/src/main/java/de/jplag/reporting/reportobject/model/JPlagReport.java
  34. 1 1
      jplag/src/main/java/de/jplag/reporting/reportobject/model/Match.java
  35. 1 1
      jplag/src/main/java/de/jplag/reporting/reportobject/model/Metric.java
  36. 1 1
      jplag/src/main/java/de/jplag/reporting/reportobject/model/OverviewReport.java
  37. 1 1
      jplag/src/main/java/de/jplag/reporting/reportobject/model/TopComparison.java
  38. 0 29
      jplag/src/main/java/de/jplag/reporting2/Report.java
  39. 28 0
      jplag/src/main/java/de/jplag/strategy/AbstractComparisonStrategy.java
  40. 2 14
      jplag/src/main/java/de/jplag/strategy/NormalComparisonStrategy.java
  41. 0 19
      jplag/src/main/java/de/jplag/strategy/ParallelComparisonStrategy.java
  42. 3 1
      jplag/src/main/resources/de/jplag/messages.properties
  43. 1 1
      jplag/src/test/java/de/jplag/BaseCodeTest.java
  44. 0 42
      jplag/src/test/java/de/jplag/JPlagComparisonTest.java
  45. 30 0
      jplag/src/test/java/de/jplag/NormalComparisonTest.java
  46. 7 3
      jplag/src/test/java/de/jplag/TestBase.java
  47. 55 0
      jplag/src/test/java/de/jplag/cli/OldNewRootDirectoriesArgumentTest.java
  48. 1 1
      pom.xml

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

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

+ 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
 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:
 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
 ### Java API
@@ -63,7 +67,7 @@ named arguments:
 The new API makes it easy to integrate JPlag's plagiarism detection into external Java projects:
 The new API makes it easy to integrate JPlag's plagiarism detection into external Java projects:
 
 
 ```java
 ```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");
 options.setBaseCodeSubmissionName("template");
 
 
 JPlag jplag = new JPlag(options);
 JPlag jplag = new JPlag(options);

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

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

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

@@ -40,7 +40,7 @@ public class Language implements de.jplag.Language {
 
 
     @Override
     @Override
     public boolean supportsColumns() {
     public boolean supportsColumns() {
-        return false;
+        return true;
     }
     }
 
 
     @Override
     @Override

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

@@ -13,11 +13,10 @@ public class Scanner extends AbstractParser {
     public TokenList scan(File directory, String[] files) {
     public TokenList scan(File directory, String[] files) {
         tokens = new TokenList();
         tokens = new TokenList();
         errors = 0;
         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++;
                 errors++;
             }
             }
             tokens.addToken(new CPPToken(CPPTokenConstants.FILE_END, currentFile));
             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
 options
@@ -24,28 +22,25 @@ import java.io.InputStream;
 import de.jplag.cpp.NewlineStream;
 import de.jplag.cpp.NewlineStream;
 
 
 public class CPPScanner implements CPPTokenConstants {
 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 {
         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) {
         } 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;
             return false;
         }
         }
         try {
         try {
             scanner.scan();
             scanner.scan();
         } catch (ParseException e) {
         } 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;
             return false;
         } catch (TokenMgrException e) {
         } 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 false;
         }
         }
         return true;
         return true;
@@ -281,29 +276,29 @@ void scan(): {}
 
 
 void token(): {}
 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"
 | "true"
 | "false"
 | "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>
 | <OCTALINT>
 | <OCTALLONG>
 | <OCTALLONG>
@@ -400,7 +395,7 @@ void token(): {}
 
 
 | <BACKSLASH>
 | <BACKSLASH>
 
 
-       //| var() [LOOKAHEAD(2) "("        { scanner2.add(C_FUN,token); } ]
+       //| var() [LOOKAHEAD(2) "("        { delegatingScanner.add(C_FUN,token); } ]
 | "."
 | "."
 | "->"
 | "->"
 | ".*"
 | ".*"

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

@@ -1,763 +1,185 @@
 package de.jplag.python3;
 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 org.antlr.v4.runtime.tree.TerminalNode;
 
 
-import de.jplag.python3.grammar.Python3Listener;
+import de.jplag.python3.grammar.Python3BaseListener;
 import de.jplag.python3.grammar.Python3Parser;
 import de.jplag.python3.grammar.Python3Parser;
 
 
-public class JplagPython3Listener implements Python3Listener, Python3TokenConstants {
+public class JplagPython3Listener extends Python3BaseListener implements Python3TokenConstants {
 
 
-    private final 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
     @Override
     public void enterAssert_stmt(Python3Parser.Assert_stmtContext ctx) {
     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
     @Override
     public void enterDecorated(Python3Parser.DecoratedContext ctx) {
     public void enterDecorated(Python3Parser.DecoratedContext ctx) {
-        jplagParser.add(DEC_BEGIN, ctx.getStart());
+        parser.add(DEC_BEGIN, ctx.getStart());
     }
     }
 
 
     @Override
     @Override
     public void exitDecorated(Python3Parser.DecoratedContext ctx) {
     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
     @Override
     public void enterRaise_stmt(Python3Parser.Raise_stmtContext ctx) {
     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
     @Override
     public void enterExcept_clause(Python3Parser.Except_clauseContext ctx) {
     public void enterExcept_clause(Python3Parser.Except_clauseContext ctx) {
-        jplagParser.add(EXCEPT_BEGIN, ctx.getStart());
+        parser.add(EXCEPT_BEGIN, ctx.getStart());
     }
     }
 
 
     @Override
     @Override
     public void exitExcept_clause(Python3Parser.Except_clauseContext ctx) {
     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
     @Override
     public void enterDictorsetmaker(Python3Parser.DictorsetmakerContext ctx) {
     public void enterDictorsetmaker(Python3Parser.DictorsetmakerContext ctx) {
-        jplagParser.add(ARRAY, ctx.getStart());
-    }
-
-    @Override
-    public void exitDictorsetmaker(Python3Parser.DictorsetmakerContext ctx) {
+        parser.add(ARRAY, ctx.getStart());
     }
     }
 
 
     @Override
     @Override
     public void enterReturn_stmt(Python3Parser.Return_stmtContext ctx) {
     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
     @Override
     public void enterWhile_stmt(Python3Parser.While_stmtContext ctx) {
     public void enterWhile_stmt(Python3Parser.While_stmtContext ctx) {
-        jplagParser.add(WHILE_BEGIN, ctx.getStart());
+        parser.add(WHILE_BEGIN, ctx.getStart());
     }
     }
 
 
     @Override
     @Override
     public void exitWhile_stmt(Python3Parser.While_stmtContext ctx) {
     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
     @Override
     public void enterYield_arg(Python3Parser.Yield_argContext ctx) {
     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
     @Override
     public void enterImport_stmt(Python3Parser.Import_stmtContext ctx) {
     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
     @Override
     public void enterLambdef(Python3Parser.LambdefContext ctx) {
     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
     @Override
     public void enterTry_stmt(Python3Parser.Try_stmtContext ctx) {
     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
     @Override
     public void enterBreak_stmt(Python3Parser.Break_stmtContext ctx) {
     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
     @Override
     public void enterTestlist_comp(Python3Parser.Testlist_compContext ctx) {
     public void enterTestlist_comp(Python3Parser.Testlist_compContext ctx) {
         if (ctx.getText().contains(",")) {
         if (ctx.getText().contains(",")) {
-            jplagParser.add(ARRAY, ctx.getStart());
+            parser.add(ARRAY, ctx.getStart());
         }
         }
     }
     }
 
 
-    @Override
-    public void exitTestlist_comp(Python3Parser.Testlist_compContext ctx) {
-    }
-
     @Override
     @Override
     public void enterIf_stmt(Python3Parser.If_stmtContext ctx) {
     public void enterIf_stmt(Python3Parser.If_stmtContext ctx) {
-        jplagParser.add(IF_BEGIN, ctx.getStart());
+        parser.add(IF_BEGIN, ctx.getStart());
     }
     }
 
 
     @Override
     @Override
     public void exitIf_stmt(Python3Parser.If_stmtContext ctx) {
     public void exitIf_stmt(Python3Parser.If_stmtContext ctx) {
-        jplagParser.addEnd(IF_END, ctx.getStart());
+        parser.addEnd(IF_END, ctx.getStart());
     }
     }
 
 
     @Override
     @Override
     public void enterWith_stmt(Python3Parser.With_stmtContext ctx) {
     public void enterWith_stmt(Python3Parser.With_stmtContext ctx) {
-        jplagParser.add(WITH_BEGIN, ctx.getStart());
+        parser.add(WITH_BEGIN, ctx.getStart());
     }
     }
 
 
     @Override
     @Override
     public void exitWith_stmt(Python3Parser.With_stmtContext ctx) {
     public void exitWith_stmt(Python3Parser.With_stmtContext ctx) {
-        jplagParser.addEnd(WITH_END, ctx.getStart());
+        parser.addEnd(WITH_END, ctx.getStart());
     }
     }
 
 
     @Override
     @Override
     public void enterClassdef(Python3Parser.ClassdefContext ctx) {
     public void enterClassdef(Python3Parser.ClassdefContext ctx) {
-        jplagParser.add(CLASS_BEGIN, ctx.getStart());
+        parser.add(CLASS_BEGIN, ctx.getStart());
     }
     }
 
 
     @Override
     @Override
     public void exitClassdef(Python3Parser.ClassdefContext ctx) {
     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
     @Override
     public void enterTrailer(Python3Parser.TrailerContext ctx) {
     public void enterTrailer(Python3Parser.TrailerContext ctx) {
         if (ctx.getText().charAt(0) == '(') {
         if (ctx.getText().charAt(0) == '(') {
-            jplagParser.add(APPLY, ctx.getStart());
+            parser.add(APPLY, ctx.getStart());
         } else {
         } 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
     @Override
     public void enterFuncdef(Python3Parser.FuncdefContext ctx) {
     public void enterFuncdef(Python3Parser.FuncdefContext ctx) {
-        jplagParser.add(METHOD_BEGIN, ctx.getStart());
+        parser.add(METHOD_BEGIN, ctx.getStart());
     }
     }
 
 
     @Override
     @Override
     public void exitFuncdef(Python3Parser.FuncdefContext ctx) {
     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
     @Override
     public void enterAugassign(Python3Parser.AugassignContext ctx) {
     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
     @Override
     public void enterYield_stmt(Python3Parser.Yield_stmtContext ctx) {
     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
     @Override
     public void enterContinue_stmt(Python3Parser.Continue_stmtContext ctx) {
     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
     @Override
     public void enterFor_stmt(Python3Parser.For_stmtContext ctx) {
     public void enterFor_stmt(Python3Parser.For_stmtContext ctx) {
-        jplagParser.add(FOR_BEGIN, ctx.getStart());
+        parser.add(FOR_BEGIN, ctx.getStart());
     }
     }
 
 
     @Override
     @Override
     public void exitFor_stmt(Python3Parser.For_stmtContext ctx) {
     public void exitFor_stmt(Python3Parser.For_stmtContext ctx) {
-        jplagParser.addEnd(FOR_END, ctx.getStart());
+        parser.addEnd(FOR_END, ctx.getStart());
     }
     }
 
 
     @Override
     @Override
     public void enterDel_stmt(Python3Parser.Del_stmtContext ctx) {
     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
     @Override
     public void visitTerminal(TerminalNode node) {
     public void visitTerminal(TerminalNode node) {
         if (node.getText().equals("=")) {
         if (node.getText().equals("=")) {
-            jplagParser.add(ASSIGN, node.getSymbol());
+            parser.add(ASSIGN, node.getSymbol());
         } else if (node.getText().equals("finally")) {
         } 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) {
-    }
 }
 }

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

@@ -31,7 +31,6 @@ public class Parser extends AbstractParser {
             if (!parseFile(directory, files[i])) {
             if (!parseFile(directory, files[i])) {
                 errors++;
                 errors++;
             }
             }
-            System.gc();// Emeric
             tokens.addToken(new Python3Token(Python3TokenConstants.FILE_END, files[i], -1, -1, -1));
             tokens.addToken(new Python3Token(Python3TokenConstants.FILE_END, files[i], -1, -1, -1));
         }
         }
         this.parseEnd();
         this.parseEnd();

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

@@ -42,7 +42,7 @@ public class Language implements de.jplag.Language {
 
 
     @Override
     @Override
     public boolean supportsColumns() {
     public boolean supportsColumns() {
-        return false;
+        return true;
     }
     }
 
 
     @Override
     @Override

+ 1 - 1
jplag/pom.xml

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

+ 51 - 5
jplag/src/main/java/de/jplag/CLI.java

@@ -1,7 +1,37 @@
 package de.jplag;
 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.Optional;
 import java.util.Random;
 import java.util.Random;
 
 
@@ -19,8 +49,8 @@ import de.jplag.options.JPlagOptions;
 import de.jplag.options.LanguageOption;
 import de.jplag.options.LanguageOption;
 import de.jplag.options.SimilarityMetric;
 import de.jplag.options.SimilarityMetric;
 import de.jplag.options.Verbosity;
 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;
 import de.jplag.strategy.ComparisonMode;
 
 
 /**
 /**
@@ -98,8 +128,16 @@ public class CLI {
         if (fileSuffixString != null) {
         if (fileSuffixString != null) {
             fileSuffixes = fileSuffixString.replaceAll("\\s+", "").split(",");
             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));
         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.setBaseCodeSubmissionName(BASE_CODE.getFrom(namespace));
         options.setVerbosity(Verbosity.fromOption(VERBOSITY.getFrom(namespace)));
         options.setVerbosity(Verbosity.fromOption(VERBOSITY.getFrom(namespace)));
         options.setDebugParser(DEBUG.getFrom(namespace));
         options.setDebugParser(DEBUG.getFrom(namespace));
@@ -152,4 +190,12 @@ public class CLI {
         var randomDescription = DESCRIPTIONS[new Random().nextInt(DESCRIPTIONS.length)];
         var randomDescription = DESCRIPTIONS[new Random().nextInt(DESCRIPTIONS.length)];
         return String.format("JPlag - %s" + System.lineSeparator() + CREDITS, randomDescription);
         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));
+    }
 }
 }

+ 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_COMPARISON_MODE;
 import static de.jplag.options.JPlagOptions.DEFAULT_SHOWN_COMPARISONS;
 import static de.jplag.options.JPlagOptions.DEFAULT_SHOWN_COMPARISONS;
 import static de.jplag.options.JPlagOptions.DEFAULT_SIMILARITY_THRESHOLD;
 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 static net.sourceforge.argparse4j.impl.Arguments.storeTrue;
 
 
 import java.util.Collection;
 import java.util.Collection;
@@ -30,8 +31,9 @@ import de.jplag.strategy.ComparisonMode;
  * @author Timur Saglam
  * @author Timur Saglam
  */
  */
 public enum CommandLineArgument {
 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())),
     LANGUAGE(new Builder("-l", String.class).defaultsTo(LanguageOption.getDefault().getDisplayName()).choices(LanguageOption.getAllDisplayNames())),
     BASE_CODE("-bc", String.class),
     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
     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) {
         if (type == Boolean.class) {
             argument.action(storeTrue());
             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.nargs(numberOfValues.toString());
+            argument.action(append());
         }
         }
     }
     }
 
 

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

@@ -52,7 +52,7 @@ public class JPlag {
      * If an exclusion file is given, it is read in and all strings are saved in the set "excluded".
      * If an exclusion file is given, it is read in and all strings are saved in the set "excluded".
      * @param exclusionFileName the file name or path
      * @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))) {
         try (BufferedReader reader = new BufferedReader(new FileReader(exclusionFileName, JPlagOptions.CHARSET))) {
             final var excludedFileNames = reader.lines().collect(Collectors.toSet());
             final var excludedFileNames = reader.lines().collect(Collectors.toSet());
             if (options.getVerbosity() == LONG) {
             if (options.getVerbosity() == LONG) {

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

@@ -1,12 +1,8 @@
 package de.jplag;
 package de.jplag;
 
 
 import java.util.ArrayList;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Comparator;
 import java.util.Comparator;
-import java.util.LinkedHashSet;
 import java.util.List;
 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.
  * This method represents the whole result of a comparison between two submissions.
@@ -30,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.
      * 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)
      * @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) {
         for (Match match : matches) {
             if (match.overlap(startOfFirst, startOfSecond, length)) {
             if (match.overlap(startOfFirst, startOfSecond, length)) {
                 return;
                 return;
@@ -39,16 +35,6 @@ public class JPlagComparison implements Comparator<JPlagComparison> { // FIXME T
         matches.add(new Match(startOfFirst, startOfSecond, length));
         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(Match::getLength).max().orElse(0);
-        int color = 255 * length / longestMatch;
-        String help = (color < 16 ? "0" : "") + Integer.toHexString(color);
-        return "#" + help + "0000";
-    }
-
     @Override
     @Override
     public int compare(JPlagComparison comparison1, JPlagComparison comparison2) {
     public int compare(JPlagComparison comparison1, JPlagComparison comparison2) {
         return Float.compare(comparison2.similarity(), comparison1.similarity()); // comparison2 first!
         return Float.compare(comparison2.similarity(), comparison1.similarity()); // comparison2 first!
@@ -62,34 +48,6 @@ public class JPlagComparison implements Comparator<JPlagComparison> { // FIXME T
         return (compare(this, (JPlagComparison) other) == 0);
         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.
      * @return the base code matches of the first submission.
      */
      */
@@ -138,22 +96,6 @@ public class JPlagComparison implements Comparator<JPlagComparison> { // FIXME T
         return secondSubmission;
         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.
      * @return Maximum similarity in percent of both submissions.
      */
      */
@@ -217,24 +159,6 @@ public class JPlagComparison implements Comparator<JPlagComparison> { // FIXME T
         return ((int) (secondBasecodeSimilarity() * ROUNDING_FACTOR)) / (float) ROUNDING_FACTOR;
         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(indices::add);
-        Comparator<Integer> comparator = Comparator.comparingInt((Integer i) -> selectStartof(i, useFirst));
-        indices.sort(comparator);
-        return indices;
-    }
-
-    private int selectStartof(Integer index, boolean useFirst) {
-        Match match = matches.get(index);
-        return match.getStart(useFirst);
-    }
-
     @Override
     @Override
     public String toString() {
     public String toString() {
         return firstSubmission.getName() + " <-> " + secondSubmission.getName();
         return firstSubmission.getName() + " <-> " + secondSubmission.getName();

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

@@ -35,15 +35,6 @@ public class Match {
         return startOfSecond;
         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.
      * @return the length of the similar sections, meaning the number of tokens.
      */
      */

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

@@ -5,7 +5,8 @@ package de.jplag;
  */
  */
 public enum NumberOfArgumentValues {
 public enum NumberOfArgumentValues {
     SINGLE_VALUE(""),
     SINGLE_VALUE(""),
-    ONE_OR_MORE_VALUES("+"),;
+    ONE_OR_MORE_VALUES("+"),
+    ZERO_OR_MORE_VALUES("*");
 
 
     private final String representation;
     private final String representation;
 
 

+ 78 - 149
jplag/src/main/java/de/jplag/Submission.java

@@ -1,20 +1,10 @@
 package de.jplag;
 package de.jplag;
 
 
-import java.io.BufferedReader;
 import java.io.File;
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
 import java.io.IOException;
 import java.io.IOException;
-import java.io.InputStreamReader;
 import java.nio.file.Files;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Path;
-import java.util.ArrayList;
 import java.util.Collection;
 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.
  * Represents a single submission. A submission can contain multiple files.
@@ -35,6 +25,11 @@ public class Submission implements Comparable<Submission> {
      */
      */
     private final File submissionRootFile;
     private final File submissionRootFile;
 
 
+    /**
+     * Whether the submission is new. That is, must be checked for plagiarism.
+     */
+    private final boolean isNew;
+
     /**
     /**
      * Files of the submission.
      * Files of the submission.
      */
      */
@@ -62,18 +57,32 @@ public class Submission implements Comparable<Submission> {
      * Creates a submission.
      * Creates a submission.
      * @param name Identification of the submission (directory or filename).
      * @param name Identification of the submission (directory or filename).
      * @param submissionRootFile is the submission file, or the root of the submission itself.
      * @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 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 language is the language of the submission.
      * @param errorCollector is the interface for error reporting.
      * @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.name = name;
         this.submissionRootFile = submissionRootFile;
         this.submissionRootFile = submissionRootFile;
+        this.isNew = isNew;
         this.files = files;
         this.files = files;
         this.language = language;
         this.language = language;
         this.errorCollector = errorCollector;
         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.
      * @return a list of files this submission consists of.
      */
      */
@@ -88,14 +97,6 @@ public class Submission implements Comparable<Submission> {
         return name;
         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.
      * @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]));
-            } 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.
      * Sets the tokens that have been parsed from the files this submission consists of.
      * @param tokenList is the list of these tokens.
      * @param tokenList is the list of these tokens.
@@ -279,15 +180,6 @@ public class Submission implements Comparable<Submission> {
         this.tokenList = tokenList;
         this.tokenList = tokenList;
     }
     }
 
 
-    public void markAsErroneous() {
-        hasErrors = true;
-    }
-
-    @Override
-    public int compareTo(Submission other) {
-        return name.compareTo(other.name);
-    }
-
     @Override
     @Override
     public String toString() {
     public String toString() {
         return name;
         return name;
@@ -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);
         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;
+    }
 }
 }

+ 53 - 14
jplag/src/main/java/de/jplag/SubmissionSetBuilder.java

@@ -51,19 +51,25 @@ public class SubmissionSetBuilder {
      * @throws ExitException if the directory cannot be read.
      * @throws ExitException if the directory cannot be read.
      */
      */
     public SubmissionSet buildSubmissionSet() throws ExitException {
     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
         // For backward compatibility, don't prefix submission names with their root directory
         // if there is only one 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.
         // Collect valid looking entries from the root directories.
         Map<File, Submission> foundSubmissions = new HashMap<>();
         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.
         // Merge everything in a submission set.
         List<Submission> submissions = new ArrayList<>(foundSubmissions.values());
         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.
      * 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());
         Set<File> canonicalRootDirectories = new HashSet<>(rootDirectoryNames.size());
         for (String rootDirectoryName : rootDirectoryNames) {
         for (String rootDirectoryName : rootDirectoryNames) {
             File rootDirectory = new File(rootDirectoryName);
             File rootDirectory = new File(rootDirectoryName);
@@ -94,17 +104,43 @@ public class SubmissionSetBuilder {
         return canonicalRootDirectories;
         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.
         // Extract the basecode submission if necessary.
         Optional<Submission> baseCodeSubmission = Optional.empty();
         Optional<Submission> baseCodeSubmission = Optional.empty();
         if (options.hasBaseCode()) {
         if (options.hasBaseCode()) {
             String baseCodeName = options.getBaseCodeSubmissionName().get();
             String baseCodeName = options.getBaseCodeSubmissionName().get();
             Submission baseCode = loadBaseCodeAsPath(baseCodeName);
             Submission baseCode = loadBaseCodeAsPath(baseCodeName);
             if (baseCode == null) {
             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!");
                     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.
                 // Single root-directory, try the legacy way of specifying basecode.
                 baseCode = loadBaseCodeViaName(baseCodeName, rootDirectory, foundSubmissions);
                 baseCode = loadBaseCodeViaName(baseCodeName, rootDirectory, foundSubmissions);
             }
             }
@@ -140,7 +176,7 @@ public class SubmissionSetBuilder {
         try {
         try {
             // Use an unlikely short name for the base code. If all is well, this name should not appear
             // 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
             // in the output since basecode matches are removed from it
-            return processSubmission(basecodeSubmission.getName(), basecodeSubmission);
+            return processSubmission(basecodeSubmission.getName(), basecodeSubmission, false);
         } catch (SubmissionException exception) {
         } catch (SubmissionException exception) {
             throw new BasecodeException(exception.getMessage(), exception); // Change thrown exception to basecode exception.
             throw new BasecodeException(exception.getMessage(), exception); // Change thrown exception to basecode exception.
         }
         }
@@ -228,10 +264,11 @@ public class SubmissionSetBuilder {
     /**
     /**
      * Process the given directory entry as a submission, the path MUST not be excluded.
      * Process the given directory entry as a submission, the path MUST not be excluded.
      * @param submissionFile the file for the submission.
      * @param submissionFile the file for the submission.
+     * @param isNew states whether submissions found in the root directory must be checked for plagiarism.
      * @return The entry converted to a submission.
      * @return The entry converted to a submission.
      * @throws ExitException when an error has been found with the entry.
      * @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) {
         if (submissionFile.isDirectory() && options.getSubdirectoryName() != null) {
             // Use subdirectory instead
             // Use subdirectory instead
@@ -248,15 +285,17 @@ public class SubmissionSetBuilder {
         }
         }
 
 
         submissionFile = makeCanonical(submissionFile, it -> new SubmissionException("Cannot create submission: " + submissionName, it));
         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.
      * Process entries in the root directory to check whether they qualify as submissions.
      * @param rootDirectory is the root directory being examined.
      * @param rootDirectory is the root directory being examined.
      * @param foundSubmissions Submissions found so far, is updated in-place.
      * @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)) {
         for (String fileName : listSubmissionFiles(rootDirectory)) {
             File submissionFile = new File(rootDirectory, fileName);
             File submissionFile = new File(rootDirectory, fileName);
 
 
@@ -264,7 +303,7 @@ public class SubmissionSetBuilder {
             if (errorMessage == null) {
             if (errorMessage == null) {
                 String rootDirectoryPrefix = multipleRoots ? (rootDirectory.getName() + File.separator) : "";
                 String rootDirectoryPrefix = multipleRoots ? (rootDirectory.getName() + File.separator) : "";
                 String submissionName = rootDirectoryPrefix + fileName;
                 String submissionName = rootDirectoryPrefix + fileName;
-                Submission submission = processSubmission(submissionName, submissionFile);
+                Submission submission = processSubmission(submissionName, submissionFile, isNew);
                 foundSubmissions.put(submission.getRoot(), submission);
                 foundSubmissions.put(submission.getRoot(), submission);
             } else {
             } else {
                 System.out.println(errorMessage);
                 System.out.println(errorMessage);

+ 22 - 8
jplag/src/main/java/de/jplag/options/JPlagOptions.java

@@ -80,9 +80,14 @@ public class JPlagOptions {
     private Set<String> excludedFiles = Collections.emptySet();
     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.
      * Path name of the directory containing the base code.
@@ -124,8 +129,9 @@ public class JPlagOptions {
     /**
     /**
      * Constructor with required attributes.
      * 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;
         this.languageOption = languageOption;
     }
     }
 
 
@@ -165,8 +171,12 @@ public class JPlagOptions {
         return minimumTokenMatch;
         return minimumTokenMatch;
     }
     }
 
 
-    public List<String> getRootDirectoryNames() {
-        return rootDirectoryNames;
+    public List<String> getSubmissionDirectories() {
+        return submissionDirectories;
+    }
+
+    public List<String> getOldSubmissionDirectories() {
+        return oldSubmissionDirectories;
     }
     }
 
 
     public SimilarityMetric getSimilarityMetric() {
     public SimilarityMetric getSimilarityMetric() {
@@ -260,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) {
     public void setSimilarityMetric(SimilarityMetric similarityMetric) {

+ 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;
-
-    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 final 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.io.File;
 import java.util.List;
 import java.util.List;
 
 
 import de.jplag.JPlagResult;
 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
 // 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;
-
-    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;
 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.List;
-import java.util.Map;
-import java.util.TreeMap;
 
 
-import de.jplag.JPlagComparison;
 import de.jplag.JPlagResult;
 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.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Path;
@@ -8,8 +8,8 @@ import java.util.List;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 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.
  * 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.BufferedReader;
 import java.io.File;
 import java.io.File;
 import java.io.FileReader;
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.IOException;
 import java.text.SimpleDateFormat;
 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 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.
  * 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();
         List<JPlagComparison> comparisons = result.getComparisons();
         OverviewReport overviewReport = new OverviewReport();
         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("") : "";
         String baseCodePath = result.getOptions().hasBaseCode() ? result.getOptions().getBaseCodeSubmissionName().orElse("") : "";
         overviewReport.setBaseCodeFolderPath(baseCodePath);
         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;
 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;
 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;
 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;
 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;
 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;
 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.List;
 import java.util.Set;
 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;
 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);
-}

+ 28 - 0
jplag/src/main/java/de/jplag/strategy/AbstractComparisonStrategy.java

@@ -1,5 +1,7 @@
 package de.jplag.strategy;
 package de.jplag.strategy;
 
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Optional;
 import java.util.Optional;
 
 
 import de.jplag.GreedyStringTiling;
 import de.jplag.GreedyStringTiling;
@@ -47,4 +49,30 @@ public abstract class AbstractComparisonStrategy implements ComparisonStrategy {
         }
         }
         return Optional.empty();
         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;
+    }
 }
 }

+ 2 - 14
jplag/src/main/java/de/jplag/strategy/NormalComparisonStrategy.java

@@ -25,22 +25,10 @@ public class NormalComparisonStrategy extends AbstractComparisonStrategy {
 
 
         List<Submission> submissions = submissionSet.getSubmissions();
         List<Submission> submissions = submissionSet.getSubmissions();
         long timeBeforeStartInMillis = System.currentTimeMillis();
         long timeBeforeStartInMillis = System.currentTimeMillis();
-        int i, j, numberOfSubmissions = submissions.size();
-        Submission first, second;
         List<JPlagComparison> comparisons = new ArrayList<>();
         List<JPlagComparison> comparisons = new ArrayList<>();
 
 
-        for (i = 0; i < (numberOfSubmissions - 1); i++) {
-            first = submissions.get(i);
-            if (first.getTokenList() == null) {
-                continue;
-            }
-            for (j = (i + 1); j < numberOfSubmissions; j++) {
-                second = submissions.get(j);
-                if (second.getTokenList() == null) {
-                    continue;
-                }
-                compareSubmissions(first, second, withBaseCode).ifPresent(comparisons::add);
-            }
+        for (SubmissionTuple tuple : buildComparisonTuples(submissions)) {
+            compareSubmissions(tuple.getLeft(), tuple.getRight(), withBaseCode).ifPresent(comparisons::add);
         }
         }
 
 
         long durationInMillis = System.currentTimeMillis() - timeBeforeStartInMillis;
         long durationInMillis = System.currentTimeMillis() - timeBeforeStartInMillis;

+ 0 - 19
jplag/src/main/java/de/jplag/strategy/ParallelComparisonStrategy.java

@@ -71,25 +71,6 @@ public class ParallelComparisonStrategy extends AbstractComparisonStrategy {
         return new JPlagResult(comparisons, submissionSet, durationInMillis, options);
         return new JPlagResult(comparisons, submissionSet, durationInMillis, options);
     }
     }
 
 
-    /**
-     * @return a list of all submission tuples to be processed.
-     */
-    private 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) {
-                for (int j = (i + 1); j < submissions.size(); j++) {
-                    Submission second = submissions.get(j);
-                    if (second.getTokenList() != null) {
-                        tuples.add(new SubmissionTuple(first, second));
-                    }
-                }
-            }
-        }
-        return tuples;
-    }
-
     /**
     /**
      * Creates a runnable which compares a submission tuple. If the submissions are locked, the runnable is re-submitted.
      * Creates a runnable which compares a submission tuple. If the submissions are locked, the runnable is re-submitted.
      * @param tuple contains the submissions to compare.
      * @param tuple contains the submissions to compare.

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

@@ -6,7 +6,9 @@ CommandLineArgument.ExcludeFile=All files named in this file will be ignored in
 CommandLineArgument.Language=Select the language to parse the submissions
 CommandLineArgument.Language=Select the language to parse the submissions
 CommandLineArgument.MinTokenMatch=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
 CommandLineArgument.MinTokenMatch=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
 CommandLineArgument.ResultFolder=Name of the directory in which the comparison results will be stored
 CommandLineArgument.ResultFolder=Name of the directory in which the comparison results will be stored
-CommandLineArgument.RootDirectory=Root-directory that contains submissions
+CommandLineArgument.RootDirectory=Root-directory with submissions to check for plagiarism
+CommandLineArgument.PlagiarismDirectory=Root-directory with submissions to check for plagiarism
+CommandLineArgument.PriorDirectory=Root-directory with prior submissions to compare against
 CommandLineArgument.ShownComparisons=The maximum number of comparisons that will be shown in the generated report, if set to -1 all comparisons will be shown
 CommandLineArgument.ShownComparisons=The maximum number of comparisons that will be shown in the generated report, if set to -1 all comparisons will be shown
 CommandLineArgument.SimilarityThreshold=Comparison similarity threshold [0-100]: All comparisons above this threshold will be saved
 CommandLineArgument.SimilarityThreshold=Comparison similarity threshold [0-100]: All comparisons above this threshold will be saved
 CommandLineArgument.Subdirectory=Look in directories <root-dir>/*/<dir> for programs
 CommandLineArgument.Subdirectory=Look in directories <root-dir>/*/<dir> for programs

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

@@ -52,7 +52,7 @@ public class BaseCodeTest extends TestBase {
 
 
     @Test(expected = RootDirectoryException.class)
     @Test(expected = RootDirectoryException.class)
     public void testInvalidRoot() throws ExitException {
     public void testInvalidRoot() throws ExitException {
-        runJPlag("basecode", it -> it.setRootDirectoryNames(List.of("WrongRoot")));
+        runJPlag("basecode", it -> it.setSubmissionDirectories(List.of("WrongRoot")));
     }
     }
 
 
     @Test(expected = BasecodeException.class)
     @Test(expected = BasecodeException.class)

+ 0 - 42
jplag/src/test/java/de/jplag/JPlagComparisonTest.java

@@ -1,42 +0,0 @@
-package de.jplag;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-
-import java.util.List;
-
-import org.junit.Test;
-
-import de.jplag.exceptions.ExitException;
-
-public class JPlagComparisonTest extends TestBase {
-
-    private static final String[] FILES = {"GSTiling.java", "Match.java", "Matches.java", "Structure.java", "Submission.java", "Table.java",
-            "Token.java"};
-    private static final String[] FILES_2 = {"Match.java", "Matches.java", "Structure.java", "Submission.java", "Table.java", "Token.java"};
-    private static final String[] FILES_3 = {"Table.java", "Token.java"};
-
-    /**
-     * Tests whether the right set of files corresponding to a set of matches is produced by the {@link JPlagComparison}.
-     */
-    @Test
-    public void testFilesOfMatches() throws ExitException {
-        JPlagResult result = runJPlagWithDefaultOptions("PartialPlagiarism");
-        List<JPlagComparison> comparisons = result.getComparisons();
-        assertEquals(5, result.getNumberOfSubmissions());
-        assertEquals(10, comparisons.size());
-        for (int submission = 0; submission < 2; submission++) { // for both submissions in a comparison
-            assertArrayEquals(FILES, comparisons.get(0).files(submission));
-            for (int i = 1; i < 3; i++) {
-                assertArrayEquals(FILES_2, comparisons.get(i).files(submission));
-            }
-            for (int i = 3; i < 6; i++) {
-                assertArrayEquals(FILES_3, comparisons.get(i).files(submission));
-            }
-            for (int i = 6; i < 10; i++) {
-                assertArrayEquals(new String[] {}, comparisons.get(i).files(submission));
-            }
-        }
-
-    }
-}

+ 30 - 0
jplag/src/test/java/de/jplag/NormalComparisonTest.java

@@ -118,4 +118,34 @@ public class NormalComparisonTest extends TestBase {
         runJPlag(paths, it -> it.setBaseCodeSubmissionName(basecodePath));
         runJPlag(paths, it -> it.setBaseCodeSubmissionName(basecodePath));
         fail("No basecode exception was thrown!");
         fail("No basecode exception was thrown!");
     }
     }
+
+    @Test
+    public void testDisjunctNewAndOldRootDirectories() throws ExitException {
+        List<String> newDirectories = List.of(getBasePath("SimpleDuplicate")); // 2 submissions
+        List<String> oldDirectories = List.of(getBasePath("basecode")); // 3 submissions
+        JPlagResult result = runJPlag(newDirectories, oldDirectories, it -> {
+        });
+        int numberOfExpectedComparison = 1 + 3 * 2;
+        assertEquals(numberOfExpectedComparison, result.getComparisons().size());
+    }
+
+    @Test
+    public void testOverlappingNewAndOldDirectoriesOverlap() throws ExitException {
+        List<String> newDirectories = List.of(getBasePath("SimpleDuplicate")); // 2 submissions
+        List<String> oldDirectories = List.of(getBasePath("SimpleDuplicate"));
+        JPlagResult result = runJPlag(newDirectories, oldDirectories, it -> {
+        });
+        int numberOfExpectedComparison = 1;
+        assertEquals(numberOfExpectedComparison, result.getComparisons().size());
+    }
+
+    @Test
+    public void testBasecodeInOldDirectory() throws ExitException {
+        String basecodePath = getBasePath("basecode", "base");
+        List<String> newDirectories = List.of(getBasePath("SimpleDuplicate")); // 2 submissions
+        List<String> oldDirectories = List.of(getBasePath("basecode")); // 3 - 1 submissions
+        JPlagResult result = runJPlag(newDirectories, oldDirectories, it -> it.setBaseCodeSubmissionName(basecodePath));
+        int numberOfExpectedComparison = 1 + 2 * 2;
+        assertEquals(numberOfExpectedComparison, result.getComparisons().size());
+    }
 }
 }

+ 7 - 3
jplag/src/test/java/de/jplag/TestBase.java

@@ -38,11 +38,15 @@ public abstract class TestBase {
     }
     }
 
 
     protected JPlagResult runJPlag(String testSampleName, Consumer<JPlagOptions> customization) throws ExitException {
     protected JPlagResult runJPlag(String testSampleName, Consumer<JPlagOptions> customization) throws ExitException {
-        return runJPlag(List.of(getBasePath(testSampleName)), customization);
+        return runJPlag(List.of(getBasePath(testSampleName)), List.of(), customization);
     }
     }
 
 
-    protected JPlagResult runJPlag(List<String> testPaths, Consumer<JPlagOptions> customization) throws ExitException {
-        JPlagOptions options = new JPlagOptions(testPaths, LanguageOption.JAVA);
+    protected JPlagResult runJPlag(List<String> newPaths, Consumer<JPlagOptions> customization) throws ExitException {
+        return runJPlag(newPaths, List.of(), customization);
+    }
+
+    protected JPlagResult runJPlag(List<String> newPaths, List<String> oldPaths, Consumer<JPlagOptions> customization) throws ExitException {
+        JPlagOptions options = new JPlagOptions(newPaths, oldPaths, LanguageOption.JAVA);
         options.setVerbosity(Verbosity.LONG);
         options.setVerbosity(Verbosity.LONG);
         customization.accept(options);
         customization.accept(options);
         JPlag jplag = new JPlag(options);
         JPlag jplag = new JPlag(options);

+ 55 - 0
jplag/src/test/java/de/jplag/cli/OldNewRootDirectoriesArgumentTest.java

@@ -0,0 +1,55 @@
+package de.jplag.cli;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class OldNewRootDirectoriesArgumentTest extends CommandLineInterfaceTest {
+    @Test
+    public void testNoRootDirectories() {
+        buildOptionsFromCLI();
+
+        assertEquals(0, options.getSubmissionDirectories().size());
+        assertEquals(0, options.getOldSubmissionDirectories().size());
+    }
+
+    @Test
+    public void testTwoRootDirectoryArguments() {
+        buildOptionsFromCLI("root1", "root2");
+
+        assertEquals(2, options.getSubmissionDirectories().size());
+        assertEquals(0, options.getOldSubmissionDirectories().size());
+    }
+
+    @Test
+    public void testNewOption() {
+        buildOptionsFromCLI("-new", "root1", "root2");
+
+        assertEquals(2, options.getSubmissionDirectories().size());
+        assertEquals(0, options.getOldSubmissionDirectories().size());
+    }
+
+    @Test
+    public void testDoubleNewOption() {
+        buildOptionsFromCLI("-new", "root1", "-new", "root2");
+
+        assertEquals(2, options.getSubmissionDirectories().size());
+        assertEquals(0, options.getOldSubmissionDirectories().size());
+    }
+
+    @Test
+    public void testOldOption() {
+        buildOptionsFromCLI("-old", "root1");
+
+        assertEquals(0, options.getSubmissionDirectories().size());
+        assertEquals(1, options.getOldSubmissionDirectories().size());
+    }
+
+    @Test
+    public void testNewAndOldOption() {
+        buildOptionsFromCLI("-new", "root1", "-old", "root2");
+
+        assertEquals(1, options.getSubmissionDirectories().size());
+        assertEquals(1, options.getOldSubmissionDirectories().size());
+    }
+}

+ 1 - 1
pom.xml

@@ -81,7 +81,7 @@
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <maven.compiler.source>17</maven.compiler.source>
         <maven.compiler.source>17</maven.compiler.source>
         <maven.compiler.target>17</maven.compiler.target>
         <maven.compiler.target>17</maven.compiler.target>
-        <spotless.version>2.21.0</spotless.version>
+        <spotless.version>2.22.1</spotless.version>
     </properties>
     </properties>
     <dependencyManagement>
     <dependencyManagement>
         <dependencies>
         <dependencies>