Browse Source

Now using loading via metainf

Dominik Fuchß 4 years ago
parent
commit
c5d0568a2d

+ 1 - 2
jplag.cli/src/main/java/de/jplag/CLI.java

@@ -21,7 +21,6 @@ import de.jplag.clustering.Preprocessing;
 import de.jplag.clustering.algorithm.InterClusterSimilarity;
 import de.jplag.exceptions.ExitException;
 import de.jplag.options.JPlagOptions;
-import de.jplag.options.LanguageOption;
 import de.jplag.options.SimilarityMetric;
 import de.jplag.options.Verbosity;
 import de.jplag.reporting.JsonReport;
@@ -113,7 +112,7 @@ public class CLI {
         addAllMultiValueArgument(NEW_DIRECTORY.getListFrom(namespace), submissionDirectories);
         addAllMultiValueArgument(OLD_DIRECTORY.getListFrom(namespace), oldSubmissionDirectories);
 
-        LanguageOption language = LanguageOption.fromDisplayName(LANGUAGE.getFrom(namespace));
+        Language language = LanguageLoader.loadLanguage(LANGUAGE.getFrom(namespace)).orElseThrow();
         JPlagOptions options = new JPlagOptions(submissionDirectories, oldSubmissionDirectories, language);
         options.setBaseCodeSubmissionName(BASE_CODE.getFrom(namespace));
         options.setVerbosity(Verbosity.fromOption(VERBOSITY.getFrom(namespace)));

+ 2 - 2
jplag.cli/src/main/java/de/jplag/CommandLineArgument.java

@@ -22,7 +22,7 @@ import net.sourceforge.argparse4j.inf.Namespace;
 import de.jplag.clustering.ClusteringAlgorithm;
 import de.jplag.clustering.ClusteringOptions;
 import de.jplag.clustering.algorithm.InterClusterSimilarity;
-import de.jplag.options.LanguageOption;
+import de.jplag.options.JPlagOptions;
 import de.jplag.options.SimilarityMetric;
 import de.jplag.strategy.ComparisonMode;
 
@@ -34,7 +34,7 @@ public enum CommandLineArgument {
     ROOT_DIRECTORY(new Builder("rootDir", String.class).nargs(NumberOfArgumentValues.ZERO_OR_MORE_VALUES)),
     NEW_DIRECTORY(new Builder("-new", String.class).nargs(NumberOfArgumentValues.ONE_OR_MORE_VALUES)),
     OLD_DIRECTORY(new Builder("-old", String.class).nargs(NumberOfArgumentValues.ONE_OR_MORE_VALUES)),
-    LANGUAGE(new Builder("-l", String.class).defaultsTo(LanguageOption.getDefault().getDisplayName()).choices(LanguageOption.getAllDisplayNames())),
+    LANGUAGE(new Builder("-l", String.class).defaultsTo(JPlagOptions.DEFAULT_LANGUAGE).choices(LanguageLoader.getAllLanguageNames())),
     BASE_CODE("-bc", String.class),
     VERBOSITY(new Builder("-v", String.class).defaultsTo("quiet").choices(List.of("quiet", "long"))), // TODO SH: Replace verbosity when integrating a
                                                                                                       // real logging library

+ 0 - 35
jplag.cli/src/test/java/de/jplag/cli/LanguageOptionTest.java

@@ -1,35 +0,0 @@
-package de.jplag.cli;
-
-import static com.github.stefanbirkner.systemlambda.SystemLambda.catchSystemExit;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import org.junit.jupiter.api.Test;
-
-import de.jplag.CommandLineArgument;
-import de.jplag.options.LanguageOption;
-
-public class LanguageOptionTest extends CommandLineInterfaceTest {
-
-    @Test
-    public void testDefaultLanguage() {
-        buildOptionsFromCLI(CURRENT_DIRECTORY);
-        assertEquals(LanguageOption.getDefault(), options.getLanguageOption());
-    }
-
-    @Test
-    public void testInvalidLanguage() throws Exception {
-        String argument = buildArgument(CommandLineArgument.LANGUAGE, "Piet");
-        int statusCode = catchSystemExit(() -> buildOptionsFromCLI(argument, CURRENT_DIRECTORY));
-        assertEquals(1, statusCode);
-    }
-
-    @Test
-    public void testValidLanguages() {
-        for (LanguageOption language : LanguageOption.values()) {
-            String argument = buildArgument(CommandLineArgument.LANGUAGE, language.getDisplayName());
-            buildOptionsFromCLI(argument, CURRENT_DIRECTORY);
-            assertEquals(language, options.getLanguageOption());
-        }
-    }
-
-}

+ 7 - 0
jplag.frontend-utils/pom.xml

@@ -8,4 +8,11 @@
         <version>${revision}</version>
     </parent>
     <artifactId>frontend-utils</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>org.kohsuke.metainf-services</groupId>
+            <artifactId>metainf-services</artifactId>
+            <version>1.9</version>
+        </dependency>
+    </dependencies>
 </project>

+ 7 - 0
jplag.frontend-utils/src/main/java/de/jplag/Language.java

@@ -7,6 +7,13 @@ import java.io.File;
  */
 public interface Language {
 
+    /**
+     * This function creates the actual initialized language.
+     * @param errorConsumer the error consumer for errors during parsing.
+     * @return the initialized language frontend
+     */
+    Language initializeLanguage(ErrorConsumer errorConsumer);
+
     /**
      * Suffixes for the files containing code of the language.
      */

+ 59 - 0
jplag.frontend-utils/src/main/java/de/jplag/LanguageLoader.java

@@ -0,0 +1,59 @@
+package de.jplag;
+
+import java.util.*;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class contains methods to load {@link Language Languages}.
+ * @author Dominik Fuchss
+ */
+public final class LanguageLoader {
+    private static final Logger logger = LoggerFactory.getLogger(LanguageLoader.class);
+
+    private LanguageLoader() {
+        throw new IllegalAccessError();
+    }
+
+    /**
+     * Load all languages that are currently in the classpath.<br>
+     * Please remember to invoke {@link Language#initializeLanguage(ErrorConsumer)} to initialize the language. This method
+     * does only return prototypes.
+     * @return the languages
+     */
+    public static List<Language> loadLanguages() {
+        List<Language> languages = new ArrayList<>();
+
+        for (Language notInitializedLanguage : ServiceLoader.load(Language.class)) {
+            logger.info("Loading Language Frontend '{}'", notInitializedLanguage.getName());
+            languages.add(notInitializedLanguage);
+        }
+
+        languages.sort(Comparator.comparing(Language::getShortName));
+
+        return languages;
+    }
+
+    /**
+     * Load a language that is currently in the classpath by its short name.<br>
+     * Please remember to invoke {@link Language#initializeLanguage(ErrorConsumer)} to initialize the language. This method
+     * does only return prototypes.
+     * @param shortName the short name of the language
+     * @return the language or an empty optional if no language has been found.
+     */
+    public static Optional<Language> loadLanguage(String shortName) {
+        var result = loadLanguages().stream().filter(it -> Objects.equals(it.getShortName(), shortName)).findFirst();
+        if (result.isEmpty())
+            logger.warn("Attempt to load Language {} was not successful", shortName);
+        return result;
+    }
+
+    /**
+     * Get a list of all available languages with their short name.
+     * @return the list of all languages
+     */
+    public static List<String> getAllLanguageNames() {
+        return loadLanguages().stream().map(Language::getShortName).toList();
+    }
+}

+ 16 - 1
jplag.frontend.chars/src/main/java/de/jplag/chars/Language.java

@@ -2,20 +2,35 @@ package de.jplag.chars;
 
 import java.io.File;
 
+import org.kohsuke.MetaInfServices;
+
 import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
 /*
  * read in text files as characters
  */
+@MetaInfServices(de.jplag.Language.class)
 public class Language implements de.jplag.Language {
 
     private final Parser parser;
 
-    public Language(ErrorConsumer program) {
+    /**
+     * Prototype Constructor for {@link MetaInfServices}.
+     */
+    public Language() {
+        this.parser = null;
+    }
+
+    private Language(ErrorConsumer program) {
         parser = new Parser(program);
     }
 
+    @Override
+    public de.jplag.Language initializeLanguage(ErrorConsumer errorConsumer) {
+        return new Language(errorConsumer);
+    }
+
     @Override
     public String[] suffixes() {
         return new String[] {".TXT", ".txt", ".ASC", ".asc", ".TEX", ".tex"};

+ 16 - 1
jplag.frontend.cpp/src/main/java/de/jplag/cpp/Language.java

@@ -2,16 +2,31 @@ package de.jplag.cpp;
 
 import java.io.File;
 
+import org.kohsuke.MetaInfServices;
+
 import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
+@MetaInfServices(de.jplag.Language.class)
 public class Language implements de.jplag.Language {
     private final Scanner scanner; // cpp code is scanned not parsed
 
-    public Language(ErrorConsumer errorConsumer) {
+    /**
+     * Prototype Constructor for {@link MetaInfServices}.
+     */
+    public Language() {
+        this.scanner = null;
+    }
+
+    private Language(ErrorConsumer errorConsumer) {
         scanner = new Scanner(errorConsumer);
     }
 
+    @Override
+    public de.jplag.Language initializeLanguage(ErrorConsumer errorConsumer) {
+        return new Language(errorConsumer);
+    }
+
     @Override
     public String[] suffixes() {
         return new String[] {".cpp", ".CPP", ".cxx", ".CXX", ".c++", ".C++", ".c", ".C", ".cc", ".CC", ".h", ".H", ".hpp", ".HPP", ".hh", ".HH"};

+ 16 - 1
jplag.frontend.csharp-6/src/main/java/de/jplag/csharp/Language.java

@@ -3,6 +3,8 @@ package de.jplag.csharp;
 import java.io.File;
 import java.util.Arrays;
 
+import org.kohsuke.MetaInfServices;
+
 import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
@@ -10,6 +12,7 @@ import de.jplag.TokenList;
  * C# language with full support of C# 6 features and below.
  * @author Timur Saglam
  */
+@MetaInfServices(de.jplag.Language.class)
 public class Language implements de.jplag.Language {
     private static final String NAME = "C# 6 Parser";
     private static final String SHORT_NAME = "C# 6";
@@ -18,10 +21,22 @@ public class Language implements de.jplag.Language {
 
     private final CSharpParserAdapter parser;
 
-    public Language(ErrorConsumer program) {
+    /**
+     * Prototype Constructor for {@link MetaInfServices}.
+     */
+    public Language() {
+        this.parser = null;
+    }
+
+    Language(ErrorConsumer program) {
         parser = new CSharpParserAdapter(program);
     }
 
+    @Override
+    public de.jplag.Language initializeLanguage(ErrorConsumer errorConsumer) {
+        return new Language(errorConsumer);
+    }
+
     @Override
     public String[] suffixes() {
         return FILE_ENDINGS;

+ 16 - 1
jplag.frontend.java/src/main/java/de/jplag/java/Language.java

@@ -2,19 +2,34 @@ package de.jplag.java;
 
 import java.io.File;
 
+import org.kohsuke.MetaInfServices;
+
 import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
 /**
  * Language for Java 9 and newer.
  */
+@MetaInfServices(de.jplag.Language.class)
 public class Language implements de.jplag.Language {
     private final Parser parser;
 
-    public Language(ErrorConsumer errorConsumer) {
+    /**
+     * Prototype Constructor for {@link MetaInfServices}.
+     */
+    public Language() {
+        this.parser = null;
+    }
+
+    private Language(ErrorConsumer errorConsumer) {
         parser = new Parser(errorConsumer);
     }
 
+    @Override
+    public de.jplag.Language initializeLanguage(ErrorConsumer errorConsumer) {
+        return new Language(errorConsumer);
+    }
+
     @Override
     public String[] suffixes() {
         return new String[] {".java", ".JAVA"};

+ 16 - 1
jplag.frontend.python-3/src/main/java/de/jplag/python3/Language.java

@@ -2,17 +2,32 @@ package de.jplag.python3;
 
 import java.io.File;
 
+import org.kohsuke.MetaInfServices;
+
 import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
+@MetaInfServices(de.jplag.Language.class)
 public class Language implements de.jplag.Language {
 
     private final Parser parser;
 
-    public Language(ErrorConsumer errorConsumer) {
+    /**
+     * Prototype Constructor for {@link MetaInfServices}.
+     */
+    public Language() {
+        this.parser = null;
+    }
+
+    private Language(ErrorConsumer errorConsumer) {
         parser = new Parser(errorConsumer);
     }
 
+    @Override
+    public de.jplag.Language initializeLanguage(ErrorConsumer errorConsumer) {
+        return new Language(errorConsumer);
+    }
+
     @Override
     public String[] suffixes() {
         return new String[] {".py"};

+ 16 - 1
jplag.frontend.scheme/src/main/java/de/jplag/scheme/Language.java

@@ -2,16 +2,31 @@ package de.jplag.scheme;
 
 import java.io.File;
 
+import org.kohsuke.MetaInfServices;
+
 import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
+@MetaInfServices(de.jplag.Language.class)
 public class Language implements de.jplag.Language {
     private final de.jplag.scheme.Parser parser;
 
-    public Language(ErrorConsumer program) {
+    /**
+     * Prototype Constructor for {@link MetaInfServices}.
+     */
+    public Language() {
+        this.parser = null;
+    }
+
+    private Language(ErrorConsumer program) {
         parser = new Parser(program);
     }
 
+    @Override
+    public de.jplag.Language initializeLanguage(ErrorConsumer errorConsumer) {
+        return new Language(errorConsumer);
+    }
+
     @Override
     public String[] suffixes() {
         return new String[] {".scm", ".SCM", ".ss", ".SS"};

+ 16 - 1
jplag.frontend.text/src/main/java/de/jplag/text/Language.java

@@ -3,17 +3,32 @@ package de.jplag.text;
 
 import java.io.File;
 
+import org.kohsuke.MetaInfServices;
+
 import de.jplag.ErrorConsumer;
 import de.jplag.TokenList;
 
+@MetaInfServices(de.jplag.Language.class)
 public class Language implements de.jplag.Language {
 
     private final Parser parser;
 
-    public Language(ErrorConsumer errorConsumer) {
+    /**
+     * Prototype Constructor for {@link MetaInfServices}.
+     */
+    public Language() {
+        this.parser = null;
+    }
+
+    private Language(ErrorConsumer errorConsumer) {
         parser = new Parser(errorConsumer);
     }
 
+    @Override
+    public de.jplag.Language initializeLanguage(ErrorConsumer errorConsumer) {
+        return new Language(errorConsumer);
+    }
+
     @Override
     public String[] suffixes() {
         return new String[] {".TXT", ".txt", ".ASC", ".asc", ".TEX", ".tex"};

+ 6 - 20
jplag/src/main/java/de/jplag/JPlag.java

@@ -5,8 +5,6 @@ import static de.jplag.options.Verbosity.LONG;
 import java.io.BufferedReader;
 import java.io.FileReader;
 import java.io.IOException;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
 import java.util.Collections;
 import java.util.Optional;
 import java.util.Set;
@@ -19,7 +17,6 @@ import de.jplag.clustering.ClusteringFactory;
 import de.jplag.exceptions.ExitException;
 import de.jplag.exceptions.SubmissionException;
 import de.jplag.options.JPlagOptions;
-import de.jplag.options.LanguageOption;
 import de.jplag.strategy.ComparisonMode;
 import de.jplag.strategy.ComparisonStrategy;
 import de.jplag.strategy.NormalComparisonStrategy;
@@ -109,23 +106,12 @@ public class JPlag {
     }
 
     private Language initializeLanguage() {
-        LanguageOption languageOption = this.options.getLanguageOption();
-
-        try {
-            Constructor<?> constructor = Class.forName(languageOption.getClassPath()).getConstructor(ErrorConsumer.class);
-            Object[] constructorParams = {errorCollector};
-
-            Language language = (Language) constructor.newInstance(constructorParams);
-
-            this.options.setLanguage(language);
-            this.options.setLanguageDefaults(language);
-            logger.info("Initialized language " + language.getName());
-            return language;
-        } catch (NoSuchMethodException | SecurityException | ClassNotFoundException | InstantiationException | IllegalAccessException
-                | IllegalArgumentException | InvocationTargetException e) {
-            e.printStackTrace();
-            throw new IllegalStateException("Language instantiation failed:" + e.getMessage());
-        }
+        Language uninitializedLanguage = this.options.getLanguage();
 
+        Language language = uninitializedLanguage.initializeLanguage(errorCollector);
+        this.options.setLanguage(language);
+        this.options.setLanguageDefaults(language);
+        logger.info("Initialized language " + language.getName());
+        return language;
     }
 }

+ 1 - 1
jplag/src/main/java/de/jplag/JPlagResult.java

@@ -109,7 +109,7 @@ public class JPlagResult {
     @Override
     public String toString() {
         return String.format("JPlagResult { comparisons: %d, duration: %d ms, language: %s, submissions: %d }", getComparisons().size(),
-                getDuration(), getOptions().getLanguageOption(), submissions.numberOfSubmissions());
+                getDuration(), getOptions().getLanguage().getName(), submissions.numberOfSubmissions());
     }
 
     /**

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

@@ -21,6 +21,8 @@ public class JPlagOptions {
 
     private static final Logger logger = LoggerFactory.getLogger("JPlag");
 
+    public static final String DEFAULT_LANGUAGE = "java";
+
     public static final ComparisonMode DEFAULT_COMPARISON_MODE = NORMAL;
     public static final float DEFAULT_SIMILARITY_THRESHOLD = 0;
     public static final int DEFAULT_SHOWN_COMPARISONS = 30;
@@ -116,11 +118,6 @@ public class JPlagOptions {
      */
     private String subdirectoryName;
 
-    /**
-     * Language to use when parsing the submissions.
-     */
-    private LanguageOption languageOption;
-
     /**
      * Level of output verbosity.
      */
@@ -134,10 +131,10 @@ public class JPlagOptions {
     /**
      * Constructor with required attributes.
      */
-    public JPlagOptions(List<String> submissionDirectories, List<String> oldSubmissionDirectories, LanguageOption languageOption) {
+    public JPlagOptions(List<String> submissionDirectories, List<String> oldSubmissionDirectories, Language language) {
         this.submissionDirectories = submissionDirectories;
         this.oldSubmissionDirectories = oldSubmissionDirectories;
-        this.languageOption = languageOption;
+        this.language = language;
     }
 
     public Optional<String> getBaseCodeSubmissionName() {
@@ -164,10 +161,6 @@ public class JPlagOptions {
         return language;
     }
 
-    public LanguageOption getLanguageOption() {
-        return languageOption;
-    }
-
     public int getMaximumNumberOfComparisons() {
         return this.maximumNumberOfComparisons;
     }
@@ -259,10 +252,6 @@ public class JPlagOptions {
         }
     }
 
-    public void setLanguageOption(LanguageOption languageOption) {
-        this.languageOption = languageOption;
-    }
-
     public void setMaximumNumberOfComparisons(int maximumNumberOfComparisons) {
         this.maximumNumberOfComparisons = Math.max(maximumNumberOfComparisons, -1);
     }

+ 0 - 48
jplag/src/main/java/de/jplag/options/LanguageOption.java

@@ -1,48 +0,0 @@
-package de.jplag.options;
-
-import static java.util.stream.Collectors.toList;
-
-import java.util.Arrays;
-import java.util.Collection;
-
-/**
- * The available languages.
- */
-public enum LanguageOption {
-    JAVA("java", "de.jplag.java.Language"),
-    PYTHON_3("python3", "de.jplag.python3.Language"),
-    C_CPP("cpp", "de.jplag.cpp.Language"),
-    C_SHARP("csharp", "de.jplag.csharp.Language"),
-    CHAR("char", "de.jplag.chars.Language"),
-    TEXT("text", "de.jplag.text.Language"),
-    SCHEME("scheme", "de.jplag.scheme.Language");
-
-    private final String classPath;
-    private final String displayName;
-
-    LanguageOption(String displayName, String classPath) {
-        this.displayName = displayName;
-        this.classPath = classPath;
-    }
-
-    public String getClassPath() {
-        return this.classPath;
-    }
-
-    public String getDisplayName() {
-        return this.displayName;
-    }
-
-    public static LanguageOption fromDisplayName(String displayName) {
-        return Arrays.stream(LanguageOption.values()).filter(languageOption -> languageOption.displayName.equals(displayName)).findFirst()
-                .orElse(getDefault());
-    }
-
-    public static Collection<String> getAllDisplayNames() {
-        return Arrays.stream(LanguageOption.values()).map(languageOption -> languageOption.displayName).collect(toList());
-    }
-
-    public static LanguageOption getDefault() {
-        return LanguageOption.JAVA;
-    }
-}

+ 16 - 0
jplag/src/test/java/de/jplag/LanguageTest.java

@@ -0,0 +1,16 @@
+package de.jplag;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * This class contains integration tests for {@link Language} and {@link LanguageLoader}.
+ * @author Dominik Fuchss
+ */
+class LanguageTest {
+    @Test
+    void testLoading() {
+        var languages = LanguageLoader.loadLanguages();
+        Assertions.assertEquals(7, languages.size());
+    }
+}

+ 1 - 2
jplag/src/test/java/de/jplag/TestBase.java

@@ -7,7 +7,6 @@ import java.util.function.Consumer;
 
 import de.jplag.exceptions.ExitException;
 import de.jplag.options.JPlagOptions;
-import de.jplag.options.LanguageOption;
 import de.jplag.options.Verbosity;
 
 public abstract class TestBase {
@@ -46,7 +45,7 @@ public abstract class TestBase {
     }
 
     protected JPlagResult runJPlag(List<String> newPaths, List<String> oldPaths, Consumer<JPlagOptions> customization) throws ExitException {
-        JPlagOptions options = new JPlagOptions(newPaths, oldPaths, LanguageOption.JAVA);
+        JPlagOptions options = new JPlagOptions(newPaths, oldPaths, LanguageLoader.loadLanguage(JPlagOptions.DEFAULT_LANGUAGE).orElseThrow());
         options.setVerbosity(Verbosity.LONG);
         customization.accept(options);
         JPlag jplag = new JPlag(options);

+ 4 - 6
jplag/src/test/java/de/jplag/special/TokenPrinterTest.java

@@ -7,13 +7,9 @@ import java.util.function.Consumer;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
-import de.jplag.JPlagResult;
-import de.jplag.Submission;
-import de.jplag.TestBase;
-import de.jplag.TokenPrinter;
+import de.jplag.*;
 import de.jplag.exceptions.ExitException;
 import de.jplag.options.JPlagOptions;
-import de.jplag.options.LanguageOption;
 
 /**
  * Special test case the does not really test anything but prints the tokens and the corresponding line of code.
@@ -26,11 +22,13 @@ public class TokenPrinterTest extends TestBase {
     private static final int MIN_TOKEN_MATCH = 5;
     private static final String PRINTER_FOLDER = "PRINTER"; // in the folder 'jplag/src/test/resources/samples'
 
+    private static final String LANGUAGE_CPP = "cpp";
+
     @Disabled
     @Test
     public void printCPPFiles() {
         printSubmissions(options -> {
-            options.setLanguageOption(LanguageOption.C_CPP);
+            options.setLanguage(LanguageLoader.loadLanguage(LANGUAGE_CPP).orElseThrow());
             options.setMinimumTokenMatch(MIN_TOKEN_MATCH); // for printing also allow small files
         });
     }