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

Re-order class to reduce conflicts with master branch.

Timur Sağlam 5 лет назад
Родитель
Сommit
eb3081d296
1 измененных файлов с 418 добавлено и 418 удалено
  1. 418 418
      jplag/src/main/java/jplag/reporting/Report.java

+ 418 - 418
jplag/src/main/java/jplag/reporting/Report.java

@@ -20,21 +20,24 @@ import jplag.ExitException;
 import jplag.JPlagComparison;
 import jplag.JPlagResult;
 import jplag.Match;
-import jplag.TokenList;
 import jplag.Submission;
 import jplag.Token;
+import jplag.TokenList;
 
 /**
  * This class writes all the HTML pages
  */
 public class Report {
 
-    private JPlagResult result;
-    private final File reportDir;
+    private final String[] pics = {"forward.gif", "back.gif"};
+
+    private final Map<JPlagComparison, Integer> comparisonToIndex = new HashMap<>();
     private final Messages msg;
+    private final File reportDir;
+
+    private JPlagResult result;
 
     private int currentComparisonIndex = 0;
-    private final Map<JPlagComparison, Integer> comparisonToIndex = new HashMap<>();
 
     public Report(File reportDir) throws ExitException {
         this.reportDir = reportDir;
@@ -43,21 +46,14 @@ public class Report {
         validateReportDir();
     }
 
-    /**
-     * Make sure the report directory exists, is a directory and has write access.
-     */
-    private void validateReportDir() throws ExitException {
-        if (!reportDir.exists() && !reportDir.mkdirs()) {
-            throw new ExitException("Cannot create report directory!");
-        }
+    public void writeResult(JPlagResult result) throws ExitException {
+        this.result = result;
 
-        if (!reportDir.isDirectory()) {
-            throw new ExitException(reportDir + " is not a directory!");
-        }
+        writeIndex();
 
-        if (!reportDir.canWrite()) {
-            throw new ExitException("Cannot write directory: " + reportDir);
-        }
+        copyStaticFiles();
+
+        writeMatches(result.getComparisons());
     }
 
     /*
@@ -105,28 +101,6 @@ public class Report {
         }
     }
 
-    private int getComparisonIndex(JPlagComparison comparison) {
-        Integer index = comparisonToIndex.get(comparison);
-
-        if (index != null) {
-            return index;
-        }
-
-        comparisonToIndex.put(comparison, currentComparisonIndex++);
-
-        return currentComparisonIndex - 1;
-    }
-
-    public void writeResult(JPlagResult result) throws ExitException {
-        this.result = result;
-
-        writeIndex();
-
-        copyStaticFiles();
-
-        writeMatches(result.getComparisons());
-    }
-
     /**
      * Create a new HTML file.
      */
@@ -140,87 +114,89 @@ public class Report {
         }
     }
 
-    private void writeHTMLHeader(HTMLFile file, String title) {
-        file.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
-        file.println("<HTML><HEAD><TITLE>" + title + "</TITLE>");
-        file.println("<META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">");
-        file.println("</HEAD>");
-    }
-
-    /**
-     * Write the index.html file.
-     */
-    private void writeIndex() throws ExitException {
-        HTMLFile htmlFile = createHTMLFile("index.html");
-
-        writeIndexBegin(htmlFile, msg.getString("Report.Search_Results"));
-        writeDistribution(htmlFile);
-
-        String csvFile = "matches_avg.csv";
-
-        writeLinksToComparisons(htmlFile, "<H4>" + msg.getString("Report.MatchesAvg"), csvFile);
+    private int getComparisonIndex(JPlagComparison comparison) {
+        Integer index = comparisonToIndex.get(comparison);
 
-        writeMatchesCSV(csvFile);
+        if (index != null) {
+            return index;
+        }
 
-        writeIndexEnd(htmlFile);
+        comparisonToIndex.put(comparison, currentComparisonIndex++);
 
-        htmlFile.close();
+        return currentComparisonIndex - 1;
     }
 
     /**
-     * Write the beginning of the index.html file.
+     * This method generates an table entry in the list of all comparisons.
      */
-    private void writeIndexBegin(HTMLFile htmlFile, String title) {
-        writeHTMLHeader(htmlFile, title);
-
-        htmlFile.println("<BODY BGCOLOR=#ffffff LINK=#000088 VLINK=#000000 TEXT=#000000>");
-        htmlFile.println("<TABLE ALIGN=center CELLPADDING=2 CELLSPACING=1>");
-        htmlFile.println("<TR VALIGN=middle ALIGN=center BGCOLOR=#ffffff><TD>" + "<IMG SRC=\"logo.gif\" ALT=\"JPlag\" BORDER=0></TD>");
-        htmlFile.println("<TD><H1><BIG>" + title + "</BIG></H1></TD></TR>");
+    private void reportComparison(HTMLFile htmlFile, JPlagComparison comparison, int index) {
+        Match match;
+        TokenList tokensA = comparison.firstSubmission.tokenList;
+        TokenList tokensB = comparison.secondSubmission.tokenList;
+        // sort();
 
-        htmlFile.println("<TR BGCOLOR=#aaaaff VALIGN=top><TD>" + msg.getString("Report.Language") + ":</TD><TD>"
-                + result.getOptions().getLanguageOption().name() + "</TD></TR>");
-        htmlFile.print("<TR BGCOLOR=#aaaaff VALIGN=top><TD>" + msg.getString("Report.Submissions") + ":</TD><TD>" + result.getNumberOfSubmissions());
+        htmlFile.println("<TABLE BORDER=\"1\" CELLSPACING=\"0\" BGCOLOR=\"#d0d0d0\">");
+        htmlFile.println("<TR><TH><TH>" + comparison.firstSubmission.name + " (" + comparison.percentA() + "%)<TH>" + comparison.secondSubmission.name
+                + " (" + comparison.percentB() + "%)<TH>" + msg.getString("AllMatches.Tokens"));
 
-        htmlFile.println("</TD></TR>");
+        for (int i = 0; i < comparison.matches.size(); i++) {
+            match = comparison.matches.get(i);
 
-        if (result.getOptions().hasBaseCode()) {
-            htmlFile.print("<TR BGCOLOR=#aaaaff VALIGN=top><TD>" + msg.getString("Report.Basecode_submission") + ":</TD>" + "<TD>"
-                    + result.getOptions().getBaseCodeSubmissionName() + "</TD></TR>");
-        }
+            Token startA = tokensA.getToken(match.startA);
+            Token endA = tokensA.getToken(match.startA + match.length - 1);
+            Token startB = tokensB.getToken(match.startB);
+            Token endB = tokensB.getToken(match.startB + match.length - 1);
 
-        if (result.getComparisons().size() > 0) {
-            htmlFile.println("<TR BGCOLOR=#aaaaff VALIGN=top><TD>" + msg.getString("Report.Matches_displayed") + ":</TD>" + "<TD>");
+            String col = Color.getHexadecimalValue(i);
 
-            htmlFile.println(result.getComparisons().size() + " (" + msg.getString("Report.Treshold") + ": "
-                    + result.getOptions().getSimilarityThreshold() + "%)<br>");
+            htmlFile.print("<TR><TD BGCOLOR=\"" + col + "\"><FONT COLOR=\"" + col + "\">-</FONT>");
+            htmlFile.print("<TD><A HREF=\"javascript:ZweiFrames('match" + index + "-0.html#" + i + "',2,'match" + index + "-1.html#" + i
+                    + "',3)\" NAME=\"" + i + "\">");
+            htmlFile.print(new String(startA.file.getBytes()));
 
-            htmlFile.println("</TD></TR>");
-        }
+            if (result.getOptions().getLanguage().usesIndex()) {
+                htmlFile.print("(" + startA.getIndex() + "-" + endA.getIndex() + ")");
+            } else {
+                htmlFile.print("(" + startA.getLine() + "-" + endA.getLine() + ")");
+            }
 
-        SimpleDateFormat dateFormat = new SimpleDateFormat();
+            htmlFile.print("<TD><A HREF=\"javascript:ZweiFrames('match" + index + "-0.html#" + i + "',2,'match" + index + "-1.html#" + i
+                    + "',3)\" NAME=\"" + i + "\">");
+            htmlFile.print(startB.file);
 
-        htmlFile.println(
-                "<TR BGCOLOR=#aaaaff VALIGN=top><TD>" + msg.getString("Report.Date") + ":</TD><TD>" + dateFormat.format(new Date()) + "</TD></TR>");
-        htmlFile.println("<TR BGCOLOR=#aaaaff>" + "<TD><EM>" + msg.getString("Report.Minimum_Match_Length") + "</EM> ("
-                + msg.getString("Report.sensitivity") + "):</TD><TD>" + result.getOptions().getMinTokenMatch() + "</TD></TR>");
-        htmlFile.println("<TR BGCOLOR=#aaaaff VALIGN=top><TD>" + msg.getString("Report.Suffixes") + ":</TD><TD>");
+            if (result.getOptions().getLanguage().usesIndex()) {
+                htmlFile.print("(" + startB.getIndex() + "-" + endB.getIndex());
+            } else {
+                htmlFile.print("(" + startB.getLine() + "-" + endB.getLine());
+            }
 
-        String[] fileSuffixes = result.getOptions().getFileSuffixes();
+            htmlFile.println(")</A><TD ALIGN=center>" + "<FONT COLOR=\"" + comparison.color(match.length) + "\">" + match.length + "</FONT>");
+        }
 
-        for (int i = 0; i < fileSuffixes.length; i++) {
-            htmlFile.print(fileSuffixes[i] + (i < fileSuffixes.length - 1 ? ", " : "</TD></TR>\n"));
+        if (result.getOptions().hasBaseCode()) {
+            htmlFile.print(
+                    "<TR><TD BGCOLOR=\"#C0C0C0\"><TD>" + msg.getString("AllMatches.Basecode") + " " + comparison.roundedPercentBasecodeA() + "%");
+            htmlFile.println("<TD>" + msg.getString("AllMatches.Basecode") + " " + comparison.roundedPercentBasecodeB() + "%<TD>&nbsp;");
         }
 
-        htmlFile.println("</TABLE>\n<HR>");
+        htmlFile.println("</TABLE>");
     }
 
     /**
-     * Write the end of the index.html file.
+     * Make sure the report directory exists, is a directory and has write access.
      */
-    private void writeIndexEnd(HTMLFile htmlFile) {
-        htmlFile.println("<HR>\n<P ALIGN=right><FONT SIZE=\"1\" FACE=\"helvetica\">JPlag</FONT></P>");
-        htmlFile.println("</BODY>\n</HTML>");
+    private void validateReportDir() throws ExitException {
+        if (!reportDir.exists() && !reportDir.mkdirs()) {
+            throw new ExitException("Cannot create report directory!");
+        }
+
+        if (!reportDir.isDirectory()) {
+            throw new ExitException(reportDir + " is not a directory!");
+        }
+
+        if (!reportDir.canWrite()) {
+            throw new ExitException("Cannot write directory: " + reportDir);
+        }
     }
 
     private void writeDistribution(HTMLFile htmlFile) {
@@ -260,72 +236,334 @@ public class Report {
         htmlFile.println("</TABLE></CENTER>\n<P>\n<HR>");
     }
 
-    private void writeLinksToComparisons(HTMLFile htmlFile, String headerStr, String csvFile) {
-        List<JPlagComparison> comparisons = result.getComparisons();
+    private void writeHTMLHeader(HTMLFile file, String title) {
+        file.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
+        file.println("<HTML><HEAD><TITLE>" + title + "</TITLE>");
+        file.println("<META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">");
+        file.println("</HEAD>");
+    }
 
-        htmlFile.println(headerStr + " (<a href=\"help-sim-" + "en" // Country tag
-                + ".html\"><small><font color=\"#000088\">" + msg.getString("Report.WhatIsThis") + "</font></small></a>):</H4>");
-        htmlFile.println("<p><a href=\"" + csvFile + "\">download csv</a></p>");
-        htmlFile.println("<TABLE CELLPADDING=3 CELLSPACING=2>");
+    /*
+     * i is the number of the match j == 0 if subA is considered, otherwise (j must then be 1) it is subB This procedure
+     * makes use of the column and length information!
+     */
+    private int writeImprovedSubmission(HTMLFile f, int i, JPlagComparison comparison, int j) throws jplag.ExitException {
+        Submission sub = (j == 0 ? comparison.firstSubmission : comparison.secondSubmission);
+        String[] files = comparison.files(j);
+        String[][] text = sub.readFiles(files);
+        TokenList tokens = (j == 0 ? comparison.firstSubmission : comparison.secondSubmission).tokenList;
 
-        for (JPlagComparison comparison : comparisons) {
-            String submissionNameA = comparison.firstSubmission.name;
-            String submissionNameB = comparison.secondSubmission.name;
+        // Markup list:
+        Comparator<MarkupText> comp = (mo1, mo2) -> {
+            int col1 = mo1.column;
+            int col2 = mo2.column;
+            if (col1 > col2) {
+                return -1;
+            } else if (col1 < col2) {
+                return 1;
+            }
+            return (mo1.frontMarkup ? -1 : 1);
+        };
+        TreeMap<MarkupText, Object> markupList = new TreeMap<>(comp);
 
-            htmlFile.print("<TR><TD BGCOLOR=" + color(comparison.percentA(), 128, 192, 128, 192, 255, 255) + ">" + submissionNameA
-                    + "</TD><TD><nobr>-&gt;</nobr>");
+        for (int x = 0; x < comparison.matches.size(); x++) {
+            Match onematch = comparison.matches.get(x);
 
-            htmlFile.print("</TD><TD BGCOLOR=" + color(comparison.percentB(), 128, 192, 128, 192, 255, 255) + " ALIGN=center><A HREF=\"match"
-                    + getComparisonIndex(comparison) + ".html\">" + submissionNameB + "</A><BR><FONT COLOR=\""
-                    + color(comparison.percent(), 0, 255, 0, 0, 0, 0) + "\">(" + (((int) (comparison.percent() * 10)) / (float) 10) + "%)</FONT>");
+            Token start = tokens.getToken(j == 0 ? onematch.startA : onematch.startB);
+            Token end = tokens.getToken((j == 0 ? onematch.startA : onematch.startB) + onematch.length - 1);
+            for (int fileIndex = 0; fileIndex < files.length; fileIndex++) {
+                if (start.file.equals(files[fileIndex]) && text[fileIndex] != null) {
+                    String tmp = "<FONT color=\"" + Color.getHexadecimalValue(x) + "\">" + (j == 1 ? "<div style=\"position:absolute;left:0\">" : "")
+                            + "<A HREF=\"javascript:ZweiFrames('match" + i + "-" + (1 - j) + ".html#" + x + "'," + (3 - j) + ",'match" + i
+                            + "-top.html#" + x + "',1)\"><IMG SRC=\"" + pics[j] + "\" ALT=\"other\" " + "BORDER=\"0\" ALIGN=\""
+                            + (j == 0 ? "right" : "left") + "\"></A>" + (j == 1 ? "</div>" : "") + "<B>";
+                    // position the icon and the beginning of the colorblock
+                    markupList.put(new MarkupText(fileIndex, start.getLine() - 1, start.getColumn() - 1, tmp, true), null);
+                    // mark the end
+                    markupList.put(new MarkupText(fileIndex, end.getLine() - 1, end.getColumn() + end.getLength() - 1, "</B></FONT>", false), null);
 
-            htmlFile.println("</TD></TR>");
+                    // the link location is placed 3 lines before the start of a block
+                    int linkLine = (Math.max(start.getLine() - 4, 0));
+                    markupList.put(new MarkupText(fileIndex, linkLine, 0, "<A NAME=\"" + x + "\"></A>", false), null);
+                }
+            }
         }
 
-        htmlFile.println("</TABLE><P>\n");
-        htmlFile.println("<!---->");
-    }
-
-    private void writeMatchesCSV(String fileName) {
-        FileWriter writer = null;
-        File csvFile = new File(reportDir, fileName);
-        List<JPlagComparison> comparisons = result.getComparisons();
-
-        try {
-            csvFile.createNewFile();
-            writer = new FileWriter(csvFile);
+        if (result.getOptions().hasBaseCode() && comparison.baseCodeMatchesA != null && comparison.baseCodeMatchesB != null) {
+            JPlagComparison baseCodeComparison = (j == 0 ? comparison.baseCodeMatchesA : comparison.baseCodeMatchesB);
 
-            for (JPlagComparison comparison : comparisons) {
-                String submissionNameA = comparison.firstSubmission.name;
-                String submissionNameB = comparison.secondSubmission.name;
+            for (int x = 0; x < baseCodeComparison.matches.size(); x++) {
+                Match onematch = baseCodeComparison.matches.get(x);
+                Token start = tokens.getToken(onematch.startA);
+                Token end = tokens.getToken(onematch.startA + onematch.length - 1);
 
-                writer.write(getComparisonIndex(comparison) + ";");
-                writer.write(submissionNameA + ";");
-                writer.write(submissionNameB + ";");
-                writer.write((((int) (comparison.percent() * 10)) / (float) 10) + ";");
-                writer.write("\n");
+                for (int fileIndex = 0; fileIndex < files.length; fileIndex++) {
+                    if (start.file.equals(files[fileIndex]) && text[fileIndex] != null) {
+                        String tmp = "<font color=\"#C0C0C0\"><EM>";
+                        // beginning of the colorblock
+                        markupList.put(new MarkupText(fileIndex, start.getLine() - 1, start.getColumn() - 1, tmp, false), null);
+                        // mark the end
+                        markupList.put(new MarkupText(fileIndex, end.getLine() - 1, end.getColumn() + end.getLength() - 1, "</EM></font>", true),
+                                null);
+                    }
+                }
             }
+        }
 
-            writer.flush();
-        } catch (Exception e) {
-            e.printStackTrace();
-        } finally {
-            try {
-                writer.close();
-            } catch (Exception ignored) {
+        // Apply changes:
+        for (MarkupText markup : markupList.keySet()) {
+            // System.out.println(markup);
+            String tmp = text[markup.fileIndex][markup.lineIndex];
+            // is there any &quot;, &amp;, &gt; or &lt; in the String?
+            if (tmp.indexOf('&') >= 0) {
+                Vector<String> tmpV = new Vector<>();
+                // convert the string into a vector
+                int strLength = tmp.length();
+                for (int k = 0; k < strLength; k++) {
+                    if (tmp.charAt(k) != '&') {
+                        tmpV.addElement(tmp.charAt(k) + "");
+                    } else { // put &quot;, &amp;, &gt; and &lt; into one element
+                        String tmpSub = tmp.substring(k);
+                        if (tmpSub.startsWith("&quot;")) {
+                            tmpV.addElement("&quot;");
+                            k = k + 5;
+                        } else if (tmpSub.startsWith("&amp;")) {
+                            tmpV.addElement("&amp;");
+                            k = k + 4;
+                        } else if (tmpSub.startsWith("&lt;")) {
+                            tmpV.addElement("&lt;");
+                            k = k + 3;
+                        } else if (tmpSub.startsWith("&gt;")) {
+                            tmpV.addElement("&gt;");
+                            k = k + 3;
+                        } else {
+                            tmpV.addElement(tmp.charAt(k) + "");
+                        }
+                    }
+                }
+                if (markup.column <= tmpV.size()) {
+                    tmpV.insertElementAt(markup.text, markup.column);
+                } else {
+                    tmpV.addElement(markup.text);
+                }
+
+                StringBuilder tmpVStr = new StringBuilder();
+                // reconvert the Vector into a String
+                for (int k = 0; k < tmpV.size(); k++) {
+                    tmpVStr.append(tmpV.elementAt(k));
+                }
+                text[markup.fileIndex][markup.lineIndex] = tmpVStr.toString();
+            } else {
+                text[markup.fileIndex][markup.lineIndex] = tmp.substring(0, (Math.min(tmp.length(), markup.column))) + markup.text
+                        + tmp.substring((Math.min(tmp.length(), markup.column)));
+            }
+        }
+
+        f.println("<div style=\"flex-grow: 1;\">");
+
+        for (int x = 0; x < text.length; x++) {
+            f.println("<h3>");
+            f.println("<center>");
+            f.println("<span>" + sub.name + "</span>");
+            f.println("<span> - </span>");
+            f.println("<span>" + files[x] + "</span>");
+            f.println("</center>");
+            f.println("</h3>");
+            f.println("<HR>");
+            if (result.getOptions().getLanguage().isPreformatted()) {
+                f.println("<PRE>");
+            }
+            for (int y = 0; y < text[x].length; y++) {
+                f.print(text[x][y]);
+                if (!result.getOptions().getLanguage().isPreformatted()) {
+                    f.println("<BR>");
+                } else {
+                    f.println();
+                }
+            }
+            if (result.getOptions().getLanguage().isPreformatted()) {
+                f.println("</PRE>");
             }
         }
+
+        f.println("</div>");
+
+        return f.bytesWritten();
     }
 
-    private void writeMatches(List<JPlagComparison> comparisons) {
-        comparisons.forEach(comparison -> {
-            try {
-                int i = getComparisonIndex(comparison);
-                writeMatch(comparison, i);
-            } catch (ExitException e) {
-                e.printStackTrace();
+    /**
+     * Write the index.html file.
+     */
+    private void writeIndex() throws ExitException {
+        HTMLFile htmlFile = createHTMLFile("index.html");
+
+        writeIndexBegin(htmlFile, msg.getString("Report.Search_Results"));
+        writeDistribution(htmlFile);
+
+        String csvFile = "matches_avg.csv";
+
+        writeLinksToComparisons(htmlFile, "<H4>" + msg.getString("Report.MatchesAvg"), csvFile);
+
+        writeMatchesCSV(csvFile);
+
+        writeIndexEnd(htmlFile);
+
+        htmlFile.close();
+    }
+
+    /**
+     * Write the beginning of the index.html file.
+     */
+    private void writeIndexBegin(HTMLFile htmlFile, String title) {
+        writeHTMLHeader(htmlFile, title);
+
+        htmlFile.println("<BODY BGCOLOR=#ffffff LINK=#000088 VLINK=#000000 TEXT=#000000>");
+        htmlFile.println("<TABLE ALIGN=center CELLPADDING=2 CELLSPACING=1>");
+        htmlFile.println("<TR VALIGN=middle ALIGN=center BGCOLOR=#ffffff><TD>" + "<IMG SRC=\"logo.gif\" ALT=\"JPlag\" BORDER=0></TD>");
+        htmlFile.println("<TD><H1><BIG>" + title + "</BIG></H1></TD></TR>");
+
+        htmlFile.println("<TR BGCOLOR=#aaaaff VALIGN=top><TD>" + msg.getString("Report.Language") + ":</TD><TD>"
+                + result.getOptions().getLanguageOption().name() + "</TD></TR>");
+        htmlFile.print("<TR BGCOLOR=#aaaaff VALIGN=top><TD>" + msg.getString("Report.Submissions") + ":</TD><TD>" + result.getNumberOfSubmissions());
+
+        htmlFile.println("</TD></TR>");
+
+        if (result.getOptions().hasBaseCode()) {
+            htmlFile.print("<TR BGCOLOR=#aaaaff VALIGN=top><TD>" + msg.getString("Report.Basecode_submission") + ":</TD>" + "<TD>"
+                    + result.getOptions().getBaseCodeSubmissionName() + "</TD></TR>");
+        }
+
+        if (result.getComparisons().size() > 0) {
+            htmlFile.println("<TR BGCOLOR=#aaaaff VALIGN=top><TD>" + msg.getString("Report.Matches_displayed") + ":</TD>" + "<TD>");
+
+            htmlFile.println(result.getComparisons().size() + " (" + msg.getString("Report.Treshold") + ": "
+                    + result.getOptions().getSimilarityThreshold() + "%)<br>");
+
+            htmlFile.println("</TD></TR>");
+        }
+
+        SimpleDateFormat dateFormat = new SimpleDateFormat();
+
+        htmlFile.println(
+                "<TR BGCOLOR=#aaaaff VALIGN=top><TD>" + msg.getString("Report.Date") + ":</TD><TD>" + dateFormat.format(new Date()) + "</TD></TR>");
+        htmlFile.println("<TR BGCOLOR=#aaaaff>" + "<TD><EM>" + msg.getString("Report.Minimum_Match_Length") + "</EM> ("
+                + msg.getString("Report.sensitivity") + "):</TD><TD>" + result.getOptions().getMinTokenMatch() + "</TD></TR>");
+        htmlFile.println("<TR BGCOLOR=#aaaaff VALIGN=top><TD>" + msg.getString("Report.Suffixes") + ":</TD><TD>");
+
+        String[] fileSuffixes = result.getOptions().getFileSuffixes();
+
+        for (int i = 0; i < fileSuffixes.length; i++) {
+            htmlFile.print(fileSuffixes[i] + (i < fileSuffixes.length - 1 ? ", " : "</TD></TR>\n"));
+        }
+
+        htmlFile.println("</TABLE>\n<HR>");
+    }
+
+    /*
+     * i is the number of the match j == 0 if subA is considered, otherwise it is subB This procedure uses only the
+     * getIndex() method of the token. It is meant to be used with the Character front end
+     */
+    private void writeIndexedSubmission(HTMLFile f, int i, JPlagComparison comparison, int j) throws ExitException {
+        Submission sub = (j == 0 ? comparison.firstSubmission : comparison.secondSubmission);
+        String[] files = comparison.files(j);
+        char[][] text = sub.readFilesChar(files);
+        TokenList tokens = (j == 0 ? comparison.firstSubmission : comparison.secondSubmission).tokenList;
+
+        // get index array with matches sorted in ascending order.
+        int[] perm = comparison.sort_permutation(j);
+
+        int index = 0; // match index
+        Match onematch = null;
+        Token start = null;
+        Token end = null;
+
+        f.println("<div style=\"flex-grow: 1;\">");
+
+        for (int fileIndex = 0; fileIndex < files.length; fileIndex++) {
+            f.println("<h3>");
+            f.println("<center>");
+            f.println("<span>" + sub.name + "</span>");
+            f.println("<span> - </span>");
+            f.println("<span>" + files[fileIndex] + "</span>");
+            f.println("</center>");
+            f.println("</h3>");
+            f.println("<HR>");
+            char[] buffer = text[fileIndex];
+
+            for (int charNr = 0; charNr < buffer.length; charNr++) {
+                if (onematch == null) {
+                    if (index < comparison.matches.size()) {
+                        onematch = comparison.matches.get(perm[index]);
+                        start = tokens.getToken(j == 0 ? onematch.startA : onematch.startB);
+                        end = tokens.getToken((j == 0 ? onematch.startA : onematch.startB) + onematch.length - 1);
+                        index++;
+                    } else {
+                        start = end = null;
+                    }
+                }
+                // begin markup
+                if (start != null && start.getIndex() == charNr) {
+                    f.print("<A NAME=\"" + perm[index - 1] + "\"></A>");
+                    f.print("<FONT color=\"" + Color.getHexadecimalValue(perm[index - 1]) + "\"><B>");
+                    // "<A HREF=\"javascript:ZweiFrames('match"+i+"-"+(1-j)+
+                    // ".html#"+index+"',"+(3-j)+",'match"+i+"-top.html#"+index+
+                    // "',1)\">"+"<IMG SRC=\""+pics[j]+
+                    // "\" ALT=\"other\" BORDER=\"0\" "+"ALIGN="+
+                    // (j==0 ? "right" : "left")+"></A>");
+                }
+                // text
+                if (buffer[charNr] == '<') {
+                    f.print("&lt;");
+                } else if (buffer[charNr] == '>') {
+                    f.print("&gt;");
+                } else if (buffer[charNr] == '\n') {
+                    f.print("<br>\n");
+                } else {
+                    f.print(buffer[charNr]);
+                }
+                // end markup
+                if (end != null && end.getIndex() == charNr) {
+                    f.print("</B></FONT>");
+                    onematch = null; // switch to next match
+                }
             }
-        });
+        }
+
+        f.println("</div>");
+    }
+
+    /**
+     * Write the end of the index.html file.
+     */
+    private void writeIndexEnd(HTMLFile htmlFile) {
+        htmlFile.println("<HR>\n<P ALIGN=right><FONT SIZE=\"1\" FACE=\"helvetica\">JPlag</FONT></P>");
+        htmlFile.println("</BODY>\n</HTML>");
+    }
+
+    private void writeLinksToComparisons(HTMLFile htmlFile, String headerStr, String csvFile) {
+        List<JPlagComparison> comparisons = result.getComparisons();
+
+        htmlFile.println(headerStr + " (<a href=\"help-sim-" + "en" // Country tag
+                + ".html\"><small><font color=\"#000088\">" + msg.getString("Report.WhatIsThis") + "</font></small></a>):</H4>");
+        htmlFile.println("<p><a href=\"" + csvFile + "\">download csv</a></p>");
+        htmlFile.println("<TABLE CELLPADDING=3 CELLSPACING=2>");
+
+        for (JPlagComparison comparison : comparisons) {
+            String submissionNameA = comparison.firstSubmission.name;
+            String submissionNameB = comparison.secondSubmission.name;
+
+            htmlFile.print("<TR><TD BGCOLOR=" + color(comparison.percentA(), 128, 192, 128, 192, 255, 255) + ">" + submissionNameA
+                    + "</TD><TD><nobr>-&gt;</nobr>");
+
+            htmlFile.print("</TD><TD BGCOLOR=" + color(comparison.percentB(), 128, 192, 128, 192, 255, 255) + " ALIGN=center><A HREF=\"match"
+                    + getComparisonIndex(comparison) + ".html\">" + submissionNameB + "</A><BR><FONT COLOR=\""
+                    + color(comparison.percent(), 0, 255, 0, 0, 0, 0) + "\">(" + (((int) (comparison.percent() * 10)) / (float) 10) + "%)</FONT>");
+
+            htmlFile.println("</TD></TR>");
+        }
+
+        htmlFile.println("</TABLE><P>\n");
+        htmlFile.println("<!---->");
     }
 
     private void writeMatch(JPlagComparison comparison, int i) throws ExitException {
@@ -384,65 +622,48 @@ public class Report {
         htmlFile.close();
     }
 
-    /**
-     * This method generates an table entry in the list of all comparisons.
-     */
-    private void reportComparison(HTMLFile htmlFile, JPlagComparison comparison, int index) {
-        Match match;
-        TokenList tokensA = comparison.firstSubmission.tokenList;
-        TokenList tokensB = comparison.secondSubmission.tokenList;
-        // sort();
-
-        htmlFile.println("<TABLE BORDER=\"1\" CELLSPACING=\"0\" BGCOLOR=\"#d0d0d0\">");
-        htmlFile.println("<TR><TH><TH>" + comparison.firstSubmission.name + " (" + comparison.percentA() + "%)<TH>" + comparison.secondSubmission.name
-                + " (" + comparison.percentB() + "%)<TH>" + msg.getString("AllMatches.Tokens"));
-
-        for (int i = 0; i < comparison.matches.size(); i++) {
-            match = comparison.matches.get(i);
-
-            Token startA = tokensA.getToken(match.startA);
-            Token endA = tokensA.getToken(match.startA + match.length - 1);
-            Token startB = tokensB.getToken(match.startB);
-            Token endB = tokensB.getToken(match.startB + match.length - 1);
-
-            String col = Color.getHexadecimalValue(i);
+    private void writeMatches(List<JPlagComparison> comparisons) {
+        comparisons.forEach(comparison -> {
+            try {
+                int i = getComparisonIndex(comparison);
+                writeMatch(comparison, i);
+            } catch (ExitException e) {
+                e.printStackTrace();
+            }
+        });
+    }
 
-            htmlFile.print("<TR><TD BGCOLOR=\"" + col + "\"><FONT COLOR=\"" + col + "\">-</FONT>");
-            htmlFile.print("<TD><A HREF=\"javascript:ZweiFrames('match" + index + "-0.html#" + i + "',2,'match" + index + "-1.html#" + i
-                    + "',3)\" NAME=\"" + i + "\">");
-            htmlFile.print(new String(startA.file.getBytes()));
+    private void writeMatchesCSV(String fileName) {
+        FileWriter writer = null;
+        File csvFile = new File(reportDir, fileName);
+        List<JPlagComparison> comparisons = result.getComparisons();
 
-            if (result.getOptions().getLanguage().usesIndex()) {
-                htmlFile.print("(" + startA.getIndex() + "-" + endA.getIndex() + ")");
-            } else {
-                htmlFile.print("(" + startA.getLine() + "-" + endA.getLine() + ")");
-            }
+        try {
+            csvFile.createNewFile();
+            writer = new FileWriter(csvFile);
 
-            htmlFile.print("<TD><A HREF=\"javascript:ZweiFrames('match" + index + "-0.html#" + i + "',2,'match" + index + "-1.html#" + i
-                    + "',3)\" NAME=\"" + i + "\">");
-            htmlFile.print(startB.file);
+            for (JPlagComparison comparison : comparisons) {
+                String submissionNameA = comparison.firstSubmission.name;
+                String submissionNameB = comparison.secondSubmission.name;
 
-            if (result.getOptions().getLanguage().usesIndex()) {
-                htmlFile.print("(" + startB.getIndex() + "-" + endB.getIndex());
-            } else {
-                htmlFile.print("(" + startB.getLine() + "-" + endB.getLine());
+                writer.write(getComparisonIndex(comparison) + ";");
+                writer.write(submissionNameA + ";");
+                writer.write(submissionNameB + ";");
+                writer.write((((int) (comparison.percent() * 10)) / (float) 10) + ";");
+                writer.write("\n");
             }
 
-            htmlFile.println(")</A><TD ALIGN=center>" + "<FONT COLOR=\"" + comparison.color(match.length) + "\">" + match.length + "</FONT>");
-        }
-
-        if (result.getOptions().hasBaseCode()) {
-            htmlFile.print(
-                    "<TR><TD BGCOLOR=\"#C0C0C0\"><TD>" + msg.getString("AllMatches.Basecode") + " " + comparison.roundedPercentBasecodeA() + "%");
-            htmlFile.println("<TD>" + msg.getString("AllMatches.Basecode") + " " + comparison.roundedPercentBasecodeB() + "%<TD>&nbsp;");
+            writer.flush();
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                writer.close();
+            } catch (Exception ignored) {
+            }
         }
-
-        htmlFile.println("</TABLE>");
     }
 
-    // SUBMISSION - here it comes...
-    private final String[] pics = {"forward.gif", "back.gif"};
-
     /*
      * i is the number of the match j == 0 if subA is considered, otherwise (j must then be 1) it is subB
      */
@@ -547,225 +768,4 @@ public class Report {
 
         f.println("</div>");
     }
-
-    /*
-     * i is the number of the match j == 0 if subA is considered, otherwise it is subB This procedure uses only the
-     * getIndex() method of the token. It is meant to be used with the Character front end
-     */
-    private void writeIndexedSubmission(HTMLFile f, int i, JPlagComparison comparison, int j) throws ExitException {
-        Submission sub = (j == 0 ? comparison.firstSubmission : comparison.secondSubmission);
-        String[] files = comparison.files(j);
-        char[][] text = sub.readFilesChar(files);
-        TokenList tokens = (j == 0 ? comparison.firstSubmission : comparison.secondSubmission).tokenList;
-
-        // get index array with matches sorted in ascending order.
-        int[] perm = comparison.sort_permutation(j);
-
-        int index = 0; // match index
-        Match onematch = null;
-        Token start = null;
-        Token end = null;
-
-        f.println("<div style=\"flex-grow: 1;\">");
-
-        for (int fileIndex = 0; fileIndex < files.length; fileIndex++) {
-            f.println("<h3>");
-            f.println("<center>");
-            f.println("<span>" + sub.name + "</span>");
-            f.println("<span> - </span>");
-            f.println("<span>" + files[fileIndex] + "</span>");
-            f.println("</center>");
-            f.println("</h3>");
-            f.println("<HR>");
-            char[] buffer = text[fileIndex];
-
-            for (int charNr = 0; charNr < buffer.length; charNr++) {
-                if (onematch == null) {
-                    if (index < comparison.matches.size()) {
-                        onematch = comparison.matches.get(perm[index]);
-                        start = tokens.getToken(j == 0 ? onematch.startA : onematch.startB);
-                        end = tokens.getToken((j == 0 ? onematch.startA : onematch.startB) + onematch.length - 1);
-                        index++;
-                    } else {
-                        start = end = null;
-                    }
-                }
-                // begin markup
-                if (start != null && start.getIndex() == charNr) {
-                    f.print("<A NAME=\"" + perm[index - 1] + "\"></A>");
-                    f.print("<FONT color=\"" + Color.getHexadecimalValue(perm[index - 1]) + "\"><B>");
-                    // "<A HREF=\"javascript:ZweiFrames('match"+i+"-"+(1-j)+
-                    // ".html#"+index+"',"+(3-j)+",'match"+i+"-top.html#"+index+
-                    // "',1)\">"+"<IMG SRC=\""+pics[j]+
-                    // "\" ALT=\"other\" BORDER=\"0\" "+"ALIGN="+
-                    // (j==0 ? "right" : "left")+"></A>");
-                }
-                // text
-                if (buffer[charNr] == '<') {
-                    f.print("&lt;");
-                } else if (buffer[charNr] == '>') {
-                    f.print("&gt;");
-                } else if (buffer[charNr] == '\n') {
-                    f.print("<br>\n");
-                } else {
-                    f.print(buffer[charNr]);
-                }
-                // end markup
-                if (end != null && end.getIndex() == charNr) {
-                    f.print("</B></FONT>");
-                    onematch = null; // switch to next match
-                }
-            }
-        }
-
-        f.println("</div>");
-    }
-
-    /*
-     * i is the number of the match j == 0 if subA is considered, otherwise (j must then be 1) it is subB This procedure
-     * makes use of the column and length information!
-     */
-    private int writeImprovedSubmission(HTMLFile f, int i, JPlagComparison comparison, int j) throws jplag.ExitException {
-        Submission sub = (j == 0 ? comparison.firstSubmission : comparison.secondSubmission);
-        String[] files = comparison.files(j);
-        String[][] text = sub.readFiles(files);
-        TokenList tokens = (j == 0 ? comparison.firstSubmission : comparison.secondSubmission).tokenList;
-
-        // Markup list:
-        Comparator<MarkupText> comp = (mo1, mo2) -> {
-            int col1 = mo1.column;
-            int col2 = mo2.column;
-            if (col1 > col2) {
-                return -1;
-            } else if (col1 < col2) {
-                return 1;
-            }
-            return (mo1.frontMarkup ? -1 : 1);
-        };
-        TreeMap<MarkupText, Object> markupList = new TreeMap<>(comp);
-
-        for (int x = 0; x < comparison.matches.size(); x++) {
-            Match onematch = comparison.matches.get(x);
-
-            Token start = tokens.getToken(j == 0 ? onematch.startA : onematch.startB);
-            Token end = tokens.getToken((j == 0 ? onematch.startA : onematch.startB) + onematch.length - 1);
-            for (int fileIndex = 0; fileIndex < files.length; fileIndex++) {
-                if (start.file.equals(files[fileIndex]) && text[fileIndex] != null) {
-                    String tmp = "<FONT color=\"" + Color.getHexadecimalValue(x) + "\">" + (j == 1 ? "<div style=\"position:absolute;left:0\">" : "")
-                            + "<A HREF=\"javascript:ZweiFrames('match" + i + "-" + (1 - j) + ".html#" + x + "'," + (3 - j) + ",'match" + i
-                            + "-top.html#" + x + "',1)\"><IMG SRC=\"" + pics[j] + "\" ALT=\"other\" " + "BORDER=\"0\" ALIGN=\""
-                            + (j == 0 ? "right" : "left") + "\"></A>" + (j == 1 ? "</div>" : "") + "<B>";
-                    // position the icon and the beginning of the colorblock
-                    markupList.put(new MarkupText(fileIndex, start.getLine() - 1, start.getColumn() - 1, tmp, true), null);
-                    // mark the end
-                    markupList.put(new MarkupText(fileIndex, end.getLine() - 1, end.getColumn() + end.getLength() - 1, "</B></FONT>", false), null);
-
-                    // the link location is placed 3 lines before the start of a block
-                    int linkLine = (Math.max(start.getLine() - 4, 0));
-                    markupList.put(new MarkupText(fileIndex, linkLine, 0, "<A NAME=\"" + x + "\"></A>", false), null);
-                }
-            }
-        }
-
-        if (result.getOptions().hasBaseCode() && comparison.baseCodeMatchesA != null && comparison.baseCodeMatchesB != null) {
-            JPlagComparison baseCodeComparison = (j == 0 ? comparison.baseCodeMatchesA : comparison.baseCodeMatchesB);
-
-            for (int x = 0; x < baseCodeComparison.matches.size(); x++) {
-                Match onematch = baseCodeComparison.matches.get(x);
-                Token start = tokens.getToken(onematch.startA);
-                Token end = tokens.getToken(onematch.startA + onematch.length - 1);
-
-                for (int fileIndex = 0; fileIndex < files.length; fileIndex++) {
-                    if (start.file.equals(files[fileIndex]) && text[fileIndex] != null) {
-                        String tmp = "<font color=\"#C0C0C0\"><EM>";
-                        // beginning of the colorblock
-                        markupList.put(new MarkupText(fileIndex, start.getLine() - 1, start.getColumn() - 1, tmp, false), null);
-                        // mark the end
-                        markupList.put(new MarkupText(fileIndex, end.getLine() - 1, end.getColumn() + end.getLength() - 1, "</EM></font>", true),
-                                null);
-                    }
-                }
-            }
-        }
-
-        // Apply changes:
-        for (MarkupText markup : markupList.keySet()) {
-            // System.out.println(markup);
-            String tmp = text[markup.fileIndex][markup.lineIndex];
-            // is there any &quot;, &amp;, &gt; or &lt; in the String?
-            if (tmp.indexOf('&') >= 0) {
-                Vector<String> tmpV = new Vector<>();
-                // convert the string into a vector
-                int strLength = tmp.length();
-                for (int k = 0; k < strLength; k++) {
-                    if (tmp.charAt(k) != '&') {
-                        tmpV.addElement(tmp.charAt(k) + "");
-                    } else { // put &quot;, &amp;, &gt; and &lt; into one element
-                        String tmpSub = tmp.substring(k);
-                        if (tmpSub.startsWith("&quot;")) {
-                            tmpV.addElement("&quot;");
-                            k = k + 5;
-                        } else if (tmpSub.startsWith("&amp;")) {
-                            tmpV.addElement("&amp;");
-                            k = k + 4;
-                        } else if (tmpSub.startsWith("&lt;")) {
-                            tmpV.addElement("&lt;");
-                            k = k + 3;
-                        } else if (tmpSub.startsWith("&gt;")) {
-                            tmpV.addElement("&gt;");
-                            k = k + 3;
-                        } else {
-                            tmpV.addElement(tmp.charAt(k) + "");
-                        }
-                    }
-                }
-                if (markup.column <= tmpV.size()) {
-                    tmpV.insertElementAt(markup.text, markup.column);
-                } else {
-                    tmpV.addElement(markup.text);
-                }
-
-                StringBuilder tmpVStr = new StringBuilder();
-                // reconvert the Vector into a String
-                for (int k = 0; k < tmpV.size(); k++) {
-                    tmpVStr.append(tmpV.elementAt(k));
-                }
-                text[markup.fileIndex][markup.lineIndex] = tmpVStr.toString();
-            } else {
-                text[markup.fileIndex][markup.lineIndex] = tmp.substring(0, (Math.min(tmp.length(), markup.column))) + markup.text
-                        + tmp.substring((Math.min(tmp.length(), markup.column)));
-            }
-        }
-
-        f.println("<div style=\"flex-grow: 1;\">");
-
-        for (int x = 0; x < text.length; x++) {
-            f.println("<h3>");
-            f.println("<center>");
-            f.println("<span>" + sub.name + "</span>");
-            f.println("<span> - </span>");
-            f.println("<span>" + files[x] + "</span>");
-            f.println("</center>");
-            f.println("</h3>");
-            f.println("<HR>");
-            if (result.getOptions().getLanguage().isPreformatted()) {
-                f.println("<PRE>");
-            }
-            for (int y = 0; y < text[x].length; y++) {
-                f.print(text[x][y]);
-                if (!result.getOptions().getLanguage().isPreformatted()) {
-                    f.println("<BR>");
-                } else {
-                    f.println();
-                }
-            }
-            if (result.getOptions().getLanguage().isPreformatted()) {
-                f.println("</PRE>");
-            }
-        }
-
-        f.println("</div>");
-
-        return f.bytesWritten();
-    }
 }