Преглед изворни кода

Merge branch 'master' into feature/floatToDouble

# Conflicts:
#	jplag.cli/src/main/java/de/jplag/CommandLineArgument.java
#	jplag/src/main/java/de/jplag/clustering/ClusteringOptions.java
#	jplag/src/main/java/de/jplag/clustering/algorithm/AgglomerativeClustering.java
Dominik Fuchß пре 4 година
родитељ
комит
f895b3d468

+ 2 - 2
README.md

@@ -98,8 +98,8 @@ JPlagResult result = jplag.run();
 List<JPlagComparison> comparisons = result.getComparisons();
 
 // Optional
-File outputDir = new File("/path/to/output");
-Report report = new Report(outputDir);
+ReportObjectFactory reportObjectFactory = new ReportObjectFactory();
+reportObjectFactory.createAndSaveReport(result, "/path/to/output");
 
 report.writeResult(result);
 ```

+ 57 - 36
jplag.cli/src/main/java/de/jplag/CLI.java

@@ -34,7 +34,6 @@ import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Optional;
 import java.util.Random;
 
 import net.sourceforge.argparse4j.ArgumentParsers;
@@ -46,14 +45,11 @@ import org.slf4j.ILoggerFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import de.jplag.clustering.ClusteringAlgorithm;
 import de.jplag.clustering.ClusteringOptions;
 import de.jplag.clustering.Preprocessing;
-import de.jplag.clustering.algorithm.InterClusterSimilarity;
 import de.jplag.exceptions.ExitException;
 import de.jplag.logger.CollectedLoggerFactory;
 import de.jplag.options.JPlagOptions;
-import de.jplag.options.SimilarityMetric;
 import de.jplag.options.Verbosity;
 import de.jplag.reporting.reportobject.ReportObjectFactory;
 import de.jplag.strategy.ComparisonMode;
@@ -161,42 +157,67 @@ public final class CLI {
         }
         var comparisonMode = comparisonModeOptional.orElse(JPlagOptions.DEFAULT_COMPARISON_MODE);
 
-        ClusteringOptions.Builder clusteringBuilder = new ClusteringOptions.Builder();
-        Optional.ofNullable((Boolean) CLUSTER_DISABLE.getFrom(namespace)).ifPresent(disabled -> clusteringBuilder.enabled(!disabled));
-        Optional.ofNullable((ClusteringAlgorithm) CLUSTER_ALGORITHM.getFrom(namespace)).ifPresent(clusteringBuilder::algorithm);
-        Optional.ofNullable((SimilarityMetric) CLUSTER_METRIC.getFrom(namespace)).ifPresent(clusteringBuilder::similarityMetric);
-        Optional.ofNullable((Float) CLUSTER_SPECTRAL_BANDWIDTH.getFrom(namespace)).ifPresent(clusteringBuilder::spectralKernelBandwidth);
-        Optional.ofNullable((Float) CLUSTER_SPECTRAL_NOISE.getFrom(namespace)).ifPresent(clusteringBuilder::spectralGaussianProcessVariance);
-        Optional.ofNullable((Integer) CLUSTER_SPECTRAL_MIN_RUNS.getFrom(namespace)).ifPresent(clusteringBuilder::spectralMinRuns);
-        Optional.ofNullable((Integer) CLUSTER_SPECTRAL_MAX_RUNS.getFrom(namespace)).ifPresent(clusteringBuilder::spectralMaxRuns);
-        Optional.ofNullable((Integer) CLUSTER_SPECTRAL_KMEANS_ITERATIONS.getFrom(namespace))
-                .ifPresent(clusteringBuilder::spectralMaxKMeansIterationPerRun);
-        Optional.ofNullable((Float) CLUSTER_AGGLOMERATIVE_THRESHOLD.getFrom(namespace)).ifPresent(clusteringBuilder::agglomerativeThreshold);
-        Optional.ofNullable((InterClusterSimilarity) CLUSTER_AGGLOMERATIVE_INTER_CLUSTER_SIMILARITY.getFrom(namespace))
-                .ifPresent(clusteringBuilder::agglomerativeInterClusterSimilarity);
-        Optional.ofNullable((Boolean) CLUSTER_PREPROCESSING_NONE.getFrom(namespace)).ifPresent(none -> {
-            if (none) {
-                clusteringBuilder.preprocessor(Preprocessing.NONE);
-            }
-        });
-        Optional.ofNullable((Boolean) CLUSTER_PREPROCESSING_CDF.getFrom(namespace)).ifPresent(cdf -> {
-            if (cdf) {
-                clusteringBuilder.preprocessor(Preprocessing.CUMULATIVE_DISTRIBUTION_FUNCTION);
-            }
-        });
-        Optional.ofNullable((Float) CLUSTER_PREPROCESSING_PERCENTILE.getFrom(namespace)).ifPresent(percentile -> {
-            clusteringBuilder.preprocessor(Preprocessing.PERCENTILE);
-            clusteringBuilder.preprocessorPercentile(percentile);
-        });
-        Optional.ofNullable((Float) CLUSTER_PREPROCESSING_THRESHOLD.getFrom(namespace)).ifPresent(threshold -> {
-            clusteringBuilder.preprocessor(Preprocessing.THRESHOLD);
-            clusteringBuilder.preprocessorPercentile(threshold);
-        });
+        ClusteringOptions clusteringOptions = getClusteringOptions(namespace);
 
         return new JPlagOptions(language, MIN_TOKEN_MATCH.getFrom(namespace), submissionDirectories, oldSubmissionDirectories,
                 BASE_CODE.getFrom(namespace), SUBDIRECTORY.getFrom(namespace), Arrays.stream(fileSuffixes).toList(), EXCLUDE_FILE.getFrom(namespace),
                 JPlagOptions.DEFAULT_SIMILARITY_METRIC, SIMILARITY_THRESHOLD.getFrom(namespace), SHOWN_COMPARISONS.getFrom(namespace),
-                clusteringBuilder.build(), comparisonMode, Verbosity.fromOption(VERBOSITY.getFrom(namespace)), DEBUG.getFrom(namespace));
+                clusteringOptions, comparisonMode, Verbosity.fromOption(VERBOSITY.getFrom(namespace)), DEBUG.getFrom(namespace));
+    }
+
+    private static ClusteringOptions getClusteringOptions(Namespace namespace) {
+        ClusteringOptions clusteringOptions = new ClusteringOptions();
+        if (CLUSTER_DISABLE.isSet(namespace)) {
+            boolean disabled = CLUSTER_DISABLE.getFrom(namespace);
+            clusteringOptions = clusteringOptions.withEnabled(!disabled);
+        }
+        if (CLUSTER_ALGORITHM.isSet(namespace)) {
+            clusteringOptions = clusteringOptions.withAlgorithm(CLUSTER_ALGORITHM.getFrom(namespace));
+        }
+        if (CLUSTER_METRIC.isSet(namespace)) {
+            clusteringOptions = clusteringOptions.withSimilarityMetric(CLUSTER_METRIC.getFrom(namespace));
+        }
+        if (CLUSTER_SPECTRAL_BANDWIDTH.isSet(namespace)) {
+            clusteringOptions = clusteringOptions.withSpectralKernelBandwidth(CLUSTER_SPECTRAL_BANDWIDTH.getFrom(namespace));
+        }
+        if (CLUSTER_SPECTRAL_NOISE.isSet(namespace)) {
+            clusteringOptions = clusteringOptions.withSpectralGaussianProcessVariance(CLUSTER_SPECTRAL_NOISE.getFrom(namespace));
+        }
+        if (CLUSTER_SPECTRAL_MIN_RUNS.isSet(namespace)) {
+            clusteringOptions = clusteringOptions.withSpectralMinRuns(CLUSTER_SPECTRAL_MIN_RUNS.getFrom(namespace));
+        }
+        if (CLUSTER_SPECTRAL_MAX_RUNS.isSet(namespace)) {
+            clusteringOptions = clusteringOptions.withSpectralMaxRuns(CLUSTER_SPECTRAL_MAX_RUNS.getFrom(namespace));
+        }
+        if (CLUSTER_SPECTRAL_KMEANS_ITERATIONS.isSet(namespace)) {
+            clusteringOptions = clusteringOptions.withSpectralMaxKMeansIterationPerRun(CLUSTER_SPECTRAL_KMEANS_ITERATIONS.getFrom(namespace));
+        }
+        if (CLUSTER_AGGLOMERATIVE_THRESHOLD.isSet(namespace)) {
+            clusteringOptions = clusteringOptions.withAgglomerativeThreshold(CLUSTER_AGGLOMERATIVE_THRESHOLD.getFrom(namespace));
+        }
+        if (CLUSTER_AGGLOMERATIVE_INTER_CLUSTER_SIMILARITY.isSet(namespace)) {
+            clusteringOptions = clusteringOptions
+                    .withAgglomerativeInterClusterSimilarity(CLUSTER_AGGLOMERATIVE_INTER_CLUSTER_SIMILARITY.getFrom(namespace));
+        }
+        if (CLUSTER_PREPROCESSING_NONE.isSet(namespace)) {
+            if (CLUSTER_PREPROCESSING_NONE.getFrom(namespace)) {
+                clusteringOptions = clusteringOptions.withPreprocessor(Preprocessing.NONE);
+            }
+        }
+        if (CLUSTER_PREPROCESSING_CDF.isSet(namespace)) {
+            if (CLUSTER_PREPROCESSING_CDF.getFrom(namespace)) {
+                clusteringOptions = clusteringOptions.withPreprocessor(Preprocessing.CUMULATIVE_DISTRIBUTION_FUNCTION);
+            }
+        }
+        if (CLUSTER_PREPROCESSING_PERCENTILE.isSet(namespace)) {
+            clusteringOptions = clusteringOptions.withPreprocessor(Preprocessing.PERCENTILE)
+                    .withPreprocessorPercentile(CLUSTER_PREPROCESSING_PERCENTILE.getFrom(namespace));
+        }
+        if (CLUSTER_PREPROCESSING_THRESHOLD.isSet(namespace)) {
+            clusteringOptions = clusteringOptions.withPreprocessor(Preprocessing.THRESHOLD)
+                    .withPreprocessorPercentile(CLUSTER_PREPROCESSING_THRESHOLD.getFrom(namespace));
+        }
+        return clusteringOptions;
     }
 
     private String generateDescription() {

+ 18 - 11
jplag.cli/src/main/java/de/jplag/CommandLineArgument.java

@@ -55,31 +55,29 @@ public enum CommandLineArgument {
     CLUSTER_DISABLE(new Builder("--cluster-skip", Boolean.class).argumentGroup(CLUSTERING_GROUP_NAME).action(Arguments.storeTrue())),
     CLUSTER_ALGORITHM(
             new Builder("--cluster-alg", ClusteringAlgorithm.class).argumentGroup(CLUSTERING_GROUP_NAME)
-                    .defaultsTo(ClusteringOptions.DEFAULTS.getAlgorithm())),
+                    .defaultsTo(new ClusteringOptions().algorithm())),
     CLUSTER_METRIC(
             new Builder("--cluster-metric", SimilarityMetric.class).argumentGroup(CLUSTERING_GROUP_NAME)
-                    .defaultsTo(ClusteringOptions.DEFAULTS.getSimilarityMetric())),
+                    .defaultsTo(new ClusteringOptions().similarityMetric())),
     CLUSTER_SPECTRAL_BANDWIDTH(
             new Builder("--cluster-spectral-bandwidth", Double.class).metaVar("bandwidth")
-                    .defaultsTo(ClusteringOptions.DEFAULTS.getSpectralKernelBandwidth()).hidden()),
+                    .defaultsTo(new ClusteringOptions().spectralKernelBandwidth()).hidden()),
     CLUSTER_SPECTRAL_NOISE(
             new Builder("--cluster-spectral-noise", Double.class).metaVar("noise")
-                    .defaultsTo(ClusteringOptions.DEFAULTS.getSpectralGaussianProcessVariance()).hidden()),
+                    .defaultsTo(new ClusteringOptions().spectralGaussianProcessVariance()).hidden()),
     CLUSTER_SPECTRAL_MIN_RUNS(
-            new Builder("--cluster-spectral-min-runs", Integer.class).metaVar("min").defaultsTo(ClusteringOptions.DEFAULTS.getSpectralMinRuns())
-                    .hidden()),
+            new Builder("--cluster-spectral-min-runs", Integer.class).metaVar("min").defaultsTo(new ClusteringOptions().spectralMinRuns()).hidden()),
     CLUSTER_SPECTRAL_MAX_RUNS(
-            new Builder("--cluster-spectral-max-runs", Integer.class).metaVar("max").defaultsTo(ClusteringOptions.DEFAULTS.getSpectralMaxRuns())
-                    .hidden()),
+            new Builder("--cluster-spectral-max-runs", Integer.class).metaVar("max").defaultsTo(new ClusteringOptions().spectralMaxRuns()).hidden()),
     CLUSTER_SPECTRAL_KMEANS_ITERATIONS(
             new Builder("--cluster-spectral-kmeans-interations", Integer.class).metaVar("iterations")
-                    .defaultsTo(ClusteringOptions.DEFAULTS.getSpectralMaxKMeansIterationPerRun()).hidden()),
+                    .defaultsTo(new ClusteringOptions().spectralMaxKMeansIterationPerRun()).hidden()),
     CLUSTER_AGGLOMERATIVE_THRESHOLD(
             new Builder("--cluster-agglomerative-threshold", Double.class).metaVar("threshold")
-                    .defaultsTo(ClusteringOptions.DEFAULTS.getAgglomerativeThreshold()).hidden()),
+                    .defaultsTo(new ClusteringOptions().agglomerativeThreshold()).hidden()),
     CLUSTER_AGGLOMERATIVE_INTER_CLUSTER_SIMILARITY(
             new Builder("--cluster-agglomerative-inter-cluster-similarity", InterClusterSimilarity.class)
-                    .defaultsTo(ClusteringOptions.DEFAULTS.getAgglomerativeInterClusterSimilarity()).hidden()),
+                    .defaultsTo(new ClusteringOptions().agglomerativeInterClusterSimilarity()).hidden()),
     CLUSTER_PREPROCESSING_NONE(new Builder("--cluster-pp-none", Boolean.class).action(Arguments.storeTrue()).hidden()),
     CLUSTER_PREPROCESSING_CDF(new Builder("--cluster-pp-cdf", Boolean.class).action(Arguments.storeTrue()).hidden()),
     CLUSTER_PREPROCESSING_PERCENTILE(new Builder("--cluster-pp-percentile", Double.class).metaVar("percentile").hidden()),
@@ -147,6 +145,15 @@ public enum CommandLineArgument {
         return namespace.get(flagWithoutDash());
     }
 
+    /**
+     * Returns whether the value of this argument is set to a value not equal to {@code null}.
+     * @param namespace stores a value for the argument
+     * @return the indicator
+     */
+    public boolean isSet(Namespace namespace) {
+        return namespace.get(flagWithoutDash()) != null;
+    }
+
     /**
      * Returns the value of this argument for arguments that allow more than a single value. Convenience method for
      * {@link Namespace#getList(String)} and {@link CommandLineArgument#flagWithoutDash()}.

+ 6 - 6
jplag.cli/src/test/java/de/jplag/cli/ClusteringTest.java

@@ -15,35 +15,35 @@ class ClusteringTest extends CommandLineInterfaceTest {
     void parseSkipClustering() {
         String argument = CommandLineArgument.CLUSTER_DISABLE.flag();
         buildOptionsFromCLI(argument, CURRENT_DIRECTORY);
-        assertEquals(false, options.clusteringOptions().isEnabled());
+        assertEquals(false, options.clusteringOptions().enabled());
     }
 
     @Test
     void parseDefaultClustering() {
         buildOptionsFromCLI(CURRENT_DIRECTORY);
-        assertEquals(true, options.clusteringOptions().isEnabled());
+        assertEquals(true, options.clusteringOptions().enabled());
     }
 
     @Test
     void parsePercentilePreProcessor() {
         String argument = buildArgument(CommandLineArgument.CLUSTER_PREPROCESSING_PERCENTILE, Double.toString(0.5));
         buildOptionsFromCLI(argument, CURRENT_DIRECTORY);
-        assertEquals(Preprocessing.PERCENTILE, options.clusteringOptions().getPreprocessor());
-        assertEquals(0.5, options.clusteringOptions().getPreprocessorPercentile(), EPSILON);
+        assertEquals(Preprocessing.PERCENTILE, options.clusteringOptions().preprocessor());
+        assertEquals(0.5, options.clusteringOptions().preprocessorPercentile(), EPSILON);
     }
 
     @Test
     void parseCdfPreProcessor() {
         String argument = CommandLineArgument.CLUSTER_PREPROCESSING_CDF.flag();
         buildOptionsFromCLI(argument, CURRENT_DIRECTORY);
-        assertEquals(Preprocessing.CUMULATIVE_DISTRIBUTION_FUNCTION, options.clusteringOptions().getPreprocessor());
+        assertEquals(Preprocessing.CUMULATIVE_DISTRIBUTION_FUNCTION, options.clusteringOptions().preprocessor());
     }
 
     @Test
     void parseNoPreProcessor() {
         String argument = CommandLineArgument.CLUSTER_PREPROCESSING_NONE.flag();
         buildOptionsFromCLI(argument, CURRENT_DIRECTORY);
-        assertEquals(Preprocessing.NONE, options.clusteringOptions().getPreprocessor());
+        assertEquals(Preprocessing.NONE, options.clusteringOptions().preprocessor());
     }
 
 }

+ 12 - 0
jplag.cli/src/test/java/de/jplag/cli/LanguageTest.java

@@ -3,6 +3,9 @@ package de.jplag.cli;
 import static com.github.stefanbirkner.systemlambda.SystemLambda.catchSystemExit;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
+import java.util.Arrays;
+import java.util.List;
+
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
@@ -37,7 +40,16 @@ class LanguageTest extends CommandLineInterfaceTest {
             String argument = buildArgument(CommandLineArgument.LANGUAGE, language.getIdentifier());
             buildOptionsFromCLI(argument, CURRENT_DIRECTORY);
             assertEquals(language.getIdentifier(), options.language().getIdentifier());
+            assertEquals(Arrays.asList(language.suffixes()), options.fileSuffixes());
         }
     }
 
+    @Test
+    void testCustomSuffixes() {
+        List<String> suffixes = List.of("x", "y", "z");
+        String argument = buildArgument(CommandLineArgument.SUFFIXES, String.join(",", suffixes));
+        buildOptionsFromCLI(argument, CURRENT_DIRECTORY);
+        assertEquals(suffixes, options.fileSuffixes());
+    }
+
 }

+ 2 - 1
jplag.frontend-utils/src/main/java/de/jplag/LanguageLoader.java

@@ -36,9 +36,10 @@ public final class LanguageLoader {
                 languages.remove(languageIdentifier);
                 continue;
             }
-            logger.info("Loading Language Frontend '{}'", language.getName());
+            logger.debug("Loading Language Frontend '{}'", language.getName());
             languages.put(languageIdentifier, language);
         }
+        logger.info("Available languages: '{}'", languages.values().stream().map(Language::getName).toList());
 
         cachedLanguageInstances = Collections.unmodifiableMap(languages);
         return cachedLanguageInstances;

+ 5 - 5
jplag/src/main/java/de/jplag/clustering/ClusteringFactory.java

@@ -24,18 +24,18 @@ public class ClusteringFactory {
     private static final Logger logger = LoggerFactory.getLogger(ClusteringFactory.class);
 
     public static List<ClusteringResult<Submission>> getClusterings(Collection<JPlagComparison> comparisons, ClusteringOptions options) {
-        if (!options.isEnabled()) {
+        if (!options.enabled()) {
             logger.warn(CLUSTERING_DISABLED);
             return Collections.emptyList();
         } else {
-            logger.info(CLUSTERING_PARAMETERS, options.getAlgorithm(), options.getPreprocessor());
+            logger.info(CLUSTERING_PARAMETERS, options.algorithm(), options.preprocessor());
         }
 
         // init algorithm
-        GenericClusteringAlgorithm clusteringAlgorithm = options.getAlgorithm().create(options);
+        GenericClusteringAlgorithm clusteringAlgorithm = options.algorithm().create(options);
 
         // init preprocessor
-        Optional<ClusteringPreprocessor> preprocessor = options.getPreprocessor().constructPreprocessor(options);
+        Optional<ClusteringPreprocessor> preprocessor = options.preprocessor().constructPreprocessor(options);
 
         if (preprocessor.isPresent()) {
             // Package preprocessor into a clustering algorithm
@@ -43,7 +43,7 @@ public class ClusteringFactory {
         }
 
         // init adapter
-        ClusteringAdapter adapter = new ClusteringAdapter(comparisons, options.getSimilarityMetric());
+        ClusteringAdapter adapter = new ClusteringAdapter(comparisons, options.similarityMetric());
 
         // run clustering
         ClusteringResult<Submission> result = adapter.doClustering(clusteringAlgorithm);

+ 104 - 214
jplag/src/main/java/de/jplag/clustering/ClusteringOptions.java

@@ -1,253 +1,143 @@
 
 package de.jplag.clustering;
 
+import java.util.Objects;
+
 import de.jplag.clustering.algorithm.InterClusterSimilarity;
 import de.jplag.options.SimilarityMetric;
 
 /**
  * Collection of all possible parameters that describe how a clustering should be performed.
+ * @param similarityMetric The similarity metric is used for clustering
+ * @param spectralKernelBandwidth The kernel bandwidth for the matern kernel used in the gaussian process for the
+ * automatic search for the number of clusters in spectral clustering. Affects the runtime and results of the spectral
+ * clustering.
+ * @param spectralGaussianProcessVariance This is the assumed level of noise in the evaluation results of a spectral
+ * clustering. Acts as normalization parameter for the Gaussian Process. The default setting works well with similarity
+ * scores in the range between zero and one. Affects the results of the spectral clustering.
+ * @param spectralMinRuns The minimal number of times the kMeans algorithm is run for the spectral clustering. These
+ * runs will use predefined numbers of clusters and will not use the bayesian optimization to determine the number of
+ * clusters.
+ * @param spectralMaxRuns The maximal number of times the kMeans algorithm is run during the bayesian optimization for
+ * spectral clustering. The bayesian optimization may be stopped before, when no more maxima of the acquisition-function
+ * are found.
+ * @param spectralMaxKMeansIterationPerRun Maximum number of iterations of the kMeans clustering per run during spectral
+ * clustering.
+ * @param agglomerativeThreshold Agglomerative clustering will merge clusters that have a similarity higher than this
+ * threshold.
+ * @param preprocessor Preprocessing for the similarity values before clustering. Preprocessing is mandatory for
+ * spectral clustering and optional for agglomerative clustering.
+ * @param enabled whether clustering should be performed
+ * @param algorithm the clustering algorithm to use
+ * @param agglomerativeInterClusterSimilarity Similarity measure between clusters in agglomerative clustering
+ * @param preprocessorThreshold up to which similarity the threshold-preprocessor zeroes out the similarities
+ * @param preprocessorPercentile up to which percentile of similarities the percentile-preprocessor zeroes out the
+ * similarities
  */
-public class ClusteringOptions {
-
-    public static final ClusteringOptions DEFAULTS = new Builder().build();
-
-    private final SimilarityMetric similarityMetric;
-    private final double spectralKernelBandwidth;
-    private final double spectralGaussianProcessVariance;
-    private final int spectralMinRuns;
-    private final int spectralMaxRuns;
-    private final int spectralMaxKMeansIterationPerRun;
-    private final double agglomerativeThreshold;
-    private final Preprocessing preprocessor;
-    private final boolean enabled;
-    private final ClusteringAlgorithm algorithm;
-    private final InterClusterSimilarity agglomerativeInterClusterSimilarity;
-    private final double preprocessorThreshold;
-    private final double preprocessorPercentile;
-
-    /**
-     * @return The similarity metric is used for clustering
-     */
-    public SimilarityMetric getSimilarityMetric() {
-        return similarityMetric;
+public record ClusteringOptions(SimilarityMetric similarityMetric, double spectralKernelBandwidth, double spectralGaussianProcessVariance,
+        int spectralMinRuns, int spectralMaxRuns, int spectralMaxKMeansIterationPerRun, double agglomerativeThreshold, Preprocessing preprocessor,
+        boolean enabled, ClusteringAlgorithm algorithm, InterClusterSimilarity agglomerativeInterClusterSimilarity, double preprocessorThreshold,
+        double preprocessorPercentile) {
+
+    public ClusteringOptions(SimilarityMetric similarityMetric, double spectralKernelBandwidth, double spectralGaussianProcessVariance,
+            int spectralMinRuns, int spectralMaxRuns, int spectralMaxKMeansIterationPerRun, double agglomerativeThreshold, Preprocessing preprocessor,
+            boolean enabled, ClusteringAlgorithm algorithm, InterClusterSimilarity agglomerativeInterClusterSimilarity, double preprocessorThreshold,
+            double preprocessorPercentile) {
+        this.similarityMetric = Objects.requireNonNull(similarityMetric);
+        this.spectralKernelBandwidth = spectralKernelBandwidth;
+        this.spectralGaussianProcessVariance = spectralGaussianProcessVariance;
+        this.spectralMinRuns = spectralMinRuns;
+        this.spectralMaxRuns = spectralMaxRuns;
+        this.spectralMaxKMeansIterationPerRun = spectralMaxKMeansIterationPerRun;
+        this.agglomerativeThreshold = agglomerativeThreshold;
+        this.preprocessor = Objects.requireNonNull(preprocessor);
+        this.enabled = enabled;
+        this.algorithm = Objects.requireNonNull(algorithm);
+        this.agglomerativeInterClusterSimilarity = Objects.requireNonNull(agglomerativeInterClusterSimilarity);
+        this.preprocessorThreshold = preprocessorThreshold;
+        this.preprocessorPercentile = preprocessorPercentile;
     }
 
-    /**
-     * The kernel bandwidth for the matern kernel used in the gaussian process for the automatic search for the number of
-     * clusters in spectral clustering. Affects the runtime and results of the spectral clustering.
-     * @return kernel bandwidth for spectral clustering
-     */
-    public double getSpectralKernelBandwidth() {
-        return spectralKernelBandwidth;
+    public ClusteringOptions() {
+        this(SimilarityMetric.MAX, 20.f, 0.05f * 0.05f, 5, 50, 200, 0.2f, Preprocessing.CUMULATIVE_DISTRIBUTION_FUNCTION, true,
+                ClusteringAlgorithm.SPECTRAL, InterClusterSimilarity.AVERAGE, 0.2f, 0.5f);
     }
 
-    /**
-     * This is the assumed level of noise in the evaluation results of a spectral clustering. Acts as normalization
-     * parameter for the Gaussian Process. The default setting works well with similarity scores in the range between zero
-     * and one. Affects the results of the spectral clustering.
-     * @return assumed variance of noise in Gaussian Process during spectral clustering.
-     */
-    public double getSpectralGaussianProcessVariance() {
-        return spectralGaussianProcessVariance;
+    public ClusteringOptions withSimilarityMetric(SimilarityMetric similarityMetric) {
+        return new ClusteringOptions(similarityMetric, spectralKernelBandwidth, spectralGaussianProcessVariance, spectralMinRuns, spectralMaxRuns,
+                spectralMaxKMeansIterationPerRun, agglomerativeThreshold, preprocessor, enabled, algorithm, agglomerativeInterClusterSimilarity,
+                preprocessorThreshold, preprocessorPercentile);
     }
 
-    /**
-     * The minimal number of times the kMeans algorithm is run for the spectral clustering. These runs will use predefined
-     * numbers of clusters and will not use the bayesian optimization to determine the number of clusters.
-     * @return minimal kMeans runs during spectral clustering
-     */
-    public int getSpectralMinRuns() {
-        return spectralMinRuns;
+    public ClusteringOptions withSpectralKernelBandwidth(double spectralKernelBandwidth) {
+        return new ClusteringOptions(similarityMetric, spectralKernelBandwidth, spectralGaussianProcessVariance, spectralMinRuns, spectralMaxRuns,
+                spectralMaxKMeansIterationPerRun, agglomerativeThreshold, preprocessor, enabled, algorithm, agglomerativeInterClusterSimilarity,
+                preprocessorThreshold, preprocessorPercentile);
     }
 
-    /**
-     * The maximal number of times the kMeans algorithm is run during the bayesian optimization for spectral clustering. The
-     * bayesian optimization may be stopped before, when no more maxima of the acquisition-function are found.
-     * @return maximal kMeans runs during spectral clustering
-     */
-    public int getSpectralMaxRuns() {
-        return spectralMaxRuns;
+    public ClusteringOptions withSpectralGaussianProcessVariance(double spectralGaussianProcessVariance) {
+        return new ClusteringOptions(similarityMetric, spectralKernelBandwidth, spectralGaussianProcessVariance, spectralMinRuns, spectralMaxRuns,
+                spectralMaxKMeansIterationPerRun, agglomerativeThreshold, preprocessor, enabled, algorithm, agglomerativeInterClusterSimilarity,
+                preprocessorThreshold, preprocessorPercentile);
     }
 
-    /**
-     * Maximum number of iterations of the kMeans clustering per run during spectral clustering.
-     * @return maximal kMeans iterations
-     */
-    public int getSpectralMaxKMeansIterationPerRun() {
-        return spectralMaxKMeansIterationPerRun;
+    public ClusteringOptions withSpectralMinRuns(int spectralMinRuns) {
+        return new ClusteringOptions(similarityMetric, spectralKernelBandwidth, spectralGaussianProcessVariance, spectralMinRuns, spectralMaxRuns,
+                spectralMaxKMeansIterationPerRun, agglomerativeThreshold, preprocessor, enabled, algorithm, agglomerativeInterClusterSimilarity,
+                preprocessorThreshold, preprocessorPercentile);
     }
 
-    /**
-     * Agglomerative clustering will merge clusters that have a similarity higher than this threshold.
-     * @return merging threshold for agglomerative clustering
-     */
-    public double getAgglomerativeThreshold() {
-        return agglomerativeThreshold;
+    public ClusteringOptions withSpectralMaxRuns(int spectralMaxRuns) {
+        return new ClusteringOptions(similarityMetric, spectralKernelBandwidth, spectralGaussianProcessVariance, spectralMinRuns, spectralMaxRuns,
+                spectralMaxKMeansIterationPerRun, agglomerativeThreshold, preprocessor, enabled, algorithm, agglomerativeInterClusterSimilarity,
+                preprocessorThreshold, preprocessorPercentile);
     }
 
-    /**
-     * Preprocessing for the similarity values before clustering. Preprocessing is mandatory for spectral clustering and
-     * optional for agglomerative clustering.
-     * @return preprocessor
-     */
-    public Preprocessing getPreprocessor() {
-        return preprocessor;
+    public ClusteringOptions withSpectralMaxKMeansIterationPerRun(int spectralMaxKMeansIterationPerRun) {
+        return new ClusteringOptions(similarityMetric, spectralKernelBandwidth, spectralGaussianProcessVariance, spectralMinRuns, spectralMaxRuns,
+                spectralMaxKMeansIterationPerRun, agglomerativeThreshold, preprocessor, enabled, algorithm, agglomerativeInterClusterSimilarity,
+                preprocessorThreshold, preprocessorPercentile);
     }
 
-    /**
-     * @return whether clustering should be performed
-     */
-    public boolean isEnabled() {
-        return enabled;
+    public ClusteringOptions withAgglomerativeThreshold(double agglomerativeThreshold) {
+        return new ClusteringOptions(similarityMetric, spectralKernelBandwidth, spectralGaussianProcessVariance, spectralMinRuns, spectralMaxRuns,
+                spectralMaxKMeansIterationPerRun, agglomerativeThreshold, preprocessor, enabled, algorithm, agglomerativeInterClusterSimilarity,
+                preprocessorThreshold, preprocessorPercentile);
     }
 
-    /**
-     * @return the clustering algorithm to use
-     */
-    public ClusteringAlgorithm getAlgorithm() {
-        return algorithm;
+    public ClusteringOptions withPreprocessor(Preprocessing preprocessor) {
+        return new ClusteringOptions(similarityMetric, spectralKernelBandwidth, spectralGaussianProcessVariance, spectralMinRuns, spectralMaxRuns,
+                spectralMaxKMeansIterationPerRun, agglomerativeThreshold, preprocessor, enabled, algorithm, agglomerativeInterClusterSimilarity,
+                preprocessorThreshold, preprocessorPercentile);
     }
 
-    /**
-     * Similarity measure between clusters in agglomerative clustering.
-     * @return similarity measure
-     */
-    public InterClusterSimilarity getAgglomerativeInterClusterSimilarity() {
-        return agglomerativeInterClusterSimilarity;
+    public ClusteringOptions withEnabled(boolean enabled) {
+        return new ClusteringOptions(similarityMetric, spectralKernelBandwidth, spectralGaussianProcessVariance, spectralMinRuns, spectralMaxRuns,
+                spectralMaxKMeansIterationPerRun, agglomerativeThreshold, preprocessor, enabled, algorithm, agglomerativeInterClusterSimilarity,
+                preprocessorThreshold, preprocessorPercentile);
     }
 
-    /**
-     * @return up to which similarity the threshold-preprocessor zeroes out the similarities
-     */
-    public double getPreprocessorThreshold() {
-        return preprocessorThreshold;
+    public ClusteringOptions withAlgorithm(ClusteringAlgorithm algorithm) {
+        return new ClusteringOptions(similarityMetric, spectralKernelBandwidth, spectralGaussianProcessVariance, spectralMinRuns, spectralMaxRuns,
+                spectralMaxKMeansIterationPerRun, agglomerativeThreshold, preprocessor, enabled, algorithm, agglomerativeInterClusterSimilarity,
+                preprocessorThreshold, preprocessorPercentile);
     }
 
-    /**
-     * @return up to which percentile of similarities the percentile-preprocessor zeroes out the similarities
-     */
-    public double getPreprocessorPercentile() {
-        return preprocessorPercentile;
+    public ClusteringOptions withAgglomerativeInterClusterSimilarity(InterClusterSimilarity agglomerativeInterClusterSimilarity) {
+        return new ClusteringOptions(similarityMetric, spectralKernelBandwidth, spectralGaussianProcessVariance, spectralMinRuns, spectralMaxRuns,
+                spectralMaxKMeansIterationPerRun, agglomerativeThreshold, preprocessor, enabled, algorithm, agglomerativeInterClusterSimilarity,
+                preprocessorThreshold, preprocessorPercentile);
     }
 
-    public static class Builder {
-
-        private SimilarityMetric similarityMetric;
-        private double spectralKernelBandwidth;
-        private double spectralGaussianProcessVariance;
-        private int spectralMinRuns;
-        private int spectralMaxRuns;
-        private int spectralMaxKMeansIterationPerRun;
-        private double agglomerativeThreshold;
-        private Preprocessing preprocessor;
-        private boolean enabled;
-        private ClusteringAlgorithm algorithm;
-        private InterClusterSimilarity agglomerativeInterClusterSimilarity;
-        private double preprocessorThreshold;
-        private double preprocessorPercentile;
-
-        public Builder() {
-            // Setting the defaults here
-            similarityMetric(SimilarityMetric.MAX);
-            spectralKernelBandwidth(20.0);
-            spectralGaussianProcessVariance(0.05 * 0.05);
-            spectralMinRuns(5);
-            spectralMaxRuns(50);
-            spectralMaxKMeansIterationPerRun(200);
-            agglomerativeThreshold(0.2);
-            preprocessor(Preprocessing.CUMULATIVE_DISTRIBUTION_FUNCTION);
-            enabled(true);
-            algorithm(ClusteringAlgorithm.SPECTRAL);
-            agglomerativeInterClusterSimilarity(InterClusterSimilarity.AVERAGE);
-            preprocessorThreshold(0.2);
-            preprocessorPercentile(0.5);
-        }
-
-        public Builder similarityMetric(SimilarityMetric similarityMetric) {
-            this.similarityMetric = similarityMetric;
-            return Builder.this;
-        }
-
-        public Builder spectralKernelBandwidth(double spectralKernelBandwidth) {
-            this.spectralKernelBandwidth = spectralKernelBandwidth;
-            return Builder.this;
-        }
-
-        public Builder spectralGaussianProcessVariance(double spectralGPVariance) {
-            this.spectralGaussianProcessVariance = spectralGPVariance;
-            return Builder.this;
-        }
-
-        public Builder spectralMinRuns(int spectralMinRuns) {
-            this.spectralMinRuns = spectralMinRuns;
-            return Builder.this;
-        }
-
-        public Builder spectralMaxRuns(int spectralMaxRuns) {
-            this.spectralMaxRuns = spectralMaxRuns;
-            return Builder.this;
-        }
-
-        public Builder spectralMaxKMeansIterationPerRun(int spectralMaxKMeansIterationPerRun) {
-            this.spectralMaxKMeansIterationPerRun = spectralMaxKMeansIterationPerRun;
-            return Builder.this;
-        }
-
-        public Builder agglomerativeThreshold(double agglomerativeThreshold) {
-            this.agglomerativeThreshold = agglomerativeThreshold;
-            return Builder.this;
-        }
-
-        public Builder preprocessor(Preprocessing preprocessor) {
-            this.preprocessor = preprocessor;
-            return Builder.this;
-        }
-
-        public Builder enabled(boolean enabled) {
-            this.enabled = enabled;
-            return Builder.this;
-        }
-
-        public Builder algorithm(ClusteringAlgorithm algorithm) {
-            this.algorithm = algorithm;
-            return Builder.this;
-        }
-
-        public Builder agglomerativeInterClusterSimilarity(InterClusterSimilarity agglomerativeInterClusterSimilarity) {
-            this.agglomerativeInterClusterSimilarity = agglomerativeInterClusterSimilarity;
-            return Builder.this;
-        }
-
-        public Builder preprocessorThreshold(double preprocessorThreshold) {
-            this.preprocessorThreshold = preprocessorThreshold;
-            return Builder.this;
-        }
-
-        public Builder preprocessorPercentile(double preprocessorPercentile) {
-            this.preprocessorPercentile = preprocessorPercentile;
-            return Builder.this;
-        }
-
-        public ClusteringOptions build() {
-
-            return new ClusteringOptions(this);
-        }
+    public ClusteringOptions withPreprocessorThreshold(double preprocessorThreshold) {
+        return new ClusteringOptions(similarityMetric, spectralKernelBandwidth, spectralGaussianProcessVariance, spectralMinRuns, spectralMaxRuns,
+                spectralMaxKMeansIterationPerRun, agglomerativeThreshold, preprocessor, enabled, algorithm, agglomerativeInterClusterSimilarity,
+                preprocessorThreshold, preprocessorPercentile);
     }
 
-    private ClusteringOptions(Builder builder) {
-        this.similarityMetric = builder.similarityMetric;
-        this.spectralKernelBandwidth = builder.spectralKernelBandwidth;
-        this.spectralGaussianProcessVariance = builder.spectralGaussianProcessVariance;
-        this.spectralMinRuns = builder.spectralMinRuns;
-        this.spectralMaxRuns = builder.spectralMaxRuns;
-        this.spectralMaxKMeansIterationPerRun = builder.spectralMaxKMeansIterationPerRun;
-        this.agglomerativeThreshold = builder.agglomerativeThreshold;
-        this.preprocessor = builder.preprocessor;
-        this.enabled = builder.enabled;
-        this.algorithm = builder.algorithm;
-        this.agglomerativeInterClusterSimilarity = builder.agglomerativeInterClusterSimilarity;
-        this.preprocessorThreshold = builder.preprocessorThreshold;
-        this.preprocessorPercentile = builder.preprocessorPercentile;
+    public ClusteringOptions withPreprocessorPercentile(double preprocessorPercentile) {
+        return new ClusteringOptions(similarityMetric, spectralKernelBandwidth, spectralGaussianProcessVariance, spectralMinRuns, spectralMaxRuns,
+                spectralMaxKMeansIterationPerRun, agglomerativeThreshold, preprocessor, enabled, algorithm, agglomerativeInterClusterSimilarity,
+                preprocessorThreshold, preprocessorPercentile);
     }
-
 }

+ 2 - 2
jplag/src/main/java/de/jplag/clustering/Preprocessing.java

@@ -15,9 +15,9 @@ public enum Preprocessing {
     /** {@link CumulativeDistributionFunctionPreprocessor} */
     CUMULATIVE_DISTRIBUTION_FUNCTION(options -> new CumulativeDistributionFunctionPreprocessor()),
     /** {@link ThresholdPreprocessor} */
-    THRESHOLD(options -> new ThresholdPreprocessor(options.getPreprocessorThreshold())),
+    THRESHOLD(options -> new ThresholdPreprocessor(options.preprocessorThreshold())),
     /** {@link PercentileThresholdProcessor} */
-    PERCENTILE(options -> new PercentileThresholdProcessor(options.getPreprocessorPercentile()));
+    PERCENTILE(options -> new PercentileThresholdProcessor(options.preprocessorPercentile()));
 
     private final Function<ClusteringOptions, ClusteringPreprocessor> constructor;
 

+ 8 - 3
jplag/src/main/java/de/jplag/clustering/algorithm/AgglomerativeClustering.java

@@ -1,6 +1,11 @@
 package de.jplag.clustering.algorithm;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.PriorityQueue;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 import org.apache.commons.math3.linear.RealMatrix;
@@ -57,7 +62,7 @@ public class AgglomerativeClustering implements GenericClusteringAlgorithm {
                 // One cluster already part of another cluster
                 continue;
             }
-            if (nearest.similarity < options.getAgglomerativeThreshold()) {
+            if (nearest.similarity < options.agglomerativeThreshold()) {
                 break;
             }
             clusters.remove(nearest.left);
@@ -65,7 +70,7 @@ public class AgglomerativeClustering implements GenericClusteringAlgorithm {
             nearest.left.submissions().addAll(nearest.right.submissions());
             Cluster combined = new Cluster(nearest.left.submissions());
             for (Cluster otherCluster : clusters) {
-                double similarity = options.getAgglomerativeInterClusterSimilarity().clusterSimilarity(combined.submissions, otherCluster.submissions,
+                double similarity = options.agglomerativeInterClusterSimilarity().clusterSimilarity(combined.submissions, otherCluster.submissions,
                         similarityMatrix);
                 similarities.add(new ClusterConnection(combined, otherCluster, similarity));
             }

+ 3 - 3
jplag/src/main/java/de/jplag/clustering/algorithm/SpectralClustering.java

@@ -85,9 +85,9 @@ public class SpectralClustering implements GenericClusteringAlgorithm {
         int maxClusters = (int) Math.ceil(dimension / 2.0);
 
         // Find number of clusters using bayesian optimization
-        RealVector lengthScale = new ArrayRealVector(1, options.getSpectralKernelBandwidth());
+        RealVector lengthScale = new ArrayRealVector(1, options.spectralKernelBandwidth());
         BayesianOptimization bo = new BayesianOptimization(new ArrayRealVector(1, minClusters), new ArrayRealVector(1, maxClusters),
-                options.getSpectralMinRuns(), options.getSpectralMaxRuns(), options.getSpectralGaussianProcessVariance(), lengthScale);
+                options.spectralMinRuns(), options.spectralMaxRuns(), options.spectralGaussianProcessVariance(), lengthScale);
         // bo.debug = true;
         BayesianOptimization.OptimizationResult<Collection<Collection<Integer>>> bayesianOptimizationResult = bo.maximize(r -> {
             int clusters = (int) Math.round(r.getEntry(0));
@@ -115,7 +115,7 @@ public class SpectralClustering implements GenericClusteringAlgorithm {
         List<ClusterableEigenVector> normRows = IntStream.range(0, dimension).filter(i -> concatenatedEigenVectors.getRowVector(i).getNorm() > 0)
                 .mapToObj(row -> new ClusterableEigenVector(row, concatenatedEigenVectors.getRowVector(row).unitVector())).toList();
 
-        Clusterer<ClusterableEigenVector> clusterer = new KMeansPlusPlusClusterer<>(numberOfClusters, options.getSpectralMaxKMeansIterationPerRun());
+        Clusterer<ClusterableEigenVector> clusterer = new KMeansPlusPlusClusterer<>(numberOfClusters, options.spectralMaxKMeansIterationPerRun());
         List<? extends Cluster<ClusterableEigenVector>> clusters = clusterer.cluster(normRows);
         return clusters.stream().map(cluster -> cluster.getPoints().stream().map(eigenVector -> eigenVector.id).collect(Collectors.toList()))
                 .collect(Collectors.toList());

+ 7 - 4
jplag/src/main/java/de/jplag/options/JPlagOptions.java

@@ -8,7 +8,11 @@ import java.io.FileReader;
 import java.io.IOException;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 import org.slf4j.Logger;
@@ -60,8 +64,7 @@ public record JPlagOptions(Language language, Integer minimumTokenMatch, List<St
 
     public JPlagOptions(Language language, List<String> submissionDirectories, List<String> oldSubmissionDirectories) {
         this(language, null, submissionDirectories, oldSubmissionDirectories, null, null, null, null, DEFAULT_SIMILARITY_METRIC,
-                DEFAULT_SIMILARITY_THRESHOLD, DEFAULT_SHOWN_COMPARISONS, new ClusteringOptions.Builder().build(), DEFAULT_COMPARISON_MODE, null,
-                false);
+                DEFAULT_SIMILARITY_THRESHOLD, DEFAULT_SHOWN_COMPARISONS, new ClusteringOptions(), DEFAULT_COMPARISON_MODE, null, false);
     }
 
     public JPlagOptions(Language language, Integer minimumTokenMatch, List<String> submissionDirectories, List<String> oldSubmissionDirectories,
@@ -71,7 +74,7 @@ public record JPlagOptions(Language language, Integer minimumTokenMatch, List<St
         this.language = language;
         this.comparisonMode = comparisonMode;
         this.debugParser = debugParser;
-        this.fileSuffixes = fileSuffixes == null ? null : Collections.unmodifiableList(fileSuffixes);
+        this.fileSuffixes = fileSuffixes == null || fileSuffixes.isEmpty() ? null : Collections.unmodifiableList(fileSuffixes);
         this.similarityThreshold = normalizeSimilarityThreshold(similarityThreshold);
         this.maximumNumberOfComparisons = normalizeMaximumNumberOfComparisons(maximumNumberOfComparisons);
         this.similarityMetric = similarityMetric;

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

@@ -88,6 +88,8 @@ public class DirectoryManager {
             deleteDirectory(zipName);
             return false;
         }
+        logger.info("Successfully zipped report files: {}", zipName);
+        logger.info("Display the results with the report viewer at https://jplag.github.io/JPlag/");
         return true;
     }
 }

+ 10 - 2
jplag/src/main/java/de/jplag/reporting/reportobject/ReportObjectFactory.java

@@ -1,6 +1,8 @@
 package de.jplag.reporting.reportobject;
 
-import static de.jplag.reporting.jsonfactory.DirectoryManager.*;
+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;
@@ -8,7 +10,11 @@ import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.StandardCopyOption;
 import java.text.SimpleDateFormat;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
@@ -48,6 +54,7 @@ public class ReportObjectFactory {
     public void createAndSaveReport(JPlagResult result, String path) {
 
         try {
+            logger.info("Start writing report files...");
             createDirectory(path);
             buildSubmissionToIdMap(result);
 
@@ -56,6 +63,7 @@ public class ReportObjectFactory {
             writeComparisons(result, path);
             writeOverview(result, path);
 
+            logger.info("Zipping report files...");
             zipAndDelete(path);
         } catch (IOException e) {
             logger.error("Could not create directory " + path + " for report viewer generation", e);

+ 3 - 3
jplag/src/test/java/de/jplag/clustering/algorithm/ClusteringData.java

@@ -32,17 +32,17 @@ public enum ClusteringData {
         setEntries(similarity, 1, 3, 0.1f);
 
         return similarity;
-    }, new int[][] {{0, 1}, {2, 3}}, new ClusteringOptions.Builder().agglomerativeThreshold(0.4f));
+    }, new int[][] {{0, 1}, {2, 3}}, new ClusteringOptions().withAgglomerativeThreshold(0.4f));
 
     private final RealMatrix similarity;
     private final Set<Set<Integer>> expected;
     private final ClusteringOptions options;
 
-    ClusteringData(Supplier<RealMatrix> similarity, int[][] expected, ClusteringOptions.Builder options) {
+    ClusteringData(Supplier<RealMatrix> similarity, int[][] expected, ClusteringOptions options) {
         this.similarity = similarity.get();
         this.expected = makeSets(
                 Arrays.stream(expected).map(intArray -> Arrays.stream(intArray).boxed().collect(Collectors.toList())).collect(Collectors.toList()));
-        this.options = options.build();
+        this.options = options;
     }
 
     public ClusteringOptions getOptions() {

Разлика између датотеке није приказан због своје велике величине
+ 258 - 329
report-viewer/package-lock.json


+ 8 - 8
report-viewer/package.json

@@ -11,36 +11,36 @@
     "@highlightjs/vue-plugin": "^2.1.0",
     "chart.js": "^3.9.1",
     "chartjs-plugin-datalabels": "^2.1.0",
-    "core-js": "^3.24.1",
+    "core-js": "^3.25.1",
     "gitart-vue-dialog": "^2.4.0",
     "highlight.js": "^11.6.0",
     "jszip": "^3.10.0",
     "node-polyfill-webpack-plugin": "^2.0.0",
     "slash": "^4.0.0",
-    "vue": "^3.2.37",
+    "vue": "^3.2.39",
     "vue-chart-3": "^3.1.8",
     "vue-draggable-next": "^2.1.1",
-    "vue-router": "^4.1.4",
+    "vue-router": "^4.1.5",
     "vue3-highlightjs": "^1.0.5",
     "vuex": "^4.0.2"
   },
   "devDependencies": {
-    "@typescript-eslint/eslint-plugin": "^5.34.0",
-    "@typescript-eslint/parser": "^5.34.0",
+    "@typescript-eslint/eslint-plugin": "^5.36.2",
+    "@typescript-eslint/parser": "^5.36.2",
     "@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/compiler-sfc": "^3.2.39",
     "@vue/eslint-config-prettier": "^7.0.0",
     "@vue/eslint-config-typescript": "^10.0.0",
-    "eslint": "^8.22.0",
+    "eslint": "^8.23.0",
     "eslint-plugin-prettier": "^4.2.1",
     "eslint-plugin-vue": "^8.7.1",
     "lint-staged": "^13.0.3",
     "prettier": "^2.7.1",
-    "typescript": "~4.7.4"
+    "typescript": "~4.8.3"
   },
   "gitHooks": {
     "pre-commit": "lint-staged"

Неке датотеке нису приказане због велике количине промена