diff --git a/cli/pom.xml b/cli/pom.xml
index fe351d8d08..e9f33c9386 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -118,6 +118,11 @@
llvmir
${revision}
+
+ de.jplag
+ multi-language
+ ${revision}
+
org.kohsuke.metainf-services
diff --git a/cli/src/main/java/de/jplag/cli/options/LanguageCandidates.java b/cli/src/main/java/de/jplag/cli/options/LanguageCandidates.java
index e1c764b8f5..0bddd656e6 100644
--- a/cli/src/main/java/de/jplag/cli/options/LanguageCandidates.java
+++ b/cli/src/main/java/de/jplag/cli/options/LanguageCandidates.java
@@ -2,6 +2,8 @@
import java.util.ArrayList;
+import de.jplag.LanguageLoader;
+
/**
* Helper class for picocli to find all available languages.
*/
diff --git a/cli/src/main/java/de/jplag/cli/options/LanguageConverter.java b/cli/src/main/java/de/jplag/cli/options/LanguageConverter.java
index 9f92ec9449..6af53239f9 100644
--- a/cli/src/main/java/de/jplag/cli/options/LanguageConverter.java
+++ b/cli/src/main/java/de/jplag/cli/options/LanguageConverter.java
@@ -1,6 +1,7 @@
package de.jplag.cli.options;
import de.jplag.Language;
+import de.jplag.LanguageLoader;
import picocli.CommandLine;
diff --git a/cli/src/main/java/de/jplag/cli/picocli/CliInputHandler.java b/cli/src/main/java/de/jplag/cli/picocli/CliInputHandler.java
index 4bc388a35d..7f909f8911 100644
--- a/cli/src/main/java/de/jplag/cli/picocli/CliInputHandler.java
+++ b/cli/src/main/java/de/jplag/cli/picocli/CliInputHandler.java
@@ -14,9 +14,9 @@
import java.util.stream.Collectors;
import de.jplag.Language;
+import de.jplag.LanguageLoader;
import de.jplag.cli.CliException;
import de.jplag.cli.options.CliOptions;
-import de.jplag.cli.options.LanguageLoader;
import de.jplag.options.LanguageOption;
import de.jplag.options.LanguageOptions;
diff --git a/cli/src/test/java/de/jplag/cli/LanguageTest.java b/cli/src/test/java/de/jplag/cli/LanguageTest.java
index 4a7c84e0a1..5de06f7516 100644
--- a/cli/src/test/java/de/jplag/cli/LanguageTest.java
+++ b/cli/src/test/java/de/jplag/cli/LanguageTest.java
@@ -13,14 +13,16 @@
import org.junit.jupiter.params.provider.MethodSource;
import de.jplag.Language;
+import de.jplag.LanguageLoader;
import de.jplag.cli.options.CliOptions;
-import de.jplag.cli.options.LanguageLoader;
import de.jplag.cli.test.CliArgument;
import de.jplag.cli.test.CliTest;
import de.jplag.exceptions.ExitException;
+import de.jplag.multilang.MultiLanguage;
import de.jplag.options.JPlagOptions;
class LanguageTest extends CliTest {
+ private static final List> ignoredLanguages = List.of(MultiLanguage.class);
@Test
void testDefaultLanguage() throws ExitException, IOException {
@@ -38,7 +40,7 @@ void testInvalidLanguage() {
@Test
void testLoading() {
var languages = LanguageLoader.getAllAvailableLanguages();
- assertEquals(19, languages.size(), "Loaded Languages: " + languages.keySet());
+ assertEquals(20, languages.size(), "Loaded Languages: " + languages.keySet());
}
@ParameterizedTest
@@ -58,6 +60,7 @@ void testCustomSuffixes() throws ExitException, IOException {
}
public static Collection getAllLanguages() {
- return LanguageLoader.getAllAvailableLanguages().values();
+ return LanguageLoader.getAllAvailableLanguages().values().stream().filter(language -> !ignoredLanguages.contains(language.getClass()))
+ .toList();
}
}
diff --git a/endtoend-testing/src/main/java/de/jplag/endtoend/helper/LanguageDeserializer.java b/endtoend-testing/src/main/java/de/jplag/endtoend/helper/LanguageDeserializer.java
index 22e231eac9..44878ab6cb 100644
--- a/endtoend-testing/src/main/java/de/jplag/endtoend/helper/LanguageDeserializer.java
+++ b/endtoend-testing/src/main/java/de/jplag/endtoend/helper/LanguageDeserializer.java
@@ -3,7 +3,7 @@
import java.io.IOException;
import de.jplag.Language;
-import de.jplag.cli.options.LanguageLoader;
+import de.jplag.LanguageLoader;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
diff --git a/cli/src/main/java/de/jplag/cli/options/LanguageLoader.java b/language-api/src/main/java/de/jplag/LanguageLoader.java
similarity index 98%
rename from cli/src/main/java/de/jplag/cli/options/LanguageLoader.java
rename to language-api/src/main/java/de/jplag/LanguageLoader.java
index 4082476381..f0b11f5878 100644
--- a/cli/src/main/java/de/jplag/cli/options/LanguageLoader.java
+++ b/language-api/src/main/java/de/jplag/LanguageLoader.java
@@ -1,4 +1,4 @@
-package de.jplag.cli.options;
+package de.jplag;
import java.util.Collections;
import java.util.Map;
@@ -11,8 +11,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import de.jplag.Language;
-
/**
* This class contains methods to load {@link Language Languages}.
* @author Dominik Fuchss
diff --git a/language-api/src/main/java/de/jplag/options/DefaultLanguageOption.java b/language-api/src/main/java/de/jplag/options/DefaultLanguageOption.java
index 3be2a255dc..892628cba6 100644
--- a/language-api/src/main/java/de/jplag/options/DefaultLanguageOption.java
+++ b/language-api/src/main/java/de/jplag/options/DefaultLanguageOption.java
@@ -20,7 +20,7 @@ public class DefaultLanguageOption implements LanguageOption {
this.hasValue = true;
}
- DefaultLanguageOption(OptionType type, String description, String name) {
+ DefaultLanguageOption(OptionType type, String name, String description) {
this(type, name, description, null);
this.hasValue = false;
}
diff --git a/languages/multi-language/pom.xml b/languages/multi-language/pom.xml
new file mode 100644
index 0000000000..ca87dfa7e1
--- /dev/null
+++ b/languages/multi-language/pom.xml
@@ -0,0 +1,26 @@
+
+
+ 4.0.0
+
+ de.jplag
+ languages
+ ${revision}
+
+ multi-language
+
+
+
+ de.jplag
+ java
+ ${revision}
+ test
+
+
+ de.jplag
+ cpp
+ ${revision}
+ test
+
+
+
+
diff --git a/languages/multi-language/src/main/java/de/jplag/multilang/MultiLanguage.java b/languages/multi-language/src/main/java/de/jplag/multilang/MultiLanguage.java
new file mode 100644
index 0000000000..827910a74e
--- /dev/null
+++ b/languages/multi-language/src/main/java/de/jplag/multilang/MultiLanguage.java
@@ -0,0 +1,55 @@
+package de.jplag.multilang;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import org.kohsuke.MetaInfServices;
+
+import de.jplag.Language;
+import de.jplag.LanguageLoader;
+import de.jplag.ParsingException;
+import de.jplag.Token;
+import de.jplag.options.LanguageOptions;
+
+@MetaInfServices(Language.class)
+public class MultiLanguage implements Language {
+ private final MultiLanguageOptions options;
+
+ public MultiLanguage() {
+ this.options = new MultiLanguageOptions();
+ }
+
+ @Override
+ public String[] suffixes() {
+ return LanguageLoader.getAllAvailableLanguages().values().stream().filter(it -> it != this).flatMap(it -> Arrays.stream(it.suffixes()))
+ .toArray(String[]::new);
+ }
+
+ @Override
+ public String getName() {
+ return "multi-language";
+ }
+
+ @Override
+ public String getIdentifier() {
+ return "multi";
+ }
+
+ @Override
+ public int minimumTokenMatch() {
+ return this.options.getLanguages().stream().mapToInt(Language::minimumTokenMatch).min().orElse(9);
+ }
+
+ @Override
+ public List parse(Set files, boolean normalize) throws ParsingException {
+ MultiLanguageParser parser = new MultiLanguageParser(this.options);
+ return parser.parseFiles(files, normalize);
+ }
+
+ @Override
+ public LanguageOptions getOptions() {
+ return this.options;
+ }
+}
diff --git a/languages/multi-language/src/main/java/de/jplag/multilang/MultiLanguageOptions.java b/languages/multi-language/src/main/java/de/jplag/multilang/MultiLanguageOptions.java
new file mode 100644
index 0000000000..eb6a65fe0a
--- /dev/null
+++ b/languages/multi-language/src/main/java/de/jplag/multilang/MultiLanguageOptions.java
@@ -0,0 +1,42 @@
+package de.jplag.multilang;
+
+import java.util.Arrays;
+import java.util.List;
+
+import de.jplag.Language;
+import de.jplag.LanguageLoader;
+import de.jplag.options.LanguageOption;
+import de.jplag.options.LanguageOptions;
+import de.jplag.options.OptionType;
+
+public class MultiLanguageOptions extends LanguageOptions {
+ private static final String ERROR_LANGUAGE_NOT_FOUND = "The selected language %s could not be found";
+ private static final String ERROR_NOT_ENOUGH_LANGUAGES = "To use multi language specify at least 1 language";
+ private static final String OPTION_DESCRIPTION_LANGUAGES = "The languages that should be used. This is a ',' separated list";
+
+ private final LanguageOption languageNames = createOption(OptionType.string(), "languages", OPTION_DESCRIPTION_LANGUAGES);
+ private List languages = null;
+
+ public List getLanguages() {
+ if (this.languages == null) {
+ if (languageNames.getValue() == null) {
+ throw new IllegalArgumentException(ERROR_NOT_ENOUGH_LANGUAGES);
+ }
+
+ this.languages = Arrays.stream(languageNames.getValue().split(","))
+ .map(name -> LanguageLoader.getLanguage(name)
+ .orElseThrow(() -> new IllegalArgumentException(String.format(ERROR_LANGUAGE_NOT_FOUND, name))))
+ .filter(language -> !language.getClass().equals(MultiLanguage.class)).toList();
+
+ if (this.languages.isEmpty()) {
+ throw new IllegalArgumentException(ERROR_NOT_ENOUGH_LANGUAGES);
+ }
+ }
+
+ return this.languages;
+ }
+
+ public LanguageOption getLanguageNames() {
+ return this.languageNames;
+ }
+}
diff --git a/languages/multi-language/src/main/java/de/jplag/multilang/MultiLanguageParser.java b/languages/multi-language/src/main/java/de/jplag/multilang/MultiLanguageParser.java
new file mode 100644
index 0000000000..d560219d37
--- /dev/null
+++ b/languages/multi-language/src/main/java/de/jplag/multilang/MultiLanguageParser.java
@@ -0,0 +1,36 @@
+package de.jplag.multilang;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import de.jplag.Language;
+import de.jplag.ParsingException;
+import de.jplag.Token;
+
+public class MultiLanguageParser {
+ private final List languages;
+
+ public MultiLanguageParser(MultiLanguageOptions options) {
+ this.languages = options.getLanguages();
+ }
+
+ public List parseFiles(Set files, boolean normalize) throws ParsingException {
+ List results = new ArrayList<>();
+ for (File file : files) {
+ Optional language = findLanguageForFile(file);
+ if (language.isPresent()) {
+ results.addAll(language.get().parse(Set.of(file), normalize));
+ }
+ }
+ return results;
+ }
+
+ private Optional findLanguageForFile(File file) {
+ return this.languages.stream().filter(language -> Arrays.stream(language.suffixes()).anyMatch(suffix -> file.getName().endsWith(suffix)))
+ .findFirst();
+ }
+}
diff --git a/languages/multi-language/src/test/java/de/java/multilang/MultilangTest.java b/languages/multi-language/src/test/java/de/java/multilang/MultilangTest.java
new file mode 100644
index 0000000000..8293a29c1a
--- /dev/null
+++ b/languages/multi-language/src/test/java/de/java/multilang/MultilangTest.java
@@ -0,0 +1,79 @@
+package de.java.multilang;
+
+import static de.jplag.SharedTokenType.FILE_END;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import de.jplag.ParsingException;
+import de.jplag.Token;
+import de.jplag.TokenType;
+import de.jplag.cpp.CPPTokenType;
+import de.jplag.java.JavaTokenType;
+import de.jplag.multilang.MultiLanguage;
+import de.jplag.multilang.MultiLanguageOptions;
+
+class MultilangTest {
+ private static File testDataDirectory;
+ private static File javaCode;
+ private static File cppCode;
+
+ private static List expectedTokens = List.of(CPPTokenType.FUNCTION_BEGIN, CPPTokenType.RETURN, CPPTokenType.FUNCTION_END, FILE_END,
+ JavaTokenType.J_CLASS_BEGIN, JavaTokenType.J_CLASS_END, FILE_END);
+
+ @BeforeAll
+ static void setUp() throws IOException {
+ testDataDirectory = Files.createTempDirectory("multiLanguageTestData").toFile();
+ cppCode = new File(testDataDirectory, "CppCode.cpp");
+ javaCode = new File(testDataDirectory, "JavaCode.java");
+
+ MultilangTest.class.getResourceAsStream("/de/jplag/multilang/testDataSet/CppCode.cpp").transferTo(new FileOutputStream(cppCode));
+ MultilangTest.class.getResourceAsStream("/de/jplag/multilang/testDataSet/JavaCode.java").transferTo(new FileOutputStream(javaCode));
+ }
+
+ @Test
+ void testMultiLanguageParsing() throws ParsingException {
+ MultiLanguage languageModule = new MultiLanguage();
+ ((MultiLanguageOptions) languageModule.getOptions()).getLanguageNames().setValue("java,cpp");
+
+ Set sources = new TreeSet<>(List.of(javaCode, cppCode)); // Using TreeSet to ensure order of entries
+ List tokens = languageModule.parse(sources, false);
+
+ Assertions.assertEquals(expectedTokens, tokens.stream().map(Token::getType).toList());
+ }
+
+ @Test
+ void testNoLanguagesConfigured() {
+ MultiLanguage languageModule = new MultiLanguage();
+ Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> {
+ languageModule.parse(Set.of(javaCode, cppCode), false);
+ });
+ }
+
+ @Test
+ void testInvalidLanguage() {
+ MultiLanguage languageModule = new MultiLanguage();
+ ((MultiLanguageOptions) languageModule.getOptions()).getLanguageNames().setValue("thisIsNotALanguage");
+
+ Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> {
+ languageModule.parse(Set.of(javaCode, cppCode), false);
+ });
+ }
+
+ @AfterAll
+ static void cleanUp() {
+ javaCode.delete();
+ cppCode.delete();
+ testDataDirectory.delete();
+ }
+}
diff --git a/languages/multi-language/src/test/resources/de/jplag/multilang/testDataSet/CppCode.cpp b/languages/multi-language/src/test/resources/de/jplag/multilang/testDataSet/CppCode.cpp
new file mode 100644
index 0000000000..e9cdae1659
--- /dev/null
+++ b/languages/multi-language/src/test/resources/de/jplag/multilang/testDataSet/CppCode.cpp
@@ -0,0 +1,3 @@
+int main() {
+ return 0;
+}
\ No newline at end of file
diff --git a/languages/multi-language/src/test/resources/de/jplag/multilang/testDataSet/JavaCode.java b/languages/multi-language/src/test/resources/de/jplag/multilang/testDataSet/JavaCode.java
new file mode 100644
index 0000000000..32aacd210f
--- /dev/null
+++ b/languages/multi-language/src/test/resources/de/jplag/multilang/testDataSet/JavaCode.java
@@ -0,0 +1,3 @@
+public class JavaCode {
+
+}
\ No newline at end of file
diff --git a/languages/pom.xml b/languages/pom.xml
index 819b1f491e..6e19523aa5 100644
--- a/languages/pom.xml
+++ b/languages/pom.xml
@@ -29,6 +29,7 @@
typescript
javascript
llvmir
+ multi-language