Browse Source

Merge branch 'master' into enforce-lines

# Conflicts:
#	jplag/src/main/java/de/jplag/reporting/reportobject/mapper/ComparisonReportMapper.java
Timur Sağlam 4 years ago
parent
commit
6b3db4dcf5
42 changed files with 955 additions and 732 deletions
  1. 22 3
      README.md
  2. 2 1
      jplag.cli/src/main/java/de/jplag/CLI.java
  3. 2 2
      jplag.cli/src/main/java/de/jplag/CommandLineArgument.java
  4. 1 3
      jplag.frontend-utils/src/main/java/de/jplag/TokenList.java
  5. 3 4
      jplag.frontend.cpp/src/main/javacc/CPP.jj
  6. 12 12
      jplag.frontend.csharp-6/src/main/antlr4/de/jplag/csharp/grammar/CSharpLexer.g4
  7. 1 1
      jplag.frontend.csharp-6/src/main/antlr4/de/jplag/csharp/grammar/CSharpParser.g4
  8. 22 22
      jplag.frontend.csharp-6/src/main/antlr4/de/jplag/csharp/grammar/CSharpPreprocessorParser.g4
  9. 14 14
      jplag.frontend.csharp-6/src/main/java/de/jplag/csharp/grammar/CSharpLexerBase.java
  10. 11 8
      jplag.frontend.csharp-6/src/main/java/de/jplag/csharp/grammar/CSharpParserBase.java
  11. 113 105
      jplag.frontend.csharp-6/src/main/java/de/jplag/csharp/grammar/CSharpPreprocessorParserBase.java
  12. 16 0
      jplag.frontend.emf-metamodel-dynamic/README.md
  13. 15 0
      jplag.frontend.emf-metamodel/README.md
  14. 1 2
      jplag.frontend.scala/pom.xml
  15. 1 1
      jplag/src/main/java/de/jplag/SubmissionSet.java
  16. 2 3
      jplag/src/main/java/de/jplag/clustering/ClusteringAdapter.java
  17. 7 10
      jplag/src/main/java/de/jplag/clustering/IntegerMapping.java
  18. 1 3
      jplag/src/main/java/de/jplag/options/LanguageOption.java
  19. 101 0
      jplag/src/main/java/de/jplag/reporting/jsonfactory/ComparisonReportWriter.java
  20. 93 0
      jplag/src/main/java/de/jplag/reporting/jsonfactory/DirectoryManager.java
  21. 13 0
      jplag/src/main/java/de/jplag/reporting/jsonfactory/DummyWriter.java
  22. 2 26
      jplag/src/main/java/de/jplag/reporting/jsonfactory/FileWriter.java
  23. 30 0
      jplag/src/main/java/de/jplag/reporting/jsonfactory/ToDiskWriter.java
  24. 102 88
      jplag/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java
  25. 8 1
      jplag/src/main/java/de/jplag/reporting/reportobject/mapper/ClusteringResultMapper.java
  26. 0 54
      jplag/src/main/java/de/jplag/reporting/reportobject/mapper/ComparisonReportMapper.java
  27. 13 6
      jplag/src/main/java/de/jplag/reporting/reportobject/mapper/MetricMapper.java
  28. 43 0
      jplag/src/main/java/de/jplag/reporting/reportobject/mapper/SubmissionNameToIdMapper.java
  29. 15 154
      jplag/src/main/java/de/jplag/reporting/reportobject/model/OverviewReport.java
  30. 1 1
      jplag/src/test/java/de/jplag/reporting/reportobject/mapper/ClusteringResultMapperTest.java
  31. 58 0
      jplag/src/test/java/de/jplag/reporting/reportobject/mapper/ComparisonReportWriterTest.java
  32. 1 1
      jplag/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java
  33. 3 3
      pom.xml
  34. 87 87
      report-viewer/package-lock.json
  35. 3 3
      report-viewer/package.json
  36. 13 32
      report-viewer/src/components/ComparisonsTable.vue
  37. 12 11
      report-viewer/src/components/IDsList.vue
  38. 7 7
      report-viewer/src/model/Overview.ts
  39. 27 5
      report-viewer/src/model/factories/OverviewFactory.ts
  40. 36 0
      report-viewer/src/store/store.ts
  41. 14 32
      report-viewer/src/views/ComparisonView.vue
  42. 27 27
      report-viewer/src/views/OverviewView.vue

+ 22 - 3
README.md

