Skip to content

Commit

Permalink
Changed the report object factory to immediately write to a zip file …
Browse files Browse the repository at this point in the history
…instead of creating single files.
  • Loading branch information
TwoOfTwelve committed Dec 5, 2023
1 parent 1c9a906 commit c00d460
Show file tree
Hide file tree
Showing 13 changed files with 251 additions and 219 deletions.
7 changes: 4 additions & 3 deletions cli/src/main/java/de/jplag/cli/CLI.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_SYNOPSIS;

import java.io.File;
import java.io.FileNotFoundException;
import java.security.SecureRandom;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -76,10 +77,10 @@ public static void main(String[] args) {
if (!parseResult.isUsageHelpRequested() && !(parseResult.subcommand() != null && parseResult.subcommand().isUsageHelpRequested())) {
JPlagOptions options = cli.buildOptionsFromArguments(parseResult);
JPlagResult result = JPlag.run(options);
ReportObjectFactory reportObjectFactory = new ReportObjectFactory();
reportObjectFactory.createAndSaveReport(result, cli.getResultFolder());
ReportObjectFactory reportObjectFactory = new ReportObjectFactory(new File(cli.getResultFolder() + ".zip"));
reportObjectFactory.createAndSaveReport(result);
}
} catch (ExitException exception) {
} catch (ExitException | FileNotFoundException exception) {
logger.error(exception.getMessage()); // do not pass exception here to keep log clean
finalizeLogger();
System.exit(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,52 +16,51 @@
import de.jplag.reporting.FilePathUtil;
import de.jplag.reporting.reportobject.model.ComparisonReport;
import de.jplag.reporting.reportobject.model.Match;
import de.jplag.reporting.reportobject.writer.JsonWriter;
import de.jplag.reporting.reportobject.writer.JPlagResultWriter;

/**
* Writes {@link ComparisonReport}s of given {@link JPlagResult} to the disk under the specified path. Instantiated with
* a function that associates a submission to its id.
*/
public class ComparisonReportWriter {

private final JsonWriter fileWriter;
private final JPlagResultWriter resultWriter;
private final Function<Submission, String> submissionToIdFunction;
private final Map<String, Map<String, String>> submissionIdToComparisonFileName = new ConcurrentHashMap<>();
private final Map<String, AtomicInteger> fileNameCollisions = new ConcurrentHashMap<>();

public ComparisonReportWriter(Function<Submission, String> submissionToIdFunction, JsonWriter fileWriter) {
public ComparisonReportWriter(Function<Submission, String> submissionToIdFunction, JPlagResultWriter jPlagResultWriter) {
this.submissionToIdFunction = submissionToIdFunction;
this.fileWriter = fileWriter;
this.resultWriter = jPlagResultWriter;
}

/**
* Generates detailed ComparisonReport DTO for each comparison in a JPlagResult and writes them to the disk as json
* files.
* @param jPlagResult The JPlagResult to generate the comparison reports from. contains information about a comparison
* @param path The path to write the comparison files to
* @return Nested map that associates each pair of submissions (by their ids) to their comparison file name. The
* comparison file name for submission with id id1 and id2 can be fetched by executing get two times:
* map.get(id1).get(id2). The nested map is symmetrical therefore, both map.get(id1).get(id2) and map.get(id2).get(id1)
* yield the same result.
*/
public Map<String, Map<String, String>> writeComparisonReports(JPlagResult jPlagResult, String path) {
public Map<String, Map<String, String>> writeComparisonReports(JPlagResult jPlagResult) {
int numberOfComparisons = jPlagResult.getOptions().maximumNumberOfComparisons();
List<JPlagComparison> comparisons = jPlagResult.getComparisons(numberOfComparisons);
writeComparisons(path, comparisons);
writeComparisons(comparisons);
return submissionIdToComparisonFileName;
}

private void writeComparisons(String path, List<JPlagComparison> comparisons) {
comparisons.parallelStream().forEach(comparison -> {
private void writeComparisons(List<JPlagComparison> comparisons) {
for (JPlagComparison comparison : comparisons) {
String firstSubmissionId = submissionToIdFunction.apply(comparison.firstSubmission());
String secondSubmissionId = submissionToIdFunction.apply(comparison.secondSubmission());
String fileName = generateComparisonName(firstSubmissionId, secondSubmissionId);
addToLookUp(firstSubmissionId, secondSubmissionId, fileName);
var comparisonReport = new ComparisonReport(firstSubmissionId, secondSubmissionId,
Map.of(SimilarityMetric.AVG.name(), comparison.similarity(), SimilarityMetric.MAX.name(), comparison.maximalSimilarity()),
convertMatchesToReportMatches(comparison));
fileWriter.writeFile(comparisonReport, path, fileName);
});
resultWriter.addJsonEntry(comparisonReport, fileName);
}
}

private void addToLookUp(String firstSubmissionId, String secondSubmissionId, String fileName) {
Expand Down Expand Up @@ -99,7 +98,7 @@ private Match convertMatchToReportMatch(JPlagComparison comparison, de.jplag.Mat
List<Token> tokensFirst = comparison.firstSubmission().getTokenList().subList(match.startOfFirst(), match.endOfFirst() + 1);
List<Token> tokensSecond = comparison.secondSubmission().getTokenList().subList(match.startOfSecond(), match.endOfSecond() + 1);

Comparator<? super Token> lineComparator = (first, second) -> first.getLine() - second.getLine();
Comparator<? super Token> lineComparator = Comparator.comparingInt(Token::getLine);

Token startOfFirst = tokensFirst.stream().min(lineComparator).orElseThrow();
Token endOfFirst = tokensFirst.stream().max(lineComparator).orElseThrow();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
package de.jplag.reporting.reportobject;

import static de.jplag.reporting.jsonfactory.DirectoryManager.createDirectory;
import static de.jplag.reporting.jsonfactory.DirectoryManager.deleteDirectory;
import static de.jplag.reporting.jsonfactory.DirectoryManager.zipDirectory;
import static de.jplag.reporting.reportobject.mapper.SubmissionNameToIdMapper.buildSubmissionNameToIdMap;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.io.FileNotFoundException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
Expand All @@ -35,143 +30,103 @@
import de.jplag.reporting.reportobject.model.OverviewReport;
import de.jplag.reporting.reportobject.model.SubmissionFileIndex;
import de.jplag.reporting.reportobject.model.Version;
import de.jplag.reporting.reportobject.writer.JsonWriter;
import de.jplag.reporting.reportobject.writer.TextWriter;
import de.jplag.reporting.reportobject.writer.JPlagResultWriter;
import de.jplag.reporting.reportobject.writer.ZipWriter;

/**
* Factory class, responsible for converting a JPlagResult object to Overview and Comparison DTO classes and writing it
* to the disk.
*/
public class ReportObjectFactory {
private static final String DIRECTORY_ERROR = "Could not create directory {} for report viewer generation";

private static final Logger logger = LoggerFactory.getLogger(ReportObjectFactory.class);

private static final JsonWriter jsonFileWriter = new JsonWriter();
public static final String OVERVIEW_FILE_NAME = "overview.json";

public static final String README_FILE_NAME = "README.txt";
private static final String[] README_CONTENT = new String[] {"This is a software plagiarism report generated by JPlag.",
"To view the report go to https://jplag.github.io/JPlag/ and drag the generated zip file onto the page."};

public static final String SUBMISSIONS_FOLDER = "files";
public static final String SUBMISSION_FILE_INDEX_FILE_NAME = "submissionFileIndex.json";
public static final Version REPORT_VIEWER_VERSION = JPlag.JPLAG_VERSION;

private static final String SUBMISSIONS_ROOT_PATH = "files/";

private Map<String, String> submissionNameToIdMap;
private Function<Submission, String> submissionToIdFunction;
private Map<String, Map<String, String>> submissionNameToNameToComparisonFileName;

private final JPlagResultWriter resultWriter;

/**
* Creates all necessary report viewer files, writes them to the disk as zip.
* @param result The JPlagResult to be converted into a report.
* @param path The Path to save the report to
* Creates a new report object factory, that can be used to write a report.
* @param resultWriter The writer to use for writing report content
*/
public void createAndSaveReport(JPlagResult result, String path) {

try {
logger.info("Start writing report files...");
createDirectory(path);
buildSubmissionToIdMap(result);
public ReportObjectFactory(JPlagResultWriter resultWriter) {
this.resultWriter = resultWriter;
}

copySubmissionFilesToReport(path, result);
/**
* Creates a new report object factory, that can be used to write a zip report.
* @param zipFile The zip file to write the report to
* @throws FileNotFoundException If the file cannot be opened for writing
*/
public ReportObjectFactory(File zipFile) throws FileNotFoundException {
this(new ZipWriter(zipFile));
}

writeComparisons(result, path);
writeOverview(result, path);
writeSubmissionIndexFile(result, path);
writeReadMeFile(path);
/**
* Creates all necessary report viewer files, writes them to the disk as zip.
* @param result The JPlagResult to be converted into a report.
*/
public void createAndSaveReport(JPlagResult result) {
logger.info("Start writing report...");
buildSubmissionToIdMap(result);

logger.info("Zipping report files...");
zipAndDelete(path);
} catch (IOException e) {
logger.error(DIRECTORY_ERROR, e, path);
}
copySubmissionFilesToReport(result);

}
writeComparisons(result);
writeOverview(result);
writeSubmissionIndexFile(result);
writeReadMeFile();

private void zipAndDelete(String path) {
boolean zipWasSuccessful = zipDirectory(path);
if (zipWasSuccessful) {
deleteDirectory(path);
} else {
logger.error("Could not zip results. The results are still available uncompressed at " + path);
}
this.resultWriter.close();
}

private void buildSubmissionToIdMap(JPlagResult result) {
submissionNameToIdMap = buildSubmissionNameToIdMap(result);
submissionToIdFunction = (Submission submission) -> submissionNameToIdMap.get(submission.getName());
}

private void copySubmissionFilesToReport(String path, JPlagResult result) {
logger.info("Start copying submission files to the output directory...");
private void copySubmissionFilesToReport(JPlagResult result) {
logger.info("Start to export results...");
List<JPlagComparison> comparisons = result.getComparisons(result.getOptions().maximumNumberOfComparisons());
Set<Submission> submissions = getSubmissions(comparisons);
File submissionsPath = createSubmissionsDirectory(path);
if (submissionsPath == null) {
return;
}
Language language = result.getOptions().language();
for (Submission submission : submissions) {
File directory = createSubmissionDirectory(path, submissionsPath, submission);
File submissionRoot = submission.getRoot();
if (directory == null) {
continue;
}
String submissionRootPath = SUBMISSIONS_ROOT_PATH + submissionToIdFunction.apply(submission) + "/";
for (File file : submission.getFiles()) {
File fullPath = createSubmissionDirectory(path, submissionsPath, submission, file, submissionRoot);
File fileToCopy = getFileToCopy(language, file);
try {
if (fullPath != null) {
Files.copy(fileToCopy.toPath(), fullPath.toPath(), StandardCopyOption.REPLACE_EXISTING);
} else {
throw new NullPointerException("Could not create file with full path");
}
} catch (IOException e) {
logger.error("Could not save submission file " + fileToCopy, e);
String relativeFilePath = file.getAbsolutePath().substring(submission.getRoot().getAbsolutePath().length());
if (relativeFilePath.isEmpty()) {
relativeFilePath = file.getName();
}
}
}
}

private File createSubmissionDirectory(String path, File submissionsPath, Submission submission, File file, File submissionRoot) {
try {
return createDirectory(submissionsPath.getPath(), submissionToIdFunction.apply(submission), file, submissionRoot);
} catch (IOException e) {
logger.error(DIRECTORY_ERROR, e, path);
return null;
}
}

private File createSubmissionDirectory(String path, File submissionsPath, Submission submission) {
try {
return createDirectory(submissionsPath.getPath(), submissionToIdFunction.apply(submission));
} catch (IOException e) {
logger.error(DIRECTORY_ERROR, e, path);
return null;
}
}
String zipPath = submissionRootPath + relativeFilePath;

private File createSubmissionsDirectory(String path) {
try {
return createDirectory(path, SUBMISSIONS_FOLDER);
} catch (IOException e) {
logger.error(DIRECTORY_ERROR, e, path);
return null;
File fileToCopy = getFileToCopy(language, file);
this.resultWriter.addFileContentEntry(zipPath, fileToCopy);
}
}
}

private File getFileToCopy(Language language, File file) {
return language.useViewFiles() ? new File(file.getPath() + language.viewFileSuffix()) : file;
}

private void writeComparisons(JPlagResult result, String path) {
ComparisonReportWriter comparisonReportWriter = new ComparisonReportWriter(submissionToIdFunction, jsonFileWriter);
submissionNameToNameToComparisonFileName = comparisonReportWriter.writeComparisonReports(result, path);
private void writeComparisons(JPlagResult result) {
ComparisonReportWriter comparisonReportWriter = new ComparisonReportWriter(submissionToIdFunction, this.resultWriter);
submissionNameToNameToComparisonFileName = comparisonReportWriter.writeComparisonReports(result);
}

private void writeOverview(JPlagResult result, String path) {

private void writeOverview(JPlagResult result) {
List<File> folders = new ArrayList<>();
folders.addAll(result.getOptions().submissionDirectories());
folders.addAll(result.getOptions().oldSubmissionDirectories());
Expand Down Expand Up @@ -201,15 +156,15 @@ private void writeOverview(JPlagResult result, String path) {
clusteringResultMapper.map(result), // clusters
totalComparisons); // totalComparisons

jsonFileWriter.writeFile(overviewReport, path, OVERVIEW_FILE_NAME);
this.resultWriter.addJsonEntry(overviewReport, OVERVIEW_FILE_NAME);

}

private void writeReadMeFile(String path) {
new TextWriter().writeFile(String.join(System.lineSeparator(), README_CONTENT), path, README_FILE_NAME);
private void writeReadMeFile() {
this.resultWriter.writeStringEntry(String.join(System.lineSeparator(), README_CONTENT), README_FILE_NAME);
}

private void writeSubmissionIndexFile(JPlagResult result, String path) {
private void writeSubmissionIndexFile(JPlagResult result) {
List<JPlagComparison> comparisons = result.getComparisons(result.getOptions().maximumNumberOfComparisons());
Set<Submission> submissions = getSubmissions(comparisons);
SubmissionFileIndex fileIndex = new SubmissionFileIndex(new HashMap<>());
Expand All @@ -221,7 +176,7 @@ private void writeSubmissionIndexFile(JPlagResult result, String path) {
}
fileIndex.fileIndexes().put(submissionNameToIdMap.get(submission.getName()), filePaths);
}
jsonFileWriter.writeFile(fileIndex, path, SUBMISSION_FILE_INDEX_FILE_NAME);
this.resultWriter.addJsonEntry(fileIndex, SUBMISSION_FILE_INDEX_FILE_NAME);
}

private Set<Submission> getSubmissions(List<JPlagComparison> comparisons) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package de.jplag.reporting.reportobject.writer;

import java.io.File;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Dummy writer, that does nothing
*/
public class DummyResultWriter implements JPlagResultWriter {
private static final Logger logger = LoggerFactory.getLogger(DummyResultWriter.class);
private static final String MESSAGE_JSON = "DummyWriter writes object {} to path {} as JSON.";
private static final String MESSAGE_FILE = "DummyWriter writes file {} to path {}.";
private static final String MESSAGE_STRING = "DummyWriter writes String ({}) to path {}.";
private static final String MESSAGE_CLOSE = "DummyWriter closed.";

@Override
public void addJsonEntry(Object jsonContent, String path) {
logger.info(MESSAGE_JSON, jsonContent, path);
}

@Override
public void addFileContentEntry(String path, File original) {
logger.info(MESSAGE_FILE, original.getAbsolutePath(), path);
}

@Override
public void writeStringEntry(String entry, String path) {
logger.info(MESSAGE_STRING, entry, path);
}

@Override
public void close() {
logger.info(MESSAGE_CLOSE);
}
}

This file was deleted.

Loading

0 comments on commit c00d460

Please sign in to comment.