Skip to content

Commit

Permalink
Merge pull request #1433 from jplag/feature/rework-logging
Browse files Browse the repository at this point in the history
Implemented progress bars for the cli
  • Loading branch information
tsaglam authored Feb 20, 2024
2 parents eaea554 + 646e803 commit da2f9be
Show file tree
Hide file tree
Showing 14 changed files with 301 additions and 57 deletions.
6 changes: 6 additions & 0 deletions cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@
<artifactId>picocli</artifactId>
<version>4.7.5</version>
</dependency>

<dependency>
<groupId>me.tongfei</groupId>
<artifactId>progressbar</artifactId>
<version>0.10.0</version>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
3 changes: 3 additions & 0 deletions cli/src/main/java/de/jplag/cli/CLI.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
import de.jplag.JPlagResult;
import de.jplag.Language;
import de.jplag.cli.logger.CollectedLoggerFactory;
import de.jplag.cli.logger.TongfeiProgressBarProvider;
import de.jplag.clustering.ClusteringOptions;
import de.jplag.clustering.Preprocessing;
import de.jplag.exceptions.ExitException;
import de.jplag.logging.ProgressBarLogger;
import de.jplag.merging.MergingOptions;
import de.jplag.options.JPlagOptions;
import de.jplag.options.LanguageOption;
Expand Down Expand Up @@ -80,6 +82,7 @@ public static void main(String[] args) {

if (!parseResult.isUsageHelpRequested() && !(parseResult.subcommand() != null && parseResult.subcommand().isUsageHelpRequested())) {
JPlagOptions options = cli.buildOptionsFromArguments(parseResult);
ProgressBarLogger.setProgressBarProvider(new TongfeiProgressBarProvider());
JPlagResult result = JPlag.run(options);
ReportObjectFactory reportObjectFactory = new ReportObjectFactory(new File(cli.getResultFilePath()));
reportObjectFactory.createAndSaveReport(result);
Expand Down
24 changes: 24 additions & 0 deletions cli/src/main/java/de/jplag/cli/logger/TongfeiProgressBar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package de.jplag.cli.logger;

import de.jplag.logging.ProgressBar;

/**
* A ProgressBar, that used the tongfei progress bar library underneath, to show progress bars on the cli.
*/
public class TongfeiProgressBar implements ProgressBar {
private final me.tongfei.progressbar.ProgressBar progressBar;

public TongfeiProgressBar(me.tongfei.progressbar.ProgressBar progressBar) {
this.progressBar = progressBar;
}

@Override
public void step(int number) {
this.progressBar.stepBy(number);
}

@Override
public void dispose() {
this.progressBar.close();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package de.jplag.cli.logger;

import de.jplag.logging.ProgressBar;
import de.jplag.logging.ProgressBarProvider;
import de.jplag.logging.ProgressBarType;

import me.tongfei.progressbar.ProgressBarBuilder;
import me.tongfei.progressbar.ProgressBarStyle;

/**
* A ProgressBar provider, that used the tongfei progress bar library underneath, to show progress bars on the cli.
*/
public class TongfeiProgressBarProvider implements ProgressBarProvider {
@Override
public ProgressBar initProgressBar(ProgressBarType type, int totalSteps) {
me.tongfei.progressbar.ProgressBar progressBar = new ProgressBarBuilder().setTaskName(type.getDefaultText()).setInitialMax(totalSteps)
.setStyle(ProgressBarStyle.UNICODE_BLOCK).build();
return new TongfeiProgressBar(progressBar);
}
}
13 changes: 13 additions & 0 deletions core/src/main/java/de/jplag/SubmissionFileData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package de.jplag;

import java.io.File;

/**
* Contains the information about a single file in a submission. For single file submissions the submission file is the
* same as the root.
* @param submissionFile The file, that is part of a submission
* @param root The root of the submission
* @param isNew Indicates weather this follows the new or the old syntax
*/
public record SubmissionFileData(File submissionFile, File root, boolean isNew) {
}
9 changes: 8 additions & 1 deletion core/src/main/java/de/jplag/SubmissionSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import de.jplag.exceptions.BasecodeException;
import de.jplag.exceptions.ExitException;
import de.jplag.exceptions.SubmissionException;
import de.jplag.logging.ProgressBar;
import de.jplag.logging.ProgressBarLogger;
import de.jplag.logging.ProgressBarType;
import de.jplag.options.JPlagOptions;

/**
Expand Down Expand Up @@ -37,6 +40,7 @@ public class SubmissionSet {
/**
* @param submissions Submissions to check for plagiarism.
* @param baseCode Base code submission if it exists or {@code null}.
* @param options The JPlag options
*/
public SubmissionSet(List<Submission> submissions, Submission baseCode, JPlagOptions options) throws ExitException {
this.allSubmissions = submissions;
Expand Down Expand Up @@ -133,6 +137,7 @@ private void parseBaseCodeSubmission(Submission baseCode) throws BasecodeExcepti

/**
* Parse all given submissions.
* @param submissions The list of submissions
*/
private void parseSubmissions(List<Submission> submissions) {
if (submissions.isEmpty()) {
Expand All @@ -143,8 +148,8 @@ private void parseSubmissions(List<Submission> submissions) {
long startTime = System.currentTimeMillis();

int tooShort = 0;
ProgressBar progressBar = ProgressBarLogger.createProgressBar(ProgressBarType.PARSING, submissions.size());
for (Submission submission : submissions) {
logger.info("Parsing submission {}", submission.getName());
boolean ok;

logger.trace("------ Parsing submission: " + submission.getName());
Expand All @@ -168,7 +173,9 @@ private void parseSubmissions(List<Submission> submissions) {
} else {
logger.error("ERROR -> Submission {} removed", currentSubmissionName);
}
progressBar.step();
}
progressBar.dispose();

int validSubmissions = submissions.size() - errors - tooShort;
logger.trace(validSubmissions + " submissions parsed successfully!");
Expand Down
82 changes: 39 additions & 43 deletions core/src/main/java/de/jplag/SubmissionSetBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand All @@ -21,6 +22,9 @@
import de.jplag.exceptions.ExitException;
import de.jplag.exceptions.RootDirectoryException;
import de.jplag.exceptions.SubmissionException;
import de.jplag.logging.ProgressBar;
import de.jplag.logging.ProgressBarLogger;
import de.jplag.logging.ProgressBarType;
import de.jplag.options.JPlagOptions;

/**
Expand All @@ -35,9 +39,9 @@ public class SubmissionSetBuilder {

/**
* Creates a builder for submission sets.
* @deprecated in favor of {@link #SubmissionSetBuilder(JPlagOptions)}.
* @param language is the language of the submissions.
* @param options are the configured options.
* @deprecated in favor of {@link #SubmissionSetBuilder(JPlagOptions)}.
*/
@Deprecated(since = "4.3.0")
public SubmissionSetBuilder(Language language, JPlagOptions options) {
Expand Down Expand Up @@ -67,14 +71,21 @@ public SubmissionSet buildSubmissionSet() throws ExitException {
int numberOfRootDirectories = submissionDirectories.size() + oldSubmissionDirectories.size();
boolean multipleRoots = (numberOfRootDirectories > 1);

// Collect valid looking entries from the root directories.
Map<File, Submission> foundSubmissions = new HashMap<>();
for (File directory : submissionDirectories) {
processRootDirectoryEntries(directory, multipleRoots, foundSubmissions, true);
List<SubmissionFileData> submissionFiles = new ArrayList<>();
for (File submissionDirectory : submissionDirectories) {
submissionFiles.addAll(listSubmissionFiles(submissionDirectory, true));
}
for (File oldDirectory : oldSubmissionDirectories) {
processRootDirectoryEntries(oldDirectory, multipleRoots, foundSubmissions, false);
for (File submissionDirectory : oldSubmissionDirectories) {
submissionFiles.addAll(listSubmissionFiles(submissionDirectory, false));
}

ProgressBar progressBar = ProgressBarLogger.createProgressBar(ProgressBarType.LOADING, submissionFiles.size());
Map<File, Submission> foundSubmissions = new HashMap<>();
for (SubmissionFileData submissionFile : submissionFiles) {
processSubmissionFile(submissionFile, multipleRoots, foundSubmissions);
progressBar.step();
}
progressBar.dispose();

Optional<Submission> baseCodeSubmission = loadBaseCode();
baseCodeSubmission.ifPresent(baseSubmission -> foundSubmissions.remove(baseSubmission.getRoot()));
Expand All @@ -84,7 +95,7 @@ public SubmissionSet buildSubmissionSet() throws ExitException {

// Some languages expect a certain order, which is ensured here:
if (options.language().expectsSubmissionOrder()) {
List<File> rootFiles = foundSubmissions.values().stream().map(it -> it.getRoot()).toList();
List<File> rootFiles = foundSubmissions.values().stream().map(Submission::getRoot).toList();
rootFiles = options.language().customizeSubmissionOrder(rootFiles);
submissions = new ArrayList<>(rootFiles.stream().map(foundSubmissions::get).toList());
}
Expand Down Expand Up @@ -155,31 +166,25 @@ private Optional<Submission> loadBaseCode() throws ExitException {

Submission baseCodeSubmission = processSubmission(baseCodeSubmissionDirectory.getName(), baseCodeSubmissionDirectory, false);
logger.info("Basecode directory \"{}\" will be used.", baseCodeSubmission.getName());
return Optional.ofNullable(baseCodeSubmission);
return Optional.of(baseCodeSubmission);
}

/**
* Read entries in the given root directory.
*/
private String[] listSubmissionFiles(File rootDirectory) throws ExitException {
private List<SubmissionFileData> listSubmissionFiles(File rootDirectory, boolean isNew) throws RootDirectoryException {
if (!rootDirectory.isDirectory()) {
throw new AssertionError("Given root is not a directory.");
}

String[] fileNames;

try {
fileNames = rootDirectory.list();
File[] files = rootDirectory.listFiles();
if (files == null) {
throw new RootDirectoryException("Cannot list files of the root directory!");
}

return Arrays.stream(files).sorted(Comparator.comparing(File::getName)).map(it -> new SubmissionFileData(it, rootDirectory, isNew))
.toList();
} catch (SecurityException exception) {
throw new RootDirectoryException("Cannot list files of the root directory! " + exception.getMessage(), exception);
}

if (fileNames == null) {
throw new RootDirectoryException("Cannot list files of the root directory!");
}

Arrays.sort(fileNames);
return fileNames;
}

/**
Expand All @@ -200,6 +205,7 @@ private String isExcludedEntry(File submissionEntry) {

/**
* Process the given directory entry as a submission, the path MUST not be excluded.
* @param submissionName The name of the submission
* @param submissionFile the file for the submission.
* @param isNew states whether submissions found in the root directory must be checked for plagiarism.
* @return The entry converted to a submission.
Expand All @@ -225,27 +231,16 @@ private Submission processSubmission(String submissionName, File submissionFile,
return new Submission(submissionName, submissionFile, isNew, parseFilesRecursively(submissionFile), options.language());
}

/**
* Process entries in the root directory to check whether they qualify as submissions.
* @param rootDirectory is the root directory being examined.
* @param foundSubmissions Submissions found so far, is updated in-place.
* @param isNew states whether submissions found in the root directory must be checked for plagiarism.
*/
private void processRootDirectoryEntries(File rootDirectory, boolean multipleRoots, Map<File, Submission> foundSubmissions, boolean isNew)
throws ExitException {
for (String fileName : listSubmissionFiles(rootDirectory)) {
File submissionFile = new File(rootDirectory, fileName);

String errorMessage = isExcludedEntry(submissionFile);
if (errorMessage == null) {
String rootDirectoryPrefix = multipleRoots ? (rootDirectory.getName() + File.separator) : "";
String submissionName = rootDirectoryPrefix + fileName;
Submission submission = processSubmission(submissionName, submissionFile, isNew);
foundSubmissions.put(submission.getRoot(), submission);
} else {
logger.error(errorMessage);
}
private void processSubmissionFile(SubmissionFileData file, boolean multipleRoots, Map<File, Submission> foundSubmissions) throws ExitException {
String errorMessage = isExcludedEntry(file.submissionFile());
if (errorMessage != null) {
logger.error(errorMessage);
}

String rootDirectoryPrefix = multipleRoots ? (file.root().getName() + File.separator) : "";
String submissionName = rootDirectoryPrefix + file.submissionFile().getName();
Submission submission = processSubmission(submissionName, file.submissionFile(), file.isNew());
foundSubmissions.put(submission.getRoot(), submission);
}

/**
Expand Down Expand Up @@ -311,4 +306,5 @@ private File makeCanonical(File file, Function<Exception, ExitException> excepti
throw exceptionWrapper.apply(exception);
}
}

}
24 changes: 24 additions & 0 deletions core/src/main/java/de/jplag/logging/ProgressBar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package de.jplag.logging;

/**
* Exposed interactions for a running progress bar.
*/
public interface ProgressBar {
/**
* Advances the progress bar by a single step
*/
default void step() {
step(1);
}

/**
* Advances the progress bar by amount steps
* @param number The number of steps
*/
void step(int number);

/**
* Closes the progress bar. After this method has been called the behaviour of the other methods is undefined.
*/
void dispose();
}
68 changes: 68 additions & 0 deletions core/src/main/java/de/jplag/logging/ProgressBarLogger.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package de.jplag.logging;

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

/**
* Provides static access to the creation of progress bars.
*/
public class ProgressBarLogger {
private static ProgressBarProvider progressBarProvider = new DummyProvider();

private ProgressBarLogger() {
// Hides default constructor
}

/**
* Creates a new {@link ProgressBar}
* @param type The type of the progress bar
* @param totalSteps The total number of steps
* @return The newly created progress bar
*/
public static ProgressBar createProgressBar(ProgressBarType type, int totalSteps) {
return progressBarProvider.initProgressBar(type, totalSteps);
}

/**
* Sets the {@link ProgressBarProvider}. Should be used by the ui before calling JPlag, if progress bars should be
* shown.
* @param progressBarProvider The provider
*/
public static void setProgressBarProvider(ProgressBarProvider progressBarProvider) {
ProgressBarLogger.progressBarProvider = progressBarProvider;
}

private static class DummyProvider implements ProgressBarProvider {
@Override
public ProgressBar initProgressBar(ProgressBarType type, int totalSteps) {
return new DummyBar(type, totalSteps);
}
}

private static class DummyBar implements ProgressBar {
private static final Logger logger = LoggerFactory.getLogger(DummyBar.class);
private int currentStep;

public DummyBar(ProgressBarType type, int totalSteps) {
this.currentStep = 0;
logger.info("{} ({})", type.getDefaultText(), totalSteps);
}

@Override
public void step() {
logger.info("Now at step {}", this.currentStep++);
}

@Override
public void step(int number) {
for (int i = 0; i < number; i++) {
step();
}
}

@Override
public void dispose() {
logger.info("Progress bar done.");
}
}
}
Loading

0 comments on commit da2f9be

Please sign in to comment.