CLI.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. package de.jplag;
  2. import static de.jplag.CommandLineArgument.BASE_CODE;
  3. import static de.jplag.CommandLineArgument.CLUSTER_AGGLOMERATIVE_INTER_CLUSTER_SIMILARITY;
  4. import static de.jplag.CommandLineArgument.CLUSTER_AGGLOMERATIVE_THRESHOLD;
  5. import static de.jplag.CommandLineArgument.CLUSTER_ALGORITHM;
  6. import static de.jplag.CommandLineArgument.CLUSTER_DISABLE;
  7. import static de.jplag.CommandLineArgument.CLUSTER_METRIC;
  8. import static de.jplag.CommandLineArgument.CLUSTER_PREPROCESSING_CDF;
  9. import static de.jplag.CommandLineArgument.CLUSTER_PREPROCESSING_NONE;
  10. import static de.jplag.CommandLineArgument.CLUSTER_PREPROCESSING_PERCENTILE;
  11. import static de.jplag.CommandLineArgument.CLUSTER_PREPROCESSING_THRESHOLD;
  12. import static de.jplag.CommandLineArgument.CLUSTER_SPECTRAL_BANDWIDTH;
  13. import static de.jplag.CommandLineArgument.CLUSTER_SPECTRAL_KMEANS_ITERATIONS;
  14. import static de.jplag.CommandLineArgument.CLUSTER_SPECTRAL_MAX_RUNS;
  15. import static de.jplag.CommandLineArgument.CLUSTER_SPECTRAL_MIN_RUNS;
  16. import static de.jplag.CommandLineArgument.CLUSTER_SPECTRAL_NOISE;
  17. import static de.jplag.CommandLineArgument.COMPARISON_MODE;
  18. import static de.jplag.CommandLineArgument.DEBUG;
  19. import static de.jplag.CommandLineArgument.EXCLUDE_FILE;
  20. import static de.jplag.CommandLineArgument.LANGUAGE;
  21. import static de.jplag.CommandLineArgument.MIN_TOKEN_MATCH;
  22. import static de.jplag.CommandLineArgument.NEW_DIRECTORY;
  23. import static de.jplag.CommandLineArgument.OLD_DIRECTORY;
  24. import static de.jplag.CommandLineArgument.RESULT_FOLDER;
  25. import static de.jplag.CommandLineArgument.ROOT_DIRECTORY;
  26. import static de.jplag.CommandLineArgument.SHOWN_COMPARISONS;
  27. import static de.jplag.CommandLineArgument.SIMILARITY_THRESHOLD;
  28. import static de.jplag.CommandLineArgument.SUBDIRECTORY;
  29. import static de.jplag.CommandLineArgument.SUFFIXES;
  30. import static de.jplag.CommandLineArgument.VERBOSITY;
  31. import java.security.SecureRandom;
  32. import java.util.ArrayList;
  33. import java.util.Arrays;
  34. import java.util.List;
  35. import java.util.Random;
  36. import net.sourceforge.argparse4j.ArgumentParsers;
  37. import net.sourceforge.argparse4j.inf.ArgumentParser;
  38. import net.sourceforge.argparse4j.inf.ArgumentParserException;
  39. import net.sourceforge.argparse4j.inf.Namespace;
  40. import org.slf4j.ILoggerFactory;
  41. import org.slf4j.Logger;
  42. import org.slf4j.LoggerFactory;
  43. import de.jplag.clustering.ClusteringOptions;
  44. import de.jplag.clustering.Preprocessing;
  45. import de.jplag.exceptions.ExitException;
  46. import de.jplag.logger.CollectedLoggerFactory;
  47. import de.jplag.options.JPlagOptions;
  48. import de.jplag.options.Verbosity;
  49. import de.jplag.reporting.reportobject.ReportObjectFactory;
  50. import de.jplag.strategy.ComparisonMode;
  51. /**
  52. * Command line interface class, allows using via command line.
  53. * @see CLI#main(String[])
  54. */
  55. public final class CLI {
  56. private static final Logger logger = LoggerFactory.getLogger(CLI.class);
  57. private static final Random RANDOM = new SecureRandom();
  58. private static final String CREDITS = "Created by IPD Tichy, Guido Malpohl, and others. JPlag logo designed by Sandro Koch. Currently maintained by Sebastian Hahner and Timur Saglam.";
  59. private static final String[] DESCRIPTIONS = {"Detecting Software Plagiarism", "Software-Archaeological Playground", "Since 1996",
  60. "Scientifically Published", "Maintained by SDQ", "RIP Structure and Table", "What else?", "You have been warned!", "Since Java 1.0",
  61. "More Abstract than Tree", "Students Nightmare", "No, changing variable names does not work", "The tech is out there!"};
  62. private static final String PROGRAM_NAME = "jplag";
  63. static final String CLUSTERING_GROUP_NAME = "Clustering";
  64. static final String ADVANCED_GROUP = "Advanced";
  65. private final ArgumentParser parser;
  66. /**
  67. * Main class for using JPlag via the CLI.
  68. * @param args are the CLI arguments that will be passed to JPlag.
  69. */
  70. public static void main(String[] args) {
  71. try {
  72. CLI cli = new CLI();
  73. Namespace arguments = cli.parseArguments(args);
  74. JPlagOptions options = cli.buildOptionsFromArguments(arguments);
  75. JPlag program = new JPlag(options);
  76. logger.info("JPlag initialized");
  77. JPlagResult result = program.run();
  78. ReportObjectFactory reportObjectFactory = new ReportObjectFactory();
  79. reportObjectFactory.createAndSaveReport(result, arguments.getString(RESULT_FOLDER.flagWithoutDash()));
  80. } catch (ExitException exception) {
  81. logger.error(exception.getMessage(), exception);
  82. finalizeLogger();
  83. System.exit(1);
  84. }
  85. }
  86. private static void finalizeLogger() {
  87. ILoggerFactory factory = LoggerFactory.getILoggerFactory();
  88. if (!(factory instanceof CollectedLoggerFactory collectedLoggerFactory))
  89. return;
  90. collectedLoggerFactory.finalizeInstances();
  91. }
  92. /**
  93. * Creates the command line interface and initializes the argument parser.
  94. */
  95. public CLI() {
  96. parser = ArgumentParsers.newFor(PROGRAM_NAME).build().defaultHelp(true).description(generateDescription());
  97. CliGroupHelper groupHelper = new CliGroupHelper(parser);
  98. for (CommandLineArgument argument : CommandLineArgument.values()) {
  99. argument.parseWith(parser, groupHelper);
  100. }
  101. }
  102. /**
  103. * Parses an array of argument strings.
  104. * @param arguments is the array to parse.
  105. * @return the parsed arguments in a {@link Namespace} format.
  106. */
  107. public Namespace parseArguments(String[] arguments) {
  108. try {
  109. return parser.parseArgs(arguments);
  110. } catch (ArgumentParserException exception) {
  111. parser.handleError(exception);
  112. System.exit(1);
  113. }
  114. return null;
  115. }
  116. /**
  117. * Builds a options instance from parsed arguments.
  118. * @param namespace encapsulates the parsed arguments in a {@link Namespace} format.
  119. * @return the newly built options.F
  120. */
  121. public JPlagOptions buildOptionsFromArguments(Namespace namespace) {
  122. String fileSuffixString = SUFFIXES.getFrom(namespace);
  123. String[] fileSuffixes = new String[] {};
  124. if (fileSuffixString != null) {
  125. fileSuffixes = fileSuffixString.replaceAll("\\s+", "").split(",");
  126. }
  127. // Collect the root directories.
  128. List<String> submissionDirectories = new ArrayList<>();
  129. List<String> oldSubmissionDirectories = new ArrayList<>();
  130. addAllMultiValueArgument(ROOT_DIRECTORY.getListFrom(namespace), submissionDirectories);
  131. addAllMultiValueArgument(NEW_DIRECTORY.getListFrom(namespace), submissionDirectories);
  132. addAllMultiValueArgument(OLD_DIRECTORY.getListFrom(namespace), oldSubmissionDirectories);
  133. var language = LanguageLoader.getLanguage(LANGUAGE.getFrom(namespace)).orElseThrow();
  134. var comparisonModeOptional = ComparisonMode.fromName(COMPARISON_MODE.getFrom(namespace));
  135. if (comparisonModeOptional.isEmpty()) {
  136. logger.warn("Unknown comparison mode, using default mode!");
  137. }
  138. var comparisonMode = comparisonModeOptional.orElse(JPlagOptions.DEFAULT_COMPARISON_MODE);
  139. ClusteringOptions clusteringOptions = getClusteringOptions(namespace);
  140. return new JPlagOptions(language, MIN_TOKEN_MATCH.getFrom(namespace), submissionDirectories, oldSubmissionDirectories,
  141. BASE_CODE.getFrom(namespace), SUBDIRECTORY.getFrom(namespace), Arrays.stream(fileSuffixes).toList(), EXCLUDE_FILE.getFrom(namespace),
  142. JPlagOptions.DEFAULT_SIMILARITY_METRIC, SIMILARITY_THRESHOLD.getFrom(namespace), SHOWN_COMPARISONS.getFrom(namespace),
  143. clusteringOptions, comparisonMode, Verbosity.fromOption(VERBOSITY.getFrom(namespace)), DEBUG.getFrom(namespace));
  144. }
  145. private static ClusteringOptions getClusteringOptions(Namespace namespace) {
  146. ClusteringOptions clusteringOptions = new ClusteringOptions();
  147. if (CLUSTER_DISABLE.isSet(namespace)) {
  148. boolean disabled = CLUSTER_DISABLE.getFrom(namespace);
  149. clusteringOptions = clusteringOptions.withEnabled(!disabled);
  150. }
  151. if (CLUSTER_ALGORITHM.isSet(namespace)) {
  152. clusteringOptions = clusteringOptions.withAlgorithm(CLUSTER_ALGORITHM.getFrom(namespace));
  153. }
  154. if (CLUSTER_METRIC.isSet(namespace)) {
  155. clusteringOptions = clusteringOptions.withSimilarityMetric(CLUSTER_METRIC.getFrom(namespace));
  156. }
  157. if (CLUSTER_SPECTRAL_BANDWIDTH.isSet(namespace)) {
  158. clusteringOptions = clusteringOptions.withSpectralKernelBandwidth(CLUSTER_SPECTRAL_BANDWIDTH.getFrom(namespace));
  159. }
  160. if (CLUSTER_SPECTRAL_NOISE.isSet(namespace)) {
  161. clusteringOptions = clusteringOptions.withSpectralGaussianProcessVariance(CLUSTER_SPECTRAL_NOISE.getFrom(namespace));
  162. }
  163. if (CLUSTER_SPECTRAL_MIN_RUNS.isSet(namespace)) {
  164. clusteringOptions = clusteringOptions.withSpectralMinRuns(CLUSTER_SPECTRAL_MIN_RUNS.getFrom(namespace));
  165. }
  166. if (CLUSTER_SPECTRAL_MAX_RUNS.isSet(namespace)) {
  167. clusteringOptions = clusteringOptions.withSpectralMaxRuns(CLUSTER_SPECTRAL_MAX_RUNS.getFrom(namespace));
  168. }
  169. if (CLUSTER_SPECTRAL_KMEANS_ITERATIONS.isSet(namespace)) {
  170. clusteringOptions = clusteringOptions.withSpectralMaxKMeansIterationPerRun(CLUSTER_SPECTRAL_KMEANS_ITERATIONS.getFrom(namespace));
  171. }
  172. if (CLUSTER_AGGLOMERATIVE_THRESHOLD.isSet(namespace)) {
  173. clusteringOptions = clusteringOptions.withAgglomerativeThreshold(CLUSTER_AGGLOMERATIVE_THRESHOLD.getFrom(namespace));
  174. }
  175. if (CLUSTER_AGGLOMERATIVE_INTER_CLUSTER_SIMILARITY.isSet(namespace)) {
  176. clusteringOptions = clusteringOptions
  177. .withAgglomerativeInterClusterSimilarity(CLUSTER_AGGLOMERATIVE_INTER_CLUSTER_SIMILARITY.getFrom(namespace));
  178. }
  179. if (CLUSTER_PREPROCESSING_NONE.isSet(namespace)) {
  180. if (CLUSTER_PREPROCESSING_NONE.getFrom(namespace)) {
  181. clusteringOptions = clusteringOptions.withPreprocessor(Preprocessing.NONE);
  182. }
  183. }
  184. if (CLUSTER_PREPROCESSING_CDF.isSet(namespace)) {
  185. if (CLUSTER_PREPROCESSING_CDF.getFrom(namespace)) {
  186. clusteringOptions = clusteringOptions.withPreprocessor(Preprocessing.CUMULATIVE_DISTRIBUTION_FUNCTION);
  187. }
  188. }
  189. if (CLUSTER_PREPROCESSING_PERCENTILE.isSet(namespace)) {
  190. clusteringOptions = clusteringOptions.withPreprocessor(Preprocessing.PERCENTILE)
  191. .withPreprocessorPercentile(CLUSTER_PREPROCESSING_PERCENTILE.getFrom(namespace));
  192. }
  193. if (CLUSTER_PREPROCESSING_THRESHOLD.isSet(namespace)) {
  194. clusteringOptions = clusteringOptions.withPreprocessor(Preprocessing.THRESHOLD)
  195. .withPreprocessorPercentile(CLUSTER_PREPROCESSING_THRESHOLD.getFrom(namespace));
  196. }
  197. return clusteringOptions;
  198. }
  199. private String generateDescription() {
  200. var randomDescription = DESCRIPTIONS[RANDOM.nextInt(DESCRIPTIONS.length)];
  201. return String.format("JPlag - %s%n%s", CREDITS, randomDescription);
  202. }
  203. private void addAllMultiValueArgument(List<List<String>> argumentValues, List<String> destinationRootDirectories) {
  204. if (argumentValues == null) {
  205. return;
  206. }
  207. argumentValues.forEach(destinationRootDirectories::addAll);
  208. }
  209. }