@@ -7,9 +7,28 @@
 [![Latest Release](https://img.shields.io/github/release/jplag/jplag.svg)](https://github.com/jplag/jplag/releases/latest)
 [![Maven Central](https://maven-badges.herokuapp.com/maven-central/de.jplag/jplag/badge.svg)](https://maven-badges.herokuapp.com/maven-central/de.jplag/jplag)
 [![License](https://img.shields.io/github/license/jplag/jplag.svg)](https://github.com/jplag/jplag/blob/master/LICENSE)
-[![Lines of code](https://img.shields.io/tokei/lines/github/jplag/jplag)](https://github.com/jplag/jplag/graphs/contributors)
-
-JPlag is a system that finds similarities among multiple sets of source code files. This way it can detect software plagiarism and collusion in software development. JPlag currently supports Java, C#, C, C++, Python 3, Scheme, and natural language text.
+[![GitHub commit activity](https://img.shields.io/github/commit-activity/y/jplag/JPlag)](https://github.com/jplag/JPlag/pulse)
+
+JPlag is a system that finds similarities among multiple sets of source code files. This way it can detect software plagiarism and collusion in software development. JPlag currently supports various programming languages, EMF metamodels, and natural language text.
+
+## Supported Languages
+
+In the following, a list of all supported languages with their supported language version is provided. A language can be selected from the command line using the `-l <cli argument name>` argument.
+
+| Language | Version | CLI Argument Name | [state](https://github.com/jplag/JPlag/wiki/3.-Language-Modules) | parser
+| --- | ---: | --- | :---: | :---: |
+| [Java](https://www.java.com) | 17 | java | mature | JavaC |
+| [C++](https://isocpp.org) | 11 | cpp | legacy | JavaCC |
+| [C#](https://docs.microsoft.com/en-us/dotnet/csharp/) | 8 | csharp | beta | ANTLR 4 |
+| [Go](https://go.dev) | 1.17 | golang | beta | ANTLR 4 |
+| [Kotlin](https://kotlinlang.org) | 1.3 | kotlin | beta | ANTLR 4 |
+| [Python](https://www.python.org) | 3.6 | python3 | legacy | ANTLR 4 |
+| [R](https://www.r-project.org/) | 3.5.0 | rlang | beta | ANTLR 4 |
+| [Scala](https://www.scala-lang.org) | 2.13.8 | scala | beta | Scalameta |
+| [Scheme](http://www.scheme-reports.org) | ? | scheme | unknown | JavaCC |
+| [EMF Metamodel](https://www.eclipse.org/modeling/emf/) | 2.25.0 | emf-metamodel | alpha | EMF |
+| [EMF Metamodel](https://www.eclipse.org/modeling/emf/) (dynamic) | 2.25.0 | emf-metamodel-dynamic | alpha | EMF |
+| Text (naive) | - | text | legacy | ANTLR |
 
 ## Download and Installation
 

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

@@ -64,7 +64,8 @@ public final class CLI {
             JPlag program = new JPlag(options);
             logger.info("JPlag initialized");
             JPlagResult result = program.run();
-            ReportObjectFactory.createAndSaveReport(result, arguments.getString(RESULT_FOLDER.flagWithoutDash()));
+            ReportObjectFactory reportObjectFactory = new ReportObjectFactory();
+            reportObjectFactory.createAndSaveReport(result, arguments.getString(RESULT_FOLDER.flagWithoutDash()));
 
         } catch (ExitException exception) {
             logger.error(exception.getMessage(), exception);

+ 2 - 2
jplag.cli/src/main/java/de/jplag/CommandLineArgument.java

@@ -161,8 +161,8 @@ public enum CommandLineArgument {
                 .or(() -> argumentGroup.map(groupHelper::getArgumentGroup)).orElse(parser);
 
         Argument argument = argContainer.addArgument(flag).help(description);
-        choices.ifPresent(it -> argument.choices(it));
-        defaultValue.ifPresent(it -> argument.setDefault(it));
+        choices.ifPresent(argument::choices);
+        defaultValue.ifPresent(argument::setDefault);
         action.ifPresent(argument::action);
         metaVar.ifPresent(argument::metavar);
         argument.type(type);

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

@@ -1,7 +1,5 @@
 package de.jplag;
 
-import static java.util.stream.Collectors.toList;
-
 import java.util.ArrayList;
 import java.util.List;
 
@@ -68,7 +66,7 @@ public class TokenList {
     @Override
     public final String toString() {
         try {
-            List<String> tokenStrings = tokens.stream().map(Token::toString).collect(toList());
+            List<String> tokenStrings = tokens.stream().map(Token::toString).toList();
             return String.join(System.lineSeparator(), tokenStrings);
         } catch (OutOfMemoryError exception) {
             return "Token list to large for output: " + tokens.size() + " Tokens";

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

@@ -16,7 +16,7 @@ package de.jplag.cpp;
 
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.InputStream;
 
 public class CPPScanner implements CPPTokenConstants {
@@ -24,11 +24,10 @@ public class CPPScanner implements CPPTokenConstants {
 
     public static boolean scanFile(File dir, String fileName, Scanner delegatingScanner) {
         CPPScanner scanner;
-        try {
-            InputStream input = new NewlineStream(new FileInputStream(new File(dir, fileName)));
+        try(InputStream input = new NewlineStream(new FileInputStream(new File(dir, fileName)))) {
             scanner = new CPPScanner(input, "UTF-8");
             scanner.delegatingScanner = delegatingScanner;
-        } catch (FileNotFoundException e) {
+        } catch (IOException e) {
             System.out.println("C/C++ Scanner: File " + fileName + " not found.");
             return false;
         }

+ 12 - 12
jplag.frontend.csharp-6/src/main/antlr4/de/jplag/csharp/grammar/CSharpLexer.g4

@@ -140,19 +140,19 @@ REAL_LITERAL:        ([0-9] ('_'* [0-9])*)? '.' [0-9] ('_'* [0-9])* ExponentPart
 CHARACTER_LITERAL:                   '\'' (~['\\\r\n\u0085\u2028\u2029] | CommonCharacter) '\'';
 REGULAR_STRING:                      '"'  (~["\\\r\n\u0085\u2028\u2029] | CommonCharacter)* '"';
 VERBATIUM_STRING:                    '@"' (~'"' | '""')* '"';
-INTERPOLATED_REGULAR_STRING_START:   '$"' { this.OnInterpolatedRegularStringStart(); } -> pushMode(INTERPOLATION_STRING);
-INTERPOLATED_VERBATIUM_STRING_START: '$@"'  { this.OnInterpolatedVerbatiumStringStart(); }  -> pushMode(INTERPOLATION_STRING);
+INTERPOLATED_REGULAR_STRING_START:   '$"' { this.onInterpolatedRegularStringStart(); } -> pushMode(INTERPOLATION_STRING);
+INTERPOLATED_VERBATIUM_STRING_START: '$@"'  { this.onInterpolatedVerbatiumStringStart(); }  -> pushMode(INTERPOLATION_STRING);
 
 //B.1.9 Operators And Punctuators
-OPEN_BRACE:               '{' { this.OnOpenBrace(); };
-CLOSE_BRACE:              '}' { this.OnCloseBrace(); };
+OPEN_BRACE:               '{' { this.onOpenBrace(); };
+CLOSE_BRACE:              '}' { this.onCloseBrace(); };
 OPEN_BRACKET:             '[';
 CLOSE_BRACKET:            ']';
 OPEN_PARENS:              '(';
 CLOSE_PARENS:             ')';
 DOT:                      '.';
 COMMA:                    ',';
-COLON:                    ':' { this.OnColon(); };
+COLON:                    ':' { this.onColon(); };
 SEMICOLON:                ';';
 PLUS:                     '+';
 MINUS:                    '-';
@@ -196,17 +196,17 @@ OP_RANGE:                 '..';
 mode INTERPOLATION_STRING;
 
 DOUBLE_CURLY_INSIDE:           '{{';
-OPEN_BRACE_INSIDE:             '{' { this.OpenBraceInside(); } -> skip, pushMode(DEFAULT_MODE);
-REGULAR_CHAR_INSIDE:           { this.IsRegularCharInside() }? SimpleEscapeSequence;
-VERBATIUM_DOUBLE_QUOTE_INSIDE: { this.IsVerbatiumDoubleQuoteInside() }? '""';
-DOUBLE_QUOTE_INSIDE:           '"' { this.OnDoubleQuoteInside(); } -> popMode;
-REGULAR_STRING_INSIDE:         { this.IsRegularCharInside() }? ~('{' | '\\' | '"')+;
-VERBATIUM_INSIDE_STRING:       { this.IsVerbatiumDoubleQuoteInside() }? ~('{' | '"')+;
+OPEN_BRACE_INSIDE:             '{' { this.openBraceInside(); } -> skip, pushMode(DEFAULT_MODE);
+REGULAR_CHAR_INSIDE:           { this.isRegularCharInside() }? SimpleEscapeSequence;
+VERBATIUM_DOUBLE_QUOTE_INSIDE: { this.isVerbatiumDoubleQuoteInside() }? '""';
+DOUBLE_QUOTE_INSIDE:           '"' { this.onDoubleQuoteInside(); } -> popMode;
+REGULAR_STRING_INSIDE:         { this.isRegularCharInside() }? ~('{' | '\\' | '"')+;
+VERBATIUM_INSIDE_STRING:       { this.isVerbatiumDoubleQuoteInside() }? ~('{' | '"')+;
 
 mode INTERPOLATION_FORMAT;
 
 DOUBLE_CURLY_CLOSE_INSIDE:      '}}' -> type(FORMAT_STRING);
-CLOSE_BRACE_INSIDE:             '}' { this.OnCloseBraceInside(); }   -> skip, popMode;
+CLOSE_BRACE_INSIDE:             '}' { this.onCloseBraceInside(); }   -> skip, popMode;
 FORMAT_STRING:                  ~'}'+;
 
 mode DIRECTIVE_MODE;

+ 1 - 1
jplag.frontend.csharp-6/src/main/antlr4/de/jplag/csharp/grammar/CSharpParser.g4

@@ -473,7 +473,7 @@ block
 	;
 
 local_variable_declaration
-	: (USING | REF | REF READONLY)? local_variable_type local_variable_declarator ( ','  local_variable_declarator { this.IsLocalVariableDeclaration() }? )*
+	: (USING | REF | REF READONLY)? local_variable_type local_variable_declarator ( ','  local_variable_declarator { this.isLocalVariableDeclaration() }? )*
 	| FIXED pointer_type fixed_pointer_declarators
 	;
 

+ 22 - 22
jplag.frontend.csharp-6/src/main/antlr4/de/jplag/csharp/grammar/CSharpPreprocessorParser.g4

@@ -7,19 +7,19 @@ parser grammar CSharpPreprocessorParser;
 options { tokenVocab=CSharpLexer; superClass=CSharpPreprocessorParserBase; }
 
 preprocessor_directive returns [Boolean value]
-	: DEFINE CONDITIONAL_SYMBOL directive_new_line_or_sharp { this.OnPreprocessorDirectiveDefine(); }  #preprocessorDeclaration
-	| UNDEF CONDITIONAL_SYMBOL directive_new_line_or_sharp { this.OnPreprocessorDirectiveUndef(); } #preprocessorDeclaration
-	| IF expr=preprocessor_expression directive_new_line_or_sharp { this.OnPreprocessorDirectiveIf(); }	  #preprocessorConditional
-	| ELIF expr=preprocessor_expression directive_new_line_or_sharp { this.OnPreprocessorDirectiveElif(); } #preprocessorConditional
-	| ELSE directive_new_line_or_sharp { this.OnPreprocessorDirectiveElse(); }    #preprocessorConditional
-	| ENDIF directive_new_line_or_sharp { this.OnPreprocessorDirectiveEndif(); } #preprocessorConditional
-	| LINE (DIGITS STRING? | DEFAULT | DIRECTIVE_HIDDEN) directive_new_line_or_sharp { this.OnPreprocessorDirectiveLine(); } #preprocessorLine
-	| ERROR TEXT directive_new_line_or_sharp { this.OnPreprocessorDirectiveError(); }   #preprocessorDiagnostic
-	| WARNING TEXT directive_new_line_or_sharp { this.OnPreprocessorDirectiveWarning(); }   #preprocessorDiagnostic
-	| REGION TEXT? directive_new_line_or_sharp { this.OnPreprocessorDirectiveRegion(); }   #preprocessorRegion
-	| ENDREGION TEXT? directive_new_line_or_sharp { this.OnPreprocessorDirectiveEndregion(); }   #preprocessorRegion
-	| PRAGMA TEXT directive_new_line_or_sharp { this.OnPreprocessorDirectivePragma(); }   #preprocessorPragma
-	| NULLABLE TEXT directive_new_line_or_sharp { this.OnPreprocessorDirectiveNullable(); }   #preprocessorNullable
+	: DEFINE CONDITIONAL_SYMBOL directive_new_line_or_sharp { this.onPreprocessorDirectiveDefine(); }  #preprocessorDeclaration
+	| UNDEF CONDITIONAL_SYMBOL directive_new_line_or_sharp { this.onPreprocessorDirectiveUndef(); } #preprocessorDeclaration
+	| IF expr=preprocessor_expression directive_new_line_or_sharp { this.onPreprocessorDirectiveIf(); }	  #preprocessorConditional
+	| ELIF expr=preprocessor_expression directive_new_line_or_sharp { this.onPreprocessorDirectiveElif(); } #preprocessorConditional
+	| ELSE directive_new_line_or_sharp { this.onPreprocessorDirectiveElse(); }    #preprocessorConditional
+	| ENDIF directive_new_line_or_sharp { this.onPreprocessorDirectiveEndif(); } #preprocessorConditional
+	| LINE (DIGITS STRING? | DEFAULT | DIRECTIVE_HIDDEN) directive_new_line_or_sharp { this.onPreprocessorDirectiveLine(); } #preprocessorLine
+	| ERROR TEXT directive_new_line_or_sharp { this.onPreprocessorDirectiveError(); }   #preprocessorDiagnostic
+	| WARNING TEXT directive_new_line_or_sharp { this.onPreprocessorDirectiveWarning(); }   #preprocessorDiagnostic
+	| REGION TEXT? directive_new_line_or_sharp { this.onPreprocessorDirectiveRegion(); }   #preprocessorRegion
+	| ENDREGION TEXT? directive_new_line_or_sharp { this.onPreprocessorDirectiveEndregion(); }   #preprocessorRegion
+	| PRAGMA TEXT directive_new_line_or_sharp { this.onPreprocessorDirectivePragma(); }   #preprocessorPragma
+	| NULLABLE TEXT directive_new_line_or_sharp { this.onPreprocessorDirectiveNullable(); }   #preprocessorNullable
 	;
 
 directive_new_line_or_sharp
@@ -28,13 +28,13 @@ directive_new_line_or_sharp
     ;
 
 preprocessor_expression returns [String value]
-	: TRUE { this.OnPreprocessorExpressionTrue(); }
-	| FALSE { this.OnPreprocessorExpressionFalse(); }
-	| CONDITIONAL_SYMBOL { this.OnPreprocessorExpressionConditionalSymbol(); }
-	| OPEN_PARENS expr=preprocessor_expression CLOSE_PARENS { this.OnPreprocessorExpressionConditionalOpenParens(); }
-	| BANG expr=preprocessor_expression { this.OnPreprocessorExpressionConditionalBang(); }
-	| expr1=preprocessor_expression OP_EQ expr2=preprocessor_expression { this.OnPreprocessorExpressionConditionalEq(); }
-	| expr1=preprocessor_expression OP_NE expr2=preprocessor_expression { this.OnPreprocessorExpressionConditionalNe(); }
-	| expr1=preprocessor_expression OP_AND expr2=preprocessor_expression { this.OnPreprocessorExpressionConditionalAnd(); }
-	| expr1=preprocessor_expression OP_OR expr2=preprocessor_expression { this.OnPreprocessorExpressionConditionalOr(); }
+	: TRUE { this.onPreprocessorExpressionTrue(); }
+	| FALSE { this.onPreprocessorExpressionFalse(); }
+	| CONDITIONAL_SYMBOL { this.onPreprocessorExpressionConditionalSymbol(); }
+	| OPEN_PARENS expr=preprocessor_expression CLOSE_PARENS { this.onPreprocessorExpressionConditionalOpenParens(); }
+	| BANG expr=preprocessor_expression { this.onPreprocessorExpressionConditionalBang(); }
+	| expr1=preprocessor_expression OP_EQ expr2=preprocessor_expression { this.onPreprocessorExpressionConditionalEq(); }
+	| expr1=preprocessor_expression OP_NE expr2=preprocessor_expression { this.onPreprocessorExpressionConditionalNe(); }
+	| expr1=preprocessor_expression OP_AND expr2=preprocessor_expression { this.onPreprocessorExpressionConditionalAnd(); }
+	| expr1=preprocessor_expression OP_OR expr2=preprocessor_expression { this.onPreprocessorExpressionConditionalOr(); }
 	;

+ 14 - 14
jplag.frontend.csharp-6/src/main/java/de/jplag/csharp/grammar/CSharpLexerBase.java

@@ -2,7 +2,8 @@ package de.jplag.csharp.grammar;
 
 import java.util.Stack;
 
-import org.antlr.v4.runtime.*;
+import org.antlr.v4.runtime.CharStream;
+import org.antlr.v4.runtime.Lexer;
 
 /**
  * This class was taken from <a href="https://github.com/antlr/grammars-v4/tree/master/csharp">antlr/grammars-v4</a>. It
@@ -14,29 +15,29 @@ abstract class CSharpLexerBase extends Lexer {
     }
 
     protected int interpolatedStringLevel;
-    protected Stack<Boolean> interpolatedVerbatiums = new Stack<Boolean>();
-    protected Stack<Integer> curlyLevels = new Stack<Integer>();
+    protected Stack<Boolean> interpolatedVerbatiums = new Stack<>();
+    protected Stack<Integer> curlyLevels = new Stack<>();
     protected boolean verbatium;
 
-    protected void OnInterpolatedRegularStringStart() {
+    protected void onInterpolatedRegularStringStart() {
         interpolatedStringLevel++;
         interpolatedVerbatiums.push(false);
         verbatium = false;
     }
 
-    protected void OnInterpolatedVerbatiumStringStart() {
+    protected void onInterpolatedVerbatiumStringStart() {
         interpolatedStringLevel++;
         interpolatedVerbatiums.push(true);
         verbatium = true;
     }
 
-    protected void OnOpenBrace() {
+    protected void onOpenBrace() {
         if (interpolatedStringLevel > 0) {
             curlyLevels.push(curlyLevels.pop() + 1);
         }
     }
 
-    protected void OnCloseBrace() {
+    protected void onCloseBrace() {
 
         if (interpolatedStringLevel > 0) {
             curlyLevels.push(curlyLevels.pop() - 1);
@@ -48,8 +49,7 @@ abstract class CSharpLexerBase extends Lexer {
         }
     }
 
-    protected void OnColon() {
-
+    protected void onColon() {
         if (interpolatedStringLevel > 0) {
             int ind = 1;
             boolean switchToFormatString = true;
@@ -66,25 +66,25 @@ abstract class CSharpLexerBase extends Lexer {
         }
     }
 
-    protected void OpenBraceInside() {
+    protected void openBraceInside() {
         curlyLevels.push(1);
     }
 
-    protected void OnDoubleQuoteInside() {
+    protected void onDoubleQuoteInside() {
         interpolatedStringLevel--;
         interpolatedVerbatiums.pop();
         verbatium = (interpolatedVerbatiums.size() > 0 ? interpolatedVerbatiums.peek() : false);
     }
 
-    protected void OnCloseBraceInside() {
+    protected void onCloseBraceInside() {
         curlyLevels.pop();
     }
 
-    protected boolean IsRegularCharInside() {
+    protected boolean isRegularCharInside() {
         return !verbatium;
     }
 
-    protected boolean IsVerbatiumDoubleQuoteInside() {
+    protected boolean isVerbatiumDoubleQuoteInside() {
         return verbatium;
     }
 }

+ 11 - 8
jplag.frontend.csharp-6/src/main/java/de/jplag/csharp/grammar/CSharpParserBase.java

@@ -3,6 +3,9 @@ package de.jplag.csharp.grammar;
 import org.antlr.v4.runtime.Parser;
 import org.antlr.v4.runtime.TokenStream;
 
+import de.jplag.csharp.grammar.CSharpParser.Local_variable_declarationContext;
+import de.jplag.csharp.grammar.CSharpParser.Local_variable_typeContext;
+
 /**
  * This class was taken from <a href="https://github.com/antlr/grammars-v4/tree/master/csharp">antlr/grammars-v4</a>. It
  * was originally written by Ken Domino. Note that this class is licensed under Eclipse Public License - v 1.0.
@@ -12,15 +15,15 @@ public abstract class CSharpParserBase extends Parser {
         super(input);
     }
 
-    protected boolean IsLocalVariableDeclaration() {
-        CSharpParser.Local_variable_declarationContext local_var_decl = (CSharpParser.Local_variable_declarationContext) this._ctx;
-        if (local_var_decl == null)
+    protected boolean isLocalVariableDeclaration() {
+        Local_variable_declarationContext localVariable = (Local_variable_declarationContext) this._ctx;
+        if (localVariable == null) {
             return true;
-        CSharpParser.Local_variable_typeContext local_variable_type = local_var_decl.local_variable_type();
-        if (local_variable_type == null)
+        }
+        Local_variable_typeContext localVariableType = localVariable.local_variable_type();
+        if (localVariableType == null) {
             return true;
-        if (local_variable_type.getText().equals("var"))
-            return false;
-        return true;
+        }
+        return !localVariableType.getText().equals("var");
     }
 }

+ 113 - 105
jplag.frontend.csharp-6/src/main/java/de/jplag/csharp/grammar/CSharpPreprocessorParserBase.java

@@ -8,174 +8,182 @@ import org.antlr.v4.runtime.Parser;
 import org.antlr.v4.runtime.ParserRuleContext;
 import org.antlr.v4.runtime.TokenStream;
 
+import de.jplag.csharp.grammar.CSharpPreprocessorParser.PreprocessorConditionalContext;
+import de.jplag.csharp.grammar.CSharpPreprocessorParser.PreprocessorDeclarationContext;
+import de.jplag.csharp.grammar.CSharpPreprocessorParser.PreprocessorDiagnosticContext;
+import de.jplag.csharp.grammar.CSharpPreprocessorParser.PreprocessorLineContext;
+import de.jplag.csharp.grammar.CSharpPreprocessorParser.PreprocessorNullableContext;
+import de.jplag.csharp.grammar.CSharpPreprocessorParser.PreprocessorPragmaContext;
+import de.jplag.csharp.grammar.CSharpPreprocessorParser.PreprocessorRegionContext;
+import de.jplag.csharp.grammar.CSharpPreprocessorParser.Preprocessor_expressionContext;
+
 /**
  * This class was taken from <a href="https://github.com/antlr/grammars-v4/tree/master/csharp">antlr/grammars-v4</a>. It
  * was originally written by Ken Domino. Note that this class is licensed under Eclipse Public License - v 1.0.
  */
-@SuppressWarnings("java:S100")
 abstract class CSharpPreprocessorParserBase extends Parser {
+    private static final String FALSE = Boolean.toString(false);
+    private static final String TRUE = Boolean.toString(true);
+    private static final String DEBUG = "DEBUG";
+
+    private final Stack<Boolean> conditions = new Stack<>();
+    private final HashSet<String> conditionalSymbols = new HashSet<>();
+
     protected CSharpPreprocessorParserBase(TokenStream input) {
         super(input);
         conditions.push(true);
-        ConditionalSymbols.add("DEBUG");
+        conditionalSymbols.add(DEBUG);
     }
 
-    Stack<Boolean> conditions = new Stack<>();
-    public HashSet<String> ConditionalSymbols = new HashSet<String>();
-
-    protected Boolean AllConditions() {
-        for (Boolean condition : conditions) {
-            if (!condition)
-                return false;
-        }
-        return true;
+    protected Boolean allConditions() {
+        return conditions.stream().allMatch(it -> it);
     }
 
-    protected void OnPreprocessorDirectiveDefine() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.PreprocessorDeclarationContext d = (CSharpPreprocessorParser.PreprocessorDeclarationContext) c;
-        ConditionalSymbols.add(d.CONDITIONAL_SYMBOL().getText());
-        d.value = AllConditions();
+    protected void onPreprocessorDirectiveDefine() {
+        ParserRuleContext ruleContext = this._ctx;
+        PreprocessorDeclarationContext context = (PreprocessorDeclarationContext) ruleContext;
+        conditionalSymbols.add(context.CONDITIONAL_SYMBOL().getText());
+        context.value = allConditions();
     }
 
-    protected void OnPreprocessorDirectiveUndef() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.PreprocessorDeclarationContext d = (CSharpPreprocessorParser.PreprocessorDeclarationContext) c;
-        ConditionalSymbols.remove(d.CONDITIONAL_SYMBOL().getText());
-        d.value = AllConditions();
+    protected void onPreprocessorDirectiveUndef() {
+        ParserRuleContext ruleContext = this._ctx;
+        PreprocessorDeclarationContext context = (PreprocessorDeclarationContext) ruleContext;
+        conditionalSymbols.remove(context.CONDITIONAL_SYMBOL().getText());
+        context.value = allConditions();
     }
 
-    protected void OnPreprocessorDirectiveIf() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.PreprocessorConditionalContext d = (CSharpPreprocessorParser.PreprocessorConditionalContext) c;
-        d.value = d.expr.value.equals("true") && AllConditions();
-        conditions.push(d.expr.value.equals("true"));
+    protected void onPreprocessorDirectiveIf() {
+        ParserRuleContext ruleContext = this._ctx;
+        PreprocessorConditionalContext context = (PreprocessorConditionalContext) ruleContext;
+        context.value = context.expr.value.equals(TRUE) && allConditions();
+        conditions.push(context.expr.value.equals(TRUE));
     }
 
-    protected void OnPreprocessorDirectiveElif() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.PreprocessorConditionalContext d = (CSharpPreprocessorParser.PreprocessorConditionalContext) c;
+    protected void onPreprocessorDirectiveElif() {
+        ParserRuleContext ruleContext = this._ctx;
+        PreprocessorConditionalContext context = (PreprocessorConditionalContext) ruleContext;
         if (Boolean.FALSE.equals(conditions.peek())) {
             conditions.pop();
-            d.value = d.expr.value.equals("true") && AllConditions();
-            conditions.push(d.expr.value.equals("true"));
+            context.value = context.expr.value.equals(TRUE) && allConditions();
+            conditions.push(context.expr.value.equals(TRUE));
         } else {
-            d.value = false;
+            context.value = false;
         }
     }
 
-    protected void OnPreprocessorDirectiveElse() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.PreprocessorConditionalContext d = (CSharpPreprocessorParser.PreprocessorConditionalContext) c;
+    protected void onPreprocessorDirectiveElse() {
+        ParserRuleContext ruleContext = this._ctx;
+        PreprocessorConditionalContext context = (PreprocessorConditionalContext) ruleContext;
         if (Boolean.FALSE.equals(conditions.peek())) {
             conditions.pop();
-            d.value = AllConditions();
+            context.value = allConditions();
             conditions.push(true);
         } else {
-            d.value = false;
+            context.value = false;
         }
     }
 
-    protected void OnPreprocessorDirectiveEndif() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.PreprocessorConditionalContext d = (CSharpPreprocessorParser.PreprocessorConditionalContext) c;
+    protected void onPreprocessorDirectiveEndif() {
+        ParserRuleContext ruleContext = this._ctx;
+        PreprocessorConditionalContext context = (PreprocessorConditionalContext) ruleContext;
         conditions.pop();
-        d.value = conditions.peek();
+        context.value = conditions.peek();
     }
 
-    protected void OnPreprocessorDirectiveLine() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.PreprocessorLineContext d = (CSharpPreprocessorParser.PreprocessorLineContext) c;
-        d.value = AllConditions();
+    protected void onPreprocessorDirectiveLine() {
+        ParserRuleContext ruleContext = this._ctx;
+        PreprocessorLineContext context = (PreprocessorLineContext) ruleContext;
+        context.value = allConditions();
     }
 
-    protected void OnPreprocessorDirectiveError() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.PreprocessorDiagnosticContext d = (CSharpPreprocessorParser.PreprocessorDiagnosticContext) c;
-        d.value = AllConditions();
+    protected void onPreprocessorDirectiveError() {
+        ParserRuleContext ruleContext = this._ctx;
+        PreprocessorDiagnosticContext context = (PreprocessorDiagnosticContext) ruleContext;
+        context.value = allConditions();
     }
 
-    protected void OnPreprocessorDirectiveWarning() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.PreprocessorDiagnosticContext d = (CSharpPreprocessorParser.PreprocessorDiagnosticContext) c;
-        d.value = AllConditions();
+    protected void onPreprocessorDirectiveWarning() {
+        ParserRuleContext ruleContext = this._ctx;
+        PreprocessorDiagnosticContext context = (PreprocessorDiagnosticContext) ruleContext;
+        context.value = allConditions();
     }
 
-    protected void OnPreprocessorDirectiveRegion() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.PreprocessorRegionContext d = (CSharpPreprocessorParser.PreprocessorRegionContext) c;
-        d.value = AllConditions();
+    protected void onPreprocessorDirectiveRegion() {
+        ParserRuleContext ruleContext = this._ctx;
+        PreprocessorRegionContext context = (PreprocessorRegionContext) ruleContext;
+        context.value = allConditions();
     }
 
-    protected void OnPreprocessorDirectiveEndregion() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.PreprocessorRegionContext d = (CSharpPreprocessorParser.PreprocessorRegionContext) c;
-        d.value = AllConditions();
+    protected void onPreprocessorDirectiveEndregion() {
+        ParserRuleContext ruleContext = this._ctx;
+        PreprocessorRegionContext context = (PreprocessorRegionContext) ruleContext;
+        context.value = allConditions();
     }
 
-    protected void OnPreprocessorDirectivePragma() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.PreprocessorPragmaContext d = (CSharpPreprocessorParser.PreprocessorPragmaContext) c;
-        d.value = AllConditions();
+    protected void onPreprocessorDirectivePragma() {
+        ParserRuleContext ruleContext = this._ctx;
+        PreprocessorPragmaContext context = (PreprocessorPragmaContext) ruleContext;
+        context.value = allConditions();
     }
 
-    protected void OnPreprocessorDirectiveNullable() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.PreprocessorNullableContext d = (CSharpPreprocessorParser.PreprocessorNullableContext) c;
-        d.value = AllConditions();
+    protected void onPreprocessorDirectiveNullable() {
+        ParserRuleContext ruleContext = this._ctx;
+        PreprocessorNullableContext context = (PreprocessorNullableContext) ruleContext;
+        context.value = allConditions();
     }
 
-    protected void OnPreprocessorExpressionTrue() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.Preprocessor_expressionContext d = (CSharpPreprocessorParser.Preprocessor_expressionContext) c;
-        d.value = "true";
+    protected void onPreprocessorExpressionTrue() {
+        ParserRuleContext ruleContext = this._ctx;
+        Preprocessor_expressionContext context = (Preprocessor_expressionContext) ruleContext;
+        context.value = TRUE;
     }
 
-    protected void OnPreprocessorExpressionFalse() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.Preprocessor_expressionContext d = (CSharpPreprocessorParser.Preprocessor_expressionContext) c;
-        d.value = "false";
+    protected void onPreprocessorExpressionFalse() {
+        ParserRuleContext ruleContext = this._ctx;
+        Preprocessor_expressionContext context = (Preprocessor_expressionContext) ruleContext;
+        context.value = FALSE;
     }
 
-    protected void OnPreprocessorExpressionConditionalSymbol() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.Preprocessor_expressionContext d = (CSharpPreprocessorParser.Preprocessor_expressionContext) c;
-        d.value = ConditionalSymbols.contains(d.CONDITIONAL_SYMBOL().getText()) ? "true" : "false";
+    protected void onPreprocessorExpressionConditionalSymbol() {
+        ParserRuleContext ruleContext = this._ctx;
+        Preprocessor_expressionContext context = (Preprocessor_expressionContext) ruleContext;
+        context.value = conditionalSymbols.contains(context.CONDITIONAL_SYMBOL().getText()) ? TRUE : FALSE;
     }
 
-    protected void OnPreprocessorExpressionConditionalOpenParens() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.Preprocessor_expressionContext d = (CSharpPreprocessorParser.Preprocessor_expressionContext) c;
-        d.value = d.expr.value;
+    protected void onPreprocessorExpressionConditionalOpenParens() {
+        ParserRuleContext ruleContext = this._ctx;
+        Preprocessor_expressionContext context = (Preprocessor_expressionContext) ruleContext;
+        context.value = context.expr.value;
     }
 
-    protected void OnPreprocessorExpressionConditionalBang() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.Preprocessor_expressionContext d = (CSharpPreprocessorParser.Preprocessor_expressionContext) c;
-        d.value = d.expr.value.equals("true") ? "false" : "true";
+    protected void onPreprocessorExpressionConditionalBang() {
+        ParserRuleContext ruleContext = this._ctx;
+        Preprocessor_expressionContext context = (Preprocessor_expressionContext) ruleContext;
+        context.value = context.expr.value.equals(TRUE) ? FALSE : TRUE;
     }
 
-    protected void OnPreprocessorExpressionConditionalEq() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.Preprocessor_expressionContext d = (CSharpPreprocessorParser.Preprocessor_expressionContext) c;
-        d.value = (Objects.equals(d.expr1.value, d.expr2.value) ? "true" : "false");
+    protected void onPreprocessorExpressionConditionalEq() {
+        ParserRuleContext ruleContext = this._ctx;
+        Preprocessor_expressionContext context = (Preprocessor_expressionContext) ruleContext;
+        context.value = (Objects.equals(context.expr1.value, context.expr2.value) ? TRUE : FALSE);
     }
 
-    protected void OnPreprocessorExpressionConditionalNe() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.Preprocessor_expressionContext d = (CSharpPreprocessorParser.Preprocessor_expressionContext) c;
-        d.value = (!Objects.equals(d.expr1.value, d.expr2.value) ? "true" : "false");
+    protected void onPreprocessorExpressionConditionalNe() {
+        ParserRuleContext ruleContext = this._ctx;
+        Preprocessor_expressionContext context = (Preprocessor_expressionContext) ruleContext;
+        context.value = (!Objects.equals(context.expr1.value, context.expr2.value) ? TRUE : FALSE);
     }
 
-    protected void OnPreprocessorExpressionConditionalAnd() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.Preprocessor_expressionContext d = (CSharpPreprocessorParser.Preprocessor_expressionContext) c;
-        d.value = (d.expr1.value.equals("true") && d.expr2.value.equals("true") ? "true" : "false");
+    protected void onPreprocessorExpressionConditionalAnd() {
+        ParserRuleContext ruleContext = this._ctx;
+        Preprocessor_expressionContext context = (Preprocessor_expressionContext) ruleContext;
+        context.value = (context.expr1.value.equals(TRUE) && context.expr2.value.equals(TRUE) ? TRUE : FALSE);
     }
 
-    protected void OnPreprocessorExpressionConditionalOr() {
-        ParserRuleContext c = this._ctx;
-        CSharpPreprocessorParser.Preprocessor_expressionContext d = (CSharpPreprocessorParser.Preprocessor_expressionContext) c;
-        d.value = (d.expr1.value.equals("true") || d.expr2.value.equals("true") ? "true" : "false");
+    protected void onPreprocessorExpressionConditionalOr() {
+        ParserRuleContext ruleContext = this._ctx;
+        Preprocessor_expressionContext context = (Preprocessor_expressionContext) ruleContext;
+        context.value = (context.expr1.value.equals(TRUE) || context.expr2.value.equals(TRUE) ? TRUE : FALSE);
     }
 }

+ 16 - 0
jplag.frontend.emf-metamodel-dynamic/README.md

@@ -0,0 +1,16 @@
+# Dynamic EMF metamodel language frontend
+The dynamic EMF metamodel language frontend allows the use of JPlag with metamodel submissions.
+It is based on the EMF API.
+
+### EMF specification compatibility
+This frontend is based on the EMF dependencies available on maven central. These might not be the newest versions of EMF. For details, the [JPlag aggregator pom](https://github.com/jplag/JPlag/blob/263e85e544152cc8b0caa3399127debb7a458746/pom.xml#L84-L86).
+
+### Token Extraction
+For the token extraction, we visit the containment tree of the metamodel and extract tokens for certain metamodel elements based on their metaclass. In this module, we extract tokens based on a [handcrafted token set](https://github.com/jplag/JPlag/blob/263e85e544152cc8b0caa3399127debb7a458746/jplag.frontend.emf-metamodel/src/main/java/de/jplag/emf/MetamodelTokenConstants.java). Note that not for all concrete metaclasses tokens are extracted. `EFactory`, `EGenericType`, and `EObject` are ignored. Moreover, for some metaclasses, multiple token types are extracted. Finally, some references are also used for token extraction.
+ 
+
+### Usage
+To use this frontend, add the `-l emf-metamodel` flag in the CLI, or use a `JPlagOption` object set to `LanguageOption.EMF` in the Java API as described in the usage information in the [readme of the main project](https://github.com/jplag/JPlag#usage) and [in the wiki](https://github.com/jplag/JPlag/wiki/1.-How-to-Use-JPlag).
+
+### More Info
+More information can be found in the paper *"Token-based Plagiarism Detection for Metamodels"* (MODELS-C 2022, accepted for publication, link coming soon). 

+ 15 - 0
jplag.frontend.emf-metamodel/README.md

@@ -0,0 +1,15 @@
+# EMF metamodel language frontend
+The EMF metamodel language frontend allows the use of JPlag with metamodel submissions.
+It is based on the EMF API.
+
+### EMF specification compatibility
+This frontend is based on the EMF dependencies available on maven central. These might not be the newest versions of EMF. For details, the [JPlag aggregator pom](https://github.com/jplag/JPlag/blob/263e85e544152cc8b0caa3399127debb7a458746/pom.xml#L84-L86).
+
+### Token Extraction
+For the token extraction, we visit the containment tree of the metamodel and extract tokens for all metamodel elements based on their concrete metaclass. In this module, we thus extract tokens based on a [dynamic token set](https://github.com/jplag/JPlag/blob/263e85e544152cc8b0caa3399127debb7a458746/jplag.frontend.emf-metamodel-dynamic/src/main/java/de/jplag/emf/dynamic/DynamicMetamodelTokenConstants.java).
+
+### Usage
+To use this frontend, add the `-l emf-metamodel-dynamic` flag in the CLI, or use a `JPlagOption` object set to `LanguageOption.EMF_DYNAMIC` in the Java API as described in the usage information in the [readme of the main project](https://github.com/jplag/JPlag#usage) and [in the wiki](https://github.com/jplag/JPlag/wiki/1.-How-to-Use-JPlag).
+
+### More Info
+More information can be found in the paper *"Token-based Plagiarism Detection for Metamodels"* (MODELS-C 2022, accepted for publication, link coming soon). 

+ 1 - 2
jplag.frontend.scala/pom.xml

@@ -23,14 +23,13 @@
         <dependency>
             <groupId>de.jplag</groupId>
             <artifactId>frontend-utils</artifactId>
-            <version>${revision}</version>
         </dependency>
 
         <!-- https://mvnrepository.com/artifact/org.scalameta/scalameta -->
         <dependency>
             <groupId>org.scalameta</groupId>
             <artifactId>scalameta_${scala.compat.version}</artifactId>
-            <version>4.5.11</version>
+            <version>4.5.13</version>
         </dependency>
 
         <!-- Test -->

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

@@ -178,7 +178,7 @@ public class SubmissionSet {
         }
 
         long duration = System.currentTimeMillis() - startTime;
-        String timePerSubmission = submissions.size() > 0 ? Long.toString(duration / submissions.size()) : "n/a";
+        String timePerSubmission = submissions.isEmpty() ? "n/a" : Long.toString(duration / submissions.size());
         logger.trace("Total time for parsing: " + TimeUtil.formatDuration(duration));
         logger.trace("Time per parsed submission: " + timePerSubmission + " msec");
     }

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

@@ -4,7 +4,6 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.function.Function;
-import java.util.stream.Collectors;
 
 import org.apache.commons.math3.linear.Array2DRowRealMatrix;
 import org.apache.commons.math3.linear.RealMatrix;
@@ -59,9 +58,9 @@ public class ClusteringAdapter {
         Collection<Collection<Integer>> intResult = algorithm.cluster(similarityMatrix);
         ClusteringResult<Integer> modularityClusterResult = ClusteringResult.fromIntegerCollections(new ArrayList<>(intResult), similarityMatrix);
         List<Cluster<Submission>> mappedClusters = modularityClusterResult.getClusters().stream()
-                .map(unmappedCluster -> new Cluster<>(unmappedCluster.getMembers().stream().map(mapping::unmap).collect(Collectors.toList()),
+                .map(unmappedCluster -> new Cluster<>(unmappedCluster.getMembers().stream().map(mapping::unmap).toList(),
                         unmappedCluster.getCommunityStrength(), unmappedCluster.getAverageSimilarity()))
-                .collect(Collectors.toList());
+                .toList();
         return new ClusteringResult<>(mappedClusters, modularityClusterResult.getCommunityStrength());
     }
 

+ 7 - 10
jplag/src/main/java/de/jplag/clustering/IntegerMapping.java

@@ -20,18 +20,15 @@ public class IntegerMapping<T> {
     }
 
     /**
-     * @param value is added to the mapping (if not already present)
+     * @param key is added to the mapping (if not already present)
      * @return the associated integer
      */
-    public int map(T value) {
-        Integer result = mapping.get(value);
-        if (result == null) {
-            int newIndex = size++;
-            mapping.put(value, newIndex);
-            backMapping.add(value);
-            return newIndex;
-        }
-        return result;
+    public int map(T key) {
+        mapping.computeIfAbsent(key, it -> {
+            backMapping.add(it);
+            return size++;
+        });
+        return mapping.get(key);
     }
 
     /**

+ 1 - 3
jplag/src/main/java/de/jplag/options/LanguageOption.java

@@ -1,7 +1,5 @@
 package de.jplag.options;
 
-import static java.util.stream.Collectors.toList;
-
 import java.util.Arrays;
 import java.util.Collection;
 
@@ -45,7 +43,7 @@ public enum LanguageOption {
     }
 
     public static Collection<String> getAllDisplayNames() {
-        return Arrays.stream(LanguageOption.values()).map(languageOption -> languageOption.displayName).collect(toList());
+        return Arrays.stream(LanguageOption.values()).map(languageOption -> languageOption.displayName).toList();
     }
 
     public static LanguageOption getDefault() {

+ 101 - 0
jplag/src/main/java/de/jplag/reporting/jsonfactory/ComparisonReportWriter.java

@@ -0,0 +1,101 @@
+package de.jplag.reporting.jsonfactory;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import de.jplag.*;
+import de.jplag.reporting.reportobject.model.ComparisonReport;
+import de.jplag.reporting.reportobject.model.Match;
+
+/**
+ * Writes {@link ComparisonReport}s of given {@link JPlagResult} to the disk under the specified path. Instantiated with
+ * a function that associates a submission to its id.
+ */
+public class ComparisonReportWriter {
+
+    private final FileWriter fileWriter;
+    private final Function<Submission, String> submissionToIdFunction;
+    private final Map<String, Map<String, String>> submissionIdToComparisonFileName = new HashMap<>();
+
+    public ComparisonReportWriter(Function<Submission, String> submissionToIdFunction, FileWriter fileWriter) {
+        this.submissionToIdFunction = submissionToIdFunction;
+        this.fileWriter = fileWriter;
+    }
+
+    /**
+     * Generates detailed ComparisonReport DTO for each comparison in a JPlagResult and writes them to the disk as json
+     * files.
+     * @param jPlagResult The JPlagResult to generate the comparison reports from. contains information about a comparison
+     * @param path The path to write the comparison files to
+     * @return Nested map that associates each pair of submissions (by their ids) to their comparison file name. The
+     * comparison file name for submission with id id1 and id2 can be fetched by executing get two times:
+     * map.get(id1).get(id2). The nested map is symmetrical therefore, both map.get(id1).get(id2) and map.get(id2).get(id1)
+     * yield the same result.
+     */
+    public Map<String, Map<String, String>> writeComparisonReports(JPlagResult jPlagResult, String path) {
+        int numberOfComparisons = jPlagResult.getOptions().getMaximumNumberOfComparisons();
+        List<JPlagComparison> comparisons = jPlagResult.getComparisons(numberOfComparisons);
+        writeComparisons(path, comparisons);
+        return submissionIdToComparisonFileName;
+    }
+
+    private void writeComparisons(String path, List<JPlagComparison> comparisons) {
+        for (JPlagComparison comparison : comparisons) {
+            String firstSubmissionId = submissionToIdFunction.apply(comparison.getFirstSubmission());
+            String secondSubmissionId = submissionToIdFunction.apply(comparison.getSecondSubmission());
+            String fileName = generateComparisonName(firstSubmissionId, secondSubmissionId);
+            addToLookUp(firstSubmissionId, secondSubmissionId, fileName);
+            var comparisonReport = new ComparisonReport(firstSubmissionId, secondSubmissionId, comparison.similarity(),
+                    convertMatchesToReportMatches(comparison));
+            fileWriter.saveAsJSON(comparisonReport, path, fileName);
+        }
+    }
+
+    private void addToLookUp(String firstSubmissionId, String secondSubmissionId, String fileName) {
+        writeToMap(secondSubmissionId, firstSubmissionId, fileName);
+        writeToMap(firstSubmissionId, secondSubmissionId, fileName);
+    }
+
+    private void writeToMap(String id1, String id2, String comparisonFileName) {
+        submissionIdToComparisonFileName.putIfAbsent(id1, new HashMap<>());
+        submissionIdToComparisonFileName.get(id1).put(id2, comparisonFileName);
+    }
+
+    private String generateComparisonName(String firstSubmissionId, String secondSubmissionId) {
+        String name = concatenate(firstSubmissionId, secondSubmissionId);
+        String finalName = name;
+        long timesNameAlreadyExists = submissionIdToComparisonFileName.values().stream().filter(map -> map.containsValue(finalName)).count();
+        if (timesNameAlreadyExists > 0) {
+            name = concatenate(firstSubmissionId, secondSubmissionId, timesNameAlreadyExists + 1);
+        }
+        return name;
+
+    }
+
+    private String concatenate(String firstSubmissionId, String secondSubmissionId, long index) {
+        return firstSubmissionId.concat("-").concat(secondSubmissionId).concat(index > 0 ? "-" + index : "").concat(".json");
+    }
+
+    private String concatenate(String firstSubmissionId, String secondSubmissionId) {
+        return concatenate(firstSubmissionId, secondSubmissionId, 0);
+    }
+
+    private List<Match> convertMatchesToReportMatches(JPlagComparison comparison) {
+        return comparison.getMatches().stream().map(match -> convertMatchToReportMatch(comparison, match)).toList();
+    }
+
+    private Match convertMatchToReportMatch(JPlagComparison comparison, de.jplag.Match match) {
+        TokenList tokensFirst = comparison.getFirstSubmission().getTokenList();
+        TokenList tokensSecond = comparison.getSecondSubmission().getTokenList();
+        Token startOfFirst = tokensFirst.getToken(match.startOfFirst());
+        Token endOfFirst = tokensFirst.getToken(match.startOfFirst() + match.length() - 1);
+        Token startOfSecond = tokensSecond.getToken(match.startOfSecond());
+        Token endOfSecond = tokensSecond.getToken(match.startOfSecond() + match.length() - 1);
+
+        return new Match(startOfFirst.getFile(), startOfSecond.getFile(), startOfFirst.getLine(), endOfFirst.getLine(), startOfSecond.getLine(),
+                endOfSecond.getLine(), match.length());
+    }
+
+}

+ 93 - 0
jplag/src/main/java/de/jplag/reporting/jsonfactory/DirectoryManager.java

@@ -0,0 +1,93 @@
+package de.jplag.reporting.jsonfactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Comparator;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides Methods for creating directories.
+ */
+public class DirectoryManager {
+    private static final Logger logger = LoggerFactory.getLogger(DirectoryManager.class);
+
+    /**
+     * Creates a directory.
+     * @param path The path under which the new directory ought to be created
+     * @param name The name of the new directory
+     * @return The created directory
+     */
+    public static File createDirectory(String path, String name) throws IOException {
+        File directory = new File(path.concat("/").concat(name));
+        if (!directory.exists() && !directory.mkdirs()) {
+            throw new IOException("Failed to create dir.");
+        }
+        return directory;
+    }
+
+    /**
+     * Create a directory with the given path
+     * @param path The path of the new directory
+     */
+    public static void createDirectory(String path) throws IOException {
+        createDirectory(path, "");
+    }
+
+    /**
+     * Delete the directory and all of its contents, identified by the given path
+     * @param path The path that identifies the directory to delete
+     */
+    public static void deleteDirectory(String path) {
+        try (var f = Files.walk(Path.of(path))) {
+            f.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
+        } catch (IOException e) {
+            logger.error("Could not delete folder " + path, e);
+        }
+    }
+
+    /**
+     * Zip the directory identified by the given path
+     * @param path The path that identifies the directory to zip
+     * @return True if zip was successful, false otherwise
+     */
+    public static boolean zipDirectory(String path) {
+        Path p = Path.of(path);
+        String zipName = path + ".zip";
+
+        try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipName))) {
+
+            Files.walkFileTree(p, new SimpleFileVisitor<>() {
+                @Override
+                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                    Path targetFile = p.relativize(file);
+                    zos.putNextEntry(new ZipEntry(targetFile.toString()));
+
+                    byte[] bytes = Files.readAllBytes(file);
+                    zos.write(bytes, 0, bytes.length);
+                    return FileVisitResult.CONTINUE;
+                }
+
+                @Override
+                public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
+                    logger.error("Unable to zip " + file, exc);
+                    throw exc;
+                }
+            });
+        } catch (IOException e) {
+            logger.error(e.getMessage(), e);
+            deleteDirectory(zipName);
+            return false;
+        }
+        return true;
+    }
+}

+ 13 - 0
jplag/src/main/java/de/jplag/reporting/jsonfactory/DummyWriter.java

@@ -0,0 +1,13 @@
+package de.jplag.reporting.jsonfactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DummyWriter implements FileWriter {
+    private static final Logger logger = LoggerFactory.getLogger(DummyWriter.class);
+
+    @Override
+    public void saveAsJSON(Object fileToSave, String folderPath, String fileName) {
+        logger.info("DummyWriter writes object " + fileToSave + " to path " + folderPath + " with name " + fileName + " as JSON.");
+    }
+}

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

@@ -1,29 +1,5 @@
 package de.jplag.reporting.jsonfactory;
 
-import java.io.IOException;
-import java.nio.file.Path;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-public class FileWriter {
-    private static final Logger logger = LoggerFactory.getLogger(FileWriter.class);
-
-    private static final ObjectMapper objectMapper = new ObjectMapper();
-
-    /**
-     * Saves the provided object to the provided path under the provided name
-     * @param fileToSave The object to save
-     * @param folderPath The path to save the object to
-     * @param fileName The name to save the object under
-     */
-    public void saveAsJSON(Object fileToSave, String folderPath, String fileName) {
-        try {
-            objectMapper.writeValue(Path.of(folderPath, fileName).toFile(), fileToSave);
-        } catch (IOException e) {
-            logger.error("Failed to save json file " + fileName + ": " + e.getMessage(), e);
-        }
-    }
+public interface FileWriter {
+    void saveAsJSON(Object fileToSave, String folderPath, String fileName);
 }

+ 30 - 0
jplag/src/main/java/de/jplag/reporting/jsonfactory/ToDiskWriter.java

@@ -0,0 +1,30 @@
+package de.jplag.reporting.jsonfactory;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class ToDiskWriter implements FileWriter {
+    private static final Logger logger = LoggerFactory.getLogger(ToDiskWriter.class);
+
+    private static final ObjectMapper objectMapper = new ObjectMapper();
+
+    /**
+     * Saves the provided object to the provided path under the provided name
+     * @param fileToSave The object to save
+     * @param folderPath The path to save the object to
+     * @param fileName The name to save the object under
+     */
+    @Override
+    public void saveAsJSON(Object fileToSave, String folderPath, String fileName) {
+        try {
+            objectMapper.writeValue(Path.of(folderPath, fileName).toFile(), fileToSave);
+        } catch (IOException e) {
+            logger.error("Failed to save json file " + fileName + ": " + e.getMessage(), e);
+        }
+    }
+}

+ 102 - 88
jplag/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java

@@ -1,15 +1,15 @@
 package de.jplag.reporting.reportobject;
 
+import static de.jplag.reporting.jsonfactory.DirectoryManager.*;
+import static de.jplag.reporting.reportobject.mapper.SubmissionNameToIdMapper.buildSubmissionNameToIdMap;
+
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.StandardCopyOption;
 import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 import org.slf4j.Logger;
@@ -19,87 +19,79 @@ import de.jplag.JPlagComparison;
 import de.jplag.JPlagResult;
 import de.jplag.Language;
 import de.jplag.Submission;
-import de.jplag.reporting.jsonfactory.FileWriter;
+import de.jplag.reporting.jsonfactory.ComparisonReportWriter;
+import de.jplag.reporting.jsonfactory.ToDiskWriter;
 import de.jplag.reporting.reportobject.mapper.ClusteringResultMapper;
-import de.jplag.reporting.reportobject.mapper.ComparisonReportMapper;
 import de.jplag.reporting.reportobject.mapper.MetricMapper;
 import de.jplag.reporting.reportobject.model.Metric;
 import de.jplag.reporting.reportobject.model.OverviewReport;
 
 /**
- * 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 and writing it
+ * to the disk.
  */
 public class ReportObjectFactory {
     private static final Logger logger = LoggerFactory.getLogger(ReportObjectFactory.class);
 
-    private static final ClusteringResultMapper clusteringResultMapper = new ClusteringResultMapper();
-    private static final MetricMapper metricMapper = new MetricMapper();
-    private static final ComparisonReportMapper comparisonReportMapper = new ComparisonReportMapper();
-    private static final FileWriter fileWriter = new FileWriter();
+    private static final ToDiskWriter fileWriter = new ToDiskWriter();
     public static final String OVERVIEW_FILE_NAME = "overview.json";
     public static final String SUBMISSIONS_FOLDER = "submissions";
+    private Map<String, String> submissionNameToIdMap;
+    private Function<Submission, String> submissionToIdFunction;
+    private Map<String, Map<String, String>> submissionNameToNameToComparisonFileName;
 
     /**
+     * Creates all necessary report viewer files, writes them to the disk as zip.
      * @param result The JPlagResult to be converted into a report.
      * @param path The Path to save the report to
      */
-    public static void createAndSaveReport(JPlagResult result, String path) {
-        createDirectory(path);
-        writeOverview(result, path);
-        copySubmissionFilesToReport(path, result);
-        comparisonReportMapper.writeComparisonReports(result, path);
-    }
+    public void createAndSaveReport(JPlagResult result, String path) {
 
-    private static File createDirectory(String path, String name) {
-        File directory = new File(path.concat("/").concat(name));
-        if (!directory.exists() && !directory.mkdirs()) {
-            logger.error("Failed to create dir.");
-        }
-        return directory;
-    }
+        try {
+            createDirectory(path);
+            buildSubmissionToIdMap(result);
 
-    private static void createDirectory(String path) {
-        createDirectory(path, "");
-    }
+            copySubmissionFilesToReport(path, result);
 
-    private static void writeOverview(JPlagResult result, String path) {
-        List<JPlagComparison> comparisons = getComparisons(result);
-        OverviewReport overviewReport = new OverviewReport();
+            writeComparisons(result, path);
+            writeOverview(result, path);
 
-        // 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);
+            zipAndDelete(path);
+        } catch (IOException e) {
+            logger.error("Could not create directory " + path + " for report viewer generation", e);
+        }
 
-        String baseCodePath = result.getOptions().hasBaseCode() ? result.getOptions().getBaseCodeSubmissionName().orElse("") : "";
-        overviewReport.setBaseCodeFolderPath(baseCodePath);
-
-        overviewReport.setLanguage(result.getOptions().getLanguage().getName());
-        overviewReport.setFileExtensions(List.of(result.getOptions().getFileSuffixes()));
-        overviewReport.setSubmissionIds(extractSubmissionNames(comparisons));
-        overviewReport.setFailedSubmissionNames(List.of());  // No number of failed submissions
-        overviewReport.setExcludedFiles(result.getOptions().getExcludedFiles());
-        overviewReport.setMatchSensitivity(result.getOptions().getMinimumTokenMatch());
-        overviewReport.setDateOfExecution(getDate());
-        overviewReport.setExecutionTime(result.getDuration());
-        overviewReport.setComparisonNames(getComparisonNames(comparisons));
-        overviewReport.setMetrics(getMetrics(result));
-        overviewReport.setClusters(clusteringResultMapper.map(result));
+    }
 
-        fileWriter.saveAsJSON(overviewReport, path, OVERVIEW_FILE_NAME);
+    private void zipAndDelete(String path) {
+        boolean zipWasSuccessful = zipDirectory(path);
+        if (zipWasSuccessful) {
+            deleteDirectory(path);
+        } else {
+            logger.error("Could not zip results. The results are still available uncompressed at " + path);
+        }
+    }
 
+    private void buildSubmissionToIdMap(JPlagResult result) {
+        submissionNameToIdMap = buildSubmissionNameToIdMap(result);
+        submissionToIdFunction = (Submission submission) -> submissionNameToIdMap.get(submission.getName());
     }
 
-    private static void copySubmissionFilesToReport(String path, JPlagResult result) {
+    private void copySubmissionFilesToReport(String path, JPlagResult result) {
         List<JPlagComparison> comparisons = result.getComparisons(result.getOptions().getMaximumNumberOfComparisons());
-        var submissions = getSubmissions(comparisons);
-        var submissionsPath = createDirectory(path, SUBMISSIONS_FOLDER);
+        Set<Submission> submissions = getSubmissions(comparisons);
+        File submissionsPath = createSubmissionsDirectory(path);
+        if (submissionsPath == null) {
+            return;
+        }
         Language language = result.getOptions().getLanguage();
-        for (var submission : submissions) {
-            File directory = createDirectory(submissionsPath.getPath(), submission.getName());
-            for (var file : submission.getFiles()) {
-                var fileToCopy = language.useViewFiles() ? new File(file.getPath() + language.viewFileSuffix()) : file;
+        for (Submission submission : submissions) {
+            File directory = createSubmissionDirectory(path, submissionsPath, submission);
+            if (directory == null) {
+                continue;
+            }
+            for (File file : submission.getFiles()) {
+                File fileToCopy = language.useViewFiles() ? new File(file.getPath() + language.viewFileSuffix()) : file;
                 try {
                     Files.copy(fileToCopy.toPath(), (new File(directory, file.getName())).toPath(), StandardCopyOption.REPLACE_EXISTING);
                 } catch (IOException e) {
@@ -109,40 +101,61 @@ public class ReportObjectFactory {
         }
     }
 
-    private static Set<Submission> getSubmissions(List<JPlagComparison> comparisons) {
-        var submissions = comparisons.stream().map(JPlagComparison::getFirstSubmission).collect(Collectors.toSet());
-        Set<Submission> secondSubmissions = comparisons.stream().map(JPlagComparison::getSecondSubmission).collect(Collectors.toSet());
-        submissions.addAll(secondSubmissions);
-        return submissions;
+    private File createSubmissionDirectory(String path, File submissionsPath, Submission submission) {
+        try {
+            return createDirectory(submissionsPath.getPath(), submissionToIdFunction.apply(submission));
+        } catch (IOException e) {
+            logger.error("Could not create directory " + path + " for report viewer generation", e);
+            return null;
+        }
     }
 
-    private static List<JPlagComparison> getComparisons(JPlagResult result) {
-        int numberOfComparisons = result.getOptions().getMaximumNumberOfComparisons();
-        return result.getComparisons(numberOfComparisons);
+    private File createSubmissionsDirectory(String path) {
+        try {
+            return createDirectory(path, SUBMISSIONS_FOLDER);
+        } catch (IOException e) {
+            logger.error("Could not create directory " + path + " for report viewer generation", e);
+            return null;
+        }
     }
 
-    /**
-     * Gets the names of all submissions.
-     * @return A list containing all submission names.
-     */
-    private static List<String> extractSubmissionNames(List<JPlagComparison> comparisons) {
-        HashSet<String> names = new HashSet<>();
-        comparisons.forEach(comparison -> {
-            names.add(comparison.getFirstSubmission().getName());
-            names.add(comparison.getSecondSubmission().getName());
-        });
-        return new ArrayList<>(names);
+    private void writeComparisons(JPlagResult result, String path) {
+        ComparisonReportWriter comparisonReportWriter = new ComparisonReportWriter(submissionToIdFunction, fileWriter);
+        submissionNameToNameToComparisonFileName = comparisonReportWriter.writeComparisonReports(result, path);
     }
 
-    /**
-     * Gets the names of all comparison.
-     * @return A list containing all comparisons.
-     */
-    private static List<String> getComparisonNames(List<JPlagComparison> comparisons) {
-        List<String> names = new ArrayList<>();
-        comparisons.forEach(
-                comparison -> names.add(String.join("-", comparison.getFirstSubmission().getName(), comparison.getSecondSubmission().getName())));
-        return names;
+    private void writeOverview(JPlagResult result, String path) {
+
+        List<String> folders = new ArrayList<>();
+        folders.addAll(result.getOptions().getSubmissionDirectories());
+        folders.addAll(result.getOptions().getOldSubmissionDirectories());
+
+        String baseCodePath = result.getOptions().hasBaseCode() ? result.getOptions().getBaseCodeSubmissionName().orElse("") : "";
+        ClusteringResultMapper clusteringResultMapper = new ClusteringResultMapper(submissionToIdFunction);
+
+        OverviewReport overviewReport = new OverviewReport(folders, // submissionFolderPath
+                baseCodePath, // baseCodeFolderPath
+                result.getOptions().getLanguage().getName(), // language
+                List.of(result.getOptions().getFileSuffixes()), // fileExtensions
+                submissionNameToIdMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)), // submissionIds
+                submissionNameToNameToComparisonFileName, // result.getOptions().getMinimumTokenMatch(),
+                List.of(), // failedSubmissionNames
+                result.getOptions().getExcludedFiles(), // excludedFiles
+                result.getOptions().getMinimumTokenMatch(), // matchSensitivity
+                getDate(),// dateOfExecution
+                result.getDuration(), // executionTime
+                getMetrics(result),// metrics
+                clusteringResultMapper.map(result)); // clusters
+
+        fileWriter.saveAsJSON(overviewReport, path, OVERVIEW_FILE_NAME);
+
+    }
+
+    private Set<Submission> getSubmissions(List<JPlagComparison> comparisons) {
+        Set<Submission> submissions = comparisons.stream().map(JPlagComparison::getFirstSubmission).collect(Collectors.toSet());
+        Set<Submission> secondSubmissions = comparisons.stream().map(JPlagComparison::getSecondSubmission).collect(Collectors.toSet());
+        submissions.addAll(secondSubmissions);
+        return submissions;
     }
 
     /**
@@ -150,11 +163,12 @@ public class ReportObjectFactory {
      * Metric.
      * @return A list contains Metric DTOs.
      */
-    private static List<Metric> getMetrics(JPlagResult result) {
+    private List<Metric> getMetrics(JPlagResult result) {
+        MetricMapper metricMapper = new MetricMapper(submissionToIdFunction);
         return List.of(metricMapper.getAverageMetric(result), metricMapper.getMaxMetric(result));
     }
 
-    private static String getDate() {
+    private String getDate() {
         SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yy");
         Date date = new Date();
         return dateFormat.format(date);

+ 8 - 1
jplag/src/main/java/de/jplag/reporting/reportobject/mapper/ClusteringResultMapper.java

@@ -2,6 +2,7 @@ package de.jplag.reporting.reportobject.mapper;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.function.Function;
 
 import de.jplag.JPlagResult;
 import de.jplag.Submission;
@@ -12,6 +13,12 @@ import de.jplag.reporting.reportobject.model.Cluster;
  * Extracts and maps the clusters from the JPlagResult to the corresponding JSON DTO
  */
 public class ClusteringResultMapper {
+    private final Function<Submission, String> submissionToIdFunction;
+
+    public ClusteringResultMapper(Function<Submission, String> submissionToIdFunction) {
+        this.submissionToIdFunction = submissionToIdFunction;
+    }
+
     public List<Cluster> map(JPlagResult result) {
         var clusteringResult = result.getClusteringResult();
         return clusteringResult.stream().map(ClusteringResult::getClusters).flatMap(Collection::stream).map(this::convertCluster).toList();
@@ -20,7 +27,7 @@ public class ClusteringResultMapper {
     private Cluster convertCluster(de.jplag.clustering.Cluster<Submission> from) {
         var strength = from.getCommunityStrength();
         var avgSimilarity = from.getAverageSimilarity();
-        var member = from.getMembers().stream().map(Submission::getName).toList();
+        var member = from.getMembers().stream().map(submissionToIdFunction).toList();
         return new Cluster(avgSimilarity, strength, member);
     }
 }

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

@@ -1,54 +0,0 @@
-package de.jplag.reporting.reportobject.mapper;
-
-import java.util.List;
-
-import de.jplag.*;
-import de.jplag.reporting.jsonfactory.FileWriter;
-import de.jplag.reporting.reportobject.model.ComparisonReport;
-import de.jplag.reporting.reportobject.model.Match;
-
-public class ComparisonReportMapper {
-
-    private static final FileWriter FILE_WRITER = new FileWriter();
-
-    /**
-     * Generates detailed ComparisonReport DTO for each comparison in a JPlagResult.
-     * @param jPlagResult The JPlagResult to generate the comparison reports from. contains information about a comparison
-     * between two submission. The JPlagResult is used to extract the information on matches between two submissions.
-     */
-    public void writeComparisonReports(JPlagResult jPlagResult, String path) {
-        int numberOfComparisons = jPlagResult.getOptions().getMaximumNumberOfComparisons();
-        List<JPlagComparison> comparisons = jPlagResult.getComparisons(numberOfComparisons);
-        writeComparisons(path, comparisons);
-    }
-
-    private void writeComparisons(String path, List<JPlagComparison> comparisons) {
-        for (JPlagComparison comparison : comparisons) {
-            var comparisonReport = new ComparisonReport(comparison.getFirstSubmission().getName(), comparison.getSecondSubmission().getName(),
-                    comparison.similarity(), convertMatchesToReportMatches(comparison));
-            String fileName = comparisonReport.firstSubmissionId().concat("-").concat(comparisonReport.secondSubmissionId()).concat(".json");
-            FILE_WRITER.saveAsJSON(comparisonReport, path, fileName);
-        }
-    }
-
-    private List<Match> convertMatchesToReportMatches(JPlagComparison comparison) {
-        return comparison.getMatches().stream().map(match -> convertMatchToReportMatch(comparison, match)).toList();
-    }
-
-    private Match convertMatchToReportMatch(JPlagComparison comparison, de.jplag.Match match) {
-        TokenList tokensFirst = comparison.getFirstSubmission().getTokenList();
-        TokenList tokensSecond = comparison.getSecondSubmission().getTokenList();
-        Token startTokenFirst = tokensFirst.getToken(match.startOfFirst());
-        Token endTokenFirst = tokensFirst.getToken(match.startOfFirst() + match.length() - 1);
-        Token startTokenSecond = tokensSecond.getToken(match.startOfSecond());
-        Token endTokenSecond = tokensSecond.getToken(match.startOfSecond() + match.length() - 1);
-
-        int startFirst = startTokenFirst.getLine();
-        int endFirst = endTokenFirst.getLine();
-        int startSecond = startTokenSecond.getLine();
-        int endSecond = endTokenSecond.getLine();
-        int tokens = match.length();
-
-        return new Match(startTokenFirst.getFile(), startTokenSecond.getFile(), startFirst, endFirst, startSecond, endSecond, tokens);
-    }
-}

+ 13 - 6
jplag/src/main/java/de/jplag/reporting/reportobject/mapper/MetricMapper.java

@@ -9,6 +9,7 @@ import java.util.stream.Collectors;
 import de.jplag.JPlagComparison;
 import de.jplag.JPlagResult;
 import de.jplag.Messages;
+import de.jplag.Submission;
 import de.jplag.options.SimilarityMetric;
 import de.jplag.reporting.reportobject.model.Metric;
 import de.jplag.reporting.reportobject.model.TopComparison;
@@ -17,6 +18,12 @@ import de.jplag.reporting.reportobject.model.TopComparison;
  * Extracts and maps metrics from the JPlagResult to the corresponding JSON DTO
  */
 public class MetricMapper {
+    private final Function<Submission, String> submissionToIdFunction;
+
+    public MetricMapper(Function<Submission, String> submissionToIdFunction) {
+        this.submissionToIdFunction = submissionToIdFunction;
+    }
+
     public Metric getAverageMetric(JPlagResult result) {
         return new Metric(SimilarityMetric.AVG.name(), intArrayToList(result.getSimilarityDistribution()), getTopComparisons(getComparisons(result)),
                 Messages.getString("SimilarityMetric.Avg.Description"));
@@ -32,22 +39,22 @@ public class MetricMapper {
         return result.getComparisons(maxNumberOfComparisons);
     }
 
-    private static List<Integer> intArrayToList(int[] array) {
+    private List<Integer> intArrayToList(int[] array) {
         return Arrays.stream(array).boxed().collect(Collectors.toList());
     }
 
-    private static List<TopComparison> getTopComparisons(List<JPlagComparison> comparisons, Function<JPlagComparison, Float> similarityExtractor) {
+    private List<TopComparison> getTopComparisons(List<JPlagComparison> comparisons, Function<JPlagComparison, Float> similarityExtractor) {
         return comparisons.stream().sorted(Comparator.comparing(similarityExtractor).reversed())
-                .map(comparison -> new TopComparison(comparison.getFirstSubmission().getName(), comparison.getSecondSubmission().getName(),
-                        similarityExtractor.apply(comparison)))
+                .map(comparison -> new TopComparison(submissionToIdFunction.apply(comparison.getFirstSubmission()),
+                        submissionToIdFunction.apply(comparison.getSecondSubmission()), similarityExtractor.apply(comparison)))
                 .toList();
     }
 
-    private static List<TopComparison> getTopComparisons(List<JPlagComparison> comparisons) {
+    private List<TopComparison> getTopComparisons(List<JPlagComparison> comparisons) {
         return getTopComparisons(comparisons, JPlagComparison::similarity);
     }
 
-    private static List<TopComparison> getMaxSimilarityTopComparisons(List<JPlagComparison> comparisons) {
+    private List<TopComparison> getMaxSimilarityTopComparisons(List<JPlagComparison> comparisons) {
         return getTopComparisons(comparisons, JPlagComparison::maximalSimilarity);
     }
 

+ 43 - 0
jplag/src/main/java/de/jplag/reporting/reportobject/mapper/SubmissionNameToIdMapper.java

@@ -0,0 +1,43 @@
+package de.jplag.reporting.reportobject.mapper;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import de.jplag.JPlagComparison;
+import de.jplag.JPlagResult;
+import de.jplag.Submission;
+
+/**
+ * Responsible for creating a mapping of all Submissions to their respective report viewer ids. This mapping is achieved
+ * by associating the {@link Submission#getName()} (JPlag's internal Submission uid) to a report viewer id. Currently,
+ * the sanitized version of the {@link Submission#getName()} serves as report viewer id.
+ */
+public class SubmissionNameToIdMapper {
+
+    private static final String FILE_SEPARATOR_REPLACEMENT = "_";
+
+    /**
+     * Builds a map that associates a Submission by its JPlag Id ({@link Submission#getName()}) to its report viewer id.
+     * @return A Map of containing an entry [name of submission -> report viewer id of submission] for each submission of
+     * the submission set.
+     */
+    public static Map<String, String> buildSubmissionNameToIdMap(JPlagResult result) {
+        HashMap<String, String> idToName = new HashMap<>();
+        getComparisons(result).forEach(comparison -> {
+            idToName.put(comparison.getFirstSubmission().getName(), sanitizeNameOf(comparison.getFirstSubmission()));
+            idToName.put(comparison.getSecondSubmission().getName(), sanitizeNameOf(comparison.getSecondSubmission()));
+        });
+        return idToName;
+    }
+
+    private static String sanitizeNameOf(Submission comparison) {
+        return comparison.getName().replace(File.separator, FILE_SEPARATOR_REPLACEMENT);
+    }
+
+    private static List<JPlagComparison> getComparisons(JPlagResult result) {
+        int numberOfComparisons = result.getOptions().getMaximumNumberOfComparisons();
+        return result.getComparisons(numberOfComparisons);
+    }
+}

+ 15 - 154
jplag/src/main/java/de/jplag/reporting/reportobject/model/OverviewReport.java

@@ -1,174 +1,35 @@
 package de.jplag.reporting.reportobject.model;
 
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 
-public class OverviewReport {
+public record OverviewReport(
 
-    @JsonProperty("submission_folder_path")
-    private List<String> submissionFolderPath;
+        @JsonProperty("submission_folder_path") List<String> submissionFolderPath,
 
-    @JsonProperty("base_code_folder_path")
-    private String baseCodeFolderPath;
+        @JsonProperty("base_code_folder_path") String baseCodeFolderPath,
 
-    @JsonProperty("language")
-    private String language;
+        @JsonProperty("language") String language,
 
-    @JsonProperty("file_extensions")
-    private List<String> fileExtensions;
+        @JsonProperty("file_extensions") List<String> fileExtensions,
 
-    @JsonProperty("submission_ids")
-    private List<String> submissionIds;
+        @JsonProperty("submission_id_to_display_name") Map<String, String> submissionIds,
+        @JsonProperty("submission_ids_to_comparison_file_name") Map<String, Map<String, String>> submissionIdsToComparisonFileName,
 
-    @JsonProperty("failed_submission_names")
-    private List<String> failedSubmissionNames;
+        @JsonProperty("failed_submission_names") List<String> failedSubmissionNames,
 
-    @JsonProperty("excluded_files")
-    private Set<String> excludedFiles;
+        @JsonProperty("excluded_files") Set<String> excludedFiles,
 
-    @JsonProperty("match_sensitivity")
-    private int matchSensitivity;
+        @JsonProperty("match_sensitivity") int matchSensitivity,
 
-    @JsonProperty("date_of_execution")
-    private String dateOfExecution;
+        @JsonProperty("date_of_execution") String dateOfExecution,
 
-    @JsonProperty("execution_time")
-    private long executionTime;
+        @JsonProperty("execution_time") long executionTime,
 
-    @JsonProperty("comparison_names")
-    private List<String> comparisonNames;
+        @JsonProperty("metrics") List<Metric> metrics,
 
-    @JsonProperty("metrics")
-    private List<Metric> metrics;
-
-    @JsonProperty("clusters")
-    private List<Cluster> clusters;
-
-    public OverviewReport() {
-        submissionFolderPath = List.of();
-        baseCodeFolderPath = "";
-        language = "";
-        fileExtensions = List.of();
-        submissionIds = List.of();
-        failedSubmissionNames = List.of();
-        excludedFiles = Set.of();
-        matchSensitivity = 0;
-        dateOfExecution = "";
-        executionTime = 0;
-        clusters = List.of();
-    }
-
-    /*******************************
-     * GETTERS
-     *******************************/
-
-    public List<String> getSubmissionFolderPath() {
-        return submissionFolderPath;
-    }
-
-    public String getBaseCodeFolderPath() {
-        return baseCodeFolderPath;
-    }
-
-    public String getLanguage() {
-        return language;
-    }
-
-    public List<String> getFileExtensions() {
-        return fileExtensions;
-    }
-
-    public List<String> getSubmissionIds() {
-        return submissionIds;
-    }
-
-    public List<String> getFailedSubmissionNames() {
-        return failedSubmissionNames;
-    }
-
-    public Set<String> getExcludedFiles() {
-        return excludedFiles;
-    }
-
-    public int getMatchSensitivity() {
-        return matchSensitivity;
-    }
-
-    public String getDateOfExecution() {
-        return dateOfExecution;
-    }
-
-    public long getExecutionTime() {
-        return executionTime;
-    }
-
-    public List<String> getComparisonNames() {
-        return comparisonNames;
-    }
-
-    public List<Metric> getMetrics() {
-        return metrics;
-    }
-
-    public List<Cluster> getClusters() {
-        return clusters;
-    }
-
-    /*******************************
-     * SETTERS
-     *******************************/
-
-    public void setSubmissionFolderPath(List<String> submissionFolderPath) {
-        this.submissionFolderPath = submissionFolderPath;
-    }
-
-    public void setBaseCodeFolderPath(String baseCodeFolderPath) {
-        this.baseCodeFolderPath = baseCodeFolderPath;
-    }
-
-    public void setLanguage(String language) {
-        this.language = language;
-    }
-
-    public void setFileExtensions(List<String> fileExtensions) {
-        this.fileExtensions = List.copyOf(fileExtensions);
-    }
-
-    public void setSubmissionIds(List<String> submissionIds) {
-        this.submissionIds = List.copyOf(submissionIds);
-    }
-
-    public void setFailedSubmissionNames(List<String> failedSubmissionNames) {
-        this.failedSubmissionNames = List.copyOf(failedSubmissionNames);
-    }
-
-    public void setExcludedFiles(Set<String> excludedFiles) {
-        this.excludedFiles = Set.copyOf(excludedFiles);
-    }
-
-    public void setMatchSensitivity(int matchSensitivity) {
-        this.matchSensitivity = matchSensitivity;
-    }
-
-    public void setDateOfExecution(String dateOfExecution) {
-        this.dateOfExecution = dateOfExecution;
-    }
-
-    public void setExecutionTime(long executionTime) {
-        this.executionTime = executionTime;
-    }
-
-    public void setComparisonNames(List<String> comparisonNames) {
-        this.comparisonNames = List.copyOf(comparisonNames);
-    }
-
-    public void setMetrics(List<Metric> metrics) {
-        this.metrics = List.copyOf(metrics);
-    }
-
-    public void setClusters(List<Cluster> clusters) {
-        this.clusters = List.copyOf(clusters);
-    }
+        @JsonProperty("clusters") List<Cluster> clusters) {
 }

+ 1 - 1
jplag/src/test/java/de/jplag/reporting/reportobject/mapper/ClusteringResultMapperTest.java

@@ -15,7 +15,7 @@ import de.jplag.clustering.Cluster;
 import de.jplag.clustering.ClusteringResult;
 
 public class ClusteringResultMapperTest {
-    private final ClusteringResultMapper clusteringResultMapper = new ClusteringResultMapper();
+    private final ClusteringResultMapper clusteringResultMapper = new ClusteringResultMapper(Submission::getName);
 
     @Test
     public void test() {

+ 58 - 0
jplag/src/test/java/de/jplag/reporting/reportobject/mapper/ComparisonReportWriterTest.java

@@ -0,0 +1,58 @@
+package de.jplag.reporting.reportobject.mapper;
+
+import java.util.Map;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import de.jplag.JPlagResult;
+import de.jplag.Submission;
+import de.jplag.TestBase;
+import de.jplag.exceptions.ExitException;
+import de.jplag.reporting.jsonfactory.ComparisonReportWriter;
+import de.jplag.reporting.jsonfactory.DummyWriter;
+import de.jplag.reporting.jsonfactory.FileWriter;
+
+public class ComparisonReportWriterTest extends TestBase {
+    private final FileWriter fileWriter = new DummyWriter();
+
+    @Test
+    public void firsLevelOfLookupMapComplete() throws ExitException {
+        JPlagResult result = runJPlagWithDefaultOptions("PartialPlagiarism");
+        var mapper = new ComparisonReportWriter(Submission::getName, fileWriter);
+
+        Map<String, Map<String, String>> stringMapMap = mapper.writeComparisonReports(result, "");
+
+        firstLevelOfMapContains(stringMapMap, "A", "B", "C", "D", "E");
+    }
+
+    @Test
+    public void secondLevelOfLookupMapComplete() throws ExitException {
+        JPlagResult result = runJPlagWithDefaultOptions("PartialPlagiarism");
+        var mapper = new ComparisonReportWriter(Submission::getName, fileWriter);
+
+        Map<String, Map<String, String>> stringMapMap = mapper.writeComparisonReports(result, "");
+
+        secondLevelOfMapContains(stringMapMap, "A", "B", "C", "D", "E");
+        secondLevelOfMapContains(stringMapMap, "B", "A", "C", "D", "E");
+        secondLevelOfMapContains(stringMapMap, "C", "B", "A", "D", "E");
+        secondLevelOfMapContains(stringMapMap, "D", "B", "C", "A", "E");
+        secondLevelOfMapContains(stringMapMap, "E", "B", "C", "D", "A");
+    }
+
+    private void secondLevelOfMapContains(Map<String, Map<String, String>> stringMapMap, String firstLevelSubmission,
+            String... secondLevelSubmissions) {
+        for (String secondLevelSubmission : secondLevelSubmissions) {
+            Assertions.assertNotNull(stringMapMap.get(firstLevelSubmission).get(secondLevelSubmission));
+            Assertions.assertFalse(stringMapMap.get(firstLevelSubmission).get(secondLevelSubmission).isEmpty());
+
+        }
+
+    }
+
+    private void firstLevelOfMapContains(Map<String, Map<String, String>> stringMapMap, String... names) {
+        for (String name : names) {
+            Assertions.assertNotNull(stringMapMap.get(name));
+        }
+    }
+}

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

@@ -15,7 +15,7 @@ import de.jplag.options.JPlagOptions;
 import de.jplag.reporting.reportobject.model.TopComparison;
 
 public class MetricMapperTest {
-    private final MetricMapper metricMapper = new MetricMapper();
+    private final MetricMapper metricMapper = new MetricMapper(Submission::getName);
 
     @Test
     public void test_getAverageMetric() {

+ 3 - 3
pom.xml

@@ -241,7 +241,7 @@
         <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
-            <version>4.6.1</version>
+            <version>4.7.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -355,7 +355,7 @@
             <plugin>
                 <groupId>org.codehaus.mojo</groupId>
                 <artifactId>flatten-maven-plugin</artifactId>
-                <version>1.2.7</version>
+                <version>1.3.0</version>
                 <configuration>
                     <updatePomFile>true</updatePomFile>
                     <flattenMode>resolveCiFriendliesOnly</flattenMode>
@@ -400,7 +400,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-javadoc-plugin</artifactId>
-                <version>3.4.0</version>
+                <version>3.4.1</version>
                 <executions>
                     <execution>
                         <id>attach-javadocs</id>

+ 87 - 87
report-viewer/package-lock.json

@@ -24,8 +24,8 @@
         "vuex": "^4.0.2"
       },
       "devDependencies": {
-        "@typescript-eslint/eslint-plugin": "^5.33.0",
-        "@typescript-eslint/parser": "^5.33.0",
+        "@typescript-eslint/eslint-plugin": "^5.33.1",
+        "@typescript-eslint/parser": "^5.33.1",
         "@vue/cli-plugin-babel": "~5.0.8",
         "@vue/cli-plugin-eslint": "~5.0.8",
         "@vue/cli-plugin-router": "~5.0.8",
@@ -34,7 +34,7 @@
         "@vue/compiler-sfc": "^3.2.37",
         "@vue/eslint-config-prettier": "^7.0.0",
         "@vue/eslint-config-typescript": "^10.0.0",
-        "eslint": "^8.21.0",
+        "eslint": "^8.22.0",
         "eslint-plugin-prettier": "^4.2.1",
         "eslint-plugin-vue": "^8.7.1",
         "lint-staged": "^13.0.3",
@@ -2221,14 +2221,14 @@
       }
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "5.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.0.tgz",
-      "integrity": "sha512-jHvZNSW2WZ31OPJ3enhLrEKvAZNyAFWZ6rx9tUwaessTc4sx9KmgMNhVcqVAl1ETnT5rU5fpXTLmY9YvC1DCNg==",
+      "version": "5.33.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.1.tgz",
+      "integrity": "sha512-S1iZIxrTvKkU3+m63YUOxYPKaP+yWDQrdhxTglVDVEVBf+aCSw85+BmJnyUaQQsk5TXFG/LpBu9fa+LrAQ91fQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "5.33.0",
-        "@typescript-eslint/type-utils": "5.33.0",
-        "@typescript-eslint/utils": "5.33.0",
+        "@typescript-eslint/scope-manager": "5.33.1",
+        "@typescript-eslint/type-utils": "5.33.1",
+        "@typescript-eslint/utils": "5.33.1",
         "debug": "^4.3.4",
         "functional-red-black-tree": "^1.0.1",
         "ignore": "^5.2.0",
@@ -2269,14 +2269,14 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "5.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.0.tgz",
-      "integrity": "sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==",
+      "version": "5.33.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.1.tgz",
+      "integrity": "sha512-IgLLtW7FOzoDlmaMoXdxG8HOCByTBXrB1V2ZQYSEV1ggMmJfAkMWTwUjjzagS6OkfpySyhKFkBw7A9jYmcHpZA==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "5.33.0",
-        "@typescript-eslint/types": "5.33.0",
-        "@typescript-eslint/typescript-estree": "5.33.0",
+        "@typescript-eslint/scope-manager": "5.33.1",
+        "@typescript-eslint/types": "5.33.1",
+        "@typescript-eslint/typescript-estree": "5.33.1",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -2296,13 +2296,13 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "5.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz",
-      "integrity": "sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==",
+      "version": "5.33.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.1.tgz",
+      "integrity": "sha512-8ibcZSqy4c5m69QpzJn8XQq9NnqAToC8OdH/W6IXPXv83vRyEDPYLdjAlUx8h/rbusq6MkW4YdQzURGOqsn3CA==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.33.0",
-        "@typescript-eslint/visitor-keys": "5.33.0"
+        "@typescript-eslint/types": "5.33.1",
+        "@typescript-eslint/visitor-keys": "5.33.1"
       },
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -2313,12 +2313,12 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "5.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.33.0.tgz",
-      "integrity": "sha512-2zB8uEn7hEH2pBeyk3NpzX1p3lF9dKrEbnXq1F7YkpZ6hlyqb2yZujqgRGqXgRBTHWIUG3NGx/WeZk224UKlIA==",
+      "version": "5.33.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.33.1.tgz",
+      "integrity": "sha512-X3pGsJsD8OiqhNa5fim41YtlnyiWMF/eKsEZGsHID2HcDqeSC5yr/uLOeph8rNF2/utwuI0IQoAK3fpoxcLl2g==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/utils": "5.33.0",
+        "@typescript-eslint/utils": "5.33.1",
         "debug": "^4.3.4",
         "tsutils": "^3.21.0"
       },
@@ -2339,9 +2339,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "5.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.0.tgz",
-      "integrity": "sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==",
+      "version": "5.33.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.1.tgz",
+      "integrity": "sha512-7K6MoQPQh6WVEkMrMW5QOA5FO+BOwzHSNd0j3+BlBwd6vtzfZceJ8xJ7Um2XDi/O3umS8/qDX6jdy2i7CijkwQ==",
       "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -2352,13 +2352,13 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "5.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.0.tgz",
-      "integrity": "sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==",
+      "version": "5.33.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.1.tgz",
+      "integrity": "sha512-JOAzJ4pJ+tHzA2pgsWQi4804XisPHOtbvwUyqsuuq8+y5B5GMZs7lI1xDWs6V2d7gE/Ez5bTGojSK12+IIPtXA==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.33.0",
-        "@typescript-eslint/visitor-keys": "5.33.0",
+        "@typescript-eslint/types": "5.33.1",
+        "@typescript-eslint/visitor-keys": "5.33.1",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -2394,15 +2394,15 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "5.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.33.0.tgz",
-      "integrity": "sha512-JxOAnXt9oZjXLIiXb5ZIcZXiwVHCkqZgof0O8KPgz7C7y0HS42gi75PdPlqh1Tf109M0fyUw45Ao6JLo7S5AHw==",
+      "version": "5.33.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.33.1.tgz",
+      "integrity": "sha512-uphZjkMaZ4fE8CR4dU7BquOV6u0doeQAr8n6cQenl/poMaIyJtBu8eys5uk6u5HiDH01Mj5lzbJ5SfeDz7oqMQ==",
       "dev": true,
       "dependencies": {
         "@types/json-schema": "^7.0.9",
-        "@typescript-eslint/scope-manager": "5.33.0",
-        "@typescript-eslint/types": "5.33.0",
-        "@typescript-eslint/typescript-estree": "5.33.0",
+        "@typescript-eslint/scope-manager": "5.33.1",
+        "@typescript-eslint/types": "5.33.1",
+        "@typescript-eslint/typescript-estree": "5.33.1",
         "eslint-scope": "^5.1.1",
         "eslint-utils": "^3.0.0"
       },
@@ -2418,12 +2418,12 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "5.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz",
-      "integrity": "sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==",
+      "version": "5.33.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.1.tgz",
+      "integrity": "sha512-nwIxOK8Z2MPWltLKMLOEZwmfBZReqUdbEoHQXeCpa+sRVARe5twpJGHCB4dk9903Yaf0nMAlGbQfaAH92F60eg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.33.0",
+        "@typescript-eslint/types": "5.33.1",
         "eslint-visitor-keys": "^3.3.0"
       },
       "engines": {
@@ -5922,9 +5922,9 @@
       }
     },
     "node_modules/eslint": {
-      "version": "8.21.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.21.0.tgz",
-      "integrity": "sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA==",
+      "version": "8.22.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.22.0.tgz",
+      "integrity": "sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA==",
       "dev": true,
       "dependencies": {
         "@eslint/eslintrc": "^1.3.0",
@@ -15428,14 +15428,14 @@
       }
     },
     "@typescript-eslint/eslint-plugin": {
-      "version": "5.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.0.tgz",
-      "integrity": "sha512-jHvZNSW2WZ31OPJ3enhLrEKvAZNyAFWZ6rx9tUwaessTc4sx9KmgMNhVcqVAl1ETnT5rU5fpXTLmY9YvC1DCNg==",
+      "version": "5.33.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.1.tgz",
+      "integrity": "sha512-S1iZIxrTvKkU3+m63YUOxYPKaP+yWDQrdhxTglVDVEVBf+aCSw85+BmJnyUaQQsk5TXFG/LpBu9fa+LrAQ91fQ==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/scope-manager": "5.33.0",
-        "@typescript-eslint/type-utils": "5.33.0",
-        "@typescript-eslint/utils": "5.33.0",
+        "@typescript-eslint/scope-manager": "5.33.1",
+        "@typescript-eslint/type-utils": "5.33.1",
+        "@typescript-eslint/utils": "5.33.1",
         "debug": "^4.3.4",
         "functional-red-black-tree": "^1.0.1",
         "ignore": "^5.2.0",
@@ -15456,52 +15456,52 @@
       }
     },
     "@typescript-eslint/parser": {
-      "version": "5.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.0.tgz",
-      "integrity": "sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==",
+      "version": "5.33.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.1.tgz",
+      "integrity": "sha512-IgLLtW7FOzoDlmaMoXdxG8HOCByTBXrB1V2ZQYSEV1ggMmJfAkMWTwUjjzagS6OkfpySyhKFkBw7A9jYmcHpZA==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/scope-manager": "5.33.0",
-        "@typescript-eslint/types": "5.33.0",
-        "@typescript-eslint/typescript-estree": "5.33.0",
+        "@typescript-eslint/scope-manager": "5.33.1",
+        "@typescript-eslint/types": "5.33.1",
+        "@typescript-eslint/typescript-estree": "5.33.1",
         "debug": "^4.3.4"
       }
     },
     "@typescript-eslint/scope-manager": {
-      "version": "5.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz",
-      "integrity": "sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==",
+      "version": "5.33.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.1.tgz",
+      "integrity": "sha512-8ibcZSqy4c5m69QpzJn8XQq9NnqAToC8OdH/W6IXPXv83vRyEDPYLdjAlUx8h/rbusq6MkW4YdQzURGOqsn3CA==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "5.33.0",
-        "@typescript-eslint/visitor-keys": "5.33.0"
+        "@typescript-eslint/types": "5.33.1",
+        "@typescript-eslint/visitor-keys": "5.33.1"
       }
     },
     "@typescript-eslint/type-utils": {
-      "version": "5.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.33.0.tgz",
-      "integrity": "sha512-2zB8uEn7hEH2pBeyk3NpzX1p3lF9dKrEbnXq1F7YkpZ6hlyqb2yZujqgRGqXgRBTHWIUG3NGx/WeZk224UKlIA==",
+      "version": "5.33.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.33.1.tgz",
+      "integrity": "sha512-X3pGsJsD8OiqhNa5fim41YtlnyiWMF/eKsEZGsHID2HcDqeSC5yr/uLOeph8rNF2/utwuI0IQoAK3fpoxcLl2g==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/utils": "5.33.0",
+        "@typescript-eslint/utils": "5.33.1",
         "debug": "^4.3.4",
         "tsutils": "^3.21.0"
       }
     },
     "@typescript-eslint/types": {
-      "version": "5.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.0.tgz",
-      "integrity": "sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==",
+      "version": "5.33.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.1.tgz",
+      "integrity": "sha512-7K6MoQPQh6WVEkMrMW5QOA5FO+BOwzHSNd0j3+BlBwd6vtzfZceJ8xJ7Um2XDi/O3umS8/qDX6jdy2i7CijkwQ==",
       "dev": true
     },
     "@typescript-eslint/typescript-estree": {
-      "version": "5.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.0.tgz",
-      "integrity": "sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==",
+      "version": "5.33.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.1.tgz",
+      "integrity": "sha512-JOAzJ4pJ+tHzA2pgsWQi4804XisPHOtbvwUyqsuuq8+y5B5GMZs7lI1xDWs6V2d7gE/Ez5bTGojSK12+IIPtXA==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "5.33.0",
-        "@typescript-eslint/visitor-keys": "5.33.0",
+        "@typescript-eslint/types": "5.33.1",
+        "@typescript-eslint/visitor-keys": "5.33.1",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -15521,26 +15521,26 @@
       }
     },
     "@typescript-eslint/utils": {
-      "version": "5.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.33.0.tgz",
-      "integrity": "sha512-JxOAnXt9oZjXLIiXb5ZIcZXiwVHCkqZgof0O8KPgz7C7y0HS42gi75PdPlqh1Tf109M0fyUw45Ao6JLo7S5AHw==",
+      "version": "5.33.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.33.1.tgz",
+      "integrity": "sha512-uphZjkMaZ4fE8CR4dU7BquOV6u0doeQAr8n6cQenl/poMaIyJtBu8eys5uk6u5HiDH01Mj5lzbJ5SfeDz7oqMQ==",
       "dev": true,
       "requires": {
         "@types/json-schema": "^7.0.9",
-        "@typescript-eslint/scope-manager": "5.33.0",
-        "@typescript-eslint/types": "5.33.0",
-        "@typescript-eslint/typescript-estree": "5.33.0",
+        "@typescript-eslint/scope-manager": "5.33.1",
+        "@typescript-eslint/types": "5.33.1",
+        "@typescript-eslint/typescript-estree": "5.33.1",
         "eslint-scope": "^5.1.1",
         "eslint-utils": "^3.0.0"
       }
     },
     "@typescript-eslint/visitor-keys": {
-      "version": "5.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz",
-      "integrity": "sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==",
+      "version": "5.33.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.1.tgz",
+      "integrity": "sha512-nwIxOK8Z2MPWltLKMLOEZwmfBZReqUdbEoHQXeCpa+sRVARe5twpJGHCB4dk9903Yaf0nMAlGbQfaAH92F60eg==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "5.33.0",
+        "@typescript-eslint/types": "5.33.1",
         "eslint-visitor-keys": "^3.3.0"
       }
     },
@@ -18241,9 +18241,9 @@
       "dev": true
     },
     "eslint": {
-      "version": "8.21.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.21.0.tgz",
-      "integrity": "sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA==",
+      "version": "8.22.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.22.0.tgz",
+      "integrity": "sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA==",
       "dev": true,
       "requires": {
         "@eslint/eslintrc": "^1.3.0",

+ 3 - 3
report-viewer/package.json

@@ -24,8 +24,8 @@
     "vuex": "^4.0.2"
   },
   "devDependencies": {
-    "@typescript-eslint/eslint-plugin": "^5.33.0",
-    "@typescript-eslint/parser": "^5.33.0",
+    "@typescript-eslint/eslint-plugin": "^5.33.1",
+    "@typescript-eslint/parser": "^5.33.1",
     "@vue/cli-plugin-babel": "~5.0.8",
     "@vue/cli-plugin-eslint": "~5.0.8",
     "@vue/cli-plugin-router": "~5.0.8",
@@ -34,7 +34,7 @@
     "@vue/compiler-sfc": "^3.2.37",
     "@vue/eslint-config-prettier": "^7.0.0",
     "@vue/eslint-config-typescript": "^10.0.0",
-    "eslint": "^8.21.0",
+    "eslint": "^8.22.0",
     "eslint-plugin-prettier": "^4.2.1",
     "eslint-plugin-vue": "^8.7.1",
     "lint-staged": "^13.0.3",

+ 13 - 32
report-viewer/src/components/ComparisonsTable.vue

@@ -18,59 +18,37 @@
         comparison.matchPercentage
       "
       class="selectable"
+      @click="
+        navigateToComparisonView(
+          comparison.firstSubmissionId,
+          comparison.secondSubmissionId
+        )
+      "
     >
-      <td
-        @click="
-          navigateToComparisonView(
-            comparison.firstSubmissionId,
-            comparison.secondSubmissionId
-          )
-        "
-      >
-        {{ index + 1 }}.
-      </td>
+      <td>{{ index + 1 }}.</td>
       <td
         :class="{
           'anonymous-style': isAnonymous(comparison.firstSubmissionId),
         }"
-        @click="
-          navigateToComparisonView(
-            comparison.firstSubmissionId,
-            comparison.secondSubmissionId
-          )
-        "
       >
         {{
           isAnonymous(comparison.firstSubmissionId)
             ? "Hidden"
-            : comparison.firstSubmissionId
+            : displayName(comparison.firstSubmissionId)
         }}
       </td>
-      <td
-        @click="
-          navigateToComparisonView(
-            comparison.firstSubmissionId,
-            comparison.secondSubmissionId
-          )
-        "
-      >
+      <td>
         <img alt=">>" src="@/assets/double_arrow_black_18dp.svg" />
       </td>
       <td
         :class="{
           'anonymous-style': isAnonymous(comparison.secondSubmissionId),
         }"
-        @click="
-          navigateToComparisonView(
-            comparison.firstSubmissionId,
-            comparison.secondSubmissionId
-          )
-        "
       >
         {{
           isAnonymous(comparison.secondSubmissionId)
             ? "Hidden"
-            : comparison.secondSubmissionId
+            : displayName(comparison.secondSubmissionId)
         }}
       </td>
       <td>{{ formattedMatchPercentage(comparison.matchPercentage) }}</td>
@@ -138,6 +116,8 @@ export default defineComponent({
     let formattedMatchPercentage = (num: number) => num.toFixed(2);
     const dialog: Ref<Array<boolean>> = ref([]);
     props.topComparisons.forEach(() => dialog.value.push(false));
+    const displayName = (submissionId: string) =>
+      store.getters.submissionDisplayName(submissionId);
 
     const toggleDialog = (index: number) => {
       dialog.value[index] = true;
@@ -223,6 +203,7 @@ export default defineComponent({
       clustersWithParticipatingMatches,
       dialog,
 
+      displayName,
       isAnonymous,
       getClustersFor,
       toggleDialog,

+ 12 - 11
report-viewer/src/components/IDsList.vue

@@ -6,32 +6,33 @@
     <div class="column-list">
       <p v-for="(id, index) in ids" :key="index"
          :class="{'selected' : !store.state.anonymous.has(id) && store.state.anonymous.size !== 0 }"
-         @click="$emit('idSent', [id])">
-        {{ id }}
+         @click="emitIdsSent([id])">
+        {{ store.getters.submissionDisplayName(id) }}
       </p>
     </div>
-    <button @click="$emit('idSent', ids)">Hide/Show all</button>
+    <button @click="emitIdsSent(ids)">Hide/Show all</button>
   </div>
 </template>
 
-<script>
+<script lang="ts">
 import {defineComponent} from "vue";
-import store from "@/store/store.ts"
+import store from "@/store/store"
 
 export default defineComponent({
+  emits: ["idSent"],
   name: "IDsList",
   props: {
     ids: {
-      type: Array,
+      type: Array<string>,
       required: true
     }
   },
-  setup(props) {
-    // eslint-disable-next-line vue/no-setup-props-destructure
-    const savedIds = props.ids
+  setup(_, {emit}) {
+    const emitIdsSent = (ids: string[]) => {
+      emit('idSent', ids);}
     return {
-      savedIds,
-      store
+      store,
+      emitIdsSent
     }
   }
 })

+ 7 - 7
report-viewer/src/model/Overview.ts

@@ -7,7 +7,7 @@ export class Overview {
   private readonly _language: string;
   private readonly _fileExtensions: Array<string>;
   private readonly _matchSensitivity: number;
-  private readonly _submissionIds: Array<string>;
+  private readonly _submissionIdsToComparisonFileName: Map<string,Map<string,string>>;
   private readonly _dateOfExecution: string;
   private readonly _durationOfExecution: number;
   private readonly _metrics: Array<Metric>;
@@ -19,22 +19,25 @@ export class Overview {
     language: string,
     fileExtensions: Array<string>,
     matchSensitivity: number,
-    submissionIds: Array<string>,
     dateOfExecution: string,
     durationOfExecution: number,
     metrics: Array<Metric>,
-    clusters: Array<Cluster>
+    clusters: Array<Cluster>,
+    submissionIdsToComparisonFileName: Map<string,Map<string,string>>
   ) {
     this._submissionFolderPath = submissionFolderPath;
     this._baseCodeFolderPath = baseCodeFolderPath;
     this._language = language;
     this._fileExtensions = fileExtensions;
     this._matchSensitivity = matchSensitivity;
-    this._submissionIds = submissionIds;
     this._dateOfExecution = dateOfExecution;
     this._durationOfExecution = durationOfExecution;
     this._metrics = metrics;
     this._clusters = clusters;
+    this. _submissionIdsToComparisonFileName = submissionIdsToComparisonFileName;
+  }
+  get submissionIdsToComparisonFileName(): Map<string,Map<string,string>> {
+    return this.submissionIdsToComparisonFileName;
   }
 
   get submissionFolderPath(): Array<string> {
@@ -57,9 +60,6 @@ export class Overview {
     return this._matchSensitivity;
   }
 
-  get submissionIds(): Array<string> {
-    return this._submissionIds;
-  }
 
   get dateOfExecution(): string {
     return this._dateOfExecution;

+ 27 - 5
report-viewer/src/model/factories/OverviewFactory.ts

@@ -2,15 +2,18 @@ import { Overview } from "../Overview";
 import { Metric } from "../Metric";
 import { ComparisonListElement } from "../ComparisonListElement";
 import { Cluster } from "@/model/Cluster";
+import store from "@/store/store";
 
 export class OverviewFactory {
+
   static getOverview(json: Record<string, unknown>): Overview {
     const submissionFolder = json.submission_folder_path as Array<string>;
     const baseCodeFolder = "";
     const language = json.language as string;
     const fileExtensions = json.file_extensions as Array<string>;
     const matchSensitivity = json.match_sensitivity as number;
-    const submissions = json.submission_ids as Array<string>;
+    const jsonSubmissions = json.submission_id_to_display_name as Map<string, string>;
+    const map = new Map<string, string>(Object.entries(jsonSubmissions));
     const dateOfExecution = json.date_of_execution as string;
     const duration = json.execution_time as number as number;
     const metrics = [] as Array<Metric>;
@@ -37,7 +40,7 @@ export class OverviewFactory {
         description: metric.description as string,
       });
     });
-
+    store.commit("saveSubmissionNames", map);
     if (json.clusters) {
       (json.clusters as Array<unknown>).forEach((jsonCluster) => {
         const cluster = jsonCluster as Record<string, unknown>;
@@ -49,18 +52,37 @@ export class OverviewFactory {
         clusters.push(newCluster);
       });
     }
-
+    
+    OverviewFactory.saveSubmissionsToComparisonNameMap(json);
     return new Overview(
       submissionFolder,
       baseCodeFolder,
       language,
       fileExtensions,
       matchSensitivity,
-      submissions,
       dateOfExecution,
       duration,
       metrics,
-      clusters
+      clusters,
+      new Map()
     );
   }
+
+  private static  saveSubmissionsToComparisonNameMap(json: Record<string, unknown>){
+    const submissionIdsToComparisonName =
+    json.submission_ids_to_comparison_file_name as Map<
+      string,
+      Map<string, string>
+    >;
+  const test: Array<Array<string | object>> = Object.entries(
+    submissionIdsToComparisonName
+  );
+  const comparisonMap = new Map<string, Map<string, string>>(
+  );
+  for (const [key, value] of test) {
+    comparisonMap.set(key as string, new Map(Object.entries(value as object)));
+  }
+
+  store.commit("saveComparisonFileLookup", comparisonMap);
+  }
 }

+ 36 - 0
report-viewer/src/store/store.ts

@@ -30,6 +30,9 @@ interface State {
    * Files string if single mode is used.
    */
   fileString: string;
+
+  fileIdToDisplayName: Map<string, string>;
+  submissionIdsToComparisonFileName: Map<string, Map<string, string>>;
 }
 
 interface File {
@@ -50,6 +53,7 @@ interface LoadConfiguration {
 
 const store = createStore<State>({
   state: {
+    submissionIdsToComparisonFileName: new Map<string, Map<string, string>>(),
     /**
      * The set of ids to be hidden.
      */
@@ -75,6 +79,7 @@ const store = createStore<State>({
      * Files string if single mode is used.
      */
     fileString: "",
+    fileIdToDisplayName: new Map(),
   },
   getters: {
     filesOfSubmission: (state) => (name: string) => {
@@ -83,6 +88,28 @@ const store = createStore<State>({
         value,
       }));
     },
+    submissionDisplayName: (state) => (id: string) => {
+      return state.fileIdToDisplayName.get(id);
+    },
+    getSubmissionIds(state): Array<string> {
+      return Array.from(state.fileIdToDisplayName.keys());
+    },
+    getComparisonFileName:
+      (state) => (submissionId: string, fileId: string) => {
+        return state.submissionIdsToComparisonFileName
+          .get(submissionId)
+          ?.get(fileId);
+      },
+      getComparisonFileForSubmissions: (state, getters) => (submissionId1: string, submissionId2: string) => {
+        const expectedFileName = getters.getComparisonFileName(submissionId1, submissionId2);
+        const index = Object.keys(store.state.files).find(
+          (name) =>
+             name.endsWith(expectedFileName)
+        );
+        return index != undefined
+          ? store.state.files[index]
+          : undefined
+      }
   },
   mutations: {
     addAnonymous(state: State, id) {
@@ -90,6 +117,12 @@ const store = createStore<State>({
         state.anonymous.add(id[i]);
       }
     },
+    saveComparisonFileLookup(
+      state: State,
+      map: Map<string, Map<string, string>>
+    ) {
+      state.submissionIdsToComparisonFileName = map;
+    },
     removeAnonymous(state, id) {
       for (let i = 0; i < id.length; i++) {
         state.anonymous.delete(id[i]);
@@ -101,6 +134,9 @@ const store = createStore<State>({
     saveFile(state, file: File) {
       state.files[file.fileName] = file.data;
     },
+    saveSubmissionNames(state, names: Map<string, string>) {
+      state.fileIdToDisplayName = names;
+    },
     saveSubmissionFile(state, submissionFile: SubmissionFile) {
       if (!state.submissions[submissionFile.name]) {
         state.submissions[submissionFile.name] = new Map();

+ 14 - 32
report-viewer/src/views/ComparisonView.vue

@@ -23,12 +23,12 @@
       </div>
       <TextInformation
         :anonymous="isAnonymous(firstId)"
-        :value="firstId"
+        :value="store.getters.submissionDisplayName(firstId)"
         label="Submission 1"
       />
       <TextInformation
         :anonymous="store.state.anonymous.has(secondId)"
-        :value="secondId"
+        :value="store.getters.submissionDisplayName(secondId)"
         label="Submission 2"
       />
       <TextInformation :value="comparison.match_percentage" label="Match %" />
@@ -85,12 +85,6 @@ export default defineComponent({
   setup(props) {
     const store = useStore();
     const router = useRouter();
-    /**
-     * Name of the comparison file. Comparison files should be named {ID1}-{ID2}
-     * @type {string}
-     */
-    const fileName1 = props.firstId.concat("-").concat(props.secondId);
-    const fileName2 = props.firstId.concat("-").concat(props.secondId);
 
     let comparison;
     //getting the comparison file based on the used mode (zip, local, single)
@@ -98,42 +92,31 @@ export default defineComponent({
       try {
         comparison = ComparisonFactory.getComparison(
           // eslint-disable-next-line @typescript-eslint/no-var-requires
-          require(`../files/${fileName1}.json`)
+          require(`../files/${store.getters.getComparisonFileName(
+            props.firstId,
+            props.secondId
+          )}.json`)
         );
       } catch (exception) {
-        try {
-          comparison = ComparisonFactory.getComparison(
-            // eslint-disable-next-line @typescript-eslint/no-var-requires
-            require(`../files/${fileName2}.json`)
-          );
-        } catch (exception) {
-          router.back();
-        }
+        router.back();
       }
     } else if (store.state.zip) {
-      const getComparisonFileFor = (id1: string, id2: string) => {
-        const index = Object.keys(store.state.files).find(
-          (name) =>
-            name.endsWith(id1.concat("-").concat(id2).concat(".json")) ||
-            name.endsWith(id2.concat("-").concat(id1).concat(".json"))
-        );
-        return index != undefined
-          ? store.state.files[index]
-          : console.log("Could not find comparison file."); // TODO introduce error page to navigate to
-      };
-
-      let comparisonFile = getComparisonFileFor(props.firstId, props.secondId);
+      let comparisonFile = store.getters.getComparisonFileForSubmissions(
+        props.firstId,
+        props.secondId
+      );
       if (comparisonFile) {
         comparison = ComparisonFactory.getComparison(
           JSON.parse(comparisonFile)
         );
+      } else {
+        console.log("Could not find comparison file."); // TODO introduce error page to navigate to
       }
     } else if (store.state.single) {
       comparison = ComparisonFactory.getComparison(
         JSON.parse(store.state.fileString)
       );
     }
-
     if (!comparison) {
       console.warn("Could not build comparison file");
       return;
@@ -219,6 +202,7 @@ export default defineComponent({
       filesOfFirst,
       filesOfSecond,
       hideLeftPanel,
+      store,
 
       toggleCollapseFirst,
       toggleCollapseSecond,
@@ -227,8 +211,6 @@ export default defineComponent({
       showMatch,
       togglePanel,
       isAnonymous,
-
-      store,
     };
   },
 });

+ 27 - 27
report-viewer/src/views/OverviewView.vue

@@ -4,7 +4,7 @@
 <template>
   <div class="container">
     <div class="column-container" style="width: 30%">
-        <h1>JPlag Report</h1>
+      <h1>JPlag Report</h1>
       <p class="section-title">Main Info:</p>
       <div id="basicInfo">
         <TextInformation
@@ -35,11 +35,11 @@
         />
         <TextInformation
           :has-additional-info="true"
-          :value="overview.submissionIds.length"
+          :value="store.getters.getSubmissionIds.size"
           additional-info-title="Submission IDs:"
           label="Submissions"
         >
-          <IDsList :ids="overview.submissionIds" @id-sent="handleId" />
+          <IDsList :ids="store.getters.getSubmissionIds" @id-sent="handleId" />
         </TextInformation>
         <TextInformation
           :value="overview.dateOfExecution"
@@ -56,19 +56,19 @@
     </div>
 
     <div class="column-container" style="width: 35%">
-        <div id="metrics">
+      <div id="metrics">
         <p class="section-title">Metric:</p>
-          <div id="metrics-list">
-            <MetricButton
-              v-for="(metric, index) in overview.metrics"
-              :id="metric.metricName"
-              :key="metric.metricName"
-              :is-selected="selectedMetric[index]"
-              :metric="metric"
-              @click="selectMetric(index)"
-            />
-          </div>
+        <div id="metrics-list">
+          <MetricButton
+            v-for="(metric, index) in overview.metrics"
+            :id="metric.metricName"
+            :key="metric.metricName"
+            :is-selected="selectedMetric[index]"
+            :metric="metric"
+            @click="selectMetric(index)"
+          />
         </div>
+      </div>
       <p class="section-title">Distribution:</p>
       <DistributionDiagram
         :distribution="distributions[selectedMetricIndex]"
@@ -78,13 +78,13 @@
     <div class="column-container" style="width: 35%">
       <p class="section-title">Top Comparisons:</p>
       <div id="comparisonsList">
-      <ComparisonsTable
-        :clusters="overview.clusters"
-        :top-comparisons="topComps[selectedMetricIndex]"
-      />
+        <ComparisonsTable
+          :clusters="overview.clusters"
+          :top-comparisons="topComps[selectedMetricIndex]"
+        />
+      </div>
     </div>
   </div>
-  </div>
 </template>
 
 <script lang="ts">
@@ -145,26 +145,26 @@ export default defineComponent({
      * Handles the selection of an Id to anonymize.
      * If all submission ids are provided as parameter it hides or displays them based on their previous state.
      * If a single id is provided it hides all of the other ids except for the chosen one.
-     * @param id
+     * @param ids
      */
-    const handleId = (id: string) => {
-      if (id.length === overview.submissionIds.length) {
+    const handleId = (ids: Array<string>) => {
+      if (ids.length === store.getters.getSubmissionIds.length) {
         if (store.state.anonymous.size > 0) {
           store.commit("resetAnonymous");
         } else {
-          store.commit("addAnonymous", id);
+          store.commit("addAnonymous", ids);
         }
       } else {
-        if (store.state.anonymous.has(id[0])) {
-          store.commit("removeAnonymous", id);
+        if (store.state.anonymous.has(ids[0])) {
+          store.commit("removeAnonymous", ids);
         } else {
           if (store.state.anonymous.size === 0) {
             store.commit(
               "addAnonymous",
-              overview.submissionIds.filter((s) => s !== id[0])
+              store.getters.getSubmissionIds.filter((s: string) => s !== ids[0])
             );
           } else {
-            store.commit("addAnonymous", id);
+            store.commit("addAnonymous", ids);
           }
         }
       }