Browse Source

Merge branch 'master' into golang

Robin Maisch 4 years ago
parent
commit
d43ce72c22
71 changed files with 6297 additions and 1451 deletions
  1. 1 1
      .github/workflows/maven.yml
  2. 1 1
      .github/workflows/publish.yml
  3. 1 1
      .github/workflows/report-viewer.yml
  4. 56 0
      .github/workflows/sonarcloud.yml
  5. 1 1
      README.md
  6. 55 7
      jplag.cli/pom.xml
  7. 28 0
      jplag.frontend.kotlin/README.md
  8. 45 0
      jplag.frontend.kotlin/pom.xml
  9. 592 0
      jplag.frontend.kotlin/src/main/antlr4/de/jplag/kotlin/grammar/KotlinLexer.g4
  10. 814 0
      jplag.frontend.kotlin/src/main/antlr4/de/jplag/kotlin/grammar/KotlinParser.g4
  11. 1647 0
      jplag.frontend.kotlin/src/main/antlr4/imports/UnicodeClasses.g4
  12. 370 0
      jplag.frontend.kotlin/src/main/java/de/jplag/kotlin/JPlagKotlinListener.java
  13. 85 0
      jplag.frontend.kotlin/src/main/java/de/jplag/kotlin/KotlinParserAdapter.java
  14. 75 0
      jplag.frontend.kotlin/src/main/java/de/jplag/kotlin/KotlinToken.java
  15. 79 0
      jplag.frontend.kotlin/src/main/java/de/jplag/kotlin/KotlinTokenConstants.java
  16. 72 0
      jplag.frontend.kotlin/src/main/java/de/jplag/kotlin/Language.java
  17. 172 0
      jplag.frontend.kotlin/src/test/java/de/jplag/kotlin/KotlinFrontendTest.java
  18. 97 0
      jplag.frontend.kotlin/src/test/resources/de/jplag/kotlin/Complete.kt
  19. 47 0
      jplag.frontend.kotlin/src/test/resources/de/jplag/kotlin/Game.kt
  20. 1 0
      jplag.frontend.rlang/pom.xml
  21. 5 4
      jplag.frontend.rlang/src/main/java/de/jplag/rlang/Language.java
  22. 4 0
      jplag/pom.xml
  23. 1 1
      jplag/src/main/java/de/jplag/JPlag.java
  24. 27 9
      jplag/src/main/java/de/jplag/JPlagResult.java
  25. 2 1
      jplag/src/main/java/de/jplag/options/LanguageOption.java
  26. 4 4
      jplag/src/main/java/de/jplag/reporting/jsonfactory/JsonFactory.java
  27. 12 26
      jplag/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java
  28. 54 0
      jplag/src/main/java/de/jplag/reporting/reportobject/mapper/MetricMapper.java
  29. 1 12
      jplag/src/main/java/de/jplag/reporting/reportobject/model/JPlagReport.java
  30. 2 36
      jplag/src/main/java/de/jplag/reporting/reportobject/model/Metric.java
  31. 2 28
      jplag/src/main/java/de/jplag/reporting/reportobject/model/TopComparison.java
  32. 3 1
      jplag/src/main/resources/de/jplag/messages.properties
  33. 3 0
      jplag/src/main/resources/de/jplag/messages_de.properties
  34. 4 0
      jplag/src/main/resources/de/jplag/messages_en.properties
  35. 3 0
      jplag/src/main/resources/de/jplag/messages_es.properties
  36. 3 0
      jplag/src/main/resources/de/jplag/messages_fr.properties
  37. 3 0
      jplag/src/main/resources/de/jplag/messages_pt.properties
  38. 5 5
      jplag/src/test/java/de/jplag/BaseCodeTest.java
  39. 2 2
      jplag/src/test/java/de/jplag/NewJavaFeaturesTest.java
  40. 13 15
      jplag/src/test/java/de/jplag/NormalComparisonTest.java
  41. 9 11
      jplag/src/test/java/de/jplag/ParallelComparisonTest.java
  42. 2 3
      jplag/src/test/java/de/jplag/clustering/ClusteringAdapterTest.java
  43. 115 0
      jplag/src/test/java/de/jplag/reporting/reportobject/mapper/MetricMapperTest.java
  44. 11 5
      jplag/src/test/java/de/jplag/special/TokenPrinterTest.java
  45. 2 2
      jplag/src/test/java/de/jplag/special/VolumeTest.java
  46. 1 0
      jplag/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
  47. 178 122
      pom.xml
  48. 281 271
      report-viewer/package-lock.json
  49. 13 13
      report-viewer/package.json
  50. 4 4
      report-viewer/src/App.vue
  51. 22 0
      report-viewer/src/assets/radar-chart-configuration.ts
  52. 89 64
      report-viewer/src/components/ClusterRadarChart.vue
  53. 54 20
      report-viewer/src/components/ClustersList.vue
  54. 70 47
      report-viewer/src/components/CodePanel.vue
  55. 151 76
      report-viewer/src/components/ComparisonsTable.vue
  56. 4 6
      report-viewer/src/components/DistributionDiagram.vue
  57. 33 31
      report-viewer/src/components/FilesContainer.vue
  58. 1 1
      report-viewer/src/components/LineOfCode.vue
  59. 39 26
      report-viewer/src/components/MetricButton.vue
  60. 1 1
      report-viewer/src/components/TextInformation.vue
  61. 10 0
      report-viewer/src/model/ClusterListElement.ts
  62. 95 91
      report-viewer/src/model/Comparison.ts
  63. 1 0
      report-viewer/src/model/Metric.ts
  64. 66 57
      report-viewer/src/model/Overview.ts
  65. 108 93
      report-viewer/src/model/factories/ComparisonFactory.ts
  66. 60 61
      report-viewer/src/model/factories/OverviewFactory.ts
  67. 22 25
      report-viewer/src/router/index.ts
  68. 93 59
      report-viewer/src/store/store.ts
  69. 147 70
      report-viewer/src/views/ComparisonView.vue
  70. 89 64
      report-viewer/src/views/FileUploadView.vue
  71. 134 73
      report-viewer/src/views/OverviewView.vue

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

@@ -35,7 +35,7 @@ jobs:
         distribution: 'temurin'
             
     - name: Run Tests
-      run: mvn test -B
+      run: mvn verify -B -U
       
     - name: Build Assembly
       run: mvn clean package assembly:single

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

@@ -23,6 +23,6 @@ jobs:
           gpg_private_key: ${{ secrets.PGP_SECRET }}
           passphrase: ${{ secrets.PGP_PW }}
       - name: Publish package
-        run: mvn --batch-mode deploy
+        run: mvn -P deployment -U -B deploy
         env:
           GPG_PASSPHRASE: ${{ secrets.PGP_PW }}

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

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

+ 56 - 0
.github/workflows/sonarcloud.yml

@@ -0,0 +1,56 @@
+name: SonarCloud Scan
+
+on:
+  push:
+    branches: [ main, master ]
+  pull_request:
+    types: [opened, synchronize, reopened]
+
+
+  # Allows you to run this workflow manually from the Actions tab
+  workflow_dispatch:
+
+jobs:
+  pre_job:
+    runs-on: ubuntu-latest
+    outputs:
+      should_skip: ${{ steps.skip_check.outputs.should_skip }}
+    steps:
+      - id: skip_check
+        uses: fkirc/skip-duplicate-actions@master
+        with:
+          concurrent_skipping: 'same_content_newer'
+          skip_after_successful_duplicate: 'true'
+  sonar:
+    needs: pre_job
+    name: SonarCloud Scan
+    runs-on: ubuntu-latest
+    if: ${{ needs.pre_job.outputs.should_skip != 'true' && github.actor != 'dependabot[bot]' }}
+
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          fetch-depth: 0  # Shallow clones should be disabled for a better relevancy of analysis
+      - name: Set up JDK
+        uses: actions/setup-java@v3
+        with:
+          distribution: 'temurin'
+          java-version: 17
+
+      - name: Cache SonarCloud packages
+        uses: actions/cache@v3
+        with:
+          path: ~/.sonar/cache
+          key: ${{ runner.os }}-sonar
+          restore-keys: ${{ runner.os }}-sonar
+      - name: Cache Maven packages
+        uses: actions/cache@v3
+        with:
+          path: ~/.m2
+          key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
+          restore-keys: ${{ runner.os }}-m2
+      - name: Build and analyze
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+        run: mvn -U -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar

+ 1 - 1
README.md

@@ -48,7 +48,7 @@ Usage: JPlag [ options ] [ <root-dir> ... ] [ -new <new-dir> ... ] [ -old <old-d
 
 named arguments:
   -h, --help       show this help message and exit
-  -l               {java,python3,cpp,csharp,rlang,golang,char,text,scheme} Select the language to parse the submissions (default: java)
+  -l               {java,python3,cpp,csharp,golang,kotlin,rlang,char,text,scheme} Select the language to parse the submissions (default: java)
   -bc BC           Path of the directory containing the base code (common framework used in all submissions)
   -v               {quiet,long} Verbosity of the logging (default: quiet)
   -d               Debug parser. Non-parsable files will be stored (default: false)

+ 55 - 7
jplag.cli/pom.xml

@@ -12,6 +12,50 @@
             <groupId>de.jplag</groupId>
             <artifactId>jplag</artifactId>
         </dependency>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>jplag</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>frontend-utils</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>text</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>java</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>python-3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>csharp-6</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>cpp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>rlang</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>kotlin</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>scheme</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>chars</artifactId>
+        </dependency>
         <dependency>
             <groupId>net.sourceforge.argparse4j</groupId>
             <artifactId>argparse4j</artifactId>
@@ -50,14 +94,18 @@
                     <finalName>jplag-${project.version}</finalName>
                 </configuration>
             </plugin>
-
             <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-deploy-plugin</artifactId>
-                <configuration>
-                    <!-- Skip deployment of CLI to Maven Central -->
-                    <skip>true</skip>
-                </configuration>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>report-aggregate</id>
+                        <goals>
+                            <goal>report-aggregate</goal>
+                        </goals>
+                        <phase>verify</phase>
+                    </execution>
+                </executions>
             </plugin>
         </plugins>
     </build>

+ 28 - 0
jplag.frontend.kotlin/README.md

@@ -0,0 +1,28 @@
+# JPlag Kotlin language frontend
+
+The JPlag Kotlin frontend allows the use of JPlag with submissions in Kotlin. <br>
+It is based on the [Kotlin ANTLR4 grammar](https://github.com/antlr/grammars-v4/tree/master/kotlin/kotlin), licensed under the Apache 2.0.
+
+### Kotlin specification compatibility
+
+The underlying grammar definition does not specify which version of Kotlin it is built on, but the presence of the `inline` keyword and the lack of support for trailing commas in argument lists indicate that it complies with Kotlin 1.3, released in late 2018.
+
+The grammar in this repo contains a fix and some modifications to allow for easier token generation, see the comment in the [KotlinParser](src/main/antlr4/de/jplag/kotlin/grammar/KotlinParser.g4).
+
+If there are any major updates or fixes to the grammar<a href="#footnote-1"><sup>1</sup></a>, they should surely be applied to this module as well. 
+
+
+### Token Extraction
+
+The choice of tokens is intended to be similar to the Java or C# frontends. It includes a range of nesting structures (class, method, control flow expressions) as well as variable declaration, object creation, assignment, and control flow altering keywords.
+
+More syntactic elements of Kotlin may turn out to be helpful to include in the future.
+
+### Usage
+
+To use the Kotlin frontend, add the `-l kotlin` flag in the CLI, or use a `JPlagOption` object set to `LanguageOption.KOTLIN` 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).
+
+<br>
+
+#### Footnotes
+<section id="footnote-1"><sup>1 </sup>The grammar files are taken from grammar-v4, with the most recent modification in <a href="https://github.com/antlr/grammars-v4/tree/9644ff90b769cecf2ee0089c88993042e401a75e/kotlin/kotlin">commit 9644ff9</a> from February 2021.</section>

+ 45 - 0
jplag.frontend.kotlin/pom.xml

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>de.jplag</groupId>
+        <artifactId>aggregator</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <artifactId>kotlin</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.antlr</groupId>
+            <artifactId>antlr4-runtime</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>frontend-utils</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>frontend-testutils</artifactId>
+            <version>${revision}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.antlr</groupId>
+                <artifactId>antlr4-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>antlr4</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 592 - 0
jplag.frontend.kotlin/src/main/antlr4/de/jplag/kotlin/grammar/KotlinLexer.g4

@@ -0,0 +1,592 @@
+/**
+ * Kotlin Grammar for ANTLR v4
+ *
+ * Based on:
+ * http://jetbrains.github.io/kotlin-spec/#_grammars_and_parsing
+ * and
+ * http://kotlinlang.org/docs/reference/grammar.html
+ *
+ * Tested on
+ * https://github.com/JetBrains/kotlin/tree/master/compiler/testData/psi
+ */
+
+/*
+ * Source: https://github.com/antlr/grammars-v4
+ * Licenced under the Apache 2.0.
+ */
+
+lexer grammar KotlinLexer;
+
+import UnicodeClasses;
+
+ShebangLine
+    : '#!' ~[\u000A\u000D]*
+      -> channel(HIDDEN)
+    ;
+
+DelimitedComment
+    : '/*' ( DelimitedComment | . )*? '*/'
+      -> channel(HIDDEN)
+    ;
+
+LineComment
+    : '//' ~[\u000A\u000D]*
+      -> channel(HIDDEN)
+    ;
+
+WS
+    : [\u0020\u0009\u000C]
+      -> skip
+    ;
+
+NL: '\u000A' | '\u000D' '\u000A' ;
+
+//SEPARATORS & OPERATIONS
+
+RESERVED: '...' ;
+DOT: '.' ;
+COMMA: ',' ;
+LPAREN: '(' -> pushMode(Inside) ;
+RPAREN: ')' ;
+LSQUARE: '[' -> pushMode(Inside) ;
+RSQUARE: ']' ;
+LCURL: '{' ;
+RCURL: '}' ;
+MULT: '*' ;
+MOD: '%' ;
+DIV: '/' ;
+ADD: '+' ;
+SUB: '-' ;
+INCR: '++' ;
+DECR: '--' ;
+CONJ: '&&' ;
+DISJ: '||' ;
+EXCL: '!' ;
+COLON: ':' ;
+SEMICOLON: ';' ;
+ASSIGNMENT: '=' ;
+ADD_ASSIGNMENT: '+=' ;
+SUB_ASSIGNMENT: '-=' ;
+MULT_ASSIGNMENT: '*=' ;
+DIV_ASSIGNMENT: '/=' ;
+MOD_ASSIGNMENT: '%=' ;
+ARROW: '->' ;
+DOUBLE_ARROW: '=>' ;
+RANGE: '..' ;
+COLONCOLON: '::' ;
+Q_COLONCOLON: '?::' ;
+DOUBLE_SEMICOLON: ';;' ;
+HASH: '#' ;
+AT: '@' ;
+QUEST: '?' ;
+ELVIS: '?:' ;
+LANGLE: '<' ;
+RANGLE: '>' ;
+LE: '<=' ;
+GE: '>=' ;
+EXCL_EQ: '!=' ;
+EXCL_EQEQ: '!==' ;
+AS_SAFE: 'as?' ;
+EQEQ: '==' ;
+EQEQEQ: '===' ;
+SINGLE_QUOTE: '\'' ;
+
+//KEYWORDS
+
+RETURN_AT: 'return@' Identifier ;
+CONTINUE_AT: 'continue@' Identifier ;
+BREAK_AT: 'break@' Identifier ;
+
+FILE: '@file' ;
+PACKAGE: 'package' ;
+IMPORT: 'import' ;
+CLASS: 'class' ;
+INTERFACE: 'interface' ;
+FUN: 'fun' ;
+OBJECT: 'object' ;
+VAL: 'val' ;
+VAR: 'var' ;
+TYPE_ALIAS: 'typealias' ;
+CONSTRUCTOR: 'constructor' ;
+BY: 'by' ;
+COMPANION: 'companion' ;
+INIT: 'init'  ;
+THIS: 'this' ;
+SUPER: 'super' ;
+TYPEOF: 'typeof' ;
+WHERE: 'where' ;
+IF: 'if' ;
+ELSE: 'else' ;
+WHEN: 'when' ;
+TRY: 'try' ;
+CATCH: 'catch' ;
+FINALLY: 'finally' ;
+FOR: 'for' ;
+DO: 'do' ;
+WHILE: 'while' ;
+THROW: 'throw' ;
+RETURN: 'return' ;
+CONTINUE: 'continue' ;
+BREAK: 'break' ;
+AS: 'as' ;
+IS: 'is' ;
+IN: 'in' ;
+NOT_IS: '!is' (WS | NL)+ ;
+NOT_IN: '!in' (WS | NL)+ ;
+OUT: 'out' ;
+FIELD: '@field' ;
+PROPERTY: '@property' ;
+GET: '@get' ;
+SET: '@set' ;
+GETTER: 'get' ;
+SETTER: 'set' ;
+RECEIVER: '@receiver' ;
+PARAM: '@param' ;
+SETPARAM: '@setparam' ;
+DELEGATE: '@delegate' ;
+DYNAMIC: 'dynamic' ;
+
+//MODIFIERS
+
+PUBLIC: 'public' ;
+PRIVATE: 'private' ;
+PROTECTED: 'protected' ;
+INTERNAL: 'internal' ;
+ENUM: 'enum' ;
+SEALED: 'sealed' ;
+ANNOTATION: 'annotation' ;
+DATA: 'data' ;
+INNER: 'inner' ;
+TAILREC: 'tailrec' ;
+OPERATOR: 'operator' ;
+INLINE: 'inline' ;
+INFIX: 'infix' ;
+EXTERNAL: 'external' ;
+SUSPEND: 'suspend' ;
+OVERRIDE: 'override' ;
+ABSTRACT: 'abstract' ;
+FINAL: 'final' ;
+OPEN: 'open' ;
+CONST: 'const' ;
+LATEINIT: 'lateinit' ;
+VARARG: 'vararg' ;
+NOINLINE: 'noinline' ;
+CROSSINLINE: 'crossinline' ;
+REIFIED: 'reified' ;
+
+//
+
+QUOTE_OPEN: '"' -> pushMode(LineString) ;
+TRIPLE_QUOTE_OPEN: '"""' -> pushMode(MultiLineString) ;
+
+RealLiteral
+    : FloatLiteral
+    | DoubleLiteral
+    ;
+
+FloatLiteral
+    : (DoubleLiteral | IntegerLiteral) [fF]
+    ;
+
+DoubleLiteral
+    : ( (DecDigitNoZero DecDigit* | '0')? '.'
+      | (DecDigitNoZero (DecDigit | '_')* DecDigit)? '.')
+     ( DecDigit+
+      | DecDigit (DecDigit | '_')+ DecDigit
+      | DecDigit+ [eE] ('+' | '-')? DecDigit+
+      | DecDigit+ [eE] ('+' | '-')? DecDigit (DecDigit | '_')+ DecDigit
+      | DecDigit (DecDigit | '_')+ DecDigit [eE] ('+' | '-')? DecDigit+
+      | DecDigit (DecDigit | '_')+ DecDigit [eE] ('+' | '-')? DecDigit (DecDigit | '_')+ DecDigit
+     )
+    ;
+
+LongLiteral
+    : (IntegerLiteral | HexLiteral | BinLiteral) 'L'
+    ;
+
+IntegerLiteral
+    : ('0'
+      | DecDigitNoZero DecDigit*
+      | DecDigitNoZero (DecDigit | '_')+ DecDigit
+      | DecDigitNoZero DecDigit* [eE] ('+' | '-')? DecDigit+
+      | DecDigitNoZero DecDigit* [eE] ('+' | '-')? DecDigit (DecDigit | '_')+ DecDigit
+      | DecDigitNoZero (DecDigit | '_')+ DecDigit [eE] ('+' | '-')? DecDigit+
+      | DecDigitNoZero (DecDigit | '_')+ DecDigit [eE] ('+' | '-')? DecDigit (DecDigit | '_')+ DecDigit
+      )
+    ;
+
+fragment DecDigit
+    : UNICODE_CLASS_ND
+    ;
+
+fragment DecDigitNoZero
+    : UNICODE_CLASS_ND_NoZeros
+    ;
+
+fragment UNICODE_CLASS_ND_NoZeros
+	: '\u0031'..'\u0039'
+	| '\u0661'..'\u0669'
+	| '\u06f1'..'\u06f9'
+	| '\u07c1'..'\u07c9'
+	| '\u0967'..'\u096f'
+	| '\u09e7'..'\u09ef'
+	| '\u0a67'..'\u0a6f'
+	| '\u0ae7'..'\u0aef'
+	| '\u0b67'..'\u0b6f'
+	| '\u0be7'..'\u0bef'
+	| '\u0c67'..'\u0c6f'
+	| '\u0ce7'..'\u0cef'
+	| '\u0d67'..'\u0d6f'
+	| '\u0de7'..'\u0def'
+	| '\u0e51'..'\u0e59'
+	| '\u0ed1'..'\u0ed9'
+	| '\u0f21'..'\u0f29'
+	| '\u1041'..'\u1049'
+	| '\u1091'..'\u1099'
+	| '\u17e1'..'\u17e9'
+	| '\u1811'..'\u1819'
+	| '\u1947'..'\u194f'
+	| '\u19d1'..'\u19d9'
+	| '\u1a81'..'\u1a89'
+	| '\u1a91'..'\u1a99'
+	| '\u1b51'..'\u1b59'
+	| '\u1bb1'..'\u1bb9'
+	| '\u1c41'..'\u1c49'
+	| '\u1c51'..'\u1c59'
+	| '\ua621'..'\ua629'
+	| '\ua8d1'..'\ua8d9'
+	| '\ua901'..'\ua909'
+	| '\ua9d1'..'\ua9d9'
+	| '\ua9f1'..'\ua9f9'
+	| '\uaa51'..'\uaa59'
+	| '\uabf1'..'\uabf9'
+	| '\uff11'..'\uff19'
+	;
+
+HexLiteral
+    : '0' [xX] HexDigit (HexDigit | '_')*
+    ;
+
+fragment HexDigit
+    : [0-9a-fA-F]
+    ;
+
+BinLiteral
+    : '0' [bB] BinDigit (BinDigit | '_')*
+    ;
+
+fragment BinDigit
+    : [01]
+    ;
+
+BooleanLiteral
+    : 'true'
+    | 'false'
+    ;
+
+NullLiteral
+    : 'null'
+    ;
+
+Identifier
+    : (Letter | '_') (Letter | '_' | DecDigit)*
+    | '`' ~('`')+ '`'
+    ;
+
+LabelReference
+    : '@' Identifier
+    ;
+
+LabelDefinition
+    : Identifier '@'
+    ;
+
+FieldIdentifier
+    : '$' Identifier
+    ;
+
+CharacterLiteral
+    : '\'' (EscapeSeq | .) '\''
+    ;
+
+fragment EscapeSeq
+    : UniCharacterLiteral
+    | EscapedIdentifier
+    ;
+
+fragment UniCharacterLiteral
+    : '\\' 'u' HexDigit HexDigit HexDigit HexDigit
+    ;
+
+fragment EscapedIdentifier
+    : '\\' ('t' | 'b' | 'r' | 'n' | '\'' | '"' | '\\' | '$')
+    ;
+
+fragment Letter
+    : UNICODE_CLASS_LL
+    | UNICODE_CLASS_LM
+    | UNICODE_CLASS_LO
+    | UNICODE_CLASS_LT
+    | UNICODE_CLASS_LU
+    | UNICODE_CLASS_NL
+    ;
+
+
+mode Inside ;
+
+Inside_RPAREN: ')' -> popMode, type(RPAREN) ;
+Inside_RSQUARE: ']' -> popMode, type(RSQUARE);
+
+Inside_LPAREN: LPAREN -> pushMode(Inside), type(LPAREN) ;
+Inside_LSQUARE: LSQUARE -> pushMode(Inside), type(LSQUARE) ;
+
+Inside_LCURL: LCURL -> type(LCURL) ;
+Inside_RCURL: RCURL -> type(RCURL) ;
+Inside_DOT: DOT -> type(DOT) ;
+Inside_COMMA: COMMA  -> type(COMMA) ;
+Inside_MULT: MULT -> type(MULT) ;
+Inside_MOD: MOD  -> type(MOD) ;
+Inside_DIV: DIV -> type(DIV) ;
+Inside_ADD: ADD  -> type(ADD) ;
+Inside_SUB: SUB  -> type(SUB) ;
+Inside_INCR: INCR  -> type(INCR) ;
+Inside_DECR: DECR  -> type(DECR) ;
+Inside_CONJ: CONJ  -> type(CONJ) ;
+Inside_DISJ: DISJ  -> type(DISJ) ;
+Inside_EXCL: EXCL  -> type(EXCL) ;
+Inside_COLON: COLON  -> type(COLON) ;
+Inside_SEMICOLON: SEMICOLON  -> type(SEMICOLON) ;
+Inside_ASSIGNMENT: ASSIGNMENT  -> type(ASSIGNMENT) ;
+Inside_ADD_ASSIGNMENT: ADD_ASSIGNMENT  -> type(ADD_ASSIGNMENT) ;
+Inside_SUB_ASSIGNMENT: SUB_ASSIGNMENT  -> type(SUB_ASSIGNMENT) ;
+Inside_MULT_ASSIGNMENT: MULT_ASSIGNMENT  -> type(MULT_ASSIGNMENT) ;
+Inside_DIV_ASSIGNMENT: DIV_ASSIGNMENT  -> type(DIV_ASSIGNMENT) ;
+Inside_MOD_ASSIGNMENT: MOD_ASSIGNMENT  -> type(MOD_ASSIGNMENT) ;
+Inside_ARROW: ARROW  -> type(ARROW) ;
+Inside_DOUBLE_ARROW: DOUBLE_ARROW  -> type(DOUBLE_ARROW) ;
+Inside_RANGE: RANGE  -> type(RANGE) ;
+Inside_RESERVED: RESERVED -> type(RESERVED) ;
+Inside_COLONCOLON: COLONCOLON  -> type(COLONCOLON) ;
+Inside_Q_COLONCOLON: Q_COLONCOLON -> type(Q_COLONCOLON) ;
+Inside_DOUBLE_SEMICOLON: DOUBLE_SEMICOLON  -> type(DOUBLE_SEMICOLON) ;
+Inside_HASH: HASH  -> type(HASH) ;
+Inside_AT: AT  -> type(AT) ;
+Inside_QUEST: QUEST  -> type(QUEST) ;
+Inside_ELVIS: ELVIS  -> type(ELVIS) ;
+Inside_LANGLE: LANGLE  -> type(LANGLE) ;
+Inside_RANGLE: RANGLE  -> type(RANGLE) ;
+Inside_LE: LE  -> type(LE) ;
+Inside_GE: GE  -> type(GE) ;
+Inside_EXCL_EQ: EXCL_EQ  -> type(EXCL_EQ) ;
+Inside_EXCL_EQEQ: EXCL_EQEQ  -> type(EXCL_EQEQ) ;
+Inside_NOT_IS: NOT_IS -> type(NOT_IS) ;
+Inside_NOT_IN: NOT_IN -> type(NOT_IN) ;
+Inside_AS_SAFE: AS_SAFE  -> type(AS_SAFE) ;
+Inside_EQEQ: EQEQ  -> type(EQEQ) ;
+Inside_EQEQEQ: EQEQEQ  -> type(EQEQEQ) ;
+Inside_SINGLE_QUOTE: SINGLE_QUOTE  -> type(SINGLE_QUOTE) ;
+Inside_QUOTE_OPEN: QUOTE_OPEN -> pushMode(LineString), type(QUOTE_OPEN) ;
+Inside_TRIPLE_QUOTE_OPEN: TRIPLE_QUOTE_OPEN -> pushMode(MultiLineString), type(TRIPLE_QUOTE_OPEN) ;
+
+Inside_VAL: VAL -> type(VAL) ;
+Inside_VAR: VAR -> type(VAR) ;
+Inside_OBJECT: OBJECT -> type(OBJECT) ;
+Inside_SUPER: SUPER -> type(SUPER) ;
+Inside_IN: IN -> type(IN) ;
+Inside_OUT: OUT -> type(OUT) ;
+Inside_FIELD: FIELD -> type(FIELD) ;
+Inside_FILE: FILE -> type(FILE) ;
+Inside_PROPERTY: PROPERTY -> type(PROPERTY) ;
+Inside_GET: GET -> type(GET) ;
+Inside_SET: SET -> type(SET) ;
+Inside_RECEIVER: RECEIVER -> type(RECEIVER) ;
+Inside_PARAM: PARAM -> type(PARAM) ;
+Inside_SETPARAM: SETPARAM -> type(SETPARAM) ;
+Inside_DELEGATE: DELEGATE -> type(DELEGATE) ;
+Inside_THROW: THROW -> type(THROW) ;
+Inside_RETURN: RETURN -> type(RETURN) ;
+Inside_CONTINUE: CONTINUE -> type(CONTINUE) ;
+Inside_BREAK: BREAK -> type(BREAK) ;
+Inside_RETURN_AT: RETURN_AT -> type(RETURN_AT) ;
+Inside_CONTINUE_AT: CONTINUE_AT -> type(CONTINUE_AT) ;
+Inside_BREAK_AT: BREAK_AT -> type(BREAK_AT) ;
+Inside_IF: IF -> type(IF) ;
+Inside_ELSE: ELSE -> type(ELSE) ;
+Inside_WHEN: WHEN -> type(WHEN) ;
+Inside_TRY: TRY -> type(TRY) ;
+Inside_CATCH: CATCH -> type(CATCH) ;
+Inside_FINALLY: FINALLY -> type(FINALLY) ;
+Inside_FOR: FOR -> type(FOR) ;
+Inside_DO: DO -> type(DO) ;
+Inside_WHILE: WHILE -> type(WHILE) ;
+
+Inside_PUBLIC: PUBLIC -> type(PUBLIC) ;
+Inside_PRIVATE: PRIVATE -> type(PRIVATE) ;
+Inside_PROTECTED: PROTECTED -> type(PROTECTED) ;
+Inside_INTERNAL: INTERNAL -> type(INTERNAL) ;
+Inside_ENUM: ENUM -> type(ENUM) ;
+Inside_SEALED: SEALED -> type(SEALED) ;
+Inside_ANNOTATION: ANNOTATION -> type(ANNOTATION) ;
+Inside_DATA: DATA -> type(DATA) ;
+Inside_INNER: INNER -> type(INNER) ;
+Inside_TAILREC: TAILREC -> type(TAILREC) ;
+Inside_OPERATOR: OPERATOR -> type(OPERATOR) ;
+Inside_INLINE: INLINE -> type(INLINE) ;
+Inside_INFIX: INFIX -> type(INFIX) ;
+Inside_EXTERNAL: EXTERNAL -> type(EXTERNAL) ;
+Inside_SUSPEND: SUSPEND -> type(SUSPEND) ;
+Inside_OVERRIDE: OVERRIDE -> type(OVERRIDE) ;
+Inside_ABSTRACT: ABSTRACT -> type(ABSTRACT) ;
+Inside_FINAL: FINAL -> type(FINAL) ;
+Inside_OPEN: OPEN -> type(OPEN) ;
+Inside_CONST: CONST -> type(CONST) ;
+Inside_LATEINIT: LATEINIT -> type(LATEINIT) ;
+Inside_VARARG: VARARG -> type(VARARG) ;
+Inside_NOINLINE: NOINLINE -> type(NOINLINE) ;
+Inside_CROSSINLINE: CROSSINLINE -> type(CROSSINLINE) ;
+Inside_REIFIED: REIFIED -> type(REIFIED) ;
+
+Inside_BooleanLiteral: BooleanLiteral -> type(BooleanLiteral) ;
+Inside_IntegerLiteral: IntegerLiteral -> type(IntegerLiteral) ;
+Inside_HexLiteral: HexLiteral -> type(HexLiteral) ;
+Inside_BinLiteral: BinLiteral -> type(BinLiteral) ;
+Inside_CharacterLiteral: CharacterLiteral -> type(CharacterLiteral) ;
+Inside_RealLiteral: RealLiteral -> type(RealLiteral) ;
+Inside_NullLiteral: NullLiteral -> type(NullLiteral) ;
+
+Inside_LongLiteral: LongLiteral -> type(LongLiteral) ;
+
+Inside_Identifier: Identifier -> type(Identifier) ;
+Inside_LabelReference: LabelReference -> type(LabelReference) ;
+Inside_LabelDefinition: LabelDefinition -> type(LabelDefinition) ;
+Inside_Comment: (LineComment | DelimitedComment) -> channel(HIDDEN) ;
+Inside_WS: WS -> skip ;
+Inside_NL: NL -> skip ;
+
+
+mode LineString ;
+
+QUOTE_CLOSE
+    : '"' -> popMode
+    ;
+
+LineStrRef
+    : FieldIdentifier
+    ;
+
+LineStrText
+    : ~('\\' | '"' | '$')+ | '$'
+    ;
+
+LineStrEscapedChar
+    : '\\' .
+    | UniCharacterLiteral
+    ;
+
+LineStrExprStart
+    : '${' -> pushMode(StringExpression)
+    ;
+
+
+mode MultiLineString ;
+
+TRIPLE_QUOTE_CLOSE
+    : MultiLineStringQuote? '"""' -> popMode
+    ;
+
+MultiLineStringQuote
+    : '"'+
+    ;
+
+MultiLineStrRef
+    : FieldIdentifier
+    ;
+
+MultiLineStrText
+    :  ~('\\' | '"' | '$')+ | '$'
+    ;
+
+MultiLineStrEscapedChar
+    : '\\' .
+    ;
+
+MultiLineStrExprStart
+    : '${' -> pushMode(StringExpression)
+    ;
+
+MultiLineNL: NL -> skip ;
+
+
+mode StringExpression ;
+
+StrExpr_RCURL: RCURL -> popMode, type(RCURL) ;
+
+StrExpr_LPAREN: LPAREN -> pushMode(Inside), type(LPAREN) ;
+StrExpr_LSQUARE: LSQUARE -> pushMode(Inside), type(LSQUARE) ;
+
+StrExpr_RPAREN: ')' -> type(RPAREN) ;
+StrExpr_RSQUARE: ']' -> type(RSQUARE);
+StrExpr_LCURL: LCURL -> pushMode(StringExpression), type(LCURL) ;
+StrExpr_DOT: DOT -> type(DOT) ;
+StrExpr_COMMA: COMMA  -> type(COMMA) ;
+StrExpr_MULT: MULT -> type(MULT) ;
+StrExpr_MOD: MOD  -> type(MOD) ;
+StrExpr_DIV: DIV -> type(DIV) ;
+StrExpr_ADD: ADD  -> type(ADD) ;
+StrExpr_SUB: SUB  -> type(SUB) ;
+StrExpr_INCR: INCR  -> type(INCR) ;
+StrExpr_DECR: DECR  -> type(DECR) ;
+StrExpr_CONJ: CONJ  -> type(CONJ) ;
+StrExpr_DISJ: DISJ  -> type(DISJ) ;
+StrExpr_EXCL: EXCL  -> type(EXCL) ;
+StrExpr_COLON: COLON  -> type(COLON) ;
+StrExpr_SEMICOLON: SEMICOLON  -> type(SEMICOLON) ;
+StrExpr_ASSIGNMENT: ASSIGNMENT  -> type(ASSIGNMENT) ;
+StrExpr_ADD_ASSIGNMENT: ADD_ASSIGNMENT  -> type(ADD_ASSIGNMENT) ;
+StrExpr_SUB_ASSIGNMENT: SUB_ASSIGNMENT  -> type(SUB_ASSIGNMENT) ;
+StrExpr_MULT_ASSIGNMENT: MULT_ASSIGNMENT  -> type(MULT_ASSIGNMENT) ;
+StrExpr_DIV_ASSIGNMENT: DIV_ASSIGNMENT  -> type(DIV_ASSIGNMENT) ;
+StrExpr_MOD_ASSIGNMENT: MOD_ASSIGNMENT  -> type(MOD_ASSIGNMENT) ;
+StrExpr_ARROW: ARROW  -> type(ARROW) ;
+StrExpr_DOUBLE_ARROW: DOUBLE_ARROW  -> type(DOUBLE_ARROW) ;
+StrExpr_RANGE: RANGE  -> type(RANGE) ;
+StrExpr_COLONCOLON: COLONCOLON  -> type(COLONCOLON) ;
+StrExpr_Q_COLONCOLON: Q_COLONCOLON -> type(Q_COLONCOLON) ;
+StrExpr_DOUBLE_SEMICOLON: DOUBLE_SEMICOLON  -> type(DOUBLE_SEMICOLON) ;
+StrExpr_HASH: HASH  -> type(HASH) ;
+StrExpr_AT: AT  -> type(AT) ;
+StrExpr_QUEST: QUEST  -> type(QUEST) ;
+StrExpr_ELVIS: ELVIS  -> type(ELVIS) ;
+StrExpr_LANGLE: LANGLE  -> type(LANGLE) ;
+StrExpr_RANGLE: RANGLE  -> type(RANGLE) ;
+StrExpr_LE: LE  -> type(LE) ;
+StrExpr_GE: GE  -> type(GE) ;
+StrExpr_EXCL_EQ: EXCL_EQ  -> type(EXCL_EQ) ;
+StrExpr_EXCL_EQEQ: EXCL_EQEQ  -> type(EXCL_EQEQ) ;
+StrExpr_AS: AS -> type(IS) ;
+StrExpr_IS: IS -> type(IN) ;
+StrExpr_IN: IN ;
+StrExpr_NOT_IS: NOT_IS -> type(NOT_IS) ;
+StrExpr_NOT_IN: NOT_IN -> type(NOT_IN) ;
+StrExpr_AS_SAFE: AS_SAFE  -> type(AS_SAFE) ;
+StrExpr_EQEQ: EQEQ  -> type(EQEQ) ;
+StrExpr_EQEQEQ: EQEQEQ  -> type(EQEQEQ) ;
+StrExpr_SINGLE_QUOTE: SINGLE_QUOTE  -> type(SINGLE_QUOTE) ;
+StrExpr_QUOTE_OPEN: QUOTE_OPEN -> pushMode(LineString), type(QUOTE_OPEN) ;
+StrExpr_TRIPLE_QUOTE_OPEN: TRIPLE_QUOTE_OPEN -> pushMode(MultiLineString), type(TRIPLE_QUOTE_OPEN) ;
+
+StrExpr_BooleanLiteral: BooleanLiteral -> type(BooleanLiteral) ;
+StrExpr_IntegerLiteral: IntegerLiteral -> type(IntegerLiteral) ;
+StrExpr_HexLiteral: HexLiteral -> type(HexLiteral) ;
+StrExpr_BinLiteral: BinLiteral -> type(BinLiteral) ;
+StrExpr_CharacterLiteral: CharacterLiteral -> type(CharacterLiteral) ;
+StrExpr_RealLiteral: RealLiteral -> type(RealLiteral) ;
+StrExpr_NullLiteral: NullLiteral -> type(NullLiteral) ;
+StrExpr_LongLiteral: LongLiteral -> type(LongLiteral) ;
+
+StrExpr_Identifier: Identifier -> type(Identifier) ;
+StrExpr_LabelReference: LabelReference -> type(LabelReference) ;
+StrExpr_LabelDefinition: LabelDefinition -> type(LabelDefinition) ;
+StrExpr_Comment: (LineComment | DelimitedComment) -> channel(HIDDEN) ;
+StrExpr_WS: WS -> skip ;
+StrExpr_NL: NL -> skip ;

+ 814 - 0
jplag.frontend.kotlin/src/main/antlr4/de/jplag/kotlin/grammar/KotlinParser.g4

@@ -0,0 +1,814 @@
+/**
+ * Kotlin Grammar for ANTLR v4
+ *
+ * Based on:
+ * http://jetbrains.github.io/kotlin-spec/#_grammars_and_parsing
+ * and
+ * http://kotlinlang.org/docs/reference/grammar.html
+ *
+ * Tested on
+ * https://github.com/JetBrains/kotlin/tree/master/compiler/testData/psi
+ */
+
+/*
+ * Source: https://github.com/antlr/grammars-v4
+ * Licenced under the Apache 2.0.
+ *
+ * Modifications:
+ * 'propertyDeclaration' rule: Add "NL*" twice (suspected fault in orginial).
+ * 'block' rule: Introduced specific types of blocks for each occurrence, namely 'tryBody', 'catchBody', 'finallyBody'.
+ * - Robin Maisch
+*/
+
+parser grammar KotlinParser;
+
+options { tokenVocab = KotlinLexer; }
+
+kotlinFile
+    : NL* preamble anysemi* (topLevelObject (anysemi+ topLevelObject?)*)? EOF
+    ;
+
+script
+    : NL* preamble anysemi* (expression (anysemi+ expression?)*)? EOF
+    ;
+
+preamble
+    : fileAnnotations? packageHeader importList
+    ;
+
+fileAnnotations
+    : fileAnnotation+
+    ;
+
+fileAnnotation
+    : (FILE COLON (LSQUARE unescapedAnnotation+ RSQUARE | unescapedAnnotation) semi?)+
+    ;
+
+packageHeader
+    : (modifierList? PACKAGE identifier semi?)?
+    ;
+
+importList
+    : importHeader*
+    ;
+
+importHeader
+    : IMPORT identifier (DOT MULT | importAlias)? semi?
+    ;
+
+importAlias
+    : AS simpleIdentifier
+    ;
+
+topLevelObject
+    : classDeclaration
+    | objectDeclaration
+    | functionDeclaration
+    | propertyDeclaration
+    | typeAlias
+    ;
+
+classDeclaration
+    : modifierList? (CLASS | INTERFACE) NL* simpleIdentifier
+    (NL* typeParameters)? (NL* primaryConstructor)?
+    (NL* COLON NL* delegationSpecifiers)?
+    (NL* typeConstraints)?
+    (NL* classBody | NL* enumClassBody)?
+    ;
+
+primaryConstructor
+    : modifierList? (CONSTRUCTOR NL*)? classParameters
+    ;
+
+classParameters
+    : LPAREN (classParameter (COMMA classParameter)*)? RPAREN
+    ;
+
+classParameter
+    : modifierList? (VAL | VAR)? simpleIdentifier COLON type (ASSIGNMENT expression)?
+    ;
+
+delegationSpecifiers
+    : annotations* delegationSpecifier (NL* COMMA NL* delegationSpecifier)*
+    ;
+
+delegationSpecifier
+    : constructorInvocation
+    | userType
+    | explicitDelegation
+    ;
+
+constructorInvocation
+    : userType callSuffix
+    ;
+
+explicitDelegation
+    : userType NL* BY NL* expression
+    ;
+
+classBody
+    : LCURL NL* classMemberDeclaration* NL* RCURL
+    ;
+
+classMemberDeclaration
+    : (classDeclaration
+    | functionDeclaration
+    | objectDeclaration
+    | companionObject
+    | propertyDeclaration
+    | anonymousInitializer
+    | secondaryConstructor
+    | typeAlias) anysemi+
+    ;
+
+anonymousInitializer
+    : INIT NL* initBlock
+    ;
+
+initBlock
+    : block
+    ;
+
+secondaryConstructor
+    : modifierList? CONSTRUCTOR NL* functionValueParameters (NL* COLON NL* constructorDelegationCall)? NL* block?
+    ;
+
+constructorDelegationCall
+    : THIS NL* valueArguments
+    | SUPER NL* valueArguments
+    ;
+
+enumClassBody
+    : LCURL NL* enumEntries? (NL* SEMICOLON NL* classMemberDeclaration*)? NL* RCURL
+    ;
+
+enumEntries
+    : (enumEntry NL*)+ SEMICOLON?
+    ;
+
+enumEntry
+    : simpleIdentifier (NL* valueArguments)? (NL* classBody)? (NL* COMMA)?
+    ;
+
+functionDeclaration
+    : modifierList? FUN
+    (NL* type NL* DOT)?
+    (NL* typeParameters)?
+    (NL* identifier)?
+    NL* functionValueParameters
+    (NL* COLON NL* type)?
+    (NL* typeConstraints)?
+    (NL* functionBody)?
+    ;
+
+functionValueParameters
+    : LPAREN (functionValueParameter (COMMA functionValueParameter)*)? RPAREN
+    ;
+
+functionValueParameter
+    : modifierList? parameter (ASSIGNMENT expression)?
+    ;
+
+parameter
+    : simpleIdentifier COLON type
+    ;
+
+functionBody
+    : block
+    | ASSIGNMENT NL* expression
+    ;
+
+objectDeclaration
+    : modifierList? OBJECT
+    NL* simpleIdentifier
+    (NL* primaryConstructor)?
+    (NL* COLON NL* delegationSpecifiers)?
+    (NL* classBody)?
+    ;
+
+companionObject
+    : modifierList? COMPANION NL* modifierList? OBJECT
+    (NL* simpleIdentifier)?
+    (NL* COLON NL* delegationSpecifiers)?
+    (NL* classBody)?
+    ;
+
+propertyDeclaration
+    : modifierList? (VAL | VAR)
+    (NL* typeParameters)?
+    (NL* type NL* DOT)?
+    (NL* (multiVariableDeclaration | variableDeclaration))
+    (NL* typeConstraints)?
+    (NL* (BY | ASSIGNMENT) NL* expression)?
+    (NL* getter (semi setter)? | NL* setter (semi getter)?)?
+    ;
+
+multiVariableDeclaration
+    : LPAREN variableDeclaration (COMMA variableDeclaration)* RPAREN
+    ;
+
+variableDeclaration
+    : simpleIdentifier (COLON type)?
+    ;
+
+getter
+    : modifierList? GETTER
+    | modifierList? GETTER NL* LPAREN RPAREN (NL* COLON NL* type)? NL* functionBody
+    ;
+
+setter
+    : modifierList? SETTER
+    | modifierList? SETTER NL* LPAREN (annotations | parameterModifier)* (simpleIdentifier | parameter) RPAREN NL* functionBody
+    ;
+
+typeAlias
+    : modifierList? TYPE_ALIAS NL* simpleIdentifier (NL* typeParameters)? NL* ASSIGNMENT NL* type
+    ;
+
+typeParameters
+    : LANGLE NL* typeParameter (NL* COMMA NL* typeParameter)* NL* RANGLE
+    ;
+
+typeParameter
+    : modifierList? NL* simpleIdentifier (NL* COLON NL* type)?
+    ;
+
+type
+    : typeModifierList?
+    ( functionType
+    | parenthesizedType
+    | nullableType
+    | typeReference)
+    ;
+
+typeModifierList
+    : (annotations | SUSPEND NL*)+
+    ;
+
+parenthesizedType
+    : LPAREN type RPAREN
+    ;
+
+nullableType
+    : (typeReference | parenthesizedType) NL* QUEST+
+    ;
+
+typeReference
+    : LPAREN typeReference RPAREN
+    | userType
+    | DYNAMIC
+    ;
+
+functionType
+    : (functionTypeReceiver NL* DOT NL*)? functionTypeParameters  NL* ARROW (NL* type)
+    ;
+
+functionTypeReceiver
+    : parenthesizedType
+    | nullableType
+    | typeReference
+    ;
+
+userType
+    : simpleUserType (NL* DOT NL* simpleUserType)*
+    ;
+
+simpleUserType
+    : simpleIdentifier (NL* typeArguments)?
+    ;
+
+//parameters for functionType
+functionTypeParameters
+    : LPAREN (parameter | type)? (COMMA (parameter | type))* RPAREN
+    ;
+
+typeConstraints
+    : WHERE NL* typeConstraint (NL* COMMA NL* typeConstraint)*
+    ;
+
+typeConstraint
+    : annotations* simpleIdentifier NL* COLON NL* type
+    ;
+
+block
+    : LCURL statements RCURL
+    ;
+
+statements
+    : anysemi* (statement (anysemi+ statement?)*)?
+    ;
+
+statement
+    : declaration
+    | blockLevelExpression
+    ;
+
+blockLevelExpression
+    : annotations* NL* expression
+    ;
+
+declaration
+    : labelDefinition*
+    ( classDeclaration
+    | functionDeclaration
+    | propertyDeclaration
+    | typeAlias)
+    ;
+
+expression
+    : disjunction (assignmentOperator disjunction)*
+    ;
+
+disjunction
+    : conjunction (NL* DISJ NL* conjunction)*
+    ;
+
+conjunction
+    : equalityComparison (NL* CONJ NL* equalityComparison)*
+    ;
+
+equalityComparison
+    : comparison (equalityOperation NL* comparison)*
+    ;
+
+comparison
+    : namedInfix (comparisonOperator NL* namedInfix)?
+    ;
+
+namedInfix
+    : elvisExpression ((inOperator NL* elvisExpression)+ | (isOperator NL* type))?
+    ;
+
+elvisExpression
+    : infixFunctionCall (NL* ELVIS NL* infixFunctionCall)*
+    ;
+
+infixFunctionCall
+    : rangeExpression (simpleIdentifier NL* rangeExpression)*
+    ;
+
+rangeExpression
+    : additiveExpression (RANGE NL* additiveExpression)*
+    ;
+
+additiveExpression
+    : multiplicativeExpression (additiveOperator NL* multiplicativeExpression)*
+    ;
+
+multiplicativeExpression
+    : typeRHS (multiplicativeOperation NL* typeRHS)*
+    ;
+
+typeRHS
+    : prefixUnaryExpression (NL* typeOperation prefixUnaryExpression)*
+    ;
+
+prefixUnaryExpression
+    : prefixUnaryOperation* postfixUnaryExpression
+    ;
+
+postfixUnaryExpression
+    : (atomicExpression | callableReference) postfixUnaryOperation*
+    ;
+
+atomicExpression
+    : parenthesizedExpression
+    | literalConstant
+    | functionLiteral
+    | thisExpression // THIS labelReference?
+    | superExpression // SUPER (LANGLE type RANGLE)? labelReference?
+    | conditionalExpression // ifExpression, whenExpression
+    | tryExpression
+    | objectLiteral
+    | jumpExpression
+    | loopExpression
+    | collectionLiteral
+    | simpleIdentifier
+    ;
+
+parenthesizedExpression
+    : LPAREN expression RPAREN
+    ;
+
+callSuffix
+    : typeArguments valueArguments? annotatedLambda*
+    | valueArguments annotatedLambda*
+    | annotatedLambda+
+    ;
+
+annotatedLambda
+    : unescapedAnnotation* LabelDefinition? NL* functionLiteral
+    ;
+
+arrayAccess
+    : LSQUARE (expression (COMMA expression)*)? RSQUARE
+    ;
+
+valueArguments
+    : LPAREN (valueArgument (COMMA valueArgument)*)? RPAREN
+    ;
+
+typeArguments
+    : LANGLE NL* typeProjection (NL* COMMA typeProjection)* NL* RANGLE
+    ;
+
+typeProjection
+    : typeProjectionModifierList? type | MULT
+    ;
+
+typeProjectionModifierList
+    : varianceAnnotation+
+    ;
+
+valueArgument
+    : (simpleIdentifier NL* ASSIGNMENT NL*)? MULT? NL* expression
+    ;
+
+literalConstant
+    : BooleanLiteral
+    | IntegerLiteral
+    | stringLiteral
+    | HexLiteral
+    | BinLiteral
+    | CharacterLiteral
+    | RealLiteral
+    | NullLiteral
+    | LongLiteral
+    ;
+
+stringLiteral
+    : lineStringLiteral
+    | multiLineStringLiteral
+    ;
+
+lineStringLiteral
+    : QUOTE_OPEN (lineStringContent | lineStringExpression)* QUOTE_CLOSE
+    ;
+
+multiLineStringLiteral
+    : TRIPLE_QUOTE_OPEN (multiLineStringContent | multiLineStringExpression | lineStringLiteral | MultiLineStringQuote)* TRIPLE_QUOTE_CLOSE
+    ;
+
+lineStringContent
+    : LineStrText
+    | LineStrEscapedChar
+    | LineStrRef
+    ;
+
+lineStringExpression
+    : LineStrExprStart expression RCURL
+    ;
+
+multiLineStringContent
+    : MultiLineStrText
+    | MultiLineStrEscapedChar
+    | MultiLineStrRef
+    ;
+
+multiLineStringExpression
+    : MultiLineStrExprStart expression RCURL
+    ;
+
+functionLiteral
+    : annotations*
+    ( LCURL NL* statements NL* RCURL
+    | LCURL NL* lambdaParameters NL* ARROW NL* statements NL* RCURL )
+    ;
+
+lambdaParameters
+    : lambdaParameter? (NL* COMMA NL* lambdaParameter)*
+    ;
+
+lambdaParameter
+    : variableDeclaration
+    | multiVariableDeclaration (NL* COLON NL* type)?
+    ;
+
+objectLiteral
+    : OBJECT (NL* COLON NL* delegationSpecifiers)? NL* classBody
+    ;
+
+collectionLiteral
+    : LSQUARE expression? (COMMA expression)* RSQUARE
+    ;
+
+thisExpression
+    : THIS LabelReference?
+    ;
+
+superExpression
+    : SUPER (LANGLE NL* type NL* RANGLE)? LabelReference?
+    ;
+
+conditionalExpression
+    : ifExpression
+    | whenExpression
+    ;
+
+ifExpression
+    : IF NL* LPAREN expression RPAREN NL* controlStructureBody? SEMICOLON?
+    (NL* ELSE NL* controlStructureBody?)?
+    ;
+
+controlStructureBody
+    : block
+    | expression
+    ;
+
+whenExpression
+    : WHEN NL* (LPAREN expression RPAREN)? NL* LCURL NL* (whenEntry NL*)* NL* RCURL
+    ;
+
+whenEntry
+    : whenCondition (NL* COMMA NL* whenCondition)* NL* ARROW NL* controlStructureBody semi?
+    | ELSE NL* ARROW NL* controlStructureBody
+    ;
+
+whenCondition
+    : expression
+    | rangeTest
+    | typeTest
+    ;
+
+rangeTest
+    : inOperator NL* expression
+    ;
+
+typeTest
+    : isOperator NL* type
+    ;
+
+tryExpression
+    : TRY NL* tryBody (NL* catchStatement)* (NL* finallyStatement)?
+    ;
+
+tryBody
+    : block
+    ;
+
+catchStatement
+    : CATCH NL* LPAREN annotations* simpleIdentifier COLON userType RPAREN NL* catchBody
+    ;
+
+catchBody
+    : block
+    ;
+
+finallyStatement
+    : FINALLY NL* finallyBody
+    ;
+
+finallyBody
+    : block
+    ;
+
+loopExpression
+    : forExpression
+    | whileExpression
+    | doWhileExpression
+    ;
+
+forExpression
+    : FOR NL* LPAREN annotations* (variableDeclaration | multiVariableDeclaration) IN expression RPAREN NL* controlStructureBody?
+    ;
+
+whileExpression
+    : WHILE NL* LPAREN expression RPAREN NL* controlStructureBody?
+    ;
+
+doWhileExpression
+    : DO NL* controlStructureBody? NL* WHILE NL* LPAREN expression RPAREN
+    ;
+
+jumpExpression
+    : THROW NL* expression
+    | (RETURN | RETURN_AT) expression?
+    | CONTINUE | CONTINUE_AT
+    | BREAK | BREAK_AT
+    ;
+
+callableReference
+    : (userType (QUEST NL*)*)? NL* (COLONCOLON | Q_COLONCOLON) NL* (identifier | CLASS)
+    ;
+
+assignmentOperator
+    : ASSIGNMENT
+    | ADD_ASSIGNMENT
+    | SUB_ASSIGNMENT
+    | MULT_ASSIGNMENT
+    | DIV_ASSIGNMENT
+    | MOD_ASSIGNMENT
+    ;
+
+equalityOperation
+    : EXCL_EQ
+    | EXCL_EQEQ
+    | EQEQ
+    | EQEQEQ
+    ;
+
+comparisonOperator
+    : LANGLE
+    | RANGLE
+    | LE
+    | GE
+    ;
+
+inOperator
+    : IN | NOT_IN
+    ;
+
+isOperator
+    : IS | NOT_IS
+    ;
+
+additiveOperator
+    : ADD | SUB
+    ;
+
+multiplicativeOperation
+    : MULT
+    | DIV
+    | MOD
+    ;
+
+typeOperation
+    : AS
+    | AS_SAFE
+    | COLON
+    ;
+
+prefixUnaryOperation
+    : INCR
+    | DECR
+    | ADD
+    | SUB
+    | EXCL
+    | annotations
+    | labelDefinition
+    ;
+
+postfixUnaryOperation
+    : INCR | DECR | EXCL EXCL
+    | callSuffix
+    | arrayAccess
+    | NL* memberAccessOperator postfixUnaryExpression
+    ;
+
+memberAccessOperator
+    : DOT | QUEST DOT
+    ;
+
+modifierList
+    : (annotations | modifier)+
+    ;
+
+modifier
+    : (classModifier
+    | memberModifier
+    | visibilityModifier
+    | varianceAnnotation
+    | functionModifier
+    | propertyModifier
+    | inheritanceModifier
+    | parameterModifier
+    | typeParameterModifier) NL*
+    ;
+
+classModifier
+    : ENUM
+    | SEALED
+    | ANNOTATION
+    | DATA
+    | INNER
+    ;
+
+memberModifier
+    : OVERRIDE
+    | LATEINIT
+    ;
+
+visibilityModifier
+    : PUBLIC
+    | PRIVATE
+    | INTERNAL
+    | PROTECTED
+    ;
+
+varianceAnnotation
+    : IN | OUT
+    ;
+
+functionModifier
+    : TAILREC
+    | OPERATOR
+    | INFIX
+    | INLINE
+    | EXTERNAL
+    | SUSPEND
+    ;
+
+propertyModifier
+    : CONST
+    ;
+
+inheritanceModifier
+    : ABSTRACT
+    | FINAL
+    | OPEN
+    ;
+
+parameterModifier
+    : VARARG
+    | NOINLINE
+    | CROSSINLINE
+    ;
+
+typeParameterModifier
+    : REIFIED
+    ;
+
+labelDefinition
+    : LabelDefinition NL*
+    ;
+
+annotations
+    : (annotation | annotationList) NL*
+    ;
+
+annotation
+    : annotationUseSiteTarget NL* COLON NL* unescapedAnnotation
+    | LabelReference (NL* typeArguments)? (NL* valueArguments)?
+    ;
+
+annotationList
+    : annotationUseSiteTarget COLON LSQUARE unescapedAnnotation+ RSQUARE
+    | AT LSQUARE unescapedAnnotation+ RSQUARE
+    ;
+
+annotationUseSiteTarget
+    : FIELD
+    | FILE
+    | PROPERTY
+    | GET
+    | SET
+    | RECEIVER
+    | PARAM
+    | SETPARAM
+    | DELEGATE
+    ;
+
+unescapedAnnotation
+    : identifier typeArguments? valueArguments?
+    ;
+
+identifier
+    : simpleIdentifier (NL* DOT simpleIdentifier)*
+    ;
+
+simpleIdentifier
+    : Identifier
+    //soft keywords:
+    | ABSTRACT
+    | ANNOTATION
+    | BY
+    | CATCH
+    | COMPANION
+    | CONSTRUCTOR
+    | CROSSINLINE
+    | DATA
+    | DYNAMIC
+    | ENUM
+    | EXTERNAL
+    | FINAL
+    | FINALLY
+    | GETTER
+    | IMPORT
+    | INFIX
+    | INIT
+    | INLINE
+    | INNER
+    | INTERNAL
+    | LATEINIT
+    | NOINLINE
+    | OPEN
+    | OPERATOR
+    | OUT
+    | OVERRIDE
+    | PRIVATE
+    | PROTECTED
+    | PUBLIC
+    | REIFIED
+    | SEALED
+    | TAILREC
+    | SETTER
+    | VARARG
+    | WHERE
+    //strong keywords
+    | CONST
+    | SUSPEND
+    ;
+
+semi: NL+ | NL* SEMICOLON NL*;
+
+anysemi: NL | SEMICOLON;

+ 1647 - 0
jplag.frontend.kotlin/src/main/antlr4/imports/UnicodeClasses.g4

@@ -0,0 +1,1647 @@
+/**
+ * Taken from http://www.antlr3.org/grammar/1345144569663/AntlrUnicode.txt
+ */
+
+lexer grammar UnicodeClasses;
+
+fragment UNICODE_CLASS_LL:
+	'\u0061'..'\u007A' |
+	'\u00B5' |
+	'\u00DF'..'\u00F6' |
+	'\u00F8'..'\u00FF' |
+	'\u0101' |
+	'\u0103' |
+	'\u0105' |
+	'\u0107' |
+	'\u0109' |
+	'\u010B' |
+	'\u010D' |
+	'\u010F' |
+	'\u0111' |
+	'\u0113' |
+	'\u0115' |
+	'\u0117' |
+	'\u0119' |
+	'\u011B' |
+	'\u011D' |
+	'\u011F' |
+	'\u0121' |
+	'\u0123' |
+	'\u0125' |
+	'\u0127' |
+	'\u0129' |
+	'\u012B' |
+	'\u012D' |
+	'\u012F' |
+	'\u0131' |
+	'\u0133' |
+	'\u0135' |
+	'\u0137' |
+	'\u0138' |
+	'\u013A' |
+	'\u013C' |
+	'\u013E' |
+	'\u0140' |
+	'\u0142' |
+	'\u0144' |
+	'\u0146' |
+	'\u0148' |
+	'\u0149' |
+	'\u014B' |
+	'\u014D' |
+	'\u014F' |
+	'\u0151' |
+	'\u0153' |
+	'\u0155' |
+	'\u0157' |
+	'\u0159' |
+	'\u015B' |
+	'\u015D' |
+	'\u015F' |
+	'\u0161' |
+	'\u0163' |
+	'\u0165' |
+	'\u0167' |
+	'\u0169' |
+	'\u016B' |
+	'\u016D' |
+	'\u016F' |
+	'\u0171' |
+	'\u0173' |
+	'\u0175' |
+	'\u0177' |
+	'\u017A' |
+	'\u017C' |
+	'\u017E'..'\u0180' |
+	'\u0183' |
+	'\u0185' |
+	'\u0188' |
+	'\u018C' |
+	'\u018D' |
+	'\u0192' |
+	'\u0195' |
+	'\u0199'..'\u019B' |
+	'\u019E' |
+	'\u01A1' |
+	'\u01A3' |
+	'\u01A5' |
+	'\u01A8' |
+	'\u01AA' |
+	'\u01AB' |
+	'\u01AD' |
+	'\u01B0' |
+	'\u01B4' |
+	'\u01B6' |
+	'\u01B9' |
+	'\u01BA' |
+	'\u01BD'..'\u01BF' |
+	'\u01C6' |
+	'\u01C9' |
+	'\u01CC' |
+	'\u01CE' |
+	'\u01D0' |
+	'\u01D2' |
+	'\u01D4' |
+	'\u01D6' |
+	'\u01D8' |
+	'\u01DA' |
+	'\u01DC' |
+	'\u01DD' |
+	'\u01DF' |
+	'\u01E1' |
+	'\u01E3' |
+	'\u01E5' |
+	'\u01E7' |
+	'\u01E9' |
+	'\u01EB' |
+	'\u01ED' |
+	'\u01EF' |
+	'\u01F0' |
+	'\u01F3' |
+	'\u01F5' |
+	'\u01F9' |
+	'\u01FB' |
+	'\u01FD' |
+	'\u01FF' |
+	'\u0201' |
+	'\u0203' |
+	'\u0205' |
+	'\u0207' |
+	'\u0209' |
+	'\u020B' |
+	'\u020D' |
+	'\u020F' |
+	'\u0211' |
+	'\u0213' |
+	'\u0215' |
+	'\u0217' |
+	'\u0219' |
+	'\u021B' |
+	'\u021D' |
+	'\u021F' |
+	'\u0221' |
+	'\u0223' |
+	'\u0225' |
+	'\u0227' |
+	'\u0229' |
+	'\u022B' |
+	'\u022D' |
+	'\u022F' |
+	'\u0231' |
+	'\u0233'..'\u0239' |
+	'\u023C' |
+	'\u023F' |
+	'\u0240' |
+	'\u0242' |
+	'\u0247' |
+	'\u0249' |
+	'\u024B' |
+	'\u024D' |
+	'\u024F'..'\u0293' |
+	'\u0295'..'\u02AF' |
+	'\u0371' |
+	'\u0373' |
+	'\u0377' |
+	'\u037B'..'\u037D' |
+	'\u0390' |
+	'\u03AC'..'\u03CE' |
+	'\u03D0' |
+	'\u03D1' |
+	'\u03D5'..'\u03D7' |
+	'\u03D9' |
+	'\u03DB' |
+	'\u03DD' |
+	'\u03DF' |
+	'\u03E1' |
+	'\u03E3' |
+	'\u03E5' |
+	'\u03E7' |
+	'\u03E9' |
+	'\u03EB' |
+	'\u03ED' |
+	'\u03EF'..'\u03F3' |
+	'\u03F5' |
+	'\u03F8' |
+	'\u03FB' |
+	'\u03FC' |
+	'\u0430'..'\u045F' |
+	'\u0461' |
+	'\u0463' |
+	'\u0465' |
+	'\u0467' |
+	'\u0469' |
+	'\u046B' |
+	'\u046D' |
+	'\u046F' |
+	'\u0471' |
+	'\u0473' |
+	'\u0475' |
+	'\u0477' |
+	'\u0479' |
+	'\u047B' |
+	'\u047D' |
+	'\u047F' |
+	'\u0481' |
+	'\u048B' |
+	'\u048D' |
+	'\u048F' |
+	'\u0491' |
+	'\u0493' |
+	'\u0495' |
+	'\u0497' |
+	'\u0499' |
+	'\u049B' |
+	'\u049D' |
+	'\u049F' |
+	'\u04A1' |
+	'\u04A3' |
+	'\u04A5' |
+	'\u04A7' |
+	'\u04A9' |
+	'\u04AB' |
+	'\u04AD' |
+	'\u04AF' |
+	'\u04B1' |
+	'\u04B3' |
+	'\u04B5' |
+	'\u04B7' |
+	'\u04B9' |
+	'\u04BB' |
+	'\u04BD' |
+	'\u04BF' |
+	'\u04C2' |
+	'\u04C4' |
+	'\u04C6' |
+	'\u04C8' |
+	'\u04CA' |
+	'\u04CC' |
+	'\u04CE' |
+	'\u04CF' |
+	'\u04D1' |
+	'\u04D3' |
+	'\u04D5' |
+	'\u04D7' |
+	'\u04D9' |
+	'\u04DB' |
+	'\u04DD' |
+	'\u04DF' |
+	'\u04E1' |
+	'\u04E3' |
+	'\u04E5' |
+	'\u04E7' |
+	'\u04E9' |
+	'\u04EB' |
+	'\u04ED' |
+	'\u04EF' |
+	'\u04F1' |
+	'\u04F3' |
+	'\u04F5' |
+	'\u04F7' |
+	'\u04F9' |
+	'\u04FB' |
+	'\u04FD' |
+	'\u04FF' |
+	'\u0501' |
+	'\u0503' |
+	'\u0505' |
+	'\u0507' |
+	'\u0509' |
+	'\u050B' |
+	'\u050D' |
+	'\u050F' |
+	'\u0511' |
+	'\u0513' |
+	'\u0515' |
+	'\u0517' |
+	'\u0519' |
+	'\u051B' |
+	'\u051D' |
+	'\u051F' |
+	'\u0521' |
+	'\u0523' |
+	'\u0525' |
+	'\u0527' |
+	'\u0561'..'\u0587' |
+	'\u1D00'..'\u1D2B' |
+	'\u1D6B'..'\u1D77' |
+	'\u1D79'..'\u1D9A' |
+	'\u1E01' |
+	'\u1E03' |
+	'\u1E05' |
+	'\u1E07' |
+	'\u1E09' |
+	'\u1E0B' |
+	'\u1E0D' |
+	'\u1E0F' |
+	'\u1E11' |
+	'\u1E13' |
+	'\u1E15' |
+	'\u1E17' |
+	'\u1E19' |
+	'\u1E1B' |
+	'\u1E1D' |
+	'\u1E1F' |
+	'\u1E21' |
+	'\u1E23' |
+	'\u1E25' |
+	'\u1E27' |
+	'\u1E29' |
+	'\u1E2B' |
+	'\u1E2D' |
+	'\u1E2F' |
+	'\u1E31' |
+	'\u1E33' |
+	'\u1E35' |
+	'\u1E37' |
+	'\u1E39' |
+	'\u1E3B' |
+	'\u1E3D' |
+	'\u1E3F' |
+	'\u1E41' |
+	'\u1E43' |
+	'\u1E45' |
+	'\u1E47' |
+	'\u1E49' |
+	'\u1E4B' |
+	'\u1E4D' |
+	'\u1E4F' |
+	'\u1E51' |
+	'\u1E53' |
+	'\u1E55' |
+	'\u1E57' |
+	'\u1E59' |
+	'\u1E5B' |
+	'\u1E5D' |
+	'\u1E5F' |
+	'\u1E61' |
+	'\u1E63' |
+	'\u1E65' |
+	'\u1E67' |
+	'\u1E69' |
+	'\u1E6B' |
+	'\u1E6D' |
+	'\u1E6F' |
+	'\u1E71' |
+	'\u1E73' |
+	'\u1E75' |
+	'\u1E77' |
+	'\u1E79' |
+	'\u1E7B' |
+	'\u1E7D' |
+	'\u1E7F' |
+	'\u1E81' |
+	'\u1E83' |
+	'\u1E85' |
+	'\u1E87' |
+	'\u1E89' |
+	'\u1E8B' |
+	'\u1E8D' |
+	'\u1E8F' |
+	'\u1E91' |
+	'\u1E93' |
+	'\u1E95'..'\u1E9D' |
+	'\u1E9F' |
+	'\u1EA1' |
+	'\u1EA3' |
+	'\u1EA5' |
+	'\u1EA7' |
+	'\u1EA9' |
+	'\u1EAB' |
+	'\u1EAD' |
+	'\u1EAF' |
+	'\u1EB1' |
+	'\u1EB3' |
+	'\u1EB5' |
+	'\u1EB7' |
+	'\u1EB9' |
+	'\u1EBB' |
+	'\u1EBD' |
+	'\u1EBF' |
+	'\u1EC1' |
+	'\u1EC3' |
+	'\u1EC5' |
+	'\u1EC7' |
+	'\u1EC9' |
+	'\u1ECB' |
+	'\u1ECD' |
+	'\u1ECF' |
+	'\u1ED1' |
+	'\u1ED3' |
+	'\u1ED5' |
+	'\u1ED7' |
+	'\u1ED9' |
+	'\u1EDB' |
+	'\u1EDD' |
+	'\u1EDF' |
+	'\u1EE1' |
+	'\u1EE3' |
+	'\u1EE5' |
+	'\u1EE7' |
+	'\u1EE9' |
+	'\u1EEB' |
+	'\u1EED' |
+	'\u1EEF' |
+	'\u1EF1' |
+	'\u1EF3' |
+	'\u1EF5' |
+	'\u1EF7' |
+	'\u1EF9' |
+	'\u1EFB' |
+	'\u1EFD' |
+	'\u1EFF'..'\u1F07' |
+	'\u1F10'..'\u1F15' |
+	'\u1F20'..'\u1F27' |
+	'\u1F30'..'\u1F37' |
+	'\u1F40'..'\u1F45' |
+	'\u1F50'..'\u1F57' |
+	'\u1F60'..'\u1F67' |
+	'\u1F70'..'\u1F7D' |
+	'\u1F80'..'\u1F87' |
+	'\u1F90'..'\u1F97' |
+	'\u1FA0'..'\u1FA7' |
+	'\u1FB0'..'\u1FB4' |
+	'\u1FB6' |
+	'\u1FB7' |
+	'\u1FBE' |
+	'\u1FC2'..'\u1FC4' |
+	'\u1FC6' |
+	'\u1FC7' |
+	'\u1FD0'..'\u1FD3' |
+	'\u1FD6' |
+	'\u1FD7' |
+	'\u1FE0'..'\u1FE7' |
+	'\u1FF2'..'\u1FF4' |
+	'\u1FF6' |
+	'\u1FF7' |
+	'\u210A' |
+	'\u210E' |
+	'\u210F' |
+	'\u2113' |
+	'\u212F' |
+	'\u2134' |
+	'\u2139' |
+	'\u213C' |
+	'\u213D' |
+	'\u2146'..'\u2149' |
+	'\u214E' |
+	'\u2184' |
+	'\u2C30'..'\u2C5E' |
+	'\u2C61' |
+	'\u2C65' |
+	'\u2C66' |
+	'\u2C68' |
+	'\u2C6A' |
+	'\u2C6C' |
+	'\u2C71' |
+	'\u2C73' |
+	'\u2C74' |
+	'\u2C76'..'\u2C7B' |
+	'\u2C81' |
+	'\u2C83' |
+	'\u2C85' |
+	'\u2C87' |
+	'\u2C89' |
+	'\u2C8B' |
+	'\u2C8D' |
+	'\u2C8F' |
+	'\u2C91' |
+	'\u2C93' |
+	'\u2C95' |
+	'\u2C97' |
+	'\u2C99' |
+	'\u2C9B' |
+	'\u2C9D' |
+	'\u2C9F' |
+	'\u2CA1' |
+	'\u2CA3' |
+	'\u2CA5' |
+	'\u2CA7' |
+	'\u2CA9' |
+	'\u2CAB' |
+	'\u2CAD' |
+	'\u2CAF' |
+	'\u2CB1' |
+	'\u2CB3' |
+	'\u2CB5' |
+	'\u2CB7' |
+	'\u2CB9' |
+	'\u2CBB' |
+	'\u2CBD' |
+	'\u2CBF' |
+	'\u2CC1' |
+	'\u2CC3' |
+	'\u2CC5' |
+	'\u2CC7' |
+	'\u2CC9' |
+	'\u2CCB' |
+	'\u2CCD' |
+	'\u2CCF' |
+	'\u2CD1' |
+	'\u2CD3' |
+	'\u2CD5' |
+	'\u2CD7' |
+	'\u2CD9' |
+	'\u2CDB' |
+	'\u2CDD' |
+	'\u2CDF' |
+	'\u2CE1' |
+	'\u2CE3' |
+	'\u2CE4' |
+	'\u2CEC' |
+	'\u2CEE' |
+	'\u2CF3' |
+	'\u2D00'..'\u2D25' |
+	'\u2D27' |
+	'\u2D2D' |
+	'\uA641' |
+	'\uA643' |
+	'\uA645' |
+	'\uA647' |
+	'\uA649' |
+	'\uA64B' |
+	'\uA64D' |
+	'\uA64F' |
+	'\uA651' |
+	'\uA653' |
+	'\uA655' |
+	'\uA657' |
+	'\uA659' |
+	'\uA65B' |
+	'\uA65D' |
+	'\uA65F' |
+	'\uA661' |
+	'\uA663' |
+	'\uA665' |
+	'\uA667' |
+	'\uA669' |
+	'\uA66B' |
+	'\uA66D' |
+	'\uA681' |
+	'\uA683' |
+	'\uA685' |
+	'\uA687' |
+	'\uA689' |
+	'\uA68B' |
+	'\uA68D' |
+	'\uA68F' |
+	'\uA691' |
+	'\uA693' |
+	'\uA695' |
+	'\uA697' |
+	'\uA723' |
+	'\uA725' |
+	'\uA727' |
+	'\uA729' |
+	'\uA72B' |
+	'\uA72D' |
+	'\uA72F'..'\uA731' |
+	'\uA733' |
+	'\uA735' |
+	'\uA737' |
+	'\uA739' |
+	'\uA73B' |
+	'\uA73D' |
+	'\uA73F' |
+	'\uA741' |
+	'\uA743' |
+	'\uA745' |
+	'\uA747' |
+	'\uA749' |
+	'\uA74B' |
+	'\uA74D' |
+	'\uA74F' |
+	'\uA751' |
+	'\uA753' |
+	'\uA755' |
+	'\uA757' |
+	'\uA759' |
+	'\uA75B' |
+	'\uA75D' |
+	'\uA75F' |
+	'\uA761' |
+	'\uA763' |
+	'\uA765' |
+	'\uA767' |
+	'\uA769' |
+	'\uA76B' |
+	'\uA76D' |
+	'\uA76F' |
+	'\uA771'..'\uA778' |
+	'\uA77A' |
+	'\uA77C' |
+	'\uA77F' |
+	'\uA781' |
+	'\uA783' |
+	'\uA785' |
+	'\uA787' |
+	'\uA78C' |
+	'\uA78E' |
+	'\uA791' |
+	'\uA793' |
+	'\uA7A1' |
+	'\uA7A3' |
+	'\uA7A5' |
+	'\uA7A7' |
+	'\uA7A9' |
+	'\uA7FA' |
+	'\uFB00'..'\uFB06' |
+	'\uFB13'..'\uFB17' |
+	'\uFF41'..'\uFF5A';
+
+fragment UNICODE_CLASS_LM:
+	'\u02B0'..'\u02C1' |
+	'\u02C6'..'\u02D1' |
+	'\u02E0'..'\u02E4' |
+	'\u02EC' |
+	'\u02EE' |
+	'\u0374' |
+	'\u037A' |
+	'\u0559' |
+	'\u0640' |
+	'\u06E5' |
+	'\u06E6' |
+	'\u07F4' |
+	'\u07F5' |
+	'\u07FA' |
+	'\u081A' |
+	'\u0824' |
+	'\u0828' |
+	'\u0971' |
+	'\u0E46' |
+	'\u0EC6' |
+	'\u10FC' |
+	'\u17D7' |
+	'\u1843' |
+	'\u1AA7' |
+	'\u1C78'..'\u1C7D' |
+	'\u1D2C'..'\u1D6A' |
+	'\u1D78' |
+	'\u1D9B'..'\u1DBF' |
+	'\u2071' |
+	'\u207F' |
+	'\u2090'..'\u209C' |
+	'\u2C7C' |
+	'\u2C7D' |
+	'\u2D6F' |
+	'\u2E2F' |
+	'\u3005' |
+	'\u3031'..'\u3035' |
+	'\u303B' |
+	'\u309D' |
+	'\u309E' |
+	'\u30FC'..'\u30FE' |
+	'\uA015' |
+	'\uA4F8'..'\uA4FD' |
+	'\uA60C' |
+	'\uA67F' |
+	'\uA717'..'\uA71F' |
+	'\uA770' |
+	'\uA788' |
+	'\uA7F8' |
+	'\uA7F9' |
+	'\uA9CF' |
+	'\uAA70' |
+	'\uAADD' |
+	'\uAAF3' |
+	'\uAAF4' |
+	'\uFF70' |
+	'\uFF9E' |
+	'\uFF9F';
+
+fragment UNICODE_CLASS_LO:
+	'\u00AA' |
+	'\u00BA' |
+	'\u01BB' |
+	'\u01C0'..'\u01C3' |
+	'\u0294' |
+	'\u05D0'..'\u05EA' |
+	'\u05F0'..'\u05F2' |
+	'\u0620'..'\u063F' |
+	'\u0641'..'\u064A' |
+	'\u066E' |
+	'\u066F' |
+	'\u0671'..'\u06D3' |
+	'\u06D5' |
+	'\u06EE' |
+	'\u06EF' |
+	'\u06FA'..'\u06FC' |
+	'\u06FF' |
+	'\u0710' |
+	'\u0712'..'\u072F' |
+	'\u074D'..'\u07A5' |
+	'\u07B1' |
+	'\u07CA'..'\u07EA' |
+	'\u0800'..'\u0815' |
+	'\u0840'..'\u0858' |
+	'\u08A0' |
+	'\u08A2'..'\u08AC' |
+	'\u0904'..'\u0939' |
+	'\u093D' |
+	'\u0950' |
+	'\u0958'..'\u0961' |
+	'\u0972'..'\u0977' |
+	'\u0979'..'\u097F' |
+	'\u0985'..'\u098C' |
+	'\u098F' |
+	'\u0990' |
+	'\u0993'..'\u09A8' |
+	'\u09AA'..'\u09B0' |
+	'\u09B2' |
+	'\u09B6'..'\u09B9' |
+	'\u09BD' |
+	'\u09CE' |
+	'\u09DC' |
+	'\u09DD' |
+	'\u09DF'..'\u09E1' |
+	'\u09F0' |
+	'\u09F1' |
+	'\u0A05'..'\u0A0A' |
+	'\u0A0F' |
+	'\u0A10' |
+	'\u0A13'..'\u0A28' |
+	'\u0A2A'..'\u0A30' |
+	'\u0A32' |
+	'\u0A33' |
+	'\u0A35' |
+	'\u0A36' |
+	'\u0A38' |
+	'\u0A39' |
+	'\u0A59'..'\u0A5C' |
+	'\u0A5E' |
+	'\u0A72'..'\u0A74' |
+	'\u0A85'..'\u0A8D' |
+	'\u0A8F'..'\u0A91' |
+	'\u0A93'..'\u0AA8' |
+	'\u0AAA'..'\u0AB0' |
+	'\u0AB2' |
+	'\u0AB3' |
+	'\u0AB5'..'\u0AB9' |
+	'\u0ABD' |
+	'\u0AD0' |
+	'\u0AE0' |
+	'\u0AE1' |
+	'\u0B05'..'\u0B0C' |
+	'\u0B0F' |
+	'\u0B10' |
+	'\u0B13'..'\u0B28' |
+	'\u0B2A'..'\u0B30' |
+	'\u0B32' |
+	'\u0B33' |
+	'\u0B35'..'\u0B39' |
+	'\u0B3D' |
+	'\u0B5C' |
+	'\u0B5D' |
+	'\u0B5F'..'\u0B61' |
+	'\u0B71' |
+	'\u0B83' |
+	'\u0B85'..'\u0B8A' |
+	'\u0B8E'..'\u0B90' |
+	'\u0B92'..'\u0B95' |
+	'\u0B99' |
+	'\u0B9A' |
+	'\u0B9C' |
+	'\u0B9E' |
+	'\u0B9F' |
+	'\u0BA3' |
+	'\u0BA4' |
+	'\u0BA8'..'\u0BAA' |
+	'\u0BAE'..'\u0BB9' |
+	'\u0BD0' |
+	'\u0C05'..'\u0C0C' |
+	'\u0C0E'..'\u0C10' |
+	'\u0C12'..'\u0C28' |
+	'\u0C2A'..'\u0C33' |
+	'\u0C35'..'\u0C39' |
+	'\u0C3D' |
+	'\u0C58' |
+	'\u0C59' |
+	'\u0C60' |
+	'\u0C61' |
+	'\u0C85'..'\u0C8C' |
+	'\u0C8E'..'\u0C90' |
+	'\u0C92'..'\u0CA8' |
+	'\u0CAA'..'\u0CB3' |
+	'\u0CB5'..'\u0CB9' |
+	'\u0CBD' |
+	'\u0CDE' |
+	'\u0CE0' |
+	'\u0CE1' |
+	'\u0CF1' |
+	'\u0CF2' |
+	'\u0D05'..'\u0D0C' |
+	'\u0D0E'..'\u0D10' |
+	'\u0D12'..'\u0D3A' |
+	'\u0D3D' |
+	'\u0D4E' |
+	'\u0D60' |
+	'\u0D61' |
+	'\u0D7A'..'\u0D7F' |
+	'\u0D85'..'\u0D96' |
+	'\u0D9A'..'\u0DB1' |
+	'\u0DB3'..'\u0DBB' |
+	'\u0DBD' |
+	'\u0DC0'..'\u0DC6' |
+	'\u0E01'..'\u0E30' |
+	'\u0E32' |
+	'\u0E33' |
+	'\u0E40'..'\u0E45' |
+	'\u0E81' |
+	'\u0E82' |
+	'\u0E84' |
+	'\u0E87' |
+	'\u0E88' |
+	'\u0E8A' |
+	'\u0E8D' |
+	'\u0E94'..'\u0E97' |
+	'\u0E99'..'\u0E9F' |
+	'\u0EA1'..'\u0EA3' |
+	'\u0EA5' |
+	'\u0EA7' |
+	'\u0EAA' |
+	'\u0EAB' |
+	'\u0EAD'..'\u0EB0' |
+	'\u0EB2' |
+	'\u0EB3' |
+	'\u0EBD' |
+	'\u0EC0'..'\u0EC4' |
+	'\u0EDC'..'\u0EDF' |
+	'\u0F00' |
+	'\u0F40'..'\u0F47' |
+	'\u0F49'..'\u0F6C' |
+	'\u0F88'..'\u0F8C' |
+	'\u1000'..'\u102A' |
+	'\u103F' |
+	'\u1050'..'\u1055' |
+	'\u105A'..'\u105D' |
+	'\u1061' |
+	'\u1065' |
+	'\u1066' |
+	'\u106E'..'\u1070' |
+	'\u1075'..'\u1081' |
+	'\u108E' |
+	'\u10D0'..'\u10FA' |
+	'\u10FD'..'\u1248' |
+	'\u124A'..'\u124D' |
+	'\u1250'..'\u1256' |
+	'\u1258' |
+	'\u125A'..'\u125D' |
+	'\u1260'..'\u1288' |
+	'\u128A'..'\u128D' |
+	'\u1290'..'\u12B0' |
+	'\u12B2'..'\u12B5' |
+	'\u12B8'..'\u12BE' |
+	'\u12C0' |
+	'\u12C2'..'\u12C5' |
+	'\u12C8'..'\u12D6' |
+	'\u12D8'..'\u1310' |
+	'\u1312'..'\u1315' |
+	'\u1318'..'\u135A' |
+	'\u1380'..'\u138F' |
+	'\u13A0'..'\u13F4' |
+	'\u1401'..'\u166C' |
+	'\u166F'..'\u167F' |
+	'\u1681'..'\u169A' |
+	'\u16A0'..'\u16EA' |
+	'\u1700'..'\u170C' |
+	'\u170E'..'\u1711' |
+	'\u1720'..'\u1731' |
+	'\u1740'..'\u1751' |
+	'\u1760'..'\u176C' |
+	'\u176E'..'\u1770' |
+	'\u1780'..'\u17B3' |
+	'\u17DC' |
+	'\u1820'..'\u1842' |
+	'\u1844'..'\u1877' |
+	'\u1880'..'\u18A8' |
+	'\u18AA' |
+	'\u18B0'..'\u18F5' |
+	'\u1900'..'\u191C' |
+	'\u1950'..'\u196D' |
+	'\u1970'..'\u1974' |
+	'\u1980'..'\u19AB' |
+	'\u19C1'..'\u19C7' |
+	'\u1A00'..'\u1A16' |
+	'\u1A20'..'\u1A54' |
+	'\u1B05'..'\u1B33' |
+	'\u1B45'..'\u1B4B' |
+	'\u1B83'..'\u1BA0' |
+	'\u1BAE' |
+	'\u1BAF' |
+	'\u1BBA'..'\u1BE5' |
+	'\u1C00'..'\u1C23' |
+	'\u1C4D'..'\u1C4F' |
+	'\u1C5A'..'\u1C77' |
+	'\u1CE9'..'\u1CEC' |
+	'\u1CEE'..'\u1CF1' |
+	'\u1CF5' |
+	'\u1CF6' |
+	'\u2135'..'\u2138' |
+	'\u2D30'..'\u2D67' |
+	'\u2D80'..'\u2D96' |
+	'\u2DA0'..'\u2DA6' |
+	'\u2DA8'..'\u2DAE' |
+	'\u2DB0'..'\u2DB6' |
+	'\u2DB8'..'\u2DBE' |
+	'\u2DC0'..'\u2DC6' |
+	'\u2DC8'..'\u2DCE' |
+	'\u2DD0'..'\u2DD6' |
+	'\u2DD8'..'\u2DDE' |
+	'\u3006' |
+	'\u303C' |
+	'\u3041'..'\u3096' |
+	'\u309F' |
+	'\u30A1'..'\u30FA' |
+	'\u30FF' |
+	'\u3105'..'\u312D' |
+	'\u3131'..'\u318E' |
+	'\u31A0'..'\u31BA' |
+	'\u31F0'..'\u31FF' |
+	'\u3400' |
+	'\u4DB5' |
+	'\u4E00' |
+	'\u9FCC' |
+	'\uA000'..'\uA014' |
+	'\uA016'..'\uA48C' |
+	'\uA4D0'..'\uA4F7' |
+	'\uA500'..'\uA60B' |
+	'\uA610'..'\uA61F' |
+	'\uA62A' |
+	'\uA62B' |
+	'\uA66E' |
+	'\uA6A0'..'\uA6E5' |
+	'\uA7FB'..'\uA801' |
+	'\uA803'..'\uA805' |
+	'\uA807'..'\uA80A' |
+	'\uA80C'..'\uA822' |
+	'\uA840'..'\uA873' |
+	'\uA882'..'\uA8B3' |
+	'\uA8F2'..'\uA8F7' |
+	'\uA8FB' |
+	'\uA90A'..'\uA925' |
+	'\uA930'..'\uA946' |
+	'\uA960'..'\uA97C' |
+	'\uA984'..'\uA9B2' |
+	'\uAA00'..'\uAA28' |
+	'\uAA40'..'\uAA42' |
+	'\uAA44'..'\uAA4B' |
+	'\uAA60'..'\uAA6F' |
+	'\uAA71'..'\uAA76' |
+	'\uAA7A' |
+	'\uAA80'..'\uAAAF' |
+	'\uAAB1' |
+	'\uAAB5' |
+	'\uAAB6' |
+	'\uAAB9'..'\uAABD' |
+	'\uAAC0' |
+	'\uAAC2' |
+	'\uAADB' |
+	'\uAADC' |
+	'\uAAE0'..'\uAAEA' |
+	'\uAAF2' |
+	'\uAB01'..'\uAB06' |
+	'\uAB09'..'\uAB0E' |
+	'\uAB11'..'\uAB16' |
+	'\uAB20'..'\uAB26' |
+	'\uAB28'..'\uAB2E' |
+	'\uABC0'..'\uABE2' |
+	'\uAC00' |
+	'\uD7A3' |
+	'\uD7B0'..'\uD7C6' |
+	'\uD7CB'..'\uD7FB' |
+	'\uF900'..'\uFA6D' |
+	'\uFA70'..'\uFAD9' |
+	'\uFB1D' |
+	'\uFB1F'..'\uFB28' |
+	'\uFB2A'..'\uFB36' |
+	'\uFB38'..'\uFB3C' |
+	'\uFB3E' |
+	'\uFB40' |
+	'\uFB41' |
+	'\uFB43' |
+	'\uFB44' |
+	'\uFB46'..'\uFBB1' |
+	'\uFBD3'..'\uFD3D' |
+	'\uFD50'..'\uFD8F' |
+	'\uFD92'..'\uFDC7' |
+	'\uFDF0'..'\uFDFB' |
+	'\uFE70'..'\uFE74' |
+	'\uFE76'..'\uFEFC' |
+	'\uFF66'..'\uFF6F' |
+	'\uFF71'..'\uFF9D' |
+	'\uFFA0'..'\uFFBE' |
+	'\uFFC2'..'\uFFC7' |
+	'\uFFCA'..'\uFFCF' |
+	'\uFFD2'..'\uFFD7' |
+	'\uFFDA'..'\uFFDC';
+
+fragment UNICODE_CLASS_LT:
+	'\u01C5' |
+	'\u01C8' |
+	'\u01CB' |
+	'\u01F2' |
+	'\u1F88'..'\u1F8F' |
+	'\u1F98'..'\u1F9F' |
+	'\u1FA8'..'\u1FAF' |
+	'\u1FBC' |
+	'\u1FCC' |
+	'\u1FFC';
+
+fragment UNICODE_CLASS_LU:
+	'\u0041'..'\u005A' |
+	'\u00C0'..'\u00D6' |
+	'\u00D8'..'\u00DE' |
+	'\u0100' |
+	'\u0102' |
+	'\u0104' |
+	'\u0106' |
+	'\u0108' |
+	'\u010A' |
+	'\u010C' |
+	'\u010E' |
+	'\u0110' |
+	'\u0112' |
+	'\u0114' |
+	'\u0116' |
+	'\u0118' |
+	'\u011A' |
+	'\u011C' |
+	'\u011E' |
+	'\u0120' |
+	'\u0122' |
+	'\u0124' |
+	'\u0126' |
+	'\u0128' |
+	'\u012A' |
+	'\u012C' |
+	'\u012E' |
+	'\u0130' |
+	'\u0132' |
+	'\u0134' |
+	'\u0136' |
+	'\u0139' |
+	'\u013B' |
+	'\u013D' |
+	'\u013F' |
+	'\u0141' |
+	'\u0143' |
+	'\u0145' |
+	'\u0147' |
+	'\u014A' |
+	'\u014C' |
+	'\u014E' |
+	'\u0150' |
+	'\u0152' |
+	'\u0154' |
+	'\u0156' |
+	'\u0158' |
+	'\u015A' |
+	'\u015C' |
+	'\u015E' |
+	'\u0160' |
+	'\u0162' |
+	'\u0164' |
+	'\u0166' |
+	'\u0168' |
+	'\u016A' |
+	'\u016C' |
+	'\u016E' |
+	'\u0170' |
+	'\u0172' |
+	'\u0174' |
+	'\u0176' |
+	'\u0178' |
+	'\u0179' |
+	'\u017B' |
+	'\u017D' |
+	'\u0181' |
+	'\u0182' |
+	'\u0184' |
+	'\u0186' |
+	'\u0187' |
+	'\u0189'..'\u018B' |
+	'\u018E'..'\u0191' |
+	'\u0193' |
+	'\u0194' |
+	'\u0196'..'\u0198' |
+	'\u019C' |
+	'\u019D' |
+	'\u019F' |
+	'\u01A0' |
+	'\u01A2' |
+	'\u01A4' |
+	'\u01A6' |
+	'\u01A7' |
+	'\u01A9' |
+	'\u01AC' |
+	'\u01AE' |
+	'\u01AF' |
+	'\u01B1'..'\u01B3' |
+	'\u01B5' |
+	'\u01B7' |
+	'\u01B8' |
+	'\u01BC' |
+	'\u01C4' |
+	'\u01C7' |
+	'\u01CA' |
+	'\u01CD' |
+	'\u01CF' |
+	'\u01D1' |
+	'\u01D3' |
+	'\u01D5' |
+	'\u01D7' |
+	'\u01D9' |
+	'\u01DB' |
+	'\u01DE' |
+	'\u01E0' |
+	'\u01E2' |
+	'\u01E4' |
+	'\u01E6' |
+	'\u01E8' |
+	'\u01EA' |
+	'\u01EC' |
+	'\u01EE' |
+	'\u01F1' |
+	'\u01F4' |
+	'\u01F6'..'\u01F8' |
+	'\u01FA' |
+	'\u01FC' |
+	'\u01FE' |
+	'\u0200' |
+	'\u0202' |
+	'\u0204' |
+	'\u0206' |
+	'\u0208' |
+	'\u020A' |
+	'\u020C' |
+	'\u020E' |
+	'\u0210' |
+	'\u0212' |
+	'\u0214' |
+	'\u0216' |
+	'\u0218' |
+	'\u021A' |
+	'\u021C' |
+	'\u021E' |
+	'\u0220' |
+	'\u0222' |
+	'\u0224' |
+	'\u0226' |
+	'\u0228' |
+	'\u022A' |
+	'\u022C' |
+	'\u022E' |
+	'\u0230' |
+	'\u0232' |
+	'\u023A' |
+	'\u023B' |
+	'\u023D' |
+	'\u023E' |
+	'\u0241' |
+	'\u0243'..'\u0246' |
+	'\u0248' |
+	'\u024A' |
+	'\u024C' |
+	'\u024E' |
+	'\u0370' |
+	'\u0372' |
+	'\u0376' |
+	'\u0386' |
+	'\u0388'..'\u038A' |
+	'\u038C' |
+	'\u038E' |
+	'\u038F' |
+	'\u0391'..'\u03A1' |
+	'\u03A3'..'\u03AB' |
+	'\u03CF' |
+	'\u03D2'..'\u03D4' |
+	'\u03D8' |
+	'\u03DA' |
+	'\u03DC' |
+	'\u03DE' |
+	'\u03E0' |
+	'\u03E2' |
+	'\u03E4' |
+	'\u03E6' |
+	'\u03E8' |
+	'\u03EA' |
+	'\u03EC' |
+	'\u03EE' |
+	'\u03F4' |
+	'\u03F7' |
+	'\u03F9' |
+	'\u03FA' |
+	'\u03FD'..'\u042F' |
+	'\u0460' |
+	'\u0462' |
+	'\u0464' |
+	'\u0466' |
+	'\u0468' |
+	'\u046A' |
+	'\u046C' |
+	'\u046E' |
+	'\u0470' |
+	'\u0472' |
+	'\u0474' |
+	'\u0476' |
+	'\u0478' |
+	'\u047A' |
+	'\u047C' |
+	'\u047E' |
+	'\u0480' |
+	'\u048A' |
+	'\u048C' |
+	'\u048E' |
+	'\u0490' |
+	'\u0492' |
+	'\u0494' |
+	'\u0496' |
+	'\u0498' |
+	'\u049A' |
+	'\u049C' |
+	'\u049E' |
+	'\u04A0' |
+	'\u04A2' |
+	'\u04A4' |
+	'\u04A6' |
+	'\u04A8' |
+	'\u04AA' |
+	'\u04AC' |
+	'\u04AE' |
+	'\u04B0' |
+	'\u04B2' |
+	'\u04B4' |
+	'\u04B6' |
+	'\u04B8' |
+	'\u04BA' |
+	'\u04BC' |
+	'\u04BE' |
+	'\u04C0' |
+	'\u04C1' |
+	'\u04C3' |
+	'\u04C5' |
+	'\u04C7' |
+	'\u04C9' |
+	'\u04CB' |
+	'\u04CD' |
+	'\u04D0' |
+	'\u04D2' |
+	'\u04D4' |
+	'\u04D6' |
+	'\u04D8' |
+	'\u04DA' |
+	'\u04DC' |
+	'\u04DE' |
+	'\u04E0' |
+	'\u04E2' |
+	'\u04E4' |
+	'\u04E6' |
+	'\u04E8' |
+	'\u04EA' |
+	'\u04EC' |
+	'\u04EE' |
+	'\u04F0' |
+	'\u04F2' |
+	'\u04F4' |
+	'\u04F6' |
+	'\u04F8' |
+	'\u04FA' |
+	'\u04FC' |
+	'\u04FE' |
+	'\u0500' |
+	'\u0502' |
+	'\u0504' |
+	'\u0506' |
+	'\u0508' |
+	'\u050A' |
+	'\u050C' |
+	'\u050E' |
+	'\u0510' |
+	'\u0512' |
+	'\u0514' |
+	'\u0516' |
+	'\u0518' |
+	'\u051A' |
+	'\u051C' |
+	'\u051E' |
+	'\u0520' |
+	'\u0522' |
+	'\u0524' |
+	'\u0526' |
+	'\u0531'..'\u0556' |
+	'\u10A0'..'\u10C5' |
+	'\u10C7' |
+	'\u10CD' |
+	'\u1E00' |
+	'\u1E02' |
+	'\u1E04' |
+	'\u1E06' |
+	'\u1E08' |
+	'\u1E0A' |
+	'\u1E0C' |
+	'\u1E0E' |
+	'\u1E10' |
+	'\u1E12' |
+	'\u1E14' |
+	'\u1E16' |
+	'\u1E18' |
+	'\u1E1A' |
+	'\u1E1C' |
+	'\u1E1E' |
+	'\u1E20' |
+	'\u1E22' |
+	'\u1E24' |
+	'\u1E26' |
+	'\u1E28' |
+	'\u1E2A' |
+	'\u1E2C' |
+	'\u1E2E' |
+	'\u1E30' |
+	'\u1E32' |
+	'\u1E34' |
+	'\u1E36' |
+	'\u1E38' |
+	'\u1E3A' |
+	'\u1E3C' |
+	'\u1E3E' |
+	'\u1E40' |
+	'\u1E42' |
+	'\u1E44' |
+	'\u1E46' |
+	'\u1E48' |
+	'\u1E4A' |
+	'\u1E4C' |
+	'\u1E4E' |
+	'\u1E50' |
+	'\u1E52' |
+	'\u1E54' |
+	'\u1E56' |
+	'\u1E58' |
+	'\u1E5A' |
+	'\u1E5C' |
+	'\u1E5E' |
+	'\u1E60' |
+	'\u1E62' |
+	'\u1E64' |
+	'\u1E66' |
+	'\u1E68' |
+	'\u1E6A' |
+	'\u1E6C' |
+	'\u1E6E' |
+	'\u1E70' |
+	'\u1E72' |
+	'\u1E74' |
+	'\u1E76' |
+	'\u1E78' |
+	'\u1E7A' |
+	'\u1E7C' |
+	'\u1E7E' |
+	'\u1E80' |
+	'\u1E82' |
+	'\u1E84' |
+	'\u1E86' |
+	'\u1E88' |
+	'\u1E8A' |
+	'\u1E8C' |
+	'\u1E8E' |
+	'\u1E90' |
+	'\u1E92' |
+	'\u1E94' |
+	'\u1E9E' |
+	'\u1EA0' |
+	'\u1EA2' |
+	'\u1EA4' |
+	'\u1EA6' |
+	'\u1EA8' |
+	'\u1EAA' |
+	'\u1EAC' |
+	'\u1EAE' |
+	'\u1EB0' |
+	'\u1EB2' |
+	'\u1EB4' |
+	'\u1EB6' |
+	'\u1EB8' |
+	'\u1EBA' |
+	'\u1EBC' |
+	'\u1EBE' |
+	'\u1EC0' |
+	'\u1EC2' |
+	'\u1EC4' |
+	'\u1EC6' |
+	'\u1EC8' |
+	'\u1ECA' |
+	'\u1ECC' |
+	'\u1ECE' |
+	'\u1ED0' |
+	'\u1ED2' |
+	'\u1ED4' |
+	'\u1ED6' |
+	'\u1ED8' |
+	'\u1EDA' |
+	'\u1EDC' |
+	'\u1EDE' |
+	'\u1EE0' |
+	'\u1EE2' |
+	'\u1EE4' |
+	'\u1EE6' |
+	'\u1EE8' |
+	'\u1EEA' |
+	'\u1EEC' |
+	'\u1EEE' |
+	'\u1EF0' |
+	'\u1EF2' |
+	'\u1EF4' |
+	'\u1EF6' |
+	'\u1EF8' |
+	'\u1EFA' |
+	'\u1EFC' |
+	'\u1EFE' |
+	'\u1F08'..'\u1F0F' |
+	'\u1F18'..'\u1F1D' |
+	'\u1F28'..'\u1F2F' |
+	'\u1F38'..'\u1F3F' |
+	'\u1F48'..'\u1F4D' |
+	'\u1F59' |
+	'\u1F5B' |
+	'\u1F5D' |
+	'\u1F5F' |
+	'\u1F68'..'\u1F6F' |
+	'\u1FB8'..'\u1FBB' |
+	'\u1FC8'..'\u1FCB' |
+	'\u1FD8'..'\u1FDB' |
+	'\u1FE8'..'\u1FEC' |
+	'\u1FF8'..'\u1FFB' |
+	'\u2102' |
+	'\u2107' |
+	'\u210B'..'\u210D' |
+	'\u2110'..'\u2112' |
+	'\u2115' |
+	'\u2119'..'\u211D' |
+	'\u2124' |
+	'\u2126' |
+	'\u2128' |
+	'\u212A'..'\u212D' |
+	'\u2130'..'\u2133' |
+	'\u213E' |
+	'\u213F' |
+	'\u2145' |
+	'\u2183' |
+	'\u2C00'..'\u2C2E' |
+	'\u2C60' |
+	'\u2C62'..'\u2C64' |
+	'\u2C67' |
+	'\u2C69' |
+	'\u2C6B' |
+	'\u2C6D'..'\u2C70' |
+	'\u2C72' |
+	'\u2C75' |
+	'\u2C7E'..'\u2C80' |
+	'\u2C82' |
+	'\u2C84' |
+	'\u2C86' |
+	'\u2C88' |
+	'\u2C8A' |
+	'\u2C8C' |
+	'\u2C8E' |
+	'\u2C90' |
+	'\u2C92' |
+	'\u2C94' |
+	'\u2C96' |
+	'\u2C98' |
+	'\u2C9A' |
+	'\u2C9C' |
+	'\u2C9E' |
+	'\u2CA0' |
+	'\u2CA2' |
+	'\u2CA4' |
+	'\u2CA6' |
+	'\u2CA8' |
+	'\u2CAA' |
+	'\u2CAC' |
+	'\u2CAE' |
+	'\u2CB0' |
+	'\u2CB2' |
+	'\u2CB4' |
+	'\u2CB6' |
+	'\u2CB8' |
+	'\u2CBA' |
+	'\u2CBC' |
+	'\u2CBE' |
+	'\u2CC0' |
+	'\u2CC2' |
+	'\u2CC4' |
+	'\u2CC6' |
+	'\u2CC8' |
+	'\u2CCA' |
+	'\u2CCC' |
+	'\u2CCE' |
+	'\u2CD0' |
+	'\u2CD2' |
+	'\u2CD4' |
+	'\u2CD6' |
+	'\u2CD8' |
+	'\u2CDA' |
+	'\u2CDC' |
+	'\u2CDE' |
+	'\u2CE0' |
+	'\u2CE2' |
+	'\u2CEB' |
+	'\u2CED' |
+	'\u2CF2' |
+	'\uA640' |
+	'\uA642' |
+	'\uA644' |
+	'\uA646' |
+	'\uA648' |
+	'\uA64A' |
+	'\uA64C' |
+	'\uA64E' |
+	'\uA650' |
+	'\uA652' |
+	'\uA654' |
+	'\uA656' |
+	'\uA658' |
+	'\uA65A' |
+	'\uA65C' |
+	'\uA65E' |
+	'\uA660' |
+	'\uA662' |
+	'\uA664' |
+	'\uA666' |
+	'\uA668' |
+	'\uA66A' |
+	'\uA66C' |
+	'\uA680' |
+	'\uA682' |
+	'\uA684' |
+	'\uA686' |
+	'\uA688' |
+	'\uA68A' |
+	'\uA68C' |
+	'\uA68E' |
+	'\uA690' |
+	'\uA692' |
+	'\uA694' |
+	'\uA696' |
+	'\uA722' |
+	'\uA724' |
+	'\uA726' |
+	'\uA728' |
+	'\uA72A' |
+	'\uA72C' |
+	'\uA72E' |
+	'\uA732' |
+	'\uA734' |
+	'\uA736' |
+	'\uA738' |
+	'\uA73A' |
+	'\uA73C' |
+	'\uA73E' |
+	'\uA740' |
+	'\uA742' |
+	'\uA744' |
+	'\uA746' |
+	'\uA748' |
+	'\uA74A' |
+	'\uA74C' |
+	'\uA74E' |
+	'\uA750' |
+	'\uA752' |
+	'\uA754' |
+	'\uA756' |
+	'\uA758' |
+	'\uA75A' |
+	'\uA75C' |
+	'\uA75E' |
+	'\uA760' |
+	'\uA762' |
+	'\uA764' |
+	'\uA766' |
+	'\uA768' |
+	'\uA76A' |
+	'\uA76C' |
+	'\uA76E' |
+	'\uA779' |
+	'\uA77B' |
+	'\uA77D' |
+	'\uA77E' |
+	'\uA780' |
+	'\uA782' |
+	'\uA784' |
+	'\uA786' |
+	'\uA78B' |
+	'\uA78D' |
+	'\uA790' |
+	'\uA792' |
+	'\uA7A0' |
+	'\uA7A2' |
+	'\uA7A4' |
+	'\uA7A6' |
+	'\uA7A8' |
+	'\uA7AA' |
+	'\uFF21'..'\uFF3A';
+
+fragment UNICODE_CLASS_ND:
+	'\u0030'..'\u0039' |
+	'\u0660'..'\u0669' |
+	'\u06F0'..'\u06F9' |
+	'\u07C0'..'\u07C9' |
+	'\u0966'..'\u096F' |
+	'\u09E6'..'\u09EF' |
+	'\u0A66'..'\u0A6F' |
+	'\u0AE6'..'\u0AEF' |
+	'\u0B66'..'\u0B6F' |
+	'\u0BE6'..'\u0BEF' |
+	'\u0C66'..'\u0C6F' |
+	'\u0CE6'..'\u0CEF' |
+	'\u0D66'..'\u0D6F' |
+	'\u0E50'..'\u0E59' |
+	'\u0ED0'..'\u0ED9' |
+	'\u0F20'..'\u0F29' |
+	'\u1040'..'\u1049' |
+	'\u1090'..'\u1099' |
+	'\u17E0'..'\u17E9' |
+	'\u1810'..'\u1819' |
+	'\u1946'..'\u194F' |
+	'\u19D0'..'\u19D9' |
+	'\u1A80'..'\u1A89' |
+	'\u1A90'..'\u1A99' |
+	'\u1B50'..'\u1B59' |
+	'\u1BB0'..'\u1BB9' |
+	'\u1C40'..'\u1C49' |
+	'\u1C50'..'\u1C59' |
+	'\uA620'..'\uA629' |
+	'\uA8D0'..'\uA8D9' |
+	'\uA900'..'\uA909' |
+	'\uA9D0'..'\uA9D9' |
+	'\uAA50'..'\uAA59' |
+	'\uABF0'..'\uABF9' |
+	'\uFF10'..'\uFF19';
+
+fragment UNICODE_CLASS_NL:
+	'\u16EE'..'\u16F0' |
+	'\u2160'..'\u2182' |
+	'\u2185'..'\u2188' |
+	'\u3007' |
+	'\u3021'..'\u3029' |
+	'\u3038'..'\u303A' |
+	'\uA6E6'..'\uA6EF';

+ 370 - 0
jplag.frontend.kotlin/src/main/java/de/jplag/kotlin/JPlagKotlinListener.java

@@ -0,0 +1,370 @@
+package de.jplag.kotlin;
+
+import static de.jplag.kotlin.KotlinTokenConstants.*;
+
+import java.util.Optional;
+
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.tree.TerminalNode;
+
+import de.jplag.kotlin.grammar.KotlinParser;
+import de.jplag.kotlin.grammar.KotlinParserBaseListener;
+
+public class JPlagKotlinListener extends KotlinParserBaseListener {
+    private final KotlinParserAdapter parserAdapter;
+
+    public JPlagKotlinListener(KotlinParserAdapter parserAdapter) {
+        this.parserAdapter = parserAdapter;
+    }
+
+    /**
+     * Passes a token of the given tokenType to the parserAdapter, representing the grammar's token given by token.
+     * @param tokenType the custom token type that occurred.
+     * @param token the corresponding grammar's token
+     */
+    private void transformToken(int tokenType, Token token) {
+        parserAdapter.addToken(tokenType, token.getLine(), token.getCharPositionInLine() + 1, token.getText().length());
+    }
+
+    /**
+     * Passes a token of the given tokenType to the parserAdapter, representing the current grammatical context given by
+     * start and end.
+     * @param tokenType the custom token type that occurred.
+     * @param start the first Token of the context
+     * @param end the last Token of the context
+     */
+    private void transformToken(int tokenType, Token start, Token end) {
+        parserAdapter.addToken(tokenType, start.getLine(), start.getCharPositionInLine() + 1, end.getStopIndex() - start.getStartIndex() + 1);
+    }
+
+    @Override
+    public void enterPackageHeader(KotlinParser.PackageHeaderContext context) {
+        transformToken(PACKAGE, context.getStart(), context.getStop());
+        super.enterPackageHeader(context);
+    }
+
+    @Override
+    public void enterImportHeader(KotlinParser.ImportHeaderContext context) {
+        transformToken(IMPORT, context.getStart(), context.getStop());
+        super.enterImportHeader(context);
+    }
+
+    @Override
+    public void enterClassDeclaration(KotlinParser.ClassDeclarationContext context) {
+        transformToken(CLASS_DECLARATION, context.getStart());
+        super.enterClassDeclaration(context);
+    }
+
+    @Override
+    public void enterObjectDeclaration(KotlinParser.ObjectDeclarationContext context) {
+        transformToken(OBJECT_DECLARATION, context.getStart(), context.getStop());
+        super.enterObjectDeclaration(context);
+    }
+
+    @Override
+    public void enterCompanionObject(KotlinParser.CompanionObjectContext context) {
+        transformToken(COMPANION_DECLARATION, context.getStart());
+        super.enterCompanionObject(context);
+    }
+
+    @Override
+    public void enterTypeParameter(KotlinParser.TypeParameterContext context) {
+        transformToken(TYPE_PARAMETER, context.getStart(), context.getStop());
+        super.enterTypeParameter(context);
+    }
+
+    @Override
+    public void enterPrimaryConstructor(KotlinParser.PrimaryConstructorContext context) {
+        transformToken(CONSTRUCTOR, context.getStart(), context.getStop());
+        super.enterPrimaryConstructor(context);
+    }
+
+    @Override
+    public void enterClassParameter(KotlinParser.ClassParameterContext context) {
+        transformToken(PROPERTY_DECLARATION, context.getStart(), context.getStop());
+        super.enterClassParameter(context);
+    }
+
+    @Override
+    public void enterClassBody(KotlinParser.ClassBodyContext context) {
+        transformToken(CLASS_BODY_BEGIN, context.getStart());
+        super.enterClassBody(context);
+    }
+
+    @Override
+    public void exitClassBody(KotlinParser.ClassBodyContext context) {
+        transformToken(CLASS_BODY_END, context.getStop());
+        super.exitClassBody(context);
+    }
+
+    @Override
+    public void enterEnumClassBody(KotlinParser.EnumClassBodyContext context) {
+        transformToken(ENUM_CLASS_BODY_BEGIN, context.getStart());
+        super.enterEnumClassBody(context);
+    }
+
+    @Override
+    public void exitEnumClassBody(KotlinParser.EnumClassBodyContext context) {
+        transformToken(ENUM_CLASS_BODY_END, context.getStop());
+        super.exitEnumClassBody(context);
+    }
+
+    @Override
+    public void enterEnumEntry(KotlinParser.EnumEntryContext context) {
+        transformToken(ENUM_ENTRY, context.getStart());
+        super.enterEnumEntry(context);
+    }
+
+    @Override
+    public void enterSecondaryConstructor(KotlinParser.SecondaryConstructorContext context) {
+        transformToken(CONSTRUCTOR, context.getStart(), context.getStop());
+        super.enterSecondaryConstructor(context);
+    }
+
+    @Override
+    public void enterPropertyDeclaration(KotlinParser.PropertyDeclarationContext context) {
+        transformToken(PROPERTY_DECLARATION, context.getStart());
+        super.enterPropertyDeclaration(context);
+    }
+
+    @Override
+    public void enterAnonymousInitializer(KotlinParser.AnonymousInitializerContext context) {
+        transformToken(INITIALIZER, context.getStart());
+        super.enterAnonymousInitializer(context);
+    }
+
+    @Override
+    public void enterInitBlock(KotlinParser.InitBlockContext context) {
+        transformToken(INITIALIZER_BODY_START, context.getStart());
+        super.enterInitBlock(context);
+    }
+
+    @Override
+    public void exitInitBlock(KotlinParser.InitBlockContext context) {
+        transformToken(INITIALIZER_BODY_END, context.getStop());
+        super.exitInitBlock(context);
+    }
+
+    @Override
+    public void enterFunctionDeclaration(KotlinParser.FunctionDeclarationContext context) {
+        transformToken(FUNCTION, context.getStart());
+        super.enterFunctionDeclaration(context);
+    }
+
+    @Override
+    public void enterGetter(KotlinParser.GetterContext context) {
+        transformToken(GETTER, context.getStart());
+        super.enterGetter(context);
+    }
+
+    @Override
+    public void enterSetter(KotlinParser.SetterContext context) {
+        transformToken(SETTER, context.getStart());
+        super.enterSetter(context);
+    }
+
+    @Override
+    public void enterFunctionValueParameter(KotlinParser.FunctionValueParameterContext context) {
+        transformToken(FUNCTION_PARAMETER, context.getStart(), context.getStop());
+        super.enterFunctionValueParameter(context);
+    }
+
+    @Override
+    public void enterFunctionBody(KotlinParser.FunctionBodyContext context) {
+        transformToken(FUNCTION_BODY_BEGIN, context.getStart());
+        super.enterFunctionBody(context);
+    }
+
+    @Override
+    public void exitFunctionBody(KotlinParser.FunctionBodyContext context) {
+        transformToken(FUNCTION_BODY_END, context.getStop());
+        super.exitFunctionBody(context);
+    }
+
+    @Override
+    public void enterFunctionLiteral(KotlinParser.FunctionLiteralContext context) {
+        transformToken(FUNCTION_LITERAL_BEGIN, context.getStart());
+        super.enterFunctionLiteral(context);
+    }
+
+    @Override
+    public void exitFunctionLiteral(KotlinParser.FunctionLiteralContext context) {
+        transformToken(FUNCTION_LITERAL_END, context.getStop());
+        super.exitFunctionLiteral(context);
+    }
+
+    @Override
+    public void enterForExpression(KotlinParser.ForExpressionContext context) {
+        transformToken(FOR_EXPRESSION_BEGIN, context.getStart());
+        super.enterForExpression(context);
+    }
+
+    @Override
+    public void exitForExpression(KotlinParser.ForExpressionContext context) {
+        transformToken(FOR_EXPRESSION_END, context.getStop());
+        super.exitForExpression(context);
+    }
+
+    @Override
+    public void enterIfExpression(KotlinParser.IfExpressionContext context) {
+        transformToken(IF_EXPRESSION_START, context.getStart());
+        super.enterIfExpression(context);
+    }
+
+    @Override
+    public void exitIfExpression(KotlinParser.IfExpressionContext context) {
+        transformToken(IF_EXPRESSION_END, context.getStop());
+        super.exitIfExpression(context);
+    }
+
+    @Override
+    public void enterWhileExpression(KotlinParser.WhileExpressionContext context) {
+        transformToken(WHILE_EXPRESSION_START, context.getStart());
+        super.enterWhileExpression(context);
+    }
+
+    @Override
+    public void exitWhileExpression(KotlinParser.WhileExpressionContext context) {
+        transformToken(WHILE_EXPRESSION_END, context.getStop());
+        super.exitWhileExpression(context);
+    }
+
+    @Override
+    public void enterDoWhileExpression(KotlinParser.DoWhileExpressionContext context) {
+        transformToken(DO_WHILE_EXPRESSION_START, context.getStart());
+        super.enterDoWhileExpression(context);
+    }
+
+    @Override
+    public void exitDoWhileExpression(KotlinParser.DoWhileExpressionContext context) {
+        transformToken(DO_WHILE_EXPRESSION_END, context.getStop());
+        super.exitDoWhileExpression(context);
+    }
+
+    @Override
+    public void enterTryExpression(KotlinParser.TryExpressionContext context) {
+        transformToken(TRY_EXPRESSION, context.getStart());
+        super.enterTryExpression(context);
+    }
+
+    @Override
+    public void enterTryBody(KotlinParser.TryBodyContext ctx) {
+        transformToken(TRY_BODY_START, ctx.getStart());
+        super.enterTryBody(ctx);
+    }
+
+    @Override
+    public void exitTryBody(KotlinParser.TryBodyContext ctx) {
+        transformToken(TRY_BODY_END, ctx.getStop());
+        super.exitTryBody(ctx);
+    }
+
+    @Override
+    public void enterCatchStatement(KotlinParser.CatchStatementContext context) {
+        transformToken(CATCH, context.getStart());
+        super.enterCatchStatement(context);
+    }
+
+    @Override
+    public void enterCatchBody(KotlinParser.CatchBodyContext context) {
+        transformToken(CATCH_BODY_START, context.getStart());
+        super.enterCatchBody(context);
+    }
+
+    @Override
+    public void exitCatchBody(KotlinParser.CatchBodyContext context) {
+        transformToken(CATCH_BODY_END, context.getStop());
+        super.exitCatchBody(context);
+    }
+
+    @Override
+    public void enterFinallyStatement(KotlinParser.FinallyStatementContext context) {
+        transformToken(FINALLY, context.getStart());
+        super.enterFinallyStatement(context);
+    }
+
+    @Override
+    public void enterFinallyBody(KotlinParser.FinallyBodyContext context) {
+        transformToken(FINALLY_BODY_START, context.getStart());
+        super.enterFinallyBody(context);
+    }
+
+    @Override
+    public void exitFinallyBody(KotlinParser.FinallyBodyContext context) {
+        transformToken(FINALLY_BODY_END, context.getStop());
+        super.exitFinallyBody(context);
+    }
+
+    @Override
+    public void enterWhenExpression(KotlinParser.WhenExpressionContext context) {
+        transformToken(WHEN_EXPRESSION_START, context.getStart());
+        super.enterWhenExpression(context);
+    }
+
+    @Override
+    public void exitWhenExpression(KotlinParser.WhenExpressionContext context) {
+        transformToken(WHEN_EXPRESSION_END, context.getStop());
+        super.exitWhenExpression(context);
+    }
+
+    @Override
+    public void enterWhenCondition(KotlinParser.WhenConditionContext context) {
+        transformToken(WHEN_CONDITION, context.getStart(), context.getStop());
+        super.enterWhenCondition(context);
+    }
+
+    @Override
+    public void enterControlStructureBody(KotlinParser.ControlStructureBodyContext context) {
+        transformToken(CONTROL_STRUCTURE_BODY_START, context.getStart());
+        super.enterControlStructureBody(context);
+    }
+
+    @Override
+    public void exitControlStructureBody(KotlinParser.ControlStructureBodyContext context) {
+        transformToken(CONTROL_STRUCTURE_BODY_END, context.getStop());
+        super.exitControlStructureBody(context);
+    }
+
+    @Override
+    public void enterVariableDeclaration(KotlinParser.VariableDeclarationContext context) {
+        transformToken(VARIABLE_DECLARATION, context.getStart());
+        super.enterVariableDeclaration(context);
+    }
+
+    @Override
+    public void enterConstructorInvocation(KotlinParser.ConstructorInvocationContext context) {
+        transformToken(CREATE_OBJECT, context.getStart(), context.getStop());
+        super.enterConstructorInvocation(context);
+    }
+
+    @Override
+    public void enterCallSuffix(KotlinParser.CallSuffixContext context) {
+        transformToken(FUNCTION_INVOCATION, context.getStart(), context.getStop());
+        super.enterCallSuffix(context);
+    }
+
+    @Override
+    public void enterAssignmentOperator(KotlinParser.AssignmentOperatorContext context) {
+        transformToken(ASSIGNMENT, context.getStart());
+        super.enterAssignmentOperator(context);
+    }
+
+    @Override
+    public void visitTerminal(TerminalNode node) {
+        Token token = node.getSymbol();
+        String tokenText = token.getText();
+        if (tokenText.contains("@")) {
+            tokenText = tokenText.substring(0, tokenText.indexOf("@"));
+        }
+        Optional<Integer> type = switch (tokenText) {
+            case "throw" -> Optional.of(THROW);
+            case "return" -> Optional.of(RETURN);
+            case "continue" -> Optional.of(CONTINUE);
+            case "break" -> Optional.of(BREAK);
+            default -> Optional.empty();
+        };
+
+        type.ifPresent(tokenType -> transformToken(tokenType, token));
+    }
+
+}

+ 85 - 0
jplag.frontend.kotlin/src/main/java/de/jplag/kotlin/KotlinParserAdapter.java

@@ -0,0 +1,85 @@
+package de.jplag.kotlin;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.antlr.v4.runtime.tree.ParseTreeWalker;
+
+import de.jplag.AbstractParser;
+import de.jplag.ErrorConsumer;
+import de.jplag.TokenConstants;
+import de.jplag.TokenList;
+import de.jplag.kotlin.grammar.KotlinLexer;
+import de.jplag.kotlin.grammar.KotlinParser;
+
+public class KotlinParserAdapter extends AbstractParser {
+
+    public static final int NOT_SET = -1;
+    private String currentFile;
+    private TokenList tokens;
+
+    /**
+     * Creates the KotlinParserAdapter
+     * @param consumer the ErrorConsumer that parser errors are passed on to.
+     */
+    public KotlinParserAdapter(ErrorConsumer consumer) {
+        super(consumer);
+    }
+
+    /**
+     * Parsers a list of files into a single {@link TokenList}.
+     * @param directory the directory of the files.
+     * @param fileNames the file names of the files.
+     * @return a {@link TokenList} containing all tokens of all files.
+     */
+    public TokenList parse(File directory, String[] fileNames) {
+        tokens = new TokenList();
+        for (String file : fileNames) {
+            if (!parseFile(directory, file)) {
+                errors++;
+            }
+            tokens.addToken(new KotlinToken(TokenConstants.FILE_END, file, NOT_SET, NOT_SET, NOT_SET));
+        }
+        return tokens;
+    }
+
+    private boolean parseFile(File directory, String fileName) {
+        File file = new File(directory, fileName);
+        try (FileInputStream inputStream = new FileInputStream(file)) {
+            currentFile = fileName;
+
+            KotlinLexer lexer = new KotlinLexer(CharStreams.fromStream(inputStream));
+            CommonTokenStream tokenStream = new CommonTokenStream(lexer);
+            KotlinParser parser = new KotlinParser(tokenStream);
+
+            ParserRuleContext entryContext = parser.kotlinFile();
+            ParseTreeWalker treeWalker = new ParseTreeWalker();
+
+            JPlagKotlinListener listener = new JPlagKotlinListener(this);
+            for (int i = 0; i < entryContext.getChildCount(); i++) {
+                ParseTree parseTree = entryContext.getChild(i);
+                treeWalker.walk(listener, parseTree);
+            }
+        } catch (IOException exception) {
+            getErrorConsumer().addError("Parsing Error in '%s': %s%s".formatted(fileName, File.separator, exception));
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Adds a new {@link de.jplag.Token} to the current {@link TokenList}.
+     * @param tokenType the type of the new {@link de.jplag.Token}
+     * @param line the line of the Token in the current file
+     * @param column the start column of the Token in the line
+     * @param length the length of the Token
+     */
+    /* package-private */ void addToken(int tokenType, int line, int column, int length) {
+        tokens.addToken(new KotlinToken(tokenType, currentFile, line, column, length));
+    }
+}

+ 75 - 0
jplag.frontend.kotlin/src/main/java/de/jplag/kotlin/KotlinToken.java

@@ -0,0 +1,75 @@
+package de.jplag.kotlin;
+
+import static de.jplag.kotlin.KotlinTokenConstants.*;
+
+import de.jplag.Token;
+
+public class KotlinToken extends Token {
+
+    public KotlinToken(int type, String file, int line, int column, int length) {
+        super(type, file, line, column, length);
+    }
+
+    @Override
+    public String type2string() {
+        return switch (type) {
+            case FILE_END -> "<EOF>";
+            case SEPARATOR_TOKEN -> "-----";
+            case PACKAGE -> "PACKAGE";
+            case IMPORT -> "IMPORT";
+            case CLASS_DECLARATION -> "CLASS";
+            case OBJECT_DECLARATION -> "OBJECT";
+            case COMPANION_DECLARATION -> "COMPANION";
+            case TYPE_PARAMETER -> "<T>";
+            case CONSTRUCTOR -> "CONSTRUCTOR";
+            case CLASS_BODY_BEGIN -> "CLASS{";
+            case CLASS_BODY_END -> "}CLASS";
+            case ENUM_CLASS_BODY_BEGIN -> "ENUM{";
+            case ENUM_CLASS_BODY_END -> "}ENUM";
+            case PROPERTY_DECLARATION -> "PROPERTY";
+            case INITIALIZER -> "INIT";
+            case INITIALIZER_BODY_START -> "INIT{";
+            case INITIALIZER_BODY_END -> "}INIT";
+            case FUNCTION -> "FUN";
+            case GETTER -> "GET";
+            case SETTER -> "SET";
+            case FUNCTION_PARAMETER -> "PARAM";
+            case FUNCTION_BODY_BEGIN -> "FUN{";
+            case FUNCTION_BODY_END -> "}FUN";
+            case FUNCTION_LITERAL_BEGIN -> "FUNC_LIT{";
+            case FUNCTION_LITERAL_END -> "}FUNC_LIT";
+            case FOR_EXPRESSION_BEGIN -> "FOR";
+            case FOR_EXPRESSION_END -> "}FOR";
+            case IF_EXPRESSION_START -> "IF";
+            case IF_EXPRESSION_END -> "}IF(-ELSE)";
+            case WHILE_EXPRESSION_START -> "WHILE";
+            case WHILE_EXPRESSION_END -> "}WHILE";
+            case DO_WHILE_EXPRESSION_START -> "DO";
+            case DO_WHILE_EXPRESSION_END -> "}DO-WHILE";
+            case TRY_EXPRESSION -> "TRY";
+            case TRY_BODY_START -> "TRY{";
+            case TRY_BODY_END -> "}TRY";
+            case CATCH -> "CATCH";
+            case CATCH_BODY_START -> "CATCH{";
+            case CATCH_BODY_END -> "}CATCH";
+            case FINALLY -> "FINALLY";
+            case FINALLY_BODY_START -> "FINALLY{";
+            case FINALLY_BODY_END -> "}FINALLY";
+            case WHEN_EXPRESSION_START -> "WHEN";
+            case WHEN_EXPRESSION_END -> "}WHEN";
+            case WHEN_CONDITION -> "COND";
+            case CONTROL_STRUCTURE_BODY_START -> "THEN{";
+            case CONTROL_STRUCTURE_BODY_END -> "}THEN";
+            case VARIABLE_DECLARATION -> "VARDECL";
+            case ENUM_ENTRY -> "ENUM_ENTRY";
+            case FUNCTION_INVOCATION -> "INVOC";
+            case CREATE_OBJECT -> "CONST";
+            case ASSIGNMENT -> "ASSIGN";
+            case THROW -> "THROW";
+            case RETURN -> "RETURN";
+            case CONTINUE -> "CONTINUE";
+            case BREAK -> "BREAK";
+            default -> "<UNKNOWN%d>".formatted(type);
+        };
+    }
+}

+ 79 - 0
jplag.frontend.kotlin/src/main/java/de/jplag/kotlin/KotlinTokenConstants.java

@@ -0,0 +1,79 @@
+package de.jplag.kotlin;
+
+import de.jplag.TokenConstants;
+
+public interface KotlinTokenConstants extends TokenConstants {
+
+    int PACKAGE = 2;
+    int IMPORT = 3;
+    int CLASS_DECLARATION = 4;
+    int OBJECT_DECLARATION = 5;
+    int COMPANION_DECLARATION = 6;
+
+    int TYPE_PARAMETER = 7;
+    int CONSTRUCTOR = 8;
+
+    int CLASS_BODY_BEGIN = 9;
+    int CLASS_BODY_END = 10;
+
+    int ENUM_CLASS_BODY_BEGIN = 11;
+    int ENUM_CLASS_BODY_END = 12;
+
+    int PROPERTY_DECLARATION = 13;
+
+    int INITIALIZER = 14;
+    int INITIALIZER_BODY_START = 15;
+    int INITIALIZER_BODY_END = 16;
+
+    int FUNCTION = 17;
+
+    int GETTER = 18;
+    int SETTER = 19;
+    int FUNCTION_PARAMETER = 20;
+    int FUNCTION_BODY_BEGIN = 21;
+    int FUNCTION_BODY_END = 22;
+
+    int FUNCTION_LITERAL_BEGIN = 23;
+    int FUNCTION_LITERAL_END = 24;
+
+    int FOR_EXPRESSION_BEGIN = 25;
+    int FOR_EXPRESSION_END = 26;
+
+    int IF_EXPRESSION_START = 27;
+    int IF_EXPRESSION_END = 28;
+
+    int WHILE_EXPRESSION_START = 29;
+    int WHILE_EXPRESSION_END = 30;
+
+    int DO_WHILE_EXPRESSION_START = 31;
+    int DO_WHILE_EXPRESSION_END = 32;
+
+    int TRY_EXPRESSION = 33;
+    int TRY_BODY_START = 34;
+    int TRY_BODY_END = 35;
+    int CATCH = 36;
+    int CATCH_BODY_START = 37;
+    int CATCH_BODY_END = 38;
+    int FINALLY = 39;
+    int FINALLY_BODY_START = 40;
+    int FINALLY_BODY_END = 41;
+    int WHEN_EXPRESSION_START = 42;
+    int WHEN_EXPRESSION_END = 43;
+    int WHEN_CONDITION = 44;
+
+    int CONTROL_STRUCTURE_BODY_START = 45;
+    int CONTROL_STRUCTURE_BODY_END = 46;
+
+    int VARIABLE_DECLARATION = 47;
+    int ENUM_ENTRY = 48;
+    int FUNCTION_INVOCATION = 49;
+    int CREATE_OBJECT = 50;
+    int ASSIGNMENT = 51;
+
+    int THROW = 52;
+    int RETURN = 53;
+    int CONTINUE = 54;
+    int BREAK = 55;
+
+    int NUMBER_DIFF_TOKENS = 56;
+}

+ 72 - 0
jplag.frontend.kotlin/src/main/java/de/jplag/kotlin/Language.java

@@ -0,0 +1,72 @@
+package de.jplag.kotlin;
+
+import java.io.File;
+
+import de.jplag.ErrorConsumer;
+import de.jplag.TokenList;
+
+/**
+ * This represents the Kotlin language as a language supported by JPlag.
+ */
+public class Language implements de.jplag.Language {
+
+    private static final String NAME = "Kotlin Parser";
+    private static final String SHORT_NAME = "Kotlin";
+    private static final int DEFAULT_MIN_TOKEN_MATCH = 8;
+    private static final String[] FILE_EXTENSIONS = {".kt"};
+    private final KotlinParserAdapter parserAdapter;
+
+    public Language(ErrorConsumer consumer) {
+        this.parserAdapter = new KotlinParserAdapter(consumer);
+    }
+
+    @Override
+    public String[] suffixes() {
+        return FILE_EXTENSIONS;
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public String getShortName() {
+        return SHORT_NAME;
+    }
+
+    @Override
+    public int minimumTokenMatch() {
+        return DEFAULT_MIN_TOKEN_MATCH;
+    }
+
+    @Override
+    public TokenList parse(File directory, String[] files) {
+        return parserAdapter.parse(directory, files);
+    }
+
+    @Override
+    public boolean hasErrors() {
+        return parserAdapter.hasErrors();
+    }
+
+    @Override
+    public boolean supportsColumns() {
+        return true;
+    }
+
+    @Override
+    public boolean isPreformatted() {
+        return true;
+    }
+
+    @Override
+    public boolean usesIndex() {
+        return false;
+    }
+
+    @Override
+    public int numberOfTokens() {
+        return KotlinTokenConstants.NUMBER_DIFF_TOKENS;
+    }
+}

+ 172 - 0
jplag.frontend.kotlin/src/test/java/de/jplag/kotlin/KotlinFrontendTest.java

@@ -0,0 +1,172 @@
+package de.jplag.kotlin;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.StreamSupport;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import de.jplag.Token;
+import de.jplag.TokenConstants;
+import de.jplag.TokenList;
+import de.jplag.TokenPrinter;
+import de.jplag.testutils.TestErrorConsumer;
+
+class KotlinFrontendTest {
+
+    /**
+     * Test source file that is supposed to produce a complete set of tokens, i.e. all types of tokens.
+     */
+    private static final String COMPLETE_TEST_FILE = "Complete.kt";
+
+    /**
+     * Regular expression that describes lines consisting only of whitespace and optionally a line comment.
+     */
+    private static final String EMPTY_OR_SINGLE_LINE_COMMENT = "\\s*(//.*|/\\*.*\\*/)?";
+
+    /**
+     * Regular expression that describes lines containing the start of a multiline comment and no code before it.
+     */
+    private static final String DELIMITED_COMMENT_START = "\\s*/\\*(?:(?!\\*/).)*$";
+
+    /**
+     * Regular expression that describes lines containing the end of a multiline comment and no more code after that.
+     */
+    private static final String DELIMITED_COMMENT_END = ".*\\*/\\s*$";
+    private static final String NOT_SET_STRING = "";
+    private static final int NOT_SET = -1;
+
+    private final Logger logger = LoggerFactory.getLogger("Kotlin frontend test");
+    private final String[] testFiles = new String[] {COMPLETE_TEST_FILE, "Game.kt"};
+    private final File testFileLocation = Path.of("src", "test", "resources", "de", "jplag", "kotlin").toFile();
+    private Language language;
+
+    @BeforeEach
+    void setup() {
+        TestErrorConsumer consumer = new TestErrorConsumer();
+        language = new Language(consumer);
+    }
+
+    @Test
+    void parseTestFiles() {
+        for (String fileName : testFiles) {
+            TokenList tokens = language.parse(testFileLocation, new String[] {fileName});
+            String output = TokenPrinter.printTokens(tokens, testFileLocation, List.of(fileName));
+            logger.info(output);
+
+            testSourceCoverage(fileName, tokens);
+            if (fileName.equals(COMPLETE_TEST_FILE)) {
+                testTokenCoverage(tokens, fileName);
+            }
+
+        }
+    }
+
+    /**
+     * Confirms that every type of KotlinToken has a Sting representation associated to it.
+     */
+    @Test
+    void testTokenToString() {
+        var missingTokens = IntStream.range(0, language.numberOfTokens())
+                .mapToObj(type -> new KotlinToken(type, NOT_SET_STRING, NOT_SET, NOT_SET, NOT_SET))
+                .filter(token -> token.type2string().contains("UNKNOWN")).toList();
+
+        if (!missingTokens.isEmpty()) {
+            var typeList = missingTokens.stream().map(Token::getType).map(Object::toString).collect(Collectors.joining(", "));
+            fail("Found token types with no string representation: %s".formatted(typeList));
+        }
+
+    }
+
+    /**
+     * Confirms that the code is covered to a basic extent, i.e. each line of code contains at least one token.
+     * @param fileName a code sample file name
+     * @param tokens the TokenList generated from the sample
+     */
+    private void testSourceCoverage(String fileName, TokenList tokens) {
+        File testFile = new File(testFileLocation, fileName);
+
+        try {
+            List<String> lines = Files.readAllLines(testFile.toPath());
+
+            // All lines that contain code
+            var codeLines = getCodeLines(lines);
+            // All lines that contain token
+            var tokenLines = IntStream.range(0, tokens.size()).mapToObj(tokens::getToken).mapToInt(Token::getLine).distinct().toArray();
+
+            if (codeLines.length > tokenLines.length) {
+                var diffLine = IntStream.range(0, codeLines.length)
+                        .dropWhile(lineIdx -> lineIdx < tokenLines.length && codeLines[lineIdx] == tokenLines[lineIdx]).findFirst();
+                diffLine.ifPresent(
+                        lineIdx -> fail("Line %d of file '%s' is not represented in the token list.".formatted(codeLines[lineIdx], fileName)));
+            }
+            assertArrayEquals(codeLines, tokenLines);
+        } catch (IOException exception) {
+            logger.info("Error while reading test file %s".formatted(fileName), exception);
+            fail();
+        }
+    }
+
+    /**
+     * Gets the line numbers of lines containing actual code, omitting empty lines and comment lines.
+     * @param lines lines of a code file
+     * @return an array of the line numbers of code lines
+     */
+    private int[] getCodeLines(List<String> lines) {
+        // This boxed boolean can be accessed from within the lambda method below
+        var state = new Object() {
+            boolean insideComment = false;
+        };
+
+        var codeLines = IntStream.rangeClosed(1, lines.size()).sequential().filter(idx -> {
+            String line = lines.get(idx - 1);
+            if (line.matches(EMPTY_OR_SINGLE_LINE_COMMENT)) {
+                return false;
+            } else if (line.matches(DELIMITED_COMMENT_START)) {
+                state.insideComment = true;
+                return false;
+            } else if (state.insideComment) {
+                // This fails if code follows after '*/'. If the code is formatted well, this should not happen.
+                if (line.matches(DELIMITED_COMMENT_END)) {
+                    state.insideComment = false;
+                }
+                return false;
+            }
+            return true;
+        });
+
+        return codeLines.toArray();
+
+    }
+
+    /**
+     * Confirms that all Token types are 'reachable' with a complete code example.
+     * @param tokens TokenList which is supposed to contain all types of tokens
+     * @param fileName The file name of the complete code example
+     */
+    private void testTokenCoverage(TokenList tokens, String fileName) {
+        var foundTokens = StreamSupport.stream(tokens.allTokens().spliterator(), true).mapToInt(Token::getType).sorted().distinct().toArray();
+        // Exclude SEPARATOR_TOKEN, as it does not occur
+        var allTokens = IntStream.range(0, language.numberOfTokens()).filter(i -> i != TokenConstants.SEPARATOR_TOKEN).toArray();
+
+        if (allTokens.length > foundTokens.length) {
+            var diffLine = IntStream.range(0, allTokens.length)
+                    .dropWhile(lineIdx -> lineIdx < foundTokens.length && allTokens[lineIdx] == foundTokens[lineIdx]).findFirst();
+            diffLine.ifPresent(lineIdx -> fail("Token type %s was not found in the complete code example '%s'."
+                    .formatted(new KotlinToken(allTokens[lineIdx], fileName, -1, -1, -1).type2string(), fileName)));
+        }
+        assertArrayEquals(allTokens, foundTokens);
+    }
+
+}

+ 97 - 0
jplag.frontend.kotlin/src/test/resources/de/jplag/kotlin/Complete.kt

@@ -0,0 +1,97 @@
+package de.jplag.kotlin
+
+import kotlin.io.println
+
+/**
+ * A file that should produce a complete set of [KotlinToken]s when parsed by the JPlag Kotlin frontend.
+ * Other than that, there is no inherent meaning to the code, and it is obviously full of redundancies and less than perfect stylistic choices.
+ */
+class Complete() {
+
+    object Inner {
+        var accessCount: Int = 0
+            get() = field
+            set(value) {
+                field = value
+            }
+
+        private var objectNumber: Int
+
+        init {
+            this.objectNumber = ++counter.count
+        }
+    }
+    companion object counter {
+        var count = 0;
+    }
+
+    fun get() : Inner {
+        Inner.accessCount = Inner.accessCount + 1;
+        return Inner
+    }
+
+    fun setCounter(value : Int) : Unit {
+        count = value
+    }
+
+    fun getAccessCount() : Int = Inner.accessCount
+
+}
+
+class Container<T>(value : T) : Any() {
+
+    private val value: T
+    init {
+        this.value = value;
+    }
+
+    fun isTheAnswer(): Boolean {
+        return this.value == 42
+    }
+}
+
+enum class Enumeration {
+    VALUE1, VALUE2, VALUE3;
+}
+
+fun main(args : Array<String>) {
+    val complete = Complete();
+    for (idx in 1..3) {
+        complete.get()
+    }
+    println(complete.getAccessCount())
+
+    var int : Int = 0;
+    loop@ while (true) {
+        val box = Container(int);
+        if (box.isTheAnswer()) {
+            println("Finally!")
+            break@loop
+        }
+        int = int + 2;
+    }
+
+    try {
+        do {
+            println(1/0)
+            continue
+        } while (false)
+    } catch (exception : ArithmeticException) {
+        println("This should not have happened.")
+    } finally {
+        println("Now, let's carry on.")
+    }
+
+    val weekday = "Friday"
+    val message = when (weekday) {
+        "Monday", "Tuesday" -> "Oh no :c"
+        "Saturday", "Sunday" -> "It's the weekend, yay :)"
+        else -> {
+            if (!Regex("[A-Z][a-z]*").matches(weekday)) {
+                throw IllegalArgumentException("$weekday is not a valid day of the week!")
+            } else "It's almost weekend. Stay strong :)"
+        }
+    }
+
+}
+

+ 47 - 0
jplag.frontend.kotlin/src/test/resources/de/jplag/kotlin/Game.kt

@@ -0,0 +1,47 @@
+/**
+ * A number-guessing game implemented in Kotlin.
+ */
+fun main() {
+    val upperLimit = 100
+
+    var candidates = (1..upperLimit).toList()
+
+    var filters = (1..upperLimit).map({ it -> listOf(greaterThanFilter(it), lessThanFilter(it), multipleOfFilter(it))}).flatten()
+    while (candidates.size > 1) {
+        filters = filters.sortedWith(FilterComparator(candidates))
+        val chosen = filters.get(0)
+        println("Is your number " + chosen + "?" )
+        val answer = readBoolean()
+    }
+
+}
+
+class Filter(desc: String, f: (Int) -> Boolean) {
+    val f = f;
+    val desc = desc;
+
+    fun apply(ns: List<Int>) = ns.filter(this.f)
+    fun rate(ns: List<Int>) = Math.abs(apply(ns).size - opposite().apply(ns).size)
+    fun opposite() = Filter("not " + this.desc, { m: Int -> !this.f(m) })
+    override fun toString() = this.desc;
+}
+
+class FilterComparator(ns: List<Int>) : Comparator<Filter> {
+    val ns = ns;
+
+    override fun compare(p0: Filter, p1: Filter) : Int = p0.rate(ns) - p1.rate(ns)
+}
+
+fun greaterThanFilter(n: Int) : Filter = Filter("greater than " + n, { m: Int -> m > n })
+fun lessThanFilter(n: Int) : Filter = Filter("less than " + n, { m: Int -> m < n })
+
+fun multipleOfFilter(n: Int) : Filter = Filter("a multiple of "  + n, { m: Int -> m % n == 0 })
+
+fun readBoolean() : Boolean {
+    var input = readLine()!!.toString();
+    while (!Regex("yYnN").matches(input)) {
+        println("Hmm, try again.")
+        input = readLine().toString();
+    }
+    return Regex("yY").matches(input)
+}

+ 1 - 0
jplag.frontend.rlang/pom.xml

@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
+
     <parent>
         <groupId>de.jplag</groupId>
         <artifactId>aggregator</artifactId>

+ 5 - 4
jplag.frontend.rlang/src/main/java/de/jplag/rlang/Language.java

@@ -10,9 +10,10 @@ import de.jplag.TokenList;
  */
 public class Language implements de.jplag.Language {
 
-    public static final String NAME = "R Parser";
-    public static final String SHORT_NAME = "R";
-    public static final int DEFAULT_MIN_TOKEN_MATCH = 8;
+    private static final String NAME = "R Parser";
+    private static final String SHORT_NAME = "R";
+    private static final int DEFAULT_MIN_TOKEN_MATCH = 8;
+    private static final String[] FILE_EXTENSION = {".R", ".r"};
     private final RParserAdapter parserAdapter;
 
     public Language(ErrorConsumer consumer) {
@@ -21,7 +22,7 @@ public class Language implements de.jplag.Language {
 
     @Override
     public String[] suffixes() {
-        return new String[] {".R", ".r"};
+        return FILE_EXTENSION;
     }
 
     @Override

+ 4 - 0
jplag/pom.xml

@@ -53,6 +53,10 @@
             <groupId>de.jplag</groupId>
             <artifactId>golang</artifactId>
         </dependency>
+        <dependency>
+            <groupId>de.jplag</groupId>
+            <artifactId>kotlin</artifactId>
+        </dependency>
         <dependency>
             <groupId>de.jplag</groupId>
             <artifactId>scheme</artifactId>

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

@@ -96,7 +96,7 @@ public class JPlag {
         JPlagResult result = comparisonStrategy.compareSubmissions(submissionSet);
         errorCollector.print("\nTotal time for comparing submissions: " + TimeUtil.formatDuration(result.getDuration()), null);
 
-        result.setClusteringResult(ClusteringFactory.getClusterings(result.getComparisons(), options.getClusteringOptions()));
+        result.setClusteringResult(ClusteringFactory.getClusterings(result.getAllComparisons(), options.getClusteringOptions()));
 
         return result;
     }

+ 27 - 9
jplag/src/main/java/de/jplag/JPlagResult.java

@@ -1,9 +1,11 @@
 package de.jplag;
 
 import java.util.List;
+import java.util.function.Function;
 
 import de.jplag.clustering.ClusteringResult;
 import de.jplag.options.JPlagOptions;
+import de.jplag.options.SimilarityMetric;
 
 /**
  * Encapsulates the results of a comparison of a set of source code submissions.
@@ -21,6 +23,7 @@ public class JPlagResult {
     private final int[] similarityDistribution; // 10-element array representing the similarity distribution of the detected matches.
 
     private List<ClusteringResult<Submission>> clusteringResult;
+    private final int SIMILARITY_DISTRIBUTION_SIZE = 10;
 
     public JPlagResult(List<JPlagComparison> comparisons, SubmissionSet submissions, long durationInMillis, JPlagOptions options) {
         this.comparisons = comparisons;
@@ -47,7 +50,7 @@ public class JPlagResult {
     /**
      * @return a list of all comparisons sorted by percentage (descending)
      */
-    public List<JPlagComparison> getComparisons() {
+    public List<JPlagComparison> getAllComparisons() {
         return comparisons;
     }
 
@@ -93,22 +96,34 @@ public class JPlagResult {
     }
 
     /**
-     * Returns the similarity distribution of detected matches in a 10-element array. Each entry represents the absolute
-     * frequency of matches whose similarity lies within the respective interval. Intervals: 0: [0% - 10%), 1: [10% - 20%),
-     * 2: [20% - 30%), ..., 9: [90% - 100%]
+     * For the {@link SimilarityMetric} JPlag was run with, this returns the similarity distribution of detected matches in
+     * a 10-element array. Each entry represents the absolute frequency of matches whose similarity lies within the
+     * respective interval. Intervals: 0: [0% - 10%), 1: [10% - 20%), 2: [20% - 30%), ..., 9: [90% - 100%]
      * @return the similarity distribution array.
      */
     public int[] getSimilarityDistribution() {
         return similarityDistribution;
     }
 
+    /**
+     * For the {@link SimilarityMetric#MAX} that is built in to every {@link JPlagComparison}, this returns the similarity
+     * distribution of detected matches in a 10-element array. Each entry represents the absolute frequency of matches whose
+     * similarity lies within the respective interval. Intervals: 0: [0% - 10%), 1: [10% - 20%), 2: [20% - 30%), ..., 9:
+     * [90% - 100%]
+     * @return the similarity distribution array. When JPlag was run with the {@link SimilarityMetric#MAX}, this will return
+     * the same distribution as {@link JPlagResult#getSimilarityDistribution()}
+     */
+    public int[] getMaxSimilarityDistribution() {
+        return calculateDistributionFor(comparisons, (JPlagComparison::maximalSimilarity));
+    }
+
     public List<ClusteringResult<Submission>> getClusteringResult() {
         return this.clusteringResult;
     }
 
     @Override
     public String toString() {
-        return String.format("JPlagResult { comparisons: %d, duration: %d ms, language: %s, submissions: %d }", getComparisons().size(),
+        return String.format("JPlagResult { comparisons: %d, duration: %d ms, language: %s, submissions: %d }", getAllComparisons().size(),
                 getDuration(), getOptions().getLanguageOption(), submissions.numberOfSubmissions());
     }
 
@@ -116,11 +131,14 @@ public class JPlagResult {
      * Note: Before, comparisons with a similarity below the given threshold were also included in the similarity matrix.
      */
     private int[] calculateSimilarityDistribution(List<JPlagComparison> comparisons) {
-        int[] similarityDistribution = new int[10];
-
-        comparisons.stream().map(JPlagComparison::similarity).map(percent -> percent / 10).map(Float::intValue).map(index -> index == 10 ? 9 : index)
-                .forEach(index -> similarityDistribution[index]++);
+        return calculateDistributionFor(comparisons, (JPlagComparison::similarity));
+    }
 
+    private int[] calculateDistributionFor(List<JPlagComparison> comparisons, Function<JPlagComparison, Float> similarityExtractor) {
+        int[] similarityDistribution = new int[SIMILARITY_DISTRIBUTION_SIZE];
+        comparisons.stream().map(similarityExtractor).map(percent -> percent / SIMILARITY_DISTRIBUTION_SIZE).map(Float::intValue)
+                .map(index -> index == SIMILARITY_DISTRIBUTION_SIZE ? SIMILARITY_DISTRIBUTION_SIZE - 1 : index)
+                .forEach(index -> similarityDistribution[SIMILARITY_DISTRIBUTION_SIZE - 1 - index]++);
         return similarityDistribution;
     }
 }

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

@@ -13,8 +13,9 @@ public enum LanguageOption {
     PYTHON_3("python3", "de.jplag.python3.Language"),
     C_CPP("cpp", "de.jplag.cpp.Language"),
     C_SHARP("csharp", "de.jplag.csharp.Language"),
-    R_LANG("rlang", "de.jplag.rlang.Language"),
     GO_LANG("golang", "de.jplag.golang.Language"),
+    KOTLIN("kotlin", "de.jplag.kotlin.Language"),
+    R_LANG("rlang", "de.jplag.rlang.Language"),
     CHAR("char", "de.jplag.chars.Language"),
     TEXT("text", "de.jplag.text.Language"),
     SCHEME("scheme", "de.jplag.scheme.Language");

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

@@ -31,8 +31,8 @@ public class JsonFactory {
     public static List<String> getJsonStrings(JPlagReport jPlagReport) {
         List<String> jsonReports = new ArrayList<>();
         try {
-            jsonReports.add(mapper.writeValueAsString(jPlagReport.getOverviewReport()));
-            for (ComparisonReport comparisonReport : jPlagReport.getComparisons()) {
+            jsonReports.add(mapper.writeValueAsString(jPlagReport.overviewReport()));
+            for (ComparisonReport comparisonReport : jPlagReport.comparisons()) {
                 jsonReports.add(mapper.writeValueAsString(comparisonReport));
             }
         } catch (JsonProcessingException e) {
@@ -48,8 +48,8 @@ public class JsonFactory {
     public static boolean saveJsonFiles(JPlagReport jPlagReport, String folderPath) {
         try {
             ObjectMapper mapper = new ObjectMapper();
-            mapper.writeValue(Path.of(folderPath, "overview.json").toFile(), jPlagReport.getOverviewReport());
-            for (ComparisonReport report : jPlagReport.getComparisons()) {
+            mapper.writeValue(Path.of(folderPath, "overview.json").toFile(), jPlagReport.overviewReport());
+            for (ComparisonReport report : jPlagReport.comparisons()) {
                 String name = report.getFirstSubmissionId().concat("-").concat(report.getSecondSubmissionId()).concat(".json");
                 mapper.writeValue(Path.of(folderPath, name).toFile(), report);
             }

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

@@ -13,6 +13,7 @@ import org.slf4j.LoggerFactory;
 
 import de.jplag.*;
 import de.jplag.reporting.reportobject.mapper.ClusteringResultMapper;
+import de.jplag.reporting.reportobject.mapper.MetricMapper;
 import de.jplag.reporting.reportobject.model.*;
 import de.jplag.reporting.reportobject.model.Match;
 
@@ -23,6 +24,7 @@ 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();
 
     /**
      * Converts a JPlagResult to a JPlagReport.
@@ -38,8 +40,7 @@ public class ReportObjectFactory {
      * Generates an Overview DTO of a JPlagResult.
      */
     private static OverviewReport generateOverviewReport(JPlagResult result) {
-        int numberOfComparisons = result.getOptions().getMaximumNumberOfComparisons();
-        List<JPlagComparison> comparisons = result.getComparisons(numberOfComparisons);
+        List<JPlagComparison> comparisons = getComparisons(result);
         OverviewReport overviewReport = new OverviewReport();
 
         // TODO: Consider to treat entries that were checked differently from old entries with prior work.
@@ -72,8 +73,7 @@ public class ReportObjectFactory {
      */
     private static List<ComparisonReport> generateComparisonReports(JPlagResult result) {
         List<ComparisonReport> comparisons = new ArrayList<>();
-        int numberOfComparisons = result.getOptions().getMaximumNumberOfComparisons();
-        result.getComparisons(numberOfComparisons).forEach(comparison -> comparisons.add( //
+        getComparisons(result).forEach(comparison -> comparisons.add( //
                 new ComparisonReport(comparison.getFirstSubmission().getName(), //
                         comparison.getSecondSubmission().getName(), //
                         comparison.similarity(), //
@@ -84,6 +84,11 @@ public class ReportObjectFactory {
         return comparisons;
     }
 
+    private static List<JPlagComparison> getComparisons(JPlagResult result) {
+        int numberOfComparisons = result.getOptions().getMaximumNumberOfComparisons();
+        return result.getComparisons(numberOfComparisons);
+    }
+
     private static List<Match> convertMatchesToReportMatches(JPlagResult result, JPlagComparison comparison, List<de.jplag.Match> matches) {
         return matches.stream().map(match -> convertMatchToReportMatch(comparison, match, result.getOptions().getLanguage().usesIndex()))
                 .collect(Collectors.toList());
@@ -113,29 +118,13 @@ public class ReportObjectFactory {
         return names;
     }
 
-    // Currently, only one metric can be obtained.
-
     /**
-     * Gets the used metric in a JPlag comparison.
+     * Gets the used metrics in a JPlag comparison. As Max Metric is included in every JPlag run, this always include Max
+     * Metric.
      * @return A list contains Metric DTOs.
      */
     private static List<Metric> getMetrics(JPlagResult result) {
-        List<Metric> metrics = new ArrayList<>();
-        metrics.add(new Metric(result.getOptions().getSimilarityMetric().name(), result.getOptions().getSimilarityThreshold(),
-                intArrayToList(result.getSimilarityDistribution()), getTopComparisons(result.getComparisons())));
-        return metrics;
-    }
-
-    /**
-     * Converts JPlagComparison to a DTO for displaying only comparisons. See
-     * {@link #generateComparisonReports(JPlagResult)} for a more detailed representation of a comparison.
-     * @return List containing TopComparison DTOs.
-     */
-    private static List<TopComparison> getTopComparisons(List<JPlagComparison> comparisons) {
-        List<TopComparison> topComparisons = new ArrayList<>();
-        comparisons.forEach(comparison -> topComparisons.add(
-                new TopComparison(comparison.getFirstSubmission().getName(), comparison.getSecondSubmission().getName(), comparison.similarity())));
-        return topComparisons;
+        return List.of(metricMapper.getAverageMetric(result), metricMapper.getMaxMetric(result));
     }
 
     /**
@@ -189,7 +178,4 @@ public class ReportObjectFactory {
         return dateFormat.format(date);
     }
 
-    private static List<Integer> intArrayToList(int[] array) {
-        return Arrays.stream(array).boxed().collect(Collectors.toList());
-    }
 }

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

@@ -0,0 +1,54 @@
+package de.jplag.reporting.reportobject.mapper;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import de.jplag.JPlagComparison;
+import de.jplag.JPlagResult;
+import de.jplag.Messages;
+import de.jplag.options.SimilarityMetric;
+import de.jplag.reporting.reportobject.model.Metric;
+import de.jplag.reporting.reportobject.model.TopComparison;
+
+/**
+ * Extracts and maps metrics from the JPlagResult to the corresponding JSON DTO
+ */
+public class MetricMapper {
+    public Metric getAverageMetric(JPlagResult result) {
+        return new Metric(SimilarityMetric.AVG.name(), intArrayToList(result.getSimilarityDistribution()), getTopComparisons(getComparisons(result)),
+                Messages.getString("SimilarityMetric.Avg.Description"));
+    }
+
+    public Metric getMaxMetric(JPlagResult result) {
+        return new Metric(SimilarityMetric.MAX.name(), intArrayToList(result.getMaxSimilarityDistribution()),
+                getMaxSimilarityTopComparisons(getComparisons(result)), Messages.getString("SimilarityMetric.Max.Description"));
+    }
+
+    private List<JPlagComparison> getComparisons(JPlagResult result) {
+        int maxNumberOfComparisons = result.getOptions().getMaximumNumberOfComparisons();
+        return result.getComparisons(maxNumberOfComparisons);
+    }
+
+    private static 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) {
+        List<TopComparison> topComparisons = new ArrayList<>();
+        comparisons.forEach(comparison -> topComparisons.add(new TopComparison(comparison.getFirstSubmission().getName(),
+                comparison.getSecondSubmission().getName(), similarityExtractor.apply(comparison))));
+        return topComparisons;
+    }
+
+    private static List<TopComparison> getTopComparisons(List<JPlagComparison> comparisons) {
+        return getTopComparisons(comparisons, JPlagComparison::similarity);
+    }
+
+    private static List<TopComparison> getMaxSimilarityTopComparisons(List<JPlagComparison> comparisons) {
+        return getTopComparisons(comparisons, JPlagComparison::maximalSimilarity);
+    }
+
+}

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

@@ -2,20 +2,9 @@ package de.jplag.reporting.reportobject.model;
 
 import java.util.List;
 
-public class JPlagReport {
-    private final OverviewReport overviewReport;
-    private final List<ComparisonReport> comparisons;
-
+public record JPlagReport(OverviewReport overviewReport, List<ComparisonReport> comparisons) {
     public JPlagReport(OverviewReport overviewReport, List<ComparisonReport> comparisons) {
         this.overviewReport = overviewReport;
         this.comparisons = List.copyOf(comparisons);
     }
-
-    public OverviewReport getOverviewReport() {
-        return overviewReport;
-    }
-
-    public List<ComparisonReport> getComparisons() {
-        return comparisons;
-    }
 }

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

@@ -4,40 +4,6 @@ import java.util.List;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 
-public class Metric {
-
-    @JsonProperty("name")
-    private final String name;
-
-    @JsonProperty("threshold")
-    private final float threshold;
-
-    @JsonProperty("distribution")
-    private final List<Integer> distribution;
-
-    @JsonProperty("topComparisons")
-    private final List<TopComparison> topComparisons;
-
-    public Metric(String name, float threshold, List<Integer> distribution, List<TopComparison> topComparisons) {
-        this.name = name;
-        this.threshold = threshold;
-        this.distribution = List.copyOf(distribution);
-        this.topComparisons = List.copyOf(topComparisons);
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public float getThreshold() {
-        return threshold;
-    }
-
-    public List<Integer> getDistribution() {
-        return distribution;
-    }
-
-    public List<TopComparison> getTopComparisons() {
-        return topComparisons;
-    }
+public record Metric(@JsonProperty("name") String name, @JsonProperty("distribution") List<Integer> distribution,
+        @JsonProperty("topComparisons") List<TopComparison> topComparisons, @JsonProperty String description) {
 }

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

@@ -2,33 +2,7 @@ package de.jplag.reporting.reportobject.model;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 
-public class TopComparison {
-
-    @JsonProperty("first_submission")
-    private final String firstSubmission;
-
-    @JsonProperty("second_submission")
-    private final String secondSubmission;
-
-    @JsonProperty("match_percentage")
-    private final float matchPercentage;
-
-    public TopComparison(String firstSubmission, String secondSubmission, float matchPercentage) {
-        this.firstSubmission = firstSubmission;
-        this.secondSubmission = secondSubmission;
-        this.matchPercentage = matchPercentage;
-    }
-
-    public String getFirstSubmission() {
-        return firstSubmission;
-    }
-
-    public String getSecondSubmission() {
-        return secondSubmission;
-    }
-
-    public float getMatchPercentage() {
-        return matchPercentage;
-    }
+public record TopComparison(@JsonProperty("first_submission") String firstSubmission, @JsonProperty("second_submission") String secondSubmission,
+        @JsonProperty("match_percentage") float matchPercentage) {
 
 }

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

@@ -26,4 +26,6 @@ CommandLineArgument.ClusterAgglomerativeInterClusterSimilarity=How to measure th
 CommandLineArgument.ClusterPreprocessingNone=Do not use any preprocessing before clustering. Not recommended for spectral clustering.
 CommandLineArgument.ClusterPreprocessingCdf=Before clustering, the value of the cumulative distribution function of all similarities is estimated. The similarities are multiplied with these estimates. This has the effect of supressing similarities that are low compared to other similarities.
 CommandLineArgument.ClusterPreprocessingPercentile=Any similarity smaller than the given percentile will be suppressed during clustering.
-CommandLineArgument.ClusterPreprocessingThreshold=Any similarity smaller than the given threshold value will be suppressed during clustering.
+CommandLineArgument.ClusterPreprocessingThreshold=Any similarity smaller than the given threshold value will be suppressed during clustering.
+SimilarityMetric.Avg.Description = Average of both program coverages. This is the default similarity which works in most cases: Matches with a high average similarity indicate that the programs work in a very similar way.
+SimilarityMetric.Max.Description = Maximum of both program coverages. This ranking is especially useful if the programs are very different in size. This can happen when dead code was inserted to disguise the origin of the plagiarized program.

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

@@ -57,3 +57,6 @@ AllMatches.Distribution=Verteilung
 AllMatches.Length=L&auml;nge
 AllMatches.Number_of_matches=Anzahl der &Uuml;bereinstimmungen
 AllMatches.Basecode=Referenz&uuml;bereinstimmung
+
+SimilarityMetric.Avg.Description = Average of both program coverages. This is the default similarity which works in most cases: Matches with a high average similarity indicate that the programs work in a very similar way.
+SimilarityMetric.Max.Description = Maximum of both program coverages. This ranking is especially useful if the programs are very different in size. This can happen when dead code was inserted to disguise the origin of the plagiarized program.

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

@@ -35,6 +35,7 @@ Report.HELP=HELP
 Report.Distribution=Distribution
 Report.Token_Distribution=Token Distribution
 
+
 Clusters.MIN_single_link=MIN, single link
 Clusters.AVR_group_average=AVR, group average
 Clusters.MAX_complete_link=MAX, complete link
@@ -57,3 +58,6 @@ AllMatches.Distribution=Distribution
 AllMatches.Length=Length
 AllMatches.Number_of_matches=No of matches
 AllMatches.Basecode=Basecode
+
+SimilarityMetric.Avg.Description = Average of both program coverages. This is the default similarity which works in most cases: Matches with a high average similarity indicate that the programs work in a very similar way.
+SimilarityMetric.Max.Description = Maximum of both program coverages. This ranking is especially useful if the programs are very different in size. This can happen when dead code was inserted to disguise the origin of the plagiarized program.

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

@@ -57,3 +57,6 @@ AllMatches.Distribution=Distribuci
 AllMatches.Length=Longitud
 AllMatches.Number_of_matches=No de coincidencias
 AllMatches.Basecode=Código Base
+
+SimilarityMetric.Avg.Description = Average of both program coverages. This is the default similarity which works in most cases: Matches with a high average similarity indicate that the programs work in a very similar way.
+SimilarityMetric.Max.Description = Maximum of both program coverages. This ranking is especially useful if the programs are very different in size. This can happen when dead code was inserted to disguise the origin of the plagiarized program.

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

@@ -57,3 +57,6 @@ AllMatches.Distribution=Distribution
 AllMatches.Length=Length
 AllMatches.Number_of_matches=No of matches
 AllMatches.Basecode=Code source de reference
+
+SimilarityMetric.Avg.Description = Average of both program coverages. This is the default similarity which works in most cases: Matches with a high average similarity indicate that the programs work in a very similar way.
+SimilarityMetric.Max.Description = Maximum of both program coverages. This ranking is especially useful if the programs are very different in size. This can happen when dead code was inserted to disguise the origin of the plagiarized program.

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

@@ -57,3 +57,6 @@ AllMatches.Distribution=Distribution
 AllMatches.Length=Length
 AllMatches.Number_of_matches=No of matches
 AllMatches.Basecode=Basecode
+
+SimilarityMetric.Avg.Description = Average of both program coverages. This is the default similarity which works in most cases: Matches with a high average similarity indicate that the programs work in a very similar way.
+SimilarityMetric.Max.Description = Maximum of both program coverages. This ranking is especially useful if the programs are very different in size. This can happen when dead code was inserted to disguise the origin of the plagiarized program.

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

@@ -39,10 +39,10 @@ public class BaseCodeTest extends TestBase {
 
     private void verifyResults(JPlagResult result) {
         assertEquals(2, result.getNumberOfSubmissions());
-        assertEquals(1, result.getComparisons().size());
-        assertEquals(1, result.getComparisons().get(0).getMatches().size());
-        assertEquals(1, result.getSimilarityDistribution()[8]);
-        assertEquals(85f, result.getComparisons().get(0).similarity(), DELTA);
+        assertEquals(1, result.getAllComparisons().size());
+        assertEquals(1, result.getAllComparisons().get(0).getMatches().size());
+        assertEquals(1, result.getSimilarityDistribution()[1]);
+        assertEquals(85f, result.getAllComparisons().get(0).similarity(), DELTA);
     }
 
     @Test
@@ -52,7 +52,7 @@ public class BaseCodeTest extends TestBase {
     }
 
     @Test
-    void testInvalidRoot() throws ExitException {
+    void testInvalidRoot() {
         assertThrows(RootDirectoryException.class, () -> runJPlag("basecode", it -> it.setSubmissionDirectories(List.of("WrongRoot"))));
     }
 

+ 2 - 2
jplag/src/test/java/de/jplag/NewJavaFeaturesTest.java

@@ -23,10 +23,10 @@ public class NewJavaFeaturesTest extends TestBase {
         for (Submission submission : result.getSubmissions().getSubmissions()) {
             assertEquals(6, submission.getFiles().size(), String.format(CHANGE_MESSAGE, "Files"));
         }
-        assertEquals(1, result.getComparisons().size(), String.format(CHANGE_MESSAGE, "Comparisons"));
+        assertEquals(1, result.getAllComparisons().size(), String.format(CHANGE_MESSAGE, "Comparisons"));
 
         // Check similarity and number of matches:
-        var comparison = result.getComparisons().get(0);
+        var comparison = result.getAllComparisons().get(0);
         assertEquals(EXPECTED_SIMILARITY, comparison.similarity(), DELTA);
         assertEquals(EXPECTED_MATCHES, comparison.getMatches().size());
     }

+ 13 - 15
jplag/src/test/java/de/jplag/NormalComparisonTest.java

@@ -21,10 +21,10 @@ class NormalComparisonTest extends TestBase {
         JPlagResult result = runJPlagWithDefaultOptions("SimpleDuplicate");
 
         assertEquals(2, result.getNumberOfSubmissions());
-        assertEquals(1, result.getComparisons().size());
-        assertEquals(1, result.getComparisons().get(0).getMatches().size());
-        assertEquals(1, result.getSimilarityDistribution()[6]);
-        assertEquals(62.07f, result.getComparisons().get(0).similarity(), 0.1f);
+        assertEquals(1, result.getAllComparisons().size());
+        assertEquals(1, result.getAllComparisons().get(0).getMatches().size());
+        assertEquals(1, result.getSimilarityDistribution()[3]);
+        assertEquals(62.07f, result.getAllComparisons().get(0).similarity(), 0.1f);
     }
 
     /**
@@ -35,11 +35,9 @@ class NormalComparisonTest extends TestBase {
         JPlagResult result = runJPlagWithDefaultOptions("NoDuplicate");
 
         assertEquals(3, result.getNumberOfSubmissions());
-        assertEquals(3, result.getComparisons().size());
+        assertEquals(3, result.getAllComparisons().size());
 
-        result.getComparisons().forEach(comparison -> {
-            assertEquals(0f, comparison.similarity(), 0.1f);
-        });
+        result.getAllComparisons().forEach(comparison -> assertEquals(0f, comparison.similarity(), 0.1f));
     }
 
     /**
@@ -52,10 +50,10 @@ class NormalComparisonTest extends TestBase {
         JPlagResult result = runJPlagWithDefaultOptions("PartialPlagiarism");
 
         assertEquals(5, result.getNumberOfSubmissions());
-        assertEquals(10, result.getComparisons().size());
+        assertEquals(10, result.getAllComparisons().size());
 
         // All comparisons with E shall have no matches
-        result.getComparisons().stream()
+        result.getAllComparisons().stream()
                 .filter(comparison -> comparison.getSecondSubmission().getName().equals("E") || comparison.getFirstSubmission().getName().equals("E"))
                 .forEach(comparison -> assertEquals(0f, comparison.similarity(), DELTA));
 
@@ -81,7 +79,7 @@ class NormalComparisonTest extends TestBase {
     }
 
     private Optional<JPlagComparison> getSelectedComparison(JPlagResult result, String nameA, String nameB) {
-        return result.getComparisons().stream().filter(
+        return result.getAllComparisons().stream().filter(
                 comparison -> comparison.getFirstSubmission().getName().equals(nameA) && comparison.getSecondSubmission().getName().equals(nameB)
                         || comparison.getFirstSubmission().getName().equals(nameB) && comparison.getSecondSubmission().getName().equals(nameA))
                 .findFirst();
@@ -112,7 +110,7 @@ class NormalComparisonTest extends TestBase {
     }
 
     @Test
-    public void testMultiRootDirBasecodeName() throws ExitException {
+    public void testMultiRootDirBasecodeName() {
         List<String> paths = List.of(getBasePath("basecode"), getBasePath("SimpleDuplicate"));
         String basecodePath = "base"; // Should *not* find basecode/base
         assertThrows(BasecodeException.class, () -> runJPlag(paths, it -> it.setBaseCodeSubmissionName(basecodePath)));
@@ -125,7 +123,7 @@ class NormalComparisonTest extends TestBase {
         JPlagResult result = runJPlag(newDirectories, oldDirectories, it -> {
         });
         int numberOfExpectedComparison = 1 + 3 * 2;
-        assertEquals(numberOfExpectedComparison, result.getComparisons().size());
+        assertEquals(numberOfExpectedComparison, result.getAllComparisons().size());
     }
 
     @Test
@@ -135,7 +133,7 @@ class NormalComparisonTest extends TestBase {
         JPlagResult result = runJPlag(newDirectories, oldDirectories, it -> {
         });
         int numberOfExpectedComparison = 1;
-        assertEquals(numberOfExpectedComparison, result.getComparisons().size());
+        assertEquals(numberOfExpectedComparison, result.getAllComparisons().size());
     }
 
     @Test
@@ -145,6 +143,6 @@ class NormalComparisonTest extends TestBase {
         List<String> oldDirectories = List.of(getBasePath("basecode")); // 3 - 1 submissions
         JPlagResult result = runJPlag(newDirectories, oldDirectories, it -> it.setBaseCodeSubmissionName(basecodePath));
         int numberOfExpectedComparison = 1 + 2 * 2;
-        assertEquals(numberOfExpectedComparison, result.getComparisons().size());
+        assertEquals(numberOfExpectedComparison, result.getAllComparisons().size());
     }
 }

+ 9 - 11
jplag/src/test/java/de/jplag/ParallelComparisonTest.java

@@ -25,10 +25,10 @@ public class ParallelComparisonTest extends TestBase {
         JPlagResult result = runJPlag("SimpleDuplicate", it -> it.setComparisonMode(PARALLEL));
 
         assertEquals(2, result.getNumberOfSubmissions());
-        assertEquals(1, result.getComparisons().size());
-        assertEquals(1, result.getComparisons().get(0).getMatches().size());
-        assertEquals(1, result.getSimilarityDistribution()[6]);
-        assertEquals(62.07f, result.getComparisons().get(0).similarity(), DELTA);
+        assertEquals(1, result.getAllComparisons().size());
+        assertEquals(1, result.getAllComparisons().get(0).getMatches().size());
+        assertEquals(1, result.getSimilarityDistribution()[3]);
+        assertEquals(62.07f, result.getAllComparisons().get(0).similarity(), DELTA);
     }
 
     /**
@@ -39,11 +39,9 @@ public class ParallelComparisonTest extends TestBase {
         JPlagResult result = runJPlag("NoDuplicate", it -> it.setComparisonMode(PARALLEL));
 
         assertEquals(3, result.getNumberOfSubmissions());
-        assertEquals(3, result.getComparisons().size());
+        assertEquals(3, result.getAllComparisons().size());
 
-        result.getComparisons().forEach(comparison -> {
-            assertEquals(0f, comparison.similarity(), DELTA);
-        });
+        result.getAllComparisons().forEach(comparison -> assertEquals(0f, comparison.similarity(), DELTA));
     }
 
     /**
@@ -56,10 +54,10 @@ public class ParallelComparisonTest extends TestBase {
         JPlagResult result = runJPlag("PartialPlagiarism", it -> it.setComparisonMode(PARALLEL));
 
         assertEquals(5, result.getNumberOfSubmissions());
-        assertEquals(10, result.getComparisons().size());
+        assertEquals(10, result.getAllComparisons().size());
 
         // All comparisons with E shall have no matches
-        result.getComparisons().stream()
+        result.getAllComparisons().stream()
                 .filter(comparison -> comparison.getSecondSubmission().getName().equals("E") || comparison.getFirstSubmission().getName().equals("E"))
                 .forEach(comparison -> assertEquals(0f, comparison.similarity(), DELTA));
 
@@ -85,7 +83,7 @@ public class ParallelComparisonTest extends TestBase {
     }
 
     private Optional<JPlagComparison> getSelectedComparison(JPlagResult result, String nameA, String nameB) {
-        return result.getComparisons().stream().filter(
+        return result.getAllComparisons().stream().filter(
                 comparison -> comparison.getFirstSubmission().getName().equals(nameA) && comparison.getSecondSubmission().getName().equals(nameB)
                         || comparison.getFirstSubmission().getName().equals(nameB) && comparison.getSecondSubmission().getName().equals(nameA))
                 .findFirst();

+ 2 - 3
jplag/src/test/java/de/jplag/clustering/ClusteringAdapterTest.java

@@ -23,7 +23,7 @@ public class ClusteringAdapterTest {
 
     @Test
     public void testClustering() {
-        List<Submission> submissions = IntStream.range(0, 4).mapToObj(x -> mock(Submission.class)).collect(Collectors.toList());
+        List<Submission> submissions = IntStream.range(0, 4).mapToObj(x -> mock(Submission.class)).toList();
         List<JPlagComparison> comparisons = new ArrayList<>(6);
         for (int i = 0; i < submissions.size(); i++) {
             for (int j = i + 1; j < submissions.size(); j++) {
@@ -38,8 +38,7 @@ public class ClusteringAdapterTest {
         GenericClusteringAlgorithm algorithm = mock(GenericClusteringAlgorithm.class);
         when(algorithm.cluster(any(RealMatrix.class))).then((InvocationOnMock invocation) -> {
             RealMatrix arg = invocation.getArgument(0);
-            Collection<Collection<Integer>> result = List.of(IntStream.range(0, arg.getRowDimension()).boxed().collect(Collectors.toList()));
-            return result;
+            return List.of(IntStream.range(0, arg.getRowDimension()).boxed().collect(Collectors.toList()));
         });
 
         ClusteringAdapter clustering = new ClusteringAdapter(comparisons, x -> 0.f);

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

@@ -0,0 +1,115 @@
+package de.jplag.reporting.reportobject.mapper;
+
+import static org.mockito.Mockito.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import de.jplag.JPlagComparison;
+import de.jplag.JPlagResult;
+import de.jplag.Submission;
+import de.jplag.options.JPlagOptions;
+import de.jplag.reporting.reportobject.model.TopComparison;
+
+public class MetricMapperTest {
+    private final MetricMapper metricMapper = new MetricMapper();
+
+    @Test
+    public void test_getAverageMetric() {
+        // given
+        JPlagResult jPlagResult = createJPlagResult(MockMetric.AVG, distribution(2, 3, 5, 7, 11, 13, 17, 19, 23, 29),
+                comparison(submission("1"), submission("2"), .7f), comparison(submission("3"), submission("4"), .3f));
+        // when
+        var result = metricMapper.getAverageMetric(jPlagResult);
+
+        // then
+        Assertions.assertEquals("AVG", result.name());
+        Assertions.assertIterableEquals(List.of(2, 3, 5, 7, 11, 13, 17, 19, 23, 29), result.distribution());
+        Assertions.assertEquals(List.of(new TopComparison("1", "2", .7f), new TopComparison("3", "4", .3f)), result.topComparisons());
+        Assertions.assertEquals(
+                "Average of both program coverages. This is the default similarity which"
+                        + " works in most cases: Matches with a high average similarity indicate that the programs work " + "in a very similar way.",
+                result.description());
+    }
+
+    @Test
+    public void test_getMaxMetric() {
+        // given
+        JPlagResult jPlagResult = createJPlagResult(MockMetric.MAX, distribution(2, 3, 5, 7, 11, 13, 17, 19, 23, 29),
+                comparison(submission("00"), submission("01"), .7f), comparison(submission("10"), submission("11"), .3f));
+        // when
+        var result = metricMapper.getMaxMetric(jPlagResult);
+
+        // then
+        Assertions.assertEquals("MAX", result.name());
+        Assertions.assertIterableEquals(List.of(2, 3, 5, 7, 11, 13, 17, 19, 23, 29), result.distribution());
+        Assertions.assertEquals(List.of(new TopComparison("00", "01", .7f), new TopComparison("10", "11", .3f)), result.topComparisons());
+        Assertions.assertEquals(
+                "Maximum of both program coverages. This ranking is especially useful if the programs are very "
+                        + "different in size. This can happen when dead code was inserted to disguise the origin of the plagiarized program.",
+                result.description());
+    }
+
+    private int[] distribution(int... numbers) {
+        return numbers;
+    }
+
+    private CreateSubmission submission(String name) {
+        return new CreateSubmission(name);
+    }
+
+    private Comparison comparison(CreateSubmission submission1, CreateSubmission submission2, float similarity) {
+        return new Comparison(submission1, submission2, similarity);
+    }
+
+    private JPlagResult createJPlagResult(MockMetric metricToMock, int[] distribution, Comparison... createComparisonsDto) {
+        JPlagResult jPlagResult = mock(JPlagResult.class);
+
+        if (metricToMock.equals(MockMetric.AVG)) {
+            doReturn(distribution).when(jPlagResult).getSimilarityDistribution();
+        } else if (metricToMock.equals(MockMetric.MAX)) {
+            doReturn(distribution).when(jPlagResult).getMaxSimilarityDistribution();
+
+        }
+
+        JPlagOptions options = mock(JPlagOptions.class);
+        doReturn(createComparisonsDto.length).when(options).getMaximumNumberOfComparisons();
+        doReturn(options).when(jPlagResult).getOptions();
+
+        List<JPlagComparison> comparisonList = new ArrayList<>();
+        for (Comparison comparisonDto : createComparisonsDto) {
+            Submission submission1 = mock(Submission.class);
+            doReturn(comparisonDto.submission1.name).when(submission1).getName();
+            Submission submission2 = mock(Submission.class);
+            doReturn(comparisonDto.submission2.name).when(submission2).getName();
+
+            JPlagComparison mockedComparison = mock(JPlagComparison.class);
+            doReturn(submission1).when(mockedComparison).getFirstSubmission();
+            doReturn(submission2).when(mockedComparison).getSecondSubmission();
+            if (metricToMock.equals(MockMetric.AVG)) {
+                doReturn(comparisonDto.similarity).when(mockedComparison).similarity();
+            } else if (metricToMock.equals(MockMetric.MAX)) {
+                doReturn(comparisonDto.similarity).when(mockedComparison).maximalSimilarity();
+            }
+            comparisonList.add(mockedComparison);
+        }
+
+        doReturn(comparisonList).when(jPlagResult).getComparisons(anyInt());
+        return jPlagResult;
+    }
+
+    private enum MockMetric {
+        MAX,
+        AVG
+    }
+
+    private record Comparison(CreateSubmission submission1, CreateSubmission submission2, float similarity) {
+    }
+
+    private record CreateSubmission(String name) {
+    }
+
+}

+ 11 - 5
jplag/src/test/java/de/jplag/special/TokenPrinterTest.java

@@ -28,7 +28,7 @@ public class TokenPrinterTest extends TestBase {
 
     @Disabled
     @Test
-    public void printCPPFiles() {
+    void printCPPFiles() {
         printSubmissions(options -> {
             options.setLanguageOption(LanguageOption.C_CPP);
             options.setMinimumTokenMatch(MIN_TOKEN_MATCH); // for printing also allow small files
@@ -37,26 +37,32 @@ public class TokenPrinterTest extends TestBase {
 
     @Disabled
     @Test
-    public void printJavaFiles() {
+    void printJavaFiles() {
         printSubmissions(options -> {
             options.setMinimumTokenMatch(MIN_TOKEN_MATCH); // for printing also allow small files
         });
     }
 
-    @Disabled
+    @Disabled("Not a meaningful test, used for designing the token set")
     @Test
-    public void printRLangFiles() {
+    void printRLangFiles() {
         printSubmissions(options -> {
             options.setLanguageOption(LanguageOption.R_LANG);
             options.setMinimumTokenMatch(MIN_TOKEN_MATCH); // for printing also allow small files
         });
     }
 
-    @Disabled("No meaningful test, just an aid to design frontend")
+    @Disabled("Not a meaningful test, used for designing the token set")
     @Test
     void printGoFiles() {
         printSubmissions(options -> options.setLanguageOption(LanguageOption.GO_LANG));
     }
+    
+    @Disabled("Not a meaningful test, used for designing the token set")
+    @Test
+    void printKotlinFiles() {
+        printSubmissions(options -> options.setLanguageOption(LanguageOption.KOTLIN));
+    }  
 
     private void printSubmissions(Consumer<JPlagOptions> optionsCustomization) {
         try {

+ 2 - 2
jplag/src/test/java/de/jplag/special/VolumeTest.java

@@ -51,10 +51,10 @@ public class VolumeTest extends TestBase {
 
         var csv = readCSVResults(String.format("%s/%s", this.getBasePath(), "matches_avg.csv"));
 
-        assertEquals(csv.size(), results.getComparisons().size());
+        assertEquals(csv.size(), results.getAllComparisons().size());
         System.out.println("Volume test size: " + csv.size());
 
-        results.getComparisons().forEach(result -> {
+        results.getAllComparisons().forEach(result -> {
             var key = result.getFirstSubmission().getName() + result.getSecondSubmission().getName();
 
             assertTrue(csv.containsKey(key));

+ 1 - 0
jplag/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker

@@ -0,0 +1 @@
+mock-maker-inline

+ 178 - 122
pom.xml

@@ -7,7 +7,8 @@
     <packaging>pom</packaging>
 
     <name>JPlag Plagiarism Detector</name>
-    <description>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.</description>
+    <description>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.</description>
     <url>http://jplag.de</url>
     <organization>
         <name>Karlsruhe Institute of Technology (KIT), KASTEL – Institute of Information Security and Dependability</name>
@@ -41,21 +42,6 @@
         </developer>
     </developers>
 
-    <modules>
-        <module>jplag.frontend-utils</module>
-        <module>jplag.frontend-testutils</module>
-        <module>jplag.frontend.chars</module>
-        <module>jplag.frontend.cpp</module>
-        <module>jplag.frontend.csharp-6</module>
-        <module>jplag.frontend.golang</module>
-        <module>jplag.frontend.java</module>
-        <module>jplag.frontend.python-3</module>
-        <module>jplag.frontend.rlang</module>
-        <module>jplag.frontend.scheme</module>
-        <module>jplag.frontend.text</module>
-        <module>jplag</module>
-        <module>jplag.cli</module>
-    </modules>
     <scm>
         <connection>scm:git:git://github.com/jplag/JPlag.git</connection>
         <developerConnection>scm:git:ssh://github.com:jplag/JPlag.git</developerConnection>
@@ -71,10 +57,6 @@
         <url>https://github.com/jplag/jplag/actions</url>
     </ciManagement>
     <distributionManagement>
-        <repository>
-            <id>ossrh</id>
-            <url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
-        </repository>
         <snapshotRepository>
             <id>ossrh</id>
             <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
@@ -83,10 +65,22 @@
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <sonar.projectKey>jplag_JPlag</sonar.projectKey>
+        <sonar.moduleKey>${project.groupId}:${project.artifactId}</sonar.moduleKey>
+        <sonar.organization>jplag</sonar.organization>
+        <sonar.host.url>https://sonarcloud.io</sonar.host.url>
+        <!--suppress UnresolvedMavenProperty -->
+        <sonar.coverage.jacoco.xmlReportPaths>${maven.multiModuleProjectDirectory}/jplag.cli/target/site/jacoco-aggregate/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths>
+
         <maven.compiler.source>17</maven.compiler.source>
         <maven.compiler.target>17</maven.compiler.target>
-        <spotless.version>2.22.8</spotless.version>
+        <spotless.version>2.23.0</spotless.version>
         <slf4j.version>1.7.36</slf4j.version>
+
+        <antlr2.version>2.7.7</antlr2.version>
+        <antlr3.version>3.5</antlr3.version>
+        <antlr4.version>4.10.1</antlr4.version>
+
         <!-- The Revision of JPlag -->
         <revision>4.0.0-SNAPSHOT</revision>
     </properties>
@@ -97,27 +91,27 @@
             <dependency>
                 <groupId>antlr</groupId>
                 <artifactId>antlr</artifactId>
-                <version>2.7.7</version>
+                <version>${antlr2.version}</version>
             </dependency>
             <dependency>
                 <groupId>org.antlr</groupId>
                 <artifactId>antlr4-runtime</artifactId>
-                <version>4.10.1</version>
+                <version>${antlr4.version}</version>
             </dependency>
             <dependency>
                 <groupId>org.antlr</groupId>
                 <artifactId>antlr4</artifactId>
-                <version>4.10.1</version>
+                <version>${antlr4.version}</version>
             </dependency>
             <dependency>
                 <groupId>org.antlr</groupId>
                 <artifactId>antlr3</artifactId>
-                <version>3.5</version>
+                <version>${antlr3.version}</version>
             </dependency>
             <dependency>
                 <groupId>org.antlr</groupId>
                 <artifactId>antlr3-runtime</artifactId>
-                <version>3.5</version>
+                <version>${antlr3.version}</version>
             </dependency>
             <dependency>
                 <groupId>net.sourceforge.argparse4j</groupId>
@@ -139,64 +133,69 @@
 
             <!-- JPLAG -->
             <dependency>
-                <groupId>${project.groupId}</groupId>
+                <groupId>de.jplag</groupId>
                 <artifactId>text</artifactId>
-                <version>${project.version}</version>
+                <version>${revision}</version>
             </dependency>
             <dependency>
-                <groupId>${project.groupId}</groupId>
+                <groupId>de.jplag</groupId>
                 <artifactId>java</artifactId>
-                <version>${project.version}</version>
+                <version>${revision}</version>
             </dependency>
             <dependency>
-                <groupId>${project.groupId}</groupId>
+                <groupId>de.jplag</groupId>
                 <artifactId>python-3</artifactId>
-                <version>${project.version}</version>
+                <version>${revision}</version>
             </dependency>
             <dependency>
-                <groupId>${project.groupId}</groupId>
+                <groupId>de.jplag</groupId>
                 <artifactId>csharp-6</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>${project.groupId}</groupId>
-                <artifactId>rlang</artifactId>
-                <version>${project.version}</version>
+                <version>${revision}</version>
             </dependency>
             <dependency>
-                <groupId>${project.groupId}</groupId>
+                <groupId>de.jplag</groupId>
                 <artifactId>cpp</artifactId>
-                <version>${project.version}</version>
+                <version>${revision}</version>
             </dependency>
             <dependency>
-                <groupId>${project.groupId}</groupId>
+                <groupId>de.jplag</groupId>
                 <artifactId>golang</artifactId>
-                <version>${project.version}</version>
+                <version>${revision}</version>
             </dependency>
             <dependency>
-                <groupId>${project.groupId}</groupId>
+                <groupId>de.jplag</groupId>
+                <artifactId>kotlin</artifactId>
+                <version>${revision}</version>
+            </dependency>
+            <dependency>
+                <groupId>de.jplag</groupId>
+                <artifactId>rlang</artifactId>
+                <version>${revision}</version>
+            </dependency>
+            <dependency>
+                <groupId>de.jplag</groupId>
                 <artifactId>scheme</artifactId>
-                <version>${project.version}</version>
+                <version>${revision}</version>
             </dependency>
             <dependency>
-                <groupId>${project.groupId}</groupId>
+                <groupId>de.jplag</groupId>
                 <artifactId>chars</artifactId>
-                <version>${project.version}</version>
+                <version>${revision}</version>
             </dependency>
             <dependency>
-                <groupId>${project.groupId}</groupId>
+                <groupId>de.jplag</groupId>
                 <artifactId>frontend-utils</artifactId>
-                <version>${project.version}</version>
+                <version>${revision}</version>
             </dependency>
             <dependency>
-                <groupId>${project.groupId}</groupId>
+                <groupId>de.jplag</groupId>
                 <artifactId>frontend-testutils</artifactId>
-                <version>${project.version}</version>
+                <version>${revision}</version>
             </dependency>
             <dependency>
-                <groupId>${project.groupId}</groupId>
+                <groupId>de.jplag</groupId>
                 <artifactId>jplag</artifactId>
-                <version>${project.version}</version>
+                <version>${revision}</version>
             </dependency>
         </dependencies>
     </dependencyManagement>
@@ -205,7 +204,7 @@
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter-engine</artifactId>
-            <version>5.8.2</version>
+            <version>5.9.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -234,43 +233,11 @@
     <build>
         <pluginManagement>
             <plugins>
-                <plugin>
-                    <groupId>org.codehaus.mojo</groupId>
-                    <artifactId>flatten-maven-plugin</artifactId>
-                    <version>1.2.7</version>
-                    <configuration>
-                        <updatePomFile>true</updatePomFile>
-                        <flattenMode>resolveCiFriendliesOnly</flattenMode>
-                    </configuration>
-                    <executions>
-                        <execution>
-                            <id>flatten</id>
-                            <goals>
-                                <goal>flatten</goal>
-                            </goals>
-                            <phase>process-resources</phase>
-                        </execution>
-                        <execution>
-                            <id>flatten.clean</id>
-                            <goals>
-                                <goal>clean</goal>
-                            </goals>
-                            <phase>clean</phase>
-                        </execution>
-                    </executions>
-                </plugin>
-
                 <plugin>
                     <groupId>com.helger.maven</groupId>
                     <artifactId>ph-javacc-maven-plugin</artifactId>
                     <version>4.1.5</version>
                 </plugin>
-
-                <plugin>
-                    <groupId>org.codehaus.mojo</groupId>
-                    <artifactId>build-helper-maven-plugin</artifactId>
-                    <version>3.3.0</version>
-                </plugin>
                 <plugin>
                     <groupId>org.codehaus.mojo</groupId>
                     <artifactId>antlr-maven-plugin</artifactId>
@@ -279,10 +246,11 @@
                 <plugin>
                     <groupId>org.antlr</groupId>
                     <artifactId>antlr4-maven-plugin</artifactId>
-                    <version>4.10.1</version>
+                    <version>${antlr4.version}</version>
                 </plugin>
 
                 <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-jar-plugin</artifactId>
                     <version>3.2.2</version>
                     <configuration>
@@ -302,8 +270,9 @@
                     </executions>
                 </plugin>
                 <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-assembly-plugin</artifactId>
-                    <version>3.3.0</version>
+                    <version>3.4.2</version>
                     <configuration>
                         <descriptorRefs>
                             <descriptorRef>jar-with-dependencies</descriptorRef>
@@ -318,43 +287,75 @@
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
-                    <artifactId>maven-javadoc-plugin</artifactId>
+                    <artifactId>maven-surefire-plugin</artifactId>
+                    <version>3.0.0-M7</version>
+                </plugin>
+                <plugin>
+                    <groupId>org.jacoco</groupId>
+                    <artifactId>jacoco-maven-plugin</artifactId>
+                    <version>0.8.8</version>
                     <executions>
                         <execution>
-                            <id>attach-javadocs</id>
+                            <id>prepare-agent</id>
                             <goals>
-                                <goal>jar</goal>
+                                <goal>prepare-agent</goal>
                             </goals>
                         </execution>
-                    </executions>
-                </plugin>
-                <plugin>
-                    <groupId>org.apache.maven.plugins</groupId>
-                    <artifactId>maven-source-plugin</artifactId>
-                    <executions>
                         <execution>
-                            <id>attach-sources</id>
+                            <id>report</id>
                             <goals>
-                                <goal>jar</goal>
+                                <goal>report</goal>
                             </goals>
+                            <configuration>
+                                <formats>
+                                    <format>XML</format>
+                                </formats>
+                            </configuration>
                         </execution>
                     </executions>
                 </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-gpg-plugin</artifactId>
+                    <version>3.0.1</version>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-deploy-plugin</artifactId>
+                    <version>3.0.0</version>
+                </plugin>
             </plugins>
         </pluginManagement>
         <plugins>
             <plugin>
                 <groupId>org.codehaus.mojo</groupId>
                 <artifactId>flatten-maven-plugin</artifactId>
-            </plugin>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-surefire-plugin</artifactId>
-                <version>3.0.0-M7</version>
+                <version>1.2.7</version>
+                <configuration>
+                    <updatePomFile>true</updatePomFile>
+                    <flattenMode>resolveCiFriendliesOnly</flattenMode>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>flatten</id>
+                        <goals>
+                            <goal>flatten</goal>
+                        </goals>
+                        <phase>process-resources</phase>
+                    </execution>
+                    <execution>
+                        <id>flatten.clean</id>
+                        <goals>
+                            <goal>clean</goal>
+                        </goals>
+                        <phase>clean</phase>
+                    </execution>
+                </executions>
             </plugin>
             <plugin>
                 <groupId>org.codehaus.mojo</groupId>
                 <artifactId>build-helper-maven-plugin</artifactId>
+                <version>3.3.0</version>
                 <executions>
                     <execution>
                         <id>add-source</id>
@@ -374,6 +375,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-javadoc-plugin</artifactId>
+                <version>3.4.0</version>
                 <executions>
                     <execution>
                         <id>attach-javadocs</id>
@@ -386,6 +388,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-source-plugin</artifactId>
+                <version>3.2.1</version>
                 <executions>
                     <execution>
                         <id>attach-sources</id>
@@ -395,24 +398,6 @@
                     </execution>
                 </executions>
             </plugin>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-gpg-plugin</artifactId>
-                <executions>
-                    <execution>
-                        <id>sign-artifacts</id>
-                        <goals>
-                            <goal>sign</goal>
-                        </goals>
-                        <phase>verify</phase>
-                        <configuration>
-                            <keyname>0xC6E9DAC2</keyname>
-                            <!--suppress UnresolvedMavenProperty -->
-                            <passphrase>${env.GPG_PASSPHRASE}</passphrase>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
             <plugin>
                 <groupId>org.sonatype.plugins</groupId>
                 <artifactId>nexus-staging-maven-plugin</artifactId>
@@ -450,6 +435,77 @@
                     </pom>
                 </configuration>
             </plugin>
+            <plugin>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+            </plugin>
         </plugins>
     </build>
+    <profiles>
+        <profile>
+            <id>module-defaults</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <modules>
+                <module>jplag.frontend-utils</module>
+                <module>jplag.frontend-testutils</module>
+                <module>jplag.frontend.chars</module>
+                <module>jplag.frontend.cpp</module>
+                <module>jplag.frontend.csharp-6</module>
+                <module>jplag.frontend.golang</module>
+                <module>jplag.frontend.java</module>
+                <module>jplag.frontend.kotlin</module>
+                <module>jplag.frontend.python-3</module>
+                <module>jplag.frontend.rlang</module>
+                <module>jplag.frontend.scheme</module>
+                <module>jplag.frontend.text</module>
+                <module>jplag</module>
+                <module>jplag.cli</module>
+            </modules>
+        </profile>
+        <profile>
+            <id>deployment</id>
+            <activation>
+                <activeByDefault>false</activeByDefault>
+            </activation>
+            <modules>
+                <module>jplag.frontend-utils</module>
+                <module>jplag.frontend-testutils</module>
+                <module>jplag.frontend.chars</module>
+                <module>jplag.frontend.cpp</module>
+                <module>jplag.frontend.csharp-6</module>
+                <module>jplag.frontend.golang</module>
+                <module>jplag.frontend.java</module>
+                <module>jplag.frontend.kotlin</module>
+                <module>jplag.frontend.python-3</module>
+                <module>jplag.frontend.rlang</module>
+                <module>jplag.frontend.scheme</module>
+                <module>jplag.frontend.text</module>
+                <module>jplag</module>
+            </modules>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-gpg-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>sign-artifacts</id>
+                                <goals>
+                                    <goal>sign</goal>
+                                </goals>
+                                <phase>verify</phase>
+                                <configuration>
+                                    <keyname>0xC6E9DAC2</keyname>
+                                    <!--suppress UnresolvedMavenProperty -->
+                                    <passphrase>${env.GPG_PASSPHRASE}</passphrase>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
 </project>

File diff suppressed because it is too large
+ 281 - 271
report-viewer/package-lock.json


+ 13 - 13
report-viewer/package.json

@@ -9,31 +9,31 @@
   },
   "dependencies": {
     "@highlightjs/vue-plugin": "^2.1.0",
-    "chart.js": "^3.8.0",
+    "chart.js": "^3.8.2",
     "chartjs-plugin-datalabels": "^2.0.0",
-    "core-js": "^3.23.3",
-    "gitart-vue-dialog": "^2.3.1",
-    "highlight.js": "^11.5.1",
+    "core-js": "^3.24.0",
+    "gitart-vue-dialog": "^2.4.0",
+    "highlight.js": "^11.6.0",
     "jszip": "^3.10.0",
     "vue": "^3.2.37",
     "vue-chart-3": "^3.1.8",
     "vue-draggable-next": "^2.1.1",
-    "vue-router": "^4.0.16",
+    "vue-router": "^4.1.2",
     "vue3-highlightjs": "^1.0.5",
     "vuex": "^4.0.2"
   },
   "devDependencies": {
-    "@typescript-eslint/eslint-plugin": "^5.30.0",
-    "@typescript-eslint/parser": "^5.30.0",
-    "@vue/cli-plugin-babel": "~5.0.6",
-    "@vue/cli-plugin-eslint": "~5.0.6",
-    "@vue/cli-plugin-router": "~5.0.1",
-    "@vue/cli-plugin-typescript": "~5.0.6",
-    "@vue/cli-service": "~5.0.6",
+    "@typescript-eslint/eslint-plugin": "^5.31.0",
+    "@typescript-eslint/parser": "^5.31.0",
+    "@vue/cli-plugin-babel": "~5.0.8",
+    "@vue/cli-plugin-eslint": "~5.0.8",
+    "@vue/cli-plugin-router": "~5.0.8",
+    "@vue/cli-plugin-typescript": "~5.0.8",
+    "@vue/cli-service": "~5.0.8",
     "@vue/compiler-sfc": "^3.2.37",
     "@vue/eslint-config-prettier": "^7.0.0",
     "@vue/eslint-config-typescript": "^10.0.0",
-    "eslint": "^8.18.0",
+    "eslint": "^8.20.0",
     "eslint-plugin-prettier": "^4.2.1",
     "eslint-plugin-vue": "^8.7.1",
     "lint-staged": "^13.0.3",

+ 4 - 4
report-viewer/src/App.vue

@@ -5,13 +5,13 @@
 
 <style>
 @font-face {
-  font-family: "JetBrains Mono";
-  src: url("./fonts/jetbrains-mono/JetBrainsMono-Light.ttf");
+  font-family: "JetBrains Mono NL";
+  src: url("./fonts/jetbrains-mono/JetBrainsMonoNL-Light.ttf");
 }
 
 @font-face {
-  font-family: "JetBrains Mono";
-  src: url("./fonts/jetbrains-mono/JetBrainsMono-Bold.ttf");
+  font-family: "JetBrains Mono NL";
+  src: url("./fonts/jetbrains-mono/JetBrainsMonoNL-Bold.ttf");
   font-weight: bold;
 }
 

+ 22 - 0
report-viewer/src/assets/radar-chart-configuration.ts

@@ -0,0 +1,22 @@
+
+ const radarChartStyle ={
+    fill: true,
+    backgroundColor: "rgba(149, 168, 241, 0.5)",
+    borderColor: "rgba(149, 168, 241, 1)",
+    pointBackgroundColor: "rgba(149, 168, 241, 1)",
+    pointBorderColor: "#fff",
+    pointHoverBackgroundColor: "#fff",
+    pointHoverBorderColor: "rgb(255, 99, 132)",
+  }
+  const radarChartOptions = {
+    legend: {
+      display: false,
+    },
+    scales: {
+      r: {
+        suggestedMin: 50,
+        suggestedMax: 100,
+      },
+    },
+  }
+  export {radarChartStyle,radarChartOptions}

+ 89 - 64
report-viewer/src/components/ClusterRadarChart.vue

@@ -4,88 +4,110 @@
 -->
 <template>
   <div class="wrapper">
-    <select v-model="selectedMember">
-      <option v-for="(member, index) in Object.keys(cluster.members)" :key="index">{{ member }}</option>
-    </select>
-    <RadarChart :chartData="chartData" :options="options" class="chart"></RadarChart>
+    <div v-if="!hasNoMember" class="wrapper">
+      <select v-model="selectedMember">
+        <option v-for="(member, index) in cluster.members.keys()" :key="index">
+          {{ member }}
+        </option>
+      </select>
+      <RadarChart
+        :chartData="chartData"
+        :options="options"
+        class="chart"
+      ></RadarChart>
+    </div>
+    <div v-if="hasNoMember" class="no-member">
+      <span>This cluster has no members.</span>
+    </div>
   </div>
 </template>
 
-<script>
-import {defineComponent, ref, watch} from "vue";
-import {RadarChart} from "vue-chart-3"
-import {Chart, registerables} from 'chart.js';
-import ChartDataLabels from 'chartjs-plugin-datalabels';
+<script lang="ts">
+import { defineComponent, PropType, Ref, ref, watch } from "vue";
+import { RadarChart } from "vue-chart-3";
+import { Chart, ChartData, registerables } from "chart.js";
+import ChartDataLabels from "chartjs-plugin-datalabels";
+import { ClusterListElement } from "@/model/ClusterListElement";
+import {
+  radarChartStyle,
+  radarChartOptions,
+} from "@/assets/radar-chart-configuration";
 
 Chart.register(...registerables);
 Chart.register(ChartDataLabels);
 
 export default defineComponent({
   name: "ClusterRadarChart",
-  components: {RadarChart},
+  components: { RadarChart },
   props: {
-    cluster: {}
+    cluster: {
+      type: Object as PropType<ClusterListElement>,
+      required: true,
+    },
   },
   setup(props) {
-    const selectedMember = ref("")
+    const getIdOfFirstSubmission = () => {
+      const firstMember = props.cluster.members.keys().next().value;
+      hasNoMember = !firstMember;
+      return firstMember;
+    };
 
-    const createLabelsFor = (member) => {
-      let matchedWith = []
-      props.cluster.members[member].forEach(m => matchedWith.push(m.matchedWith))
-      return matchedWith
-    }
-    const createDataSetFor = (member) => {
-      let data = []
-      props.cluster.members[member].forEach(m => data.push(m.percentage))
-      return data
-    }
+    const createLabelsFor = (member: string): Array<string> => {
+      let matchedWith = new Array<string>();
+      props.cluster.members
+        .get(member)
+        ?.forEach((m) => matchedWith.push(m.matchedWith));
+      return matchedWith;
+    };
+    const createDataSetFor = (member: string) => {
+      let data = new Array<number>();
+      props.cluster.members
+        .get(member)
+        ?.forEach((m) => data.push(roundToTwoDecimals(m.percentage)));
+      return data;
+    };
+    const roundToTwoDecimals = (num: number): number =>
+      Math.round((num + Number.EPSILON) * 100) / 100;
 
-    const chartStyle = {
-      fill: true,
-      backgroundColor: 'rgba(149, 168, 241, 0.5)',
-      borderColor: 'rgba(149, 168, 241, 1)',
-      pointBackgroundColor: 'rgba(149, 168, 241, 1)',
-      pointBorderColor: '#fff',
-      pointHoverBackgroundColor: '#fff',
-      pointHoverBorderColor: 'rgb(255, 99, 132)'
-    }
+    const selectedMember = ref(getIdOfFirstSubmission());
 
-    const chartData = ref({
-      labels: createLabelsFor(Object.keys(props.cluster.members)[0]),
-      datasets: [{
-        ...chartStyle,
-        label: Object.keys(props.cluster.members)[0],
-        data: createDataSetFor(Object.keys(props.cluster.members)[0])
-      }]
-    })
+    const chartData: Ref<ChartData<"radar", (number | null)[], unknown>> = ref({
+      labels: createLabelsFor(getIdOfFirstSubmission()),
+      datasets: [
+        {
+          ...radarChartStyle,
+          label: getIdOfFirstSubmission(),
+          data: createDataSetFor(getIdOfFirstSubmission()),
+        },
+      ],
+    });
 
-    const options = ref({
-      scales: {
-        r: {
-          suggestedMin: 50,
-          suggestedMax: 100
-        }
+    const options = ref(radarChartOptions);
+    let hasNoMember = false;
+    watch(
+      () => selectedMember.value,
+      (val) => {
+        chartData.value = {
+          labels: createLabelsFor(val),
+          datasets: [
+            {
+              ...radarChartStyle,
+              label: val,
+              data: createDataSetFor(val),
+            },
+          ],
+        };
       }
-    })
-
-    watch(() => selectedMember.value, (val) => {
-      chartData.value = {
-        labels: createLabelsFor(val),
-        datasets: [{
-          ...chartStyle,
-          label: val,
-          data: createDataSetFor(val),
-        }]
-      }
-    })
+    );
 
     return {
       selectedMember,
       chartData,
-      options
-    }
-  }
-})
+      options,
+      hasNoMember,
+    };
+  },
+});
 </script>
 
 <style scoped>
@@ -93,10 +115,13 @@ export default defineComponent({
   display: flex;
   flex-direction: column;
   flex-wrap: nowrap;
-
 }
-
+.no-member {
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+}
 .chart {
   height: 50vw;
 }
-</style>
+</style>

+ 54 - 20
report-viewer/src/components/ClustersList.vue

@@ -3,38 +3,66 @@
 -->
 <template>
   <div class="wrapper">
-    <h1>Clusters for comparison {{ comparison.firstSubmissionId }} > {{ comparison.secondSubmissionId }}</h1>
-    <p v-for="( cluster, index ) in clusters" :key="index" @click="toggleDialog">
-      {{ index }} - {{ cluster.averageSimilarity }}
+    <h1>
+      Clusters for comparison {{ comparison.firstSubmissionId }} >
+      {{ comparison.secondSubmissionId }}
+    </h1>
+    <p v-for="(cluster, index) in clusters" :key="index" @click="toggleDialog">
+      {{ index + 1 }}. Members:
+      <span id="members">{{ getMemberNames(cluster) }}</span> - Average
+      similarity: {{ cluster.averageSimilarity }}%
       <GDialog v-model="dialog" fullscreen>
-        <button @click="toggleDialog">Close</button>
+        <div id="dialog-header">
+          <button @click="toggleDialog">Close</button>
+        </div>
         <ClusterRadarChart :cluster="cluster"></ClusterRadarChart>
       </GDialog>
     </p>
   </div>
 </template>
 
-<script>
-import {defineComponent, ref} from "vue";
-import {GDialog} from "gitart-vue-dialog";
-import ClusterRadarChart from "@/components/ClusterRadarChart";
-
+<script lang="ts">
+import { defineComponent, ref, PropType } from "vue";
+import { GDialog } from "gitart-vue-dialog";
+import ClusterRadarChart from "@/components/ClusterRadarChart.vue";
+import { ClusterListElement } from "@/model/ClusterListElement";
+import { ComparisonListElement } from "@/model/ComparisonListElement";
 export default defineComponent({
   name: "ClustersList",
-  components: {ClusterRadarChart, GDialog},
+  components: { ClusterRadarChart, GDialog },
   props: {
-    comparison: {},
-    clusters: Array
+    comparison: {
+      type: Object as PropType<ComparisonListElement>,
+      required: true,
+
+    },
+    clusters: {
+      type: Array<ClusterListElement>,
+      required: true
   },
+  },
+
   setup() {
-    const dialog = ref(false)
-    const toggleDialog = () => dialog.value = !dialog.value
+    const dialog = ref(false);
+    const toggleDialog = () => (dialog.value = !dialog.value);
+    const getMemberNames = (cluster: ClusterListElement) => {
+      const membersIterator = cluster.members.keys();
+      const members = Array.from(membersIterator);
+      let concatenatedMembers = "";
+      const maxMembersToShow = 5;
+      concatenatedMembers = members.slice(0, maxMembersToShow).join(", ");
+      if (members.length > maxMembersToShow) {
+        concatenatedMembers += ",...";
+      }
+      return concatenatedMembers;
+    };
     return {
       dialog,
-      toggleDialog
-    }
-  }
-})
+      toggleDialog,
+      getMemberNames,
+    };
+  },
+});
 </script>
 
 <style scoped>
@@ -60,5 +88,11 @@ p:hover {
   background: var(--primary-color-dark);
   cursor: pointer;
 }
-
-</style>
+#dialog-header {
+  display: flex;
+  flex-direction: row-reverse;
+  margin-right: 1%;
+  margin-top: 0.5%;
+  box-shadow: 5px 10x #888888;
+}
+</style>

+ 70 - 47
report-viewer/src/components/CodePanel.vue

@@ -2,36 +2,61 @@
   Panel which displays a submission files with its line of code.
 -->
 <template>
-  <div :id="panelId.toString().concat(title).concat(fileIndex.toString())" class="code-panel-container">
+  <div
+    :id="panelId.toString().concat(title).concat(fileIndex.toString())"
+    class="code-panel-container"
+  >
     <div class="file-title">
       <p style="width: 90%">{{ title }}</p>
-      <button class="collapse-button" style="width: 10%" @click="$emit('toggleCollapse')">
-        <img v-if="collapse" alt="hide info" src="../assets/keyboard_double_arrow_up_black_18dp.svg">
-        <img v-else alt="additional info" src="../assets/keyboard_double_arrow_down_black_18dp.svg">
+      <button
+        class="collapse-button"
+        style="width: 10%"
+        @click="$emit('toggleCollapse')"
+      >
+        <img
+          v-if="collapse"
+          alt="hide info"
+          src="../assets/keyboard_double_arrow_up_black_18dp.svg"
+        />
+        <img
+          v-else
+          alt="additional info"
+          src="../assets/keyboard_double_arrow_down_black_18dp.svg"
+        />
       </button>
     </div>
-    <div :class="{ hidden : !collapse }" class="code-container">
-      <LineOfCode v-for="(line, index) in lines"
-                  :id="String(panelId).concat(title).concat(index)"
-                  :key="index"
-                  :color="coloringArray[index]"
-                  :is-first="isFirst[index]"
-                  :is-last="isLast[index]"
-                  :line-number="index"
-                  :text="line"
-                  :visible="collapse"
-                  @click="$emit('lineSelected', $event, linksArray[index].panel, linksArray[index].file, linksArray[index].line )"/>
+    <div :class="{ hidden: !collapse }" class="code-container">
+      <LineOfCode
+        v-for="(line, index) in lines"
+        :id="String(panelId).concat(title).concat(index)"
+        :key="index"
+        :color="coloringArray[index]"
+        :is-first="isFirst[index]"
+        :is-last="isLast[index]"
+        :line-number="index + 1"
+        :text="line"
+        :visible="collapse"
+        @click="
+          $emit(
+            'lineSelected',
+            $event,
+            linksArray[index].panel,
+            linksArray[index].file,
+            linksArray[index].line
+          )
+        "
+      />
     </div>
   </div>
 </template>
 
 <script>
-import {defineComponent, ref} from "vue";
+import { defineComponent, ref } from "vue";
 import LineOfCode from "./LineOfCode";
 
 export default defineComponent({
   name: "CodePanel",
-  components: {LineOfCode},
+  components: { LineOfCode },
   props: {
     /**
      * Title of the displayed file.
@@ -43,7 +68,7 @@ export default defineComponent({
      * Index of file amongst other files in submission.
      */
     fileIndex: {
-      type: Number
+      type: Number,
     },
     /**
      * Code lines of the file.
@@ -51,27 +76,27 @@ export default defineComponent({
      */
     lines: {
       type: Array,
-      required: true
+      required: true,
     },
     /**
      * Matches in the file
      * type: Array<MatchInSingleFile>
      */
     matches: {
-      type: Array
+      type: Array,
     },
     /**
      * Id of the FilesContainer. Needed for lines link generation.
      */
     panelId: {
-      type: Number
+      type: Number,
     },
     /**
      * Indicates whether files is collapsed or not.
      */
     collapse: {
-      type: Boolean
-    }
+      type: Boolean,
+    },
   },
   setup(props) {
     /**
@@ -86,7 +111,7 @@ export default defineComponent({
      * }
      * @type {Ref<UnwrapRef<{}>>}
      */
-    const coloringArray = ref({})
+    const coloringArray = ref({});
     /**
      * An object containing an object from which an id is to of the line to which this is linked is constructed.
      * Id object contains panel, file name, first line number of linked matched.
@@ -101,44 +126,42 @@ export default defineComponent({
      * Key is line number, value is id of linked line.
      * @type {Ref<UnwrapRef<{}>>}
      */
-    const linksArray = ref({})
+    const linksArray = ref({});
     /**
      * Indicates whether the line is last line of match. Key is line number, value is true or false.
      * @type {Ref<UnwrapRef<{}>>}
      */
-    const isLast = ref({})
+    const isLast = ref({});
     /**
      * Indicates whether the line is the first line of a match. Key is line number, value is true or false.
      * @type {Ref<UnwrapRef<{}>>}
      */
-    const isFirst = ref({})
+    const isFirst = ref({});
 
     /**
      * Initializing the the upper arrays.
      */
-    props.matches.forEach(m => {
+    props.matches.forEach((m) => {
       for (let i = m.start; i <= m.end; i++) {
         //assign match color to line
-        coloringArray.value[i] = m.color
+        coloringArray.value[i] = m.color;
         //assign link object to line.
-        linksArray.value[i] = {panel: m.linked_panel, file: m.linked_file, line: m.linked_line}
-        //check whether line is start of match and assign true if yes
-        if (i === m.start) {
-          isFirst.value[i] = true
-        }
-        //check whether line is end of match and assign true if yes
-        if (i === m.end) {
-          isLast.value[i] = true
-        }
+        linksArray.value[i] = {
+          panel: m.linked_panel,
+          file: m.linked_file,
+          line: m.linked_line,
+        };
       }
-    })
+      isFirst.value[m.start] = true;
+      isLast.value[m.end] = true;
+    });
     //assign default values for all line which are not contained in matches
     for (let i = 0; i < props.lines.length; i++) {
       if (!coloringArray.value[i]) {
-        coloringArray.value[i] = "#FFFFFF"
-        linksArray.value[i] = "-1"
-        isFirst.value[i] = false
-        isLast.value[i] = false
+        coloringArray.value[i] = "#FFFFFF";
+        linksArray.value[i] = "-1";
+        isFirst.value[i] = false;
+        isLast.value[i] = false;
       }
     }
 
@@ -147,9 +170,9 @@ export default defineComponent({
       linksArray,
       isFirst,
       isLast,
-    }
-  }
-})
+    };
+  },
+});
 </script>
 
 <style scoped>
@@ -202,4 +225,4 @@ export default defineComponent({
 .hidden {
   display: none !important;
 }
-</style>
+</style>

+ 151 - 76
report-viewer/src/components/ComparisonsTable.vue

@@ -10,120 +10,195 @@
       <th>Submission 2</th>
       <th>Match %</th>
     </tr>
-    <tr v-for="(comparison, index) in topComparisons"
-        :key="comparison.firstSubmissionId
-            + comparison.secondSubmissionId
-            + comparison.matchPercentage"
-        class="selectable"
+    <tr
+      v-for="(comparison, index) in topComparisons"
+      :key="
+        comparison.firstSubmissionId +
+        comparison.secondSubmissionId +
+        comparison.matchPercentage
+      "
+      class="selectable"
     >
-      <td @click="navigateToComparisonView(comparison.firstSubmissionId, comparison.secondSubmissionId)">{{
-          index + 1
-        }}.
+      <td
+        @click="
+          navigateToComparisonView(
+            comparison.firstSubmissionId,
+            comparison.secondSubmissionId
+          )
+        "
+      >
+        {{ index + 1 }}.
       </td>
-      <td :class="{ 'anonymous-style' : isAnonymous(comparison.firstSubmissionId)}"
-          @click="navigateToComparisonView(comparison.firstSubmissionId, comparison.secondSubmissionId)">
-        {{ isAnonymous(comparison.firstSubmissionId) ? "Hidden" : comparison.firstSubmissionId }}
+      <td
+        :class="{
+          'anonymous-style': isAnonymous(comparison.firstSubmissionId),
+        }"
+        @click="
+          navigateToComparisonView(
+            comparison.firstSubmissionId,
+            comparison.secondSubmissionId
+          )
+        "
+      >
+        {{
+          isAnonymous(comparison.firstSubmissionId)
+            ? "Hidden"
+            : comparison.firstSubmissionId
+        }}
       </td>
       <td
-          @click="navigateToComparisonView(comparison.firstSubmissionId, comparison.secondSubmissionId)">
-        <img alt=">>" src="@/assets/double_arrow_black_18dp.svg"/>
+        @click="
+          navigateToComparisonView(
+            comparison.firstSubmissionId,
+            comparison.secondSubmissionId
+          )
+        "
+      >
+        <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 }}
+      <td
+        :class="{
+          'anonymous-style': isAnonymous(comparison.secondSubmissionId),
+        }"
+        @click="
+          navigateToComparisonView(
+            comparison.firstSubmissionId,
+            comparison.secondSubmissionId
+          )
+        "
+      >
+        {{
+          isAnonymous(comparison.secondSubmissionId)
+            ? "Hidden"
+            : comparison.secondSubmissionId
+        }}
       </td>
       <td>{{ formattedMatchPercentage(comparison.matchPercentage) }}</td>
       <td>
-        <img v-if="isInCluster(comparison.firstSubmissionId, comparison.secondSubmissionId)"
-             alt=">>"
-             src="@/assets/keyboard_double_arrow_down_black_18dp.svg"
-             @click="toggleDialog(index)"
+        <img
+          v-if="
+            isInCluster(
+              comparison.firstSubmissionId,
+              comparison.secondSubmissionId
+            )
+          "
+          alt=">>"
+          src="@/assets/keyboard_double_arrow_down_black_18dp.svg"
+          @click="toggleDialog(index)"
         />
       </td>
       <GDialog v-model="dialog[index]">
-        <ClustersList :clusters="getClustersFor(comparison.firstSubmissionId, comparison.secondSubmissionId)"
-                      :comparison="comparison"/>
+        <ClustersList
+          :clusters="
+            getClustersFor(
+              comparison.firstSubmissionId,
+              comparison.secondSubmissionId
+            )
+          "
+          :comparison="comparison"
+        />
       </GDialog>
     </tr>
   </table>
 </template>
 
-<script>
-import {defineComponent, ref} from "vue";
+<script lang="ts">
+import { defineComponent, Ref, ref } from "vue";
 import router from "@/router";
-import store from "@/store/store";
-import {GDialog} from "gitart-vue-dialog";
-import ClustersList from "@/components/ClustersList";
+import { GDialog } from "gitart-vue-dialog";
+import ClustersList from "@/components/ClustersList.vue";
+import { useStore } from "vuex";
+import { Cluster } from "@/model/Cluster";
+import { ComparisonListElement } from "@/model/ComparisonListElement";
+import { ClusterListElement } from "@/model/ClusterListElement";
 
 export default defineComponent({
   name: "ComparisonsTable",
-  components: {ClustersList, GDialog},
+  components: { ClustersList, GDialog },
   props: {
     topComparisons: {
-      type: Array,
-      required: true
+      type: Array<ComparisonListElement>,
+      required: true,
     },
     clusters: {
-      type: Array
-    }
+      type: Array<Cluster>,
+      required: true,
+    },
   },
   setup(props) {
-    let formattedMatchPercentage = (number) => number.toFixed(2)
-    const dialog = ref([])
-    props.topComparisons.forEach(() => dialog.value.push(false))
+    const store = useStore();
+    let formattedMatchPercentage = (num: number) => num.toFixed(2);
+    const dialog :Ref<Array<boolean>> = ref([]);
+    props.topComparisons.forEach(() => dialog.value.push(false));
 
-    const toggleDialog = (index) => {
-      dialog.value[index] = true
-    }
+    const toggleDialog = (index:number) => {
+      dialog.value[index] = true;
+    };
 
-    const navigateToComparisonView = (firstId, secondId) => {
+    const navigateToComparisonView = (firstId: string, secondId: string) => {
       if (!store.state.single) {
         router.push({
           name: "ComparisonView",
-          query: {firstId: firstId, secondId: secondId},
-        })
+          params: { firstId, secondId },
+        });
       }
-    }
-
-    const isInCluster = (id1, id2) => {
-      return props.clusters.some(c => c.members.includes(id1) && c.members.includes(id2))
-    }
+    };
 
-    const isAnonymous = (id) => {
-      return store.state.anonymous.has(id)
-    }
+    const isInCluster = (id1: string, id2: string) => {
+      return props.clusters.some(
+        (c: Cluster) => c.members.includes(id1) && c.members.includes(id2)
+      );
+    };
 
+    const isAnonymous = (id: string) => {
+      return store.state.anonymous.has(id);
+    };
 
-    const getParticipatingMatchesForId = (id, others) => {
-      let matches = []
-      props.topComparisons.forEach(comparison => {
-        if (comparison.firstSubmissionId.includes(id) && others.includes(comparison.secondSubmissionId)) {
-          matches.push({matchedWith: comparison.secondSubmissionId, percentage: comparison.matchPercentage})
-        } else if (comparison.secondSubmissionId.includes(id) && others.includes(comparison.firstSubmissionId)) {
-          matches.push({matchedWith: comparison.firstSubmissionId, percentage: comparison.matchPercentage})
+    const getParticipatingMatchesForId = (id: string, others : Array<string>) => {
+      let matches : Array<{matchedWith: string, percentage: number,}>= [];
+      props.topComparisons.forEach((comparison) => {
+        if (
+          comparison.firstSubmissionId.includes(id) &&
+          others.includes(comparison.secondSubmissionId)
+        ) {
+          matches.push({
+            matchedWith: comparison.secondSubmissionId,
+            percentage: comparison.matchPercentage,
+          });
+        } else if (
+          comparison.secondSubmissionId.includes(id) &&
+          others.includes(comparison.firstSubmissionId)
+        ) {
+          matches.push({
+            matchedWith: comparison.firstSubmissionId,
+            percentage: comparison.matchPercentage,
+          });
         }
-      })
+      });
       return matches;
-    }
+    };
 
-    const clustersWithParticipatingMatches = props.clusters.map(cluster => {
-      let membersArray = {}
-      cluster.members.forEach(member => {
-        let others = cluster.members.filter(m => !m.includes(member))
-        membersArray[member] = getParticipatingMatchesForId(member, others)
-      })
+    const clustersWithParticipatingMatches: Array<ClusterListElement> = props.clusters.map((cluster) => {
+      let membersArray = new Map<string,Array<{matchedWith: string, percentage: number,}>>();
+      cluster.members.forEach((member: string) => {
+        let others = cluster.members.filter((m) => !m.includes(member));
+        membersArray.set(member ,getParticipatingMatchesForId(member, others));
+      });
 
       return {
         averageSimilarity: cluster.averageSimilarity,
         strength: cluster.strength,
-        members: membersArray
-      }
-    })
+        members: membersArray,
+      };
+    });
 
-    const getClustersFor = (id1, id2) => {
-      return clustersWithParticipatingMatches.filter(c => Object.keys(c.members).includes(id1)
-          && Object.keys(c.members).includes(id2))
-    }
+    const getClustersFor = (id1: string, id2: string) : Array<ClusterListElement> => {
+      return clustersWithParticipatingMatches.filter(
+        (c) =>
+          c.members.has(id1) &&
+          c.members.has(id2)
+      );
+    };
 
     return {
       clustersWithParticipatingMatches,
@@ -134,10 +209,10 @@ export default defineComponent({
       toggleDialog,
       formattedMatchPercentage,
       navigateToComparisonView,
-      isInCluster
-    }
-  }
-})
+      isInCluster,
+    };
+  },
+});
 </script>
 
 <style scoped>
@@ -180,4 +255,4 @@ td {
   background: var(--primary-color-dark);
   cursor: pointer;
 }
-</style>
+</style>

+ 4 - 6
report-viewer/src/components/DistributionDiagram.vue

@@ -3,11 +3,11 @@
 -->
 <template>
   <div class="wrapper">
-    <BarChart :chartData="chartData" :options="options" class="chart"/>
+    <BarChart :chartData="chartData" :options="options" class="chart" />
   </div>
 </template>
 
-<script>
+<script lang="ts">
 import {defineComponent, ref, watch} from "vue";
 import {BarChart} from "vue-chart-3"
 import {Chart, registerables} from 'chart.js';
@@ -21,7 +21,7 @@ export default defineComponent({
   components: {BarChart},
   props: {
     distribution: {
-      type: Array,
+      type: Array<number>,
       required: true
     }
   },
@@ -111,11 +111,9 @@ export default defineComponent({
   box-shadow: #777777 2px 3px 3px;
   display: flex;
   height: 100%;
-
 }
 
 .chart {
   width: 100%;
-
 }
-</style>
+</style>

+ 33 - 31
report-viewer/src/components/FilesContainer.vue

@@ -5,63 +5,65 @@
   <div class="files-container">
     <h1>Files of {{ filesOwner }}</h1>
     <VueDraggableNext>
-      <CodePanel v-for="(file, index) in Object.keys(files)"
-                 :key="file.concat(index)"
-                 :collapse="files[file].collapsed"
-                 :file-index="index"
-                 :lines="files[file].lines"
-                 :matches="!matches[file] ? [] : matches[file]"
-                 :panel-id="containerId"
-                 :title="file"
-                 @toggle-collapse="$emit('toggle-collapse', file)"
-                 @line-selected="lineSelected"
+      <CodePanel
+        v-for="(file, index) in files.keys()"
+        :key="file.concat(index.toString())"
+        :collapse="files.get(file)?.collapsed"
+        :file-index="index"
+        :lines="!files.get(file)?.lines ? [] : files.get(file)?.lines"
+        :matches="!matches.get(file) ? [] : matches.get(file)"
+        :panel-id="containerId"
+        :title="file"
+        @toggle-collapse="$emit('toggle-collapse', file)"
+        @line-selected="lineSelected"
       />
     </VueDraggableNext>
   </div>
 </template>
 
-<script>
-import {defineComponent} from "vue";
-import CodePanel from "@/components/CodePanel";
-import {VueDraggableNext} from "vue-draggable-next"
+<script lang="ts">
+import { defineComponent } from "vue";
+import CodePanel from "../components/CodePanel.vue";
+import { VueDraggableNext } from "vue-draggable-next";
+import { SubmissionFile } from "@/model/SubmissionFile";
+import { MatchInSingleFile } from "@/model/MatchInSingleFile";
 
 export default defineComponent({
   name: "FilesContainer",
-  components: {CodePanel, VueDraggableNext},
+  components: { CodePanel, VueDraggableNext },
   props: {
     /**
      * Id of the files container. We have only two so it is either 1 or 2.
      */
     containerId: {
       type: Number,
-      required: true
+      required: true,
     },
     /**
      * Id of the submission to thich the files belong.
      */
     filesOwner: {
       type: String,
-      required: true
+      required: true,
     },
     /**
      * Files of the submission.
      * type: Array<SubmissionFile>
      */
     files: {
+      type: Map<string,SubmissionFile>,
       required: true,
-
     },
     /**
      * Matche of submission.
-     * type: Array<MatchInSingleFile>
      */
     matches: {
-      required: true
-    }
+     type: Map<string,MatchInSingleFile>,
+      required: true,
+    },
   },
 
-  setup(props, {emit}) {
-
+  setup(props, { emit }) {
     /**
      * Passes lineSelected event, emitted from LineOfCode, to parent.
      * @param e
@@ -69,14 +71,14 @@ export default defineComponent({
      * @param file
      * @param line
      */
-    const lineSelected = (e, index, file, line) => {
-      emit('lineSelected', e, index, file, line)
-    }
+    const lineSelected = (e: unknown, index: number, file: string, line: number) => {
+      emit("lineSelected", e, index, file, line);
+    };
     return {
-      lineSelected
-    }
-  }
-})
+      lineSelected,
+    };
+  },
+});
 </script>
 
 <style scoped>
@@ -92,4 +94,4 @@ h1 {
   width: 100%;
   overflow: auto;
 }
-</style>
+</style>

+ 1 - 1
report-viewer/src/components/LineOfCode.vue

@@ -82,7 +82,7 @@ code.hljs {
 
 .hljs {
   background: transparent !important;
-  font-family: "JetBrains Mono", serif !important;
+  font-family: "JetBrains Mono NL", serif !important;
   font-weight: bold;    font-size: x-small !important;
 }
 

+ 39 - 26
report-viewer/src/components/MetricButton.vue

@@ -3,41 +3,37 @@
   metric the distribution and comparisons are displayed.
 -->
 <template>
-  <div class="wrapper" v-bind:class="{ selected : isSelected }">
-    <div class="metric">
-      <p class="metric-name">{{ metricName }}</p>
-      <img alt="?" src="@/assets/help_outline_black_18dp.svg"/>
-    </div>
-    <div class="threshold">
-      <p>Threshold: </p>
-      <p>{{ metricThreshold }}%</p>
+  <div class="wrapper" v-bind:class="{ selected: isSelected }">
+    <div>
+      <p class="metric-name">{{ metric.metricName }}</p>
+      <div class="tooltip">
+        <img alt="?" src="@/assets/help_outline_black_18dp.svg" />
+        <span class="tooltiptext">{{metric.description}}</span>
+      </div>
     </div>
   </div>
 </template>
 
-<script>
-import {defineComponent} from "vue";
+<script lang="ts">
+import { defineComponent, PropType } from "vue";
+import { Metric } from "@/model/Metric";
 
 export default defineComponent({
   name: "MetricButton",
   props: {
-    metricName: {
-      type: String,
-      required: true
-    },
-    metricThreshold: {
-      type: Number,
-      required: true
+    metric: {
+      type: Object as PropType<Metric>,
+      required: true,
     },
     isSelected: {
       type: Boolean,
-      default: false
+      default: false,
     },
   },
   setup() {
-    return {}
-  }
-})
+    return {};
+  },
+});
 </script>
 
 <style scoped>
@@ -51,7 +47,6 @@ export default defineComponent({
   margin-right: 5%;
 }
 
-
 .wrapper > div > * {
   margin: 0;
   padding: 0;
@@ -67,9 +62,7 @@ export default defineComponent({
   color: white;
 }
 
-.metric {
-  margin-bottom: 2%;
-}
+
 
 .metric-name {
   font-size: xx-large;
@@ -90,4 +83,24 @@ export default defineComponent({
 .selected p {
   color: white;
 }
-</style>
+.tooltip {
+  position: relative;
+  display: inline-block;
+}
+
+.tooltip .tooltiptext {
+  visibility: hidden;
+  width: 240px;
+  background-color: black;
+  color: #fff;
+  text-align: center;
+  padding: 10px ;
+  border-radius: 6px;
+  position: absolute;
+  z-index: 1;
+}
+
+.tooltip:hover .tooltiptext {
+  visibility: visible;
+}
+</style>

+ 1 - 1
report-viewer/src/components/TextInformation.vue

@@ -130,7 +130,7 @@ hr {
   margin: 3% 0;
   box-shadow: inset var(--shadow-color) 0 0 3px;
   border-radius: 10px;
-  font-family: "JetBrains Mono", serif;
+  font-family: "JetBrains  NL", serif;
   font-size: smaller;
 }
 

+ 10 - 0
report-viewer/src/model/ClusterListElement.ts

@@ -0,0 +1,10 @@
+type ClusterListElement = {
+  averageSimilarity: number;
+  strength: number;
+  members: ClusterListElementMember;
+};
+type ClusterListElementMember = Map<
+  string,
+  Array<{ matchedWith: string; percentage: number }>
+>;
+export { ClusterListElement, ClusterListElementMember };

+ 95 - 91
report-viewer/src/model/Comparison.ts

@@ -1,96 +1,100 @@
-import {Match} from "./Match";
-import {SubmissionFile} from "./SubmissionFile"
-import {MatchInSingleFile} from "./MatchInSingleFile";
+import { Match } from "./Match";
+import { SubmissionFile } from "./SubmissionFile";
+import { MatchInSingleFile } from "./MatchInSingleFile";
 
 /**
  * Comparison model used by the ComparisonView
  */
 export class Comparison {
-    private readonly _firstSubmissionId: string
-    private readonly _secondSubmissionId: string
-    private readonly _match_percentage: number
-
-    constructor(firstSubmissionId: string, secondSubmissionId: string, match_percentage: number) {
-        this._firstSubmissionId = firstSubmissionId;
-        this._secondSubmissionId = secondSubmissionId;
-        this._match_percentage = match_percentage;
-        this._filesOfFirstSubmission = {}
-        this._filesOfSecondSubmission = {}
-        this._colors = []
-        this._allMatches = []
-        this._matchesInFirstSubmission = {}
-        this._matchesInSecondSubmissions = {}
-    }
-
-    private _filesOfFirstSubmission: Record<string, SubmissionFile>
-
-    get filesOfFirstSubmission(): Record<string, SubmissionFile> {
-        return this._filesOfFirstSubmission;
-    }
-
-    set filesOfFirstSubmission(value: Record<string, SubmissionFile>) {
-        this._filesOfFirstSubmission = value;
-    }
-
-    private _filesOfSecondSubmission: Record<string, SubmissionFile>
-
-    get filesOfSecondSubmission(): Record<string, SubmissionFile> {
-        return this._filesOfSecondSubmission;
-    }
-
-    set filesOfSecondSubmission(value: Record<string, SubmissionFile>) {
-        this._filesOfSecondSubmission = value;
-    }
-
-    private _colors: Array<string>
-
-    get colors(): Array<string> {
-        return this._colors;
-    }
-
-    set colors(value: Array<string>) {
-        this._colors = value;
-    }
-
-    private _allMatches: Array<Match>
-
-    get allMatches(): Array<Match> {
-        return this._allMatches;
-    }
-
-    set allMatches(value: Array<Match>) {
-        this._allMatches = value;
-    }
-
-    private _matchesInFirstSubmission: Record<string, Array<MatchInSingleFile>>
-
-    get matchesInFirstSubmission(): Record<string, Array<MatchInSingleFile>> {
-        return this._matchesInFirstSubmission;
-    }
-
-    set matchesInFirstSubmission(value: Record<string, Array<MatchInSingleFile>>) {
-        this._matchesInFirstSubmission = value;
-    }
-
-    private _matchesInSecondSubmissions: Record<string, Array<MatchInSingleFile>>
-
-    get matchesInSecondSubmissions(): Record<string, Array<MatchInSingleFile>> {
-        return this._matchesInSecondSubmissions;
-    }
-
-    set matchesInSecondSubmissions(value: Record<string, Array<MatchInSingleFile>>) {
-        this._matchesInSecondSubmissions = value;
-    }
-
-    get firstSubmissionId(): string {
-        return this._firstSubmissionId;
-    }
-
-    get secondSubmissionId(): string {
-        return this._secondSubmissionId;
-    }
-
-    get match_percentage(): number {
-        return this._match_percentage;
-    }
-}
+  private readonly _firstSubmissionId: string;
+  private readonly _secondSubmissionId: string;
+  private readonly _match_percentage: number;
+
+  constructor(
+    firstSubmissionId: string,
+    secondSubmissionId: string,
+    match_percentage: number
+  ) {
+    this._firstSubmissionId = firstSubmissionId;
+    this._secondSubmissionId = secondSubmissionId;
+    this._match_percentage = match_percentage;
+    this._filesOfFirstSubmission = new Map();
+    this._filesOfSecondSubmission = new Map();
+    this._colors = [];
+    this._allMatches = [];
+    this._matchesInFirstSubmission = new Map();
+    this._matchesInSecondSubmissions = new Map();
+  }
+
+  private _filesOfFirstSubmission: Map<string, SubmissionFile>;
+
+  get filesOfFirstSubmission(): Map<string, SubmissionFile> {
+    return this._filesOfFirstSubmission;
+  }
+
+  set filesOfFirstSubmission(value: Map<string, SubmissionFile>) {
+    this._filesOfFirstSubmission = value;
+  }
+
+  private _filesOfSecondSubmission: Map<string, SubmissionFile>;
+
+  get filesOfSecondSubmission(): Map<string, SubmissionFile> {
+    return this._filesOfSecondSubmission;
+  }
+
+  set filesOfSecondSubmission(value: Map<string, SubmissionFile>) {
+    this._filesOfSecondSubmission = value;
+  }
+
+  private _colors: Array<string>;
+
+  get colors(): Array<string> {
+    return this._colors;
+  }
+
+  set colors(value: Array<string>) {
+    this._colors = value;
+  }
+
+  private _allMatches: Array<Match>;
+
+  get allMatches(): Array<Match> {
+    return this._allMatches;
+  }
+
+  set allMatches(value: Array<Match>) {
+    this._allMatches = value;
+  }
+
+  private _matchesInFirstSubmission: Map<string, Array<MatchInSingleFile>>;
+
+  get matchesInFirstSubmission(): Map<string, Array<MatchInSingleFile>> {
+    return this._matchesInFirstSubmission;
+  }
+
+  set matchesInFirstSubmission(value: Map<string, Array<MatchInSingleFile>>) {
+    this._matchesInFirstSubmission = value;
+  }
+
+  private _matchesInSecondSubmissions: Map<string, Array<MatchInSingleFile>>;
+
+  get matchesInSecondSubmissions(): Map<string, Array<MatchInSingleFile>> {
+    return this._matchesInSecondSubmissions;
+  }
+
+  set matchesInSecondSubmissions(value: Map<string, Array<MatchInSingleFile>>) {
+    this._matchesInSecondSubmissions = value;
+  }
+
+  get firstSubmissionId(): string {
+    return this._firstSubmissionId;
+  }
+
+  get secondSubmissionId(): string {
+    return this._secondSubmissionId;
+  }
+
+  get match_percentage(): number {
+    return this._match_percentage;
+  }
+}

+ 1 - 0
report-viewer/src/model/Metric.ts

@@ -5,6 +5,7 @@ import {ComparisonListElement} from "./ComparisonListElement";
  */
 export type Metric = {
     metricName: string,
+    description: string,
     metricThreshold: number,
     distribution: Array<number>,
     comparisons: Array<ComparisonListElement>

+ 66 - 57
report-viewer/src/model/Overview.ts

@@ -1,70 +1,79 @@
-import {Metric} from "./Metric";
-import {Cluster} from "@/model/Cluster";
+import { Metric } from "./Metric";
+import { Cluster } from "@/model/Cluster";
 
 export class Overview {
-    private readonly _submissionFolderPath: Array<string>
-    private readonly _baseCodeFolderPath: string
-    private readonly _language: string
-    private readonly _fileExtensions: Array<string>
-    private readonly _matchSensitivity: number
-    private readonly _submissionIds: Array<string>
-    private readonly _dateOfExecution: string
-    private readonly _durationOfExecution: number
-    private readonly _metrics: Array<Metric>
-    private readonly _clusters: Array<Cluster>
+  private readonly _submissionFolderPath: Array<string>;
+  private readonly _baseCodeFolderPath: string;
+  private readonly _language: string;
+  private readonly _fileExtensions: Array<string>;
+  private readonly _matchSensitivity: number;
+  private readonly _submissionIds: Array<string>;
+  private readonly _dateOfExecution: string;
+  private readonly _durationOfExecution: number;
+  private readonly _metrics: Array<Metric>;
+  private readonly _clusters: Array<Cluster>;
 
+  constructor(
+    submissionFolderPath: Array<string>,
+    baseCodeFolderPath: string,
+    language: string,
+    fileExtensions: Array<string>,
+    matchSensitivity: number,
+    submissionIds: Array<string>,
+    dateOfExecution: string,
+    durationOfExecution: number,
+    metrics: Array<Metric>,
+    clusters: Array<Cluster>
+  ) {
+    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;
+  }
 
-    constructor(submissionFolderPath: Array<string>, baseCodeFolderPath: string, language: string, fileExtensions: Array<string>, matchSensitivity: number, submissionIds: Array<string>, dateOfExecution: string, durationOfExecution: number, metrics: Array<Metric>, clusters: Array<Cluster>) {
-        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
-    }
+  get submissionFolderPath(): Array<string> {
+    return this._submissionFolderPath;
+  }
 
+  get baseCodeFolderPath(): string {
+    return this._baseCodeFolderPath;
+  }
 
-    get submissionFolderPath(): Array<string> {
-        return this._submissionFolderPath;
-    }
+  get language(): string {
+    return this._language;
+  }
 
-    get baseCodeFolderPath(): string {
-        return this._baseCodeFolderPath;
-    }
+  get fileExtensions(): Array<string> {
+    return this._fileExtensions;
+  }
 
-    get language(): string {
-        return this._language;
-    }
+  get matchSensitivity(): number {
+    return this._matchSensitivity;
+  }
 
-    get fileExtensions(): Array<string> {
-        return this._fileExtensions
-    }
+  get submissionIds(): Array<string> {
+    return this._submissionIds;
+  }
 
-    get matchSensitivity(): number {
-        return this._matchSensitivity;
-    }
+  get dateOfExecution(): string {
+    return this._dateOfExecution;
+  }
 
-    get submissionIds(): Array<string> {
-        return this._submissionIds;
-    }
+  get durationOfExecution(): number {
+    return this._durationOfExecution;
+  }
 
-    get dateOfExecution(): string {
-        return this._dateOfExecution;
-    }
+  get metrics(): Array<Metric> {
+    return this._metrics;
+  }
 
-    get durationOfExecution(): number {
-        return this._durationOfExecution;
-    }
-
-    get metrics(): Array<Metric> {
-        return this._metrics;
-    }
-
-    get clusters(): Array<Cluster> {
-        return this._clusters
-    }
-}
+  get clusters(): Array<Cluster> {
+    return this._clusters;
+  }
+}

+ 108 - 93
report-viewer/src/model/factories/ComparisonFactory.ts

@@ -1,106 +1,121 @@
-import {Comparison} from "../Comparison";
-import {Match} from "../Match";
-import {SubmissionFile} from "../SubmissionFile";
-import {MatchInSingleFile} from "../MatchInSingleFile";
+import { Comparison } from "../Comparison";
+import { Match } from "../Match";
+import { SubmissionFile } from "../SubmissionFile";
+import { MatchInSingleFile } from "../MatchInSingleFile";
 
 export class ComparisonFactory {
+  static getComparison(json: Record<string, unknown>): Comparison {
+    const filesOfFirst = this.convertToFilesByName(
+      json.files_of_first_submission as Array<Record<string, unknown>>
+    );
+    const filesOfSecond = this.convertToFilesByName(
+      json.files_of_second_submission as Array<Record<string, unknown>>
+    );
 
-    static getComparison(json: Record<string, unknown>): Comparison {
-        const filesOfFirst = this.convertToFilesByName(json.files_of_first_submission as Array<Record<string, unknown>>)
-        const filesOfSecond = this.convertToFilesByName(json.files_of_second_submission as Array<Record<string, unknown>>)
+    const matches = json.matches as Array<Record<string, unknown>>;
+    const colors = this.generateColorsForMatches(matches.length);
+    const coloredMatches = matches.map((match, index) =>
+      this.mapMatch(match, colors[index])
+    );
 
-        const matches = json.matches as Array<Record<string, unknown>>
-        const colors = this.generateColorsForMatches(matches.length)
-        const coloredMatches = matches.map((match, index) => this.mapMatch(match, colors[index]))
+    const matchesInFirst = this.groupMatchesByFileName(coloredMatches, 1);
+    const matchesInSecond = this.groupMatchesByFileName(coloredMatches, 2);
 
-        const matchesInFirst = this.groupMatchesByFileName(coloredMatches, 1)
-        const matchesInSecond = this.groupMatchesByFileName(coloredMatches, 2)
+    const comparison = new Comparison(
+      json.first_submission_id as string,
+      json.second_submission_id as string,
+      json.match_percentage as number
+    );
+    comparison.filesOfFirstSubmission = filesOfFirst;
+    comparison.filesOfSecondSubmission = filesOfSecond;
+    comparison.colors = colors;
+    comparison.allMatches = coloredMatches;
+    comparison.matchesInFirstSubmission = matchesInFirst;
+    comparison.matchesInSecondSubmissions = matchesInSecond;
 
-        const comparison = new Comparison(
-            json.first_submission_id as string,
-            json.second_submission_id as string,
-            json.match_percentage as number
-        )
-        comparison.filesOfFirstSubmission = filesOfFirst
-        comparison.filesOfSecondSubmission = filesOfSecond
-        comparison.colors = colors
-        comparison.allMatches = coloredMatches
-        comparison.matchesInFirstSubmission = matchesInFirst
-        comparison.matchesInSecondSubmissions = matchesInSecond
+    return comparison;
+  }
 
-        return comparison
-    }
-
-    private static convertToFilesByName(files: Array<Record<string, unknown>>): Record<string, SubmissionFile> {
-        return files.reduce((acc: Record<string, SubmissionFile>, val: Record<string, unknown>) => {
-            if (!acc[val.file_name as string]) {
-                acc[val.file_name as string] = {
-                    lines: [],
-                    collapsed: false
-                }
-            }
-            acc[val.file_name as string] = {
-                lines: val.lines as Array<string>,
-                collapsed: false
-            }
-            return acc;
-        }, {})
-    }
-
-    private static groupMatchesByFileName(matches: Array<Match>, index: number): Record<string, Array<MatchInSingleFile>> {
-        return matches.reduce((acc: Record<string, Array<MatchInSingleFile>>, val: Match) => {
-            const name = index === 1 ? val.firstFile as string : val.secondFile as string
-            if (!acc[name]) {
-                acc[name] = []
-            }
-            if (index === 1) {
-                const newVal: MatchInSingleFile = {
-                    start: val.startInFirst as number,
-                    end: val.endInFirst as number,
-                    linked_panel: 2,
-                    linked_file: val.secondFile as string,
-                    linked_line: val.startInSecond as number,
-                    color: val.color as string
-                }
-                acc[name].push(newVal)
-            } else {
-                const newVal: MatchInSingleFile = {
-                    start: val.startInSecond as number,
-                    end: val.endInSecond as number,
-                    linked_panel: 1,
-                    linked_file: val.firstFile as string,
-                    linked_line: val.startInFirst as number,
-                    color: val.color as string
-                }
-                acc[name].push(newVal)
-            }
-            return acc;
-        }, {})
-    }
+  private static convertToFilesByName(
+    files: Array<Record<string, unknown>>
+  ): Map<string, SubmissionFile> {
+    const map = new Map<string, SubmissionFile>();
+    files.forEach((val) => {
+      if (!map.get(val.file_name as string)) {
+        map.set(val.file_name as string, {
+          lines: [],
+          collapsed: false,
+        });
+      }
+      map.set(val.file_name as string, {
+        lines: val.lines as Array<string>,
+        collapsed: false,
+      });
+    });
+    return map;
+  }
 
-    private static generateColorsForMatches(num: number): Array<string> {
-        const colors = []
-        const hueDelta = Math.trunc(360 / (num))
+  private static groupMatchesByFileName(
+    matches: Array<Match>,
+    index: number
+  ): Map<string, Array<MatchInSingleFile>> {
+    const acc = new Map<string, Array<MatchInSingleFile>>();
+    matches.forEach((val) => {
+      const name =
+        index === 1 ? (val.firstFile as string) : (val.secondFile as string);
+      if (!acc.get(name)) {
+        acc.set(name, []);
+      }
+      if (index === 1) {
+        const newVal: MatchInSingleFile = {
+          start: val.startInFirst as number,
+          end: val.endInFirst as number,
+          linked_panel: 2,
+          linked_file: val.secondFile as string,
+          linked_line: val.startInSecond as number,
+          color: val.color as string,
+        };
+        acc.get(name)?.push(newVal);
+      } else {
+        const newVal: MatchInSingleFile = {
+          start: val.startInSecond as number,
+          end: val.endInSecond as number,
+          linked_panel: 1,
+          linked_file: val.firstFile as string,
+          linked_line: val.startInFirst as number,
+          color: val.color as string,
+        };
+        acc.get(name)?.push(newVal);
+      }
+    });
+    return acc;
+  }
 
-        for (let i = 0; i < num; i++) {
-            const hue = i * hueDelta
+  private static generateColorsForMatches(num: number): Array<string> {
+    const colors = [];
+    const hueDelta = Math.trunc(360 / num);
 
-            colors.push(`hsla(${hue}, 80%, 50%, 0.3)`)
-        }
-        return colors
-    }
+    for (let i = 0; i < num; i++) {
+      const hue = i * hueDelta;
 
-    private static mapMatch(match: Record<string, unknown>, color: string): Match {
-        return {
-            firstFile: match.first_file_name as string,
-            secondFile: match.second_file_name as string,
-            startInFirst: match.start_in_first as number,
-            endInFirst: match.end_in_first as number,
-            startInSecond: match.start_in_second as number,
-            endInSecond: match.end_in_second as number,
-            tokens: match.tokens as number,
-            color: color
-        };
+      colors.push(`hsla(${hue}, 80%, 50%, 0.3)`);
     }
+    return colors;
+  }
 
-}
+  private static mapMatch(
+    match: Record<string, unknown>,
+    color: string
+  ): Match {
+    return {
+      firstFile: match.first_file_name as string,
+      secondFile: match.second_file_name as string,
+      startInFirst: match.start_in_first as number,
+      endInFirst: match.end_in_first as number,
+      startInSecond: match.start_in_second as number,
+      endInSecond: match.end_in_second as number,
+      tokens: match.tokens as number,
+      color: color,
+    };
+  }
+}

+ 60 - 61
report-viewer/src/model/factories/OverviewFactory.ts

@@ -1,67 +1,66 @@
-import {Overview} from "../Overview";
-import {Metric} from "../Metric";
-import {ComparisonListElement} from "../ComparisonListElement";
-import {Cluster} from "@/model/Cluster";
+import { Overview } from "../Overview";
+import { Metric } from "../Metric";
+import { ComparisonListElement } from "../ComparisonListElement";
+import { Cluster } from "@/model/Cluster";
 
 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 dateOfExecution = json.date_of_execution as string;
+    const duration = json.execution_time as number as number;
+    const metrics = [] as Array<Metric>;
+    const clusters = [] as Array<Cluster>;
+    (json.metrics as Array<unknown>).forEach((jsonMetric) => {
+      const metric = jsonMetric as Record<string, unknown>;
+      const comparisons = [] as Array<ComparisonListElement>;
 
-    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 dateOfExecution = json.date_of_execution as string
-        const duration = json.execution_time as number as number
-        const metrics = [] as Array<Metric>
-        const clusters = [] as Array<Cluster>
-        (json.metrics as Array<unknown>).forEach((jsonMetric) => {
-            const metric = jsonMetric as Record<string, unknown>
-            const comparisons = [] as Array<ComparisonListElement>
-
-            (metric.topComparisons as Array<Record<string, unknown>>).forEach(jsonComparison => {
-                const comparison: ComparisonListElement = {
-                    firstSubmissionId: jsonComparison.first_submission as string,
-                    secondSubmissionId: jsonComparison.second_submission as string,
-                    matchPercentage: jsonComparison.match_percentage as number
-                }
-                comparisons.push(comparison)
-            })
-
-            const newMetric: Metric = {
-                metricName: metric.name as string,
-                metricThreshold: metric.threshold as number,
-                distribution: metric.distribution as Array<number>,
-                comparisons: comparisons
-            }
-            metrics.push(newMetric)
-        });
-
-        if (json.clusters) {
-            (json.clusters as Array<unknown>).forEach(jsonCluster => {
-                const cluster = jsonCluster as Record<string, unknown>
-                const newCluster: Cluster = {
-                    averageSimilarity: cluster.average_similarity as number,
-                    strength: cluster.strength as number,
-                    members: cluster.members as Array<string>
-                }
-                clusters.push(newCluster)
-            })
+      (metric.topComparisons as Array<Record<string, unknown>>).forEach(
+        (jsonComparison) => {
+          const comparison: ComparisonListElement = {
+            firstSubmissionId: jsonComparison.first_submission as string,
+            secondSubmissionId: jsonComparison.second_submission as string,
+            matchPercentage: jsonComparison.match_percentage as number,
+          };
+          comparisons.push(comparison);
         }
+      );
+      metrics.push({
+        metricName: metric.name as string,
+        metricThreshold: metric.threshold as number,
+        distribution: metric.distribution as Array<number>,
+        comparisons: comparisons,
+        description: metric.description as string
+      });
+    });
 
-
-        return new Overview(
-            submissionFolder,
-            baseCodeFolder,
-            language,
-            fileExtensions,
-            matchSensitivity,
-            submissions,
-            dateOfExecution,
-            duration,
-            metrics,
-            clusters
-        )
+    if (json.clusters) {
+      (json.clusters as Array<unknown>).forEach((jsonCluster) => {
+        const cluster = jsonCluster as Record<string, unknown>;
+        const newCluster: Cluster = {
+          averageSimilarity: cluster.average_similarity as number,
+          strength: cluster.strength as number,
+          members: cluster.members as Array<string>,
+        };
+        clusters.push(newCluster);
+      });
     }
-}
+
+    return new Overview(
+      submissionFolder,
+      baseCodeFolder,
+      language,
+      fileExtensions,
+      matchSensitivity,
+      submissions,
+      dateOfExecution,
+      duration,
+      metrics,
+      clusters
+    );
+  }
+}

+ 22 - 25
report-viewer/src/router/index.ts

@@ -1,36 +1,33 @@
-import {createRouter, createWebHistory, RouteRecordRaw} from "vue-router";
-import OverviewView from "@/views/OverviewView.vue"
-import ComparisonView from "@/views/ComparisonView.vue"
-import FileUploadView from "@/views/FileUploadView.vue"
+import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
+import OverviewView from "@/views/OverviewView.vue";
+import ComparisonView from "@/views/ComparisonView.vue";
+import FileUploadView from "@/views/FileUploadView.vue";
 
 /**
  * Router containing the navigation destinations.
  */
 const routes: Array<RouteRecordRaw> = [
-    {
-        path: "/",
-        name: "FileUploadView",
-        component: FileUploadView
-    },
-    {
-        path: "/overview",
-        name: "OverviewView",
-        component: OverviewView,
-    },
-    {
-        path: "/comparison",
-        name: "ComparisonView",
-        component: ComparisonView,
-        props: route => ({
-            firstId: route.query.firstId,
-            secondId: route.query.secondId
-        })
-    },
+  {
+    path: "/",
+    name: "FileUploadView",
+    component: FileUploadView,
+  },
+  {
+    path: "/overview",
+    name: "OverviewView",
+    component: OverviewView,
+  },
+  {
+    path: "/comparison",
+    name: "ComparisonView",
+    component: ComparisonView,
+    props: true,
+  },
 ];
 
 const router = createRouter({
-    history: createWebHistory("/JPlag"),
-    routes,
+  history: createWebHistory("/JPlag"),
+  routes,
 });
 
 export default router;

+ 93 - 59
report-viewer/src/store/store.ts

@@ -1,65 +1,99 @@
-import {createStore} from "vuex";
+import { createStore } from "vuex";
 
 /**
  * Local store. Stores the state of the application.
  */
-const store = createStore({
-    state() {
-        return {
-            /**
-             * The set of ids to be hidden.
-             */
-            anonymous: new Set(),
-            /**
-             * Stored files if zip mode is used. Stores the files as key - file name, value - file string
-             */
-            files: Object,
-            /**
-             * Indicates whether local mode is used.
-             */
-            local: Boolean,
-            /**
-             * Indicates whether zip mode is used.
-             */
-            zip: Boolean,
-            /**
-             * Indicates whether single file mode is used.
-             */
-            single: Boolean,
-            /**
-             * Files string if single mode is used.
-             */
-            fileString: {
-                type: String,
-                required: false
-            }
-        }
-    },
-    mutations: {
-        addAnonymous(state: any, id) {
-            for (let i = 0; i < id.length; i++) {
-                state.anonymous.add(id[i])
-            }
-        },
-        removeAnonymous(state, id) {
-            for (let i = 0; i < id.length; i++) {
-                state.anonymous.delete(id[i])
-            }
-        },
-        resetAnonymous(state) {
-            state.anonymous = new Set()
-        },
-        saveFile(state, file: Record<string, any>) {
-            state.files[file.fileName] = file.data
-        },
-        setLoadingType(state, payload: Record<string, any>) {
-            state.local = payload.local
-            state.zip = payload.zip
-            state.single = payload.single
-            state.fileString = payload.fileString
-        }
 
-    }
-})
+interface State {
+  /**
+   * The set of ids to be hidden.
+   */
+  anonymous: Set<string>;
+  /**
+   * Stored files if zip mode is used. Stores the files as key - file name, value - file string
+   */
+  files: Record<string, string>;
+  /**
+   * Indicates whether local mode is used.
+   */
+  local: boolean;
+  /**
+   * Indicates whether zip mode is used.
+   */
+  zip: boolean;
+  /**
+   * Indicates whether single file mode is used.
+   */
+  single: boolean;
+  /**
+   * Files string if single mode is used.
+   */
+  fileString: string;
+}
+
+interface File {
+  fileName: string;
+  data: string;
+}
+
+interface LoadConfiguration {
+  local: boolean;
+  zip: boolean;
+  single: boolean;
+  fileString: string;
+}
+
+const store = createStore<State>({
+  state: {
+    /**
+     * The set of ids to be hidden.
+     */
+    anonymous: new Set(),
+    /**
+     * Stored files if zip mode is used. Stores the files as key - file name, value - file string
+     */
+    files: {},
+    /**
+     * Indicates whether local mode is used.
+     */
+    local: false,
+    /**
+     * Indicates whether zip mode is used.
+     */
+    zip: false,
+    /**
+     * Indicates whether single file mode is used.
+     */
+    single: false,
+    /**
+     * Files string if single mode is used.
+     */
+    fileString: "",
+  },
+  mutations: {
+    addAnonymous(state: State, id) {
+      for (let i = 0; i < id.length; i++) {
+        state.anonymous.add(id[i]);
+      }
+    },
+    removeAnonymous(state, id) {
+      for (let i = 0; i < id.length; i++) {
+        state.anonymous.delete(id[i]);
+      }
+    },
+    resetAnonymous(state) {
+      state.anonymous = new Set();
+    },
+    saveFile(state, file: File) {
+      state.files[file.fileName] = file.data;
+    },
+    setLoadingType(state, payload: LoadConfiguration) {
+      state.local = payload.local;
+      state.zip = payload.zip;
+      state.single = payload.single;
+      state.fileString = payload.fileString;
+    },
+  },
+});
 
-export default store
+export default store;

+ 147 - 70
report-viewer/src/views/ComparisonView.vue

@@ -3,102 +3,164 @@
 -->
 <template>
   <div class="container">
-    <button id="show-button" :class="{hidden : !hideLeftPanel}" title="Show sidebar" @click="togglePanel">
-      <img alt="show" src="@/assets/double_arrow_black_24dp.svg">
+    <button
+      id="show-button"
+      :class="{ hidden: !hideLeftPanel }"
+      title="Show sidebar"
+      @click="togglePanel"
+    >
+      <img alt="show" src="@/assets/double_arrow_black_24dp.svg" />
     </button>
-    <div id="sidebar" :class="{ hidden : hideLeftPanel }">
+    <div id="sidebar" :class="{ hidden: hideLeftPanel }">
       <div class="title-section">
         <h1>JPlag Comparison</h1>
         <button id="hide-button" title="Hide sidebar" @click="togglePanel">
-          <img alt="hide" src="@/assets/keyboard_double_arrow_left_black_24dp.svg"></button>
+          <img
+            alt="hide"
+            src="@/assets/keyboard_double_arrow_left_black_24dp.svg"
+          />
+        </button>
       </div>
-      <TextInformation :anonymous="store.state.anonymous.has(firstId)" :value="firstId" label="Submission 1"/>
-      <TextInformation :anonymous="store.state.anonymous.has(secondId)" :value="secondId" label="Submission 2"/>
-      <TextInformation :value="comparison.match_percentage" label="Match %"/>
-      <MatchTable :id1="firstId" :id2="secondId" :matches="comparison.allMatches" @match-selected="showMatch"/>
+      <TextInformation
+        :anonymous="isAnonymous(firstId)"
+        :value="firstId"
+        label="Submission 1"
+      />
+      <TextInformation
+        :anonymous="store.state.anonymous.has(secondId)"
+        :value="secondId"
+        label="Submission 2"
+      />
+      <TextInformation :value="comparison.match_percentage" label="Match %" />
+      <MatchTable
+        :id1="firstId"
+        :id2="secondId"
+        :matches="comparison.allMatches"
+        @match-selected="showMatch"
+      />
     </div>
-    <FilesContainer :container-id="1" :files="filesOfFirst" :matches="comparison.matchesInFirstSubmission"
-                    files-owner="Submission 1"
-                    @toggle-collapse="toggleCollapseFirst"
-                    @line-selected="showMatchInSecond"/>
-    <FilesContainer :container-id="2" :files="filesOfSecond" :matches="comparison.matchesInSecondSubmissions"
-                    files-owner="Submission 2"
-                    @toggle-collapse="toggleCollapseSecond"
-                    @line-selected="showMatchInFirst"/>
+    <FilesContainer
+      :container-id="1"
+      :files="filesOfFirst"
+      :matches="comparison.matchesInFirstSubmission"
+      files-owner="Submission 1"
+      @toggle-collapse="toggleCollapseFirst"
+      @line-selected="showMatchInSecond"
+    />
+    <FilesContainer
+      :container-id="2"
+      :files="filesOfSecond"
+      :matches="comparison.matchesInSecondSubmissions"
+      files-owner="Submission 2"
+      @toggle-collapse="toggleCollapseSecond"
+      @line-selected="showMatchInFirst"
+    />
   </div>
 </template>
 
-<script>
-import {defineComponent, ref} from "vue";
-import {generateLineCodeLink} from "@/utils/Utils";
-import store from "@/store/store";
-import router from "@/router";
-import TextInformation from "@/components/TextInformation";
-import MatchTable from "@/components/MatchTable";
-import {ComparisonFactory} from "@/model/factories/ComparisonFactory";
-import FilesContainer from "@/components/FilesContainer";
+<script lang="ts">
+import { defineComponent, ref } from "vue";
+import { generateLineCodeLink } from "@/utils/Utils";
+import TextInformation from "@/components/TextInformation.vue";
+import MatchTable from "@/components/MatchTable.vue";
+import { ComparisonFactory } from "@/model/factories/ComparisonFactory";
+import FilesContainer from "@/components/FilesContainer.vue";
+import { useStore } from "vuex";
+import { useRouter } from "vue-router";
+import { Match } from "@/model/Match";
 
 export default defineComponent({
   name: "ComparisonView",
-  components: {FilesContainer, MatchTable, TextInformation},
+  components: { FilesContainer, MatchTable, TextInformation },
   props: {
     firstId: {
       type: String,
+      required: true,
     },
     secondId: {
       type: String,
+      required: true,
     },
   },
   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)
+    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)
     if (store.state.local) {
       try {
-        // eslint-disable-next-line @typescript-eslint/no-var-requires
-        comparison = ComparisonFactory.getComparison(require(`../files/${fileName1}.json`))
+        comparison = ComparisonFactory.getComparison(
+          // eslint-disable-next-line @typescript-eslint/no-var-requires
+          require(`../files/${fileName1}.json`)
+        );
       } catch (exception) {
         try {
-          // eslint-disable-next-line @typescript-eslint/no-var-requires
-          comparison = ComparisonFactory.getComparison(require(`../files/${fileName2}.json`))
+          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) {
-      if (store.state.files[fileName1.concat(".json")]) {
-        comparison = ComparisonFactory.getComparison(JSON.parse(store.state.files[fileName1.concat(".json")]))
-      } else {
-        comparison = ComparisonFactory.getComparison(JSON.parse(store.state.files[fileName2.concat(".json")]))
-      }
+      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);
+      if (comparisonFile) {
+        comparison = ComparisonFactory.getComparison(
+          JSON.parse(comparisonFile)
+        );
+      }
     } else if (store.state.single) {
-      comparison = ComparisonFactory.getComparison(JSON.parse(store.state.fileString))
+      comparison = ComparisonFactory.getComparison(
+        JSON.parse(store.state.fileString)
+      );
     }
 
-    const filesOfFirst = ref(comparison.filesOfFirstSubmission)
-    const filesOfSecond = ref(comparison.filesOfSecondSubmission)
+    if (!comparison) {
+      console.warn("Could not build comparison file");
+      return;
+    }
+    const filesOfFirst = ref(comparison.filesOfFirstSubmission);
+    const filesOfSecond = ref(comparison.filesOfSecondSubmission);
 
     /**
      * Collapses a file in the first files container.
      * @param title
      */
-    const toggleCollapseFirst = (title) => {
-      filesOfFirst.value[title].collapsed = !filesOfFirst.value[title].collapsed
-    }
+    const toggleCollapseFirst = (title: string) => {
+      const file = filesOfFirst.value.get(title);
+      if (file) {
+        file.collapsed = !file.collapsed;
+      }
+    };
     /**
      * Collapses a file in the second files container.
      * @param title
      */
-    const toggleCollapseSecond = (title) => {
-      filesOfSecond.value[title].collapsed = !filesOfSecond.value[title].collapsed
-    }
+    const toggleCollapseSecond = (title: string) => {
+      const file = filesOfSecond.value.get(title);
+      if (file) {
+        file.collapsed = !file.collapsed;
+      }
+    };
     /**
      * Shows a match in the first files container
      * @param e
@@ -106,12 +168,19 @@ export default defineComponent({
      * @param file
      * @param line
      */
-    const showMatchInFirst = (e, panel, file, line) => {
-      if (!filesOfFirst.value[file].collapsed) {
-        toggleCollapseFirst(file)
+    const showMatchInFirst = (
+      e: unknown,
+      panel: number,
+      file: string,
+      line: number
+    ) => {
+      if (!filesOfFirst.value.get(file)?.collapsed) {
+        toggleCollapseFirst(file);
       }
-      document.getElementById(generateLineCodeLink(panel, file, line)).scrollIntoView()
-    }
+      document
+        .getElementById(generateLineCodeLink(panel, file, line))
+        ?.scrollIntoView();
+    };
     /**
      * Shows a match in the second files container.
      * @param e
@@ -119,24 +188,31 @@ export default defineComponent({
      * @param file
      * @param line
      */
-    const showMatchInSecond = (e, panel, file, line) => {
-      if (!filesOfSecond.value[file].collapsed) {
-        toggleCollapseSecond(file)
+    const showMatchInSecond = (
+      e: unknown,
+      panel: number,
+      file: string,
+      line: number
+    ) => {
+      if (!filesOfSecond.value.get(file)?.collapsed) {
+        toggleCollapseSecond(file);
       }
-      document.getElementById(generateLineCodeLink(panel, file, line)).scrollIntoView()
-    }
-
-    const showMatch = (e, match) => {
-      showMatchInFirst(e, 1, match.firstFile, match.startInFirst)
-      showMatchInSecond(e, 2, match.secondFile, match.startInSecond)
-    }
+      document
+        .getElementById(generateLineCodeLink(panel, file, line))
+        ?.scrollIntoView();
+    };
 
+    const showMatch = (e: unknown, match: Match) => {
+      showMatchInFirst(e, 1, match.firstFile, match.startInFirst);
+      showMatchInSecond(e, 2, match.secondFile, match.startInSecond);
+    };
 
+    const isAnonymous = (id: string) => store.state.anonymous.has(id);
     //Left panel
-    const hideLeftPanel = ref(true)
+    const hideLeftPanel = ref(true);
     const togglePanel = () => {
-      hideLeftPanel.value = !hideLeftPanel.value
-    }
+      hideLeftPanel.value = !hideLeftPanel.value;
+    };
 
     return {
       comparison,
@@ -150,11 +226,12 @@ export default defineComponent({
       showMatchInSecond,
       showMatch,
       togglePanel,
+      isAnonymous,
 
-      store
-    }
-  }
-})
+      store,
+    };
+  },
+});
 </script>
 
 <style scoped>
@@ -234,4 +311,4 @@ h1 {
 #show-button:hover img {
   display: block;
 }
-</style>
+</style>

+ 89 - 64
report-viewer/src/views/FileUploadView.vue

@@ -2,26 +2,28 @@
   Starting view of the application. Presents the options for loading a JPlag report.
 -->
 <template>
-  <div class="container" @dragover.prevent @drop.prevent>
-    <img alt="JPlag" src="@/assets/logo-nobg.png"/>
+  <div class="container" @dragover.prevent @drop.prevent="uploadFile">
+    <img alt="JPlag" src="@/assets/logo-nobg.png" />
     <h1>JPlag Report Viewer</h1>
     <h2>Select an overview or comparison file or a zip to display.</h2>
-    <div class="drop-container" @drop="uploadFile">
-      <p> Drop a .json or .zip file here</p>
+    <div class="drop-container">
+      <p>Drop a .json or .zip on this page</p>
     </div>
     <div v-if="hasLocalFile" class="local-files-container">
       <p class="local-files-text">Detected local files!</p>
-      <button class="local-files-button" @click="continueWithLocal">Continue with local files</button>
+      <button class="local-files-button" @click="continueWithLocal">
+        Continue with local files
+      </button>
     </div>
   </div>
 </template>
 
-<script>
-import {defineComponent} from "vue";
-import jszip from "jszip"
+<script lang="ts">
+import { defineComponent } from "vue";
+import jszip from "jszip";
 import router from "@/router";
 import store from "@/store/store";
-import {getFileExtension} from "@/utils/Utils";
+import { getFileExtension } from "@/utils/Utils";
 
 export default defineComponent({
   name: "FileUploadView",
@@ -29,95 +31,119 @@ export default defineComponent({
     let hasLocalFile;
     //Tries to detect local file. If no files detected, hides local mode from screen.
     try {
-      require("../files/overview.json")
-      hasLocalFile = true
+      require("../files/overview.json");
+      hasLocalFile = true;
     } catch (e) {
-      console.log(e)
-      hasLocalFile = false
+      console.log(e);
+      hasLocalFile = false;
     }
 
-
     const navigateToOverview = () => {
       router.push({
-            name: "OverviewView",
-          }
-      )
-    }
+        name: "OverviewView",
+      });
+    };
 
-    const navigateToComparisonView = (id1, id2) => {
+    const navigateToComparisonView = (firstId: string, secondId: string) => {
       router.push({
         name: "ComparisonView",
-        query: {
-          id1: id1,
-          id2: id2
-        }
-      })
-    }
+        params: {
+          firstId,
+          secondId,
+        },
+      });
+    };
     /**
      * Handles zip file on drop. It extracts the zip and saves each file in the store.
      * @param file
      */
-    const handleZipFile = (file) => {
+    const handleZipFile = (file: File) => {
       jszip.loadAsync(file).then(async (zip) => {
         for (const fileName of Object.keys(zip.files)) {
-          console.log(fileName)
-          await zip.files[fileName].async("string").then(data => {
-                store.commit("saveFile", {fileName: fileName, data: data})
-              }
-          )
+          console.log(fileName);
+          await zip.files[fileName].async("string").then((data) => {
+            store.commit("saveFile", { fileName: fileName, data: data });
+          });
         }
-        store.commit("setLoadingType", {local: false, zip: true, single: false, fileString: ""})
-        navigateToOverview()
-      })
-    }
+        store.commit("setLoadingType", {
+          local: false,
+          zip: true,
+          single: false,
+          fileString: "",
+        });
+        navigateToOverview();
+      });
+    };
     /**
      * Handles a json file on drop. It read the file and passes the file string to next window.
      * @param str
      */
-    const handleJsonFile = (str) => {
-      let json = JSON.parse(str)
+    const handleJsonFile = (str: string) => {
+      let json = JSON.parse(str);
       if (json["submission_folder_path"]) {
-        store.commit("setLoadingType", {local: false, zip: false, single: true, fileString: str})
-        navigateToOverview()
+        store.commit("setLoadingType", {
+          local: false,
+          zip: false,
+          single: true,
+          fileString: str,
+        });
+        navigateToOverview();
       } else if (json["first_submission_id"] && json["second_submission_id"]) {
-        store.commit("setLoadingType", {local: false, zip: false, single: true, fileString: str})
-        navigateToComparisonView(json["first_submission_id"], json["second_submission_id"])
+        store.commit("setLoadingType", {
+          local: false,
+          zip: false,
+          single: true,
+          fileString: str,
+        });
+        navigateToComparisonView(
+          json["first_submission_id"],
+          json["second_submission_id"]
+        );
       }
-    }
+    };
     /**
      * Handles file drop.
      * @param e
      */
-    const uploadFile = (e) => {
-      let dropped = e.dataTransfer.files
-      if (!dropped) return
-      let files = [...dropped]
-      if (files.length > 1 || files.length === 0) return
-      let read = new FileReader()
+    const uploadFile = (e: DragEvent) => {
+      let dropped = e.dataTransfer?.files;
+      if (!dropped) return; // TODO add some kind of error message
+      let files = [...dropped];
+      if (files.length > 1 || files.length === 0) return;
+      let read = new FileReader();
       read.onload = (e) => {
-        let extension = getFileExtension(files[0])
+        let extension = getFileExtension(files[0]);
         if (extension === "zip") {
-          handleZipFile(files[0])
+          handleZipFile(files[0]);
         } else if (extension === "json") {
-          handleJsonFile(e.target.result)
+          let file = e.target?.result;
+          if (!file) return;
+          if (typeof file === "string") {
+            handleJsonFile(file);
+          }
         }
-      }
-      read.readAsText(files[0])
-    }
+      };
+      read.readAsText(files[0]);
+    };
     /**
      * Handles click on Continue with local files.
      */
     const continueWithLocal = () => {
-      store.commit("setLoadingType", {local: true, zip: false, single: false, fileString: ""})
-      navigateToOverview()
-    }
+      store.commit("setLoadingType", {
+        local: true,
+        zip: false,
+        single: false,
+        fileString: "",
+      });
+      navigateToOverview();
+    };
     return {
       continueWithLocal,
       uploadFile,
-      hasLocalFile
-    }
-  }
-})
+      hasLocalFile,
+    };
+  },
+});
 </script>
 
 <style scoped>
@@ -179,11 +205,10 @@ input {
 label {
   font-weight: bold;
   font-size: larger;
-  background: #ECECEC;
+  background: #ececec;
   border-radius: 10px;
   box-shadow: #777777 2px 3px 3px;
   padding: 2%;
   margin-top: 1%;
 }
-
-</style>
+</style>

+ 134 - 73
report-viewer/src/views/OverviewView.vue

@@ -7,25 +7,51 @@
       <h1>JPlag Report</h1>
       <p class="section-title">Main Info:</p>
       <div id="basicInfo">
-        <TextInformation :has-additional-info="hasMoreSubmissionPaths" :value="submissionPathValue"
-                         additional-info-title="" label="Directory path">
-          <p v-for="path in overview.submissionFolderPath" :key="path" :title="path">{{ path }}</p>
+        <TextInformation
+          :has-additional-info="hasMoreSubmissionPaths"
+          :value="submissionPathValue"
+          additional-info-title=""
+          label="Directory path"
+        >
+          <p
+            v-for="path in overview.submissionFolderPath"
+            :key="path"
+            :title="path"
+          >
+            {{ path }}
+          </p>
         </TextInformation>
-        <TextInformation :has-additional-info="true" :value="overview.language" additional-info-title="File extensions:"
-                         label="Language">
+        <TextInformation
+          :has-additional-info="true"
+          :value="overview.language"
+          additional-info-title="File extensions:"
+          label="Language"
+        >
           <p v-for="info in overview.fileExtensions" :key="info">{{ info }}</p>
         </TextInformation>
-        <TextInformation :value="overview.matchSensitivity" label="Match Sensitivity"/>
-        <TextInformation :has-additional-info="true" :value="overview.submissionIds.length"
-                         additional-info-title="Submission IDs:"
-                         label="Submissions">
-          <IDsList :ids="overview.submissionIds" @id-sent="handleId"/>
+        <TextInformation
+          :value="overview.matchSensitivity"
+          label="Match Sensitivity"
+        />
+        <TextInformation
+          :has-additional-info="true"
+          :value="overview.submissionIds.length"
+          additional-info-title="Submission IDs:"
+          label="Submissions"
+        >
+          <IDsList :ids="overview.submissionIds" @id-sent="handleId" />
         </TextInformation>
-        <TextInformation :value="overview.dateOfExecution" label="Date of execution"/>
-        <TextInformation :value="overview.durationOfExecution" label="Duration (in ms)"/>
+        <TextInformation
+          :value="overview.dateOfExecution"
+          label="Date of execution"
+        />
+        <TextInformation
+          :value="overview.durationOfExecution"
+          label="Duration (in ms)"
+        />
       </div>
       <div id="logo-section">
-        <img id="logo" alt="JPlag" src="@/assets/logo-nobg.png">
+        <img id="logo" alt="JPlag" src="@/assets/logo-nobg.png" />
       </div>
     </div>
 
@@ -33,55 +59,87 @@
       <div id="metrics">
         <p class="section-title">Metric:</p>
         <div id="metrics-list">
-          <MetricButton v-for="(metric, i) in overview.metrics" :id="metric.metricName"
-                        :key="metric.metricName"
-                        :is-selected="selectedMetric[i]"
-                        :metric-name="metric.metricName"
-                        :metric-threshold="metric.metricThreshold"
-                        @click="selectMetric(i)"/>
+          <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]" class="full-width"/>
+      <DistributionDiagram
+        :distribution="distributions[selectedMetricIndex]"
+        class="full-width"
+      />
     </div>
     <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>
 </template>
 
-<script>
-import {defineComponent, ref} from "vue";
-import store from "@/store/store";
+<script lang="ts">
+import { computed, defineComponent, Ref, ref } from "vue";
 import router from "@/router";
-import TextInformation from "../components/TextInformation";
-import DistributionDiagram from "@/components/DistributionDiagram";
-import MetricButton from "@/components/MetricButton";
-import ComparisonsTable from "@/components/ComparisonsTable";
-import {OverviewFactory} from "@/model/factories/OverviewFactory";
-import IDsList from "@/components/IDsList";
+import TextInformation from "../components/TextInformation.vue";
+import DistributionDiagram from "@/components/DistributionDiagram.vue";
+import MetricButton from "@/components/MetricButton.vue";
+import ComparisonsTable from "@/components/ComparisonsTable.vue";
+import { OverviewFactory } from "@/model/factories/OverviewFactory";
+import IDsList from "@/components/IDsList.vue";
+import { useStore } from "vuex";
+import { Overview } from "@/model/Overview";
+import { ComparisonListElement } from "@/model/ComparisonListElement";
 
 export default defineComponent({
   name: "OverviewView",
-  components: {IDsList, ComparisonsTable, DistributionDiagram, MetricButton, TextInformation},
+  components: {
+    IDsList,
+    ComparisonsTable,
+    DistributionDiagram,
+    MetricButton,
+    TextInformation,
+  },
   setup() {
-    let overview;
-    //Gets the overview file based on the used mode (zip, local, single).
-    if (store.state.local) {
-      try {
-        // eslint-disable-next-line @typescript-eslint/no-var-requires
-        overview = OverviewFactory.getOverview(require("../files/overview.json"))
-      } catch (e) {
-        router.back()
+    const store = useStore();
+    const overviewFile = computed(() => {
+      const index = Object.keys(store.state.files).find((name) =>
+        name.endsWith("overview.json")
+      );
+      return index != undefined
+        ? store.state.files[index]
+        : console.log("Could not find overview.json"); // TODO introduce error page to navigate to
+    });
+
+    const getOverview = (): Overview => {
+      let temp!: Overview;
+      //Gets the overview file based on the used mode (zip, local, single).
+      if (store.state.local) {
+        try {
+          // eslint-disable-next-line @typescript-eslint/no-var-requires
+          temp = OverviewFactory.getOverview(require("../files/overview.json"));
+        } catch (e) {
+          router.back();
+        }
+      } else if (store.state.zip) {
+        const overviewJson = JSON.parse(overviewFile.value);
+        temp = OverviewFactory.getOverview(overviewJson);
+      } else if (store.state.single) {
+        temp = OverviewFactory.getOverview(JSON.parse(store.state.fileString));
       }
-    } else if (store.state.zip) {
-      overview = OverviewFactory.getOverview(JSON.parse(store.state.files["overview.json"]))
-    } else if (store.state.single) {
-      overview = OverviewFactory.getOverview(JSON.parse(store.state.fileString))
-    }
+      return temp;
+    };
+
+    let overview = getOverview();
 
     /**
      * Handles the selection of an Id to anonymize.
@@ -89,58 +147,63 @@ export default defineComponent({
      * If a single id is provided it hides all of the other ids except for the chosen one.
      * @param id
      */
-    const handleId = (id) => {
+    const handleId = (id: string) => {
       if (id.length === overview.submissionIds.length) {
         if (store.state.anonymous.size > 0) {
-          store.commit("resetAnonymous")
+          store.commit("resetAnonymous");
         } else {
-          store.commit("addAnonymous", id)
+          store.commit("addAnonymous", id);
         }
       } else {
         if (store.state.anonymous.has(id[0])) {
-          store.commit("removeAnonymous", id)
+          store.commit("removeAnonymous", id);
         } else {
           if (store.state.anonymous.size === 0) {
-            store.commit("addAnonymous", overview.submissionIds.filter(s => s !== id[0]))
+            store.commit(
+              "addAnonymous",
+              overview.submissionIds.filter((s) => s !== id[0])
+            );
           } else {
-            store.commit("addAnonymous", id)
+            store.commit("addAnonymous", id);
           }
         }
       }
-    }
-
+    };
 
     //Metrics
     /**
      * Current metric to display distribution and comparisons for.
      * @type {Ref<UnwrapRef<boolean[]>>}
      */
-    let selectedMetric = ref(overview.metrics.map(() => false))
+    let selectedMetric = ref(overview.metrics.map(() => false));
     /**
      * Index of current selected metric. Used to obtain information for the metric from the distribution and top
      * comparisons array.
      * @type {Ref<UnwrapRef<number>>}
      */
-    let selectedMetricIndex = ref(0)
+    let selectedMetricIndex = ref(0);
     selectedMetric.value[0] = true;
 
-    const selectMetric = (metric) => {
+    const selectMetric = (metric: number) => {
       selectedMetric.value = selectedMetric.value.map(() => {
-        return false
-      })
-      selectedMetric.value[metric] = true
-      selectedMetricIndex.value = metric
-    }
+        return false;
+      });
+      selectedMetric.value[metric] = true;
+      selectedMetricIndex.value = metric;
+    };
 
     //Distribution
-    let distributions = ref(overview.metrics.map((m) => m.distribution))
+    let distributions = ref(overview.metrics.map((m) => m.distribution));
 
     //Top Comparisons
-    let topComps = ref(overview.metrics.map((m) => m.comparisons))
+    let topComps: Ref<Array<Array<ComparisonListElement>>> = ref(
+      overview.metrics.map((m) => m.comparisons)
+    );
 
     const hasMoreSubmissionPaths = overview.submissionFolderPath.length > 1;
-    const submissionPathValue = hasMoreSubmissionPaths ? "Click arrow to see all paths"
-        : overview.submissionFolderPath[0]
+    const submissionPathValue = hasMoreSubmissionPaths
+      ? "Click arrow to see all paths"
+      : overview.submissionFolderPath[0];
 
     return {
       overview,
@@ -152,10 +215,10 @@ export default defineComponent({
       submissionPathValue,
       handleId,
       selectMetric,
-      store
-    }
-  }
-})
+      store,
+    };
+  },
+});
 </script>
 
 <style scoped>
@@ -168,9 +231,9 @@ h1 {
 hr {
   border: 0;
   height: 2px;
-  background: linear-gradient(to right, #EDF2FB, transparent, transparent);
+  background: linear-gradient(to right, #edf2fb, transparent, transparent);
   width: 100%;
-  box-shadow: #D7E3FC 0 1px;
+  box-shadow: #d7e3fc 0 1px;
 }
 
 .container {
@@ -244,6 +307,4 @@ hr {
 #logo {
   flex-shrink: 2;
 }
-
-
-</style>
+</style>

Some files were not shown because too many files changed in this diff