diff --git a/pom.xml b/pom.xml
index fab3358d..2f6fd21f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,7 +14,7 @@
4.0.0org.vaferjdependency
- 2.9.0
+ 2.9.1-SNAPSHOTjdependencyThis project provides an API to analyse class dependencieshttp://github.com/tcurdt/jdependency
@@ -41,6 +41,12 @@
krosenvold@apache.org
+
+
+ Niels Basjes
+ nielsbasjes@apache.org
+
+ scm:git:git@github.com:tcurdt/jdependency.gitscm:git:git@github.com:tcurdt/jdependency.git
diff --git a/src/main/java/org/vafer/jdependency/Clazz.java b/src/main/java/org/vafer/jdependency/Clazz.java
index 69a0931b..cbc5a42b 100644
--- a/src/main/java/org/vafer/jdependency/Clazz.java
+++ b/src/main/java/org/vafer/jdependency/Clazz.java
@@ -19,6 +19,10 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.apache.commons.io.FilenameUtils.separatorsToUnix;
/**
* A `Clazz` represents the single class identifier inside a classpath.
@@ -32,16 +36,123 @@ public final class Clazz implements Comparable {
private final Set references = new HashSet<>();
private final Map units = new HashMap<>();
+ public static final class ClazzFile {
+ private ClazzpathUnit unit;
+ private String filename;
+
+ public ClazzFile(ClazzpathUnit unit, String filename) {
+ this.unit = unit;
+ this.filename = filename;
+ }
+
+ public ClazzpathUnit getUnit() {
+ return unit;
+ }
+
+ public String getFilename() {
+ return filename;
+ }
+
+ @Override
+ public String toString() {
+ return "ClazzFile{" +
+ "unit=" + unit +
+ ", filename='" + filename + '\'' +
+ '}';
+ }
+ }
+
+ // Usually a class is only in a single file.
+ // When using MultiRelease Jar files this can be multiple files, one for each java release specified.
+ // The default filename is under the key "8".
+ private final Map classFilenames = new HashMap<>();
+
+ // The name of the class (like "org.vafer.jdependency.Clazz")
private final String name;
public Clazz( final String pName ) {
name = pName;
}
+ private static final Pattern EXTRACT_MULTI_RELEASE_JAVA_VERSION = Pattern.compile("^(?:META-INF[\\/\\\\]versions[\\/\\\\](\\d+)[\\/\\\\])?([^.]+).class$");
+
+ public static final class ParsedFileName {
+ public String className;
+ public String forJava;
+
+ @Override
+ public String toString() {
+ return "ParsedFileName{" +
+ "className='" + className + '\'' +
+ ", forJava='" + forJava + '\'' +
+ '}';
+ }
+ }
+
+ /**
+ * Determine the class name for the provided filename.
+ *
+ * @param pFileName The filename
+ * @return the class name for the provided filename OR null if it is not a .class file.
+ */
+ public static ParsedFileName parseClassFileName(String pFileName) {
+ if (pFileName == null || !pFileName.endsWith(".class")) {
+ return null;// Not a class filename
+ }
+ // foo/bar/Foo.class -> // foo.bar.Foo
+
+ Matcher matcher = EXTRACT_MULTI_RELEASE_JAVA_VERSION.matcher(pFileName);
+ if (!matcher.matches()) {
+ return null;
+ }
+ ParsedFileName result = new ParsedFileName();
+ result.forJava = matcher.group(1);
+ result.className = separatorsToUnix(matcher.group(2)).replace('/', '.');
+
+ if (result.forJava == null || result.forJava.isEmpty()) {
+ if (result.className.contains("-")) {
+ return null;
+ }
+ result.forJava = "8";
+ }
+
+ return result;
+ }
+
+ /**
+ * Determine if the provided filename is the name of a class that is specific for a java version.
+ * @param pFileName The filename to be evaluated
+ * @return true if this is a filename for a specific java version, false if it is not
+ */
+ public static boolean isMultiReleaseClassFile(String pFileName) {
+ if (pFileName == null) {
+ return false;
+ }
+ Matcher matcher = EXTRACT_MULTI_RELEASE_JAVA_VERSION.matcher(pFileName);
+ if (!matcher.matches()) {
+ return false;
+ }
+ return matcher.group(1) != null && !matcher.group(1).isEmpty();
+ }
+
+ /**
+ * Record that this class name can be found at:
+ * @param pUnit The unit in which the class can be found
+ * @param pForJava For which Java version
+ * @param pFileName Under which filename in the jar.
+ */
+ public void addMultiReleaseFile(ClazzpathUnit pUnit, String pForJava, String pFileName) {
+ classFilenames.put(pForJava, new ClazzFile(pUnit, pFileName));
+ }
+
public String getName() {
return name;
}
+ public Map getFileNames() {
+ return classFilenames;
+ }
+
public void addClazzpathUnit( final ClazzpathUnit pUnit, final String pDigest ) {
units.put(pUnit, pDigest);
}
@@ -116,7 +227,7 @@ public int compareTo( final Clazz pO ) {
}
public String toString() {
- return name;
+ return name + " in " + classFilenames;
}
}
diff --git a/src/main/java/org/vafer/jdependency/Clazzpath.java b/src/main/java/org/vafer/jdependency/Clazzpath.java
index 12440256..c7776be3 100644
--- a/src/main/java/org/vafer/jdependency/Clazzpath.java
+++ b/src/main/java/org/vafer/jdependency/Clazzpath.java
@@ -23,17 +23,21 @@
import java.util.Map;
import java.util.Set;
import java.util.Base64;
+import java.util.TreeMap;
import java.util.jar.JarInputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
+import org.apache.commons.io.input.MessageDigestInputStream;
import org.objectweb.asm.ClassReader;
-import org.apache.commons.io.input.MessageDigestCalculatingInputStream;
import static org.apache.commons.io.FilenameUtils.normalize;
import static org.apache.commons.io.FilenameUtils.separatorsToUnix;
+import org.vafer.jdependency.Clazz.ParsedFileName;
import org.vafer.jdependency.asm.DependenciesClassAdapter;
+
+import static org.vafer.jdependency.Clazz.parseClassFileName;
import static org.vafer.jdependency.utils.StreamUtils.asStream;
@@ -46,20 +50,16 @@ public final class Clazzpath {
private final boolean versions;
private abstract static class Resource {
+ public final String fileName;
+ public final String forJava;
+ public final String name; // Class name !
- private static final int ext = ".class".length();
-
- public final String name;
-
- Resource( final String pName ) {
+ Resource( final String pFileName ) {
super();
-
- final int all = pName.length();
-
- // foo/bar/Foo.class -> // foo.bar.Foo
- this.name = separatorsToUnix(pName)
- .substring(0, all - ext)
- .replace('/', '.');
+ this.fileName = pFileName;
+ ParsedFileName parsedFileName = parseClassFileName(pFileName);
+ forJava = parsedFileName.forJava;
+ name = parsedFileName.className;
}
abstract InputStream getInputStream() throws IOException;
@@ -68,7 +68,7 @@ private abstract static class Resource {
private static boolean isValidResourceName( final String pName ) {
return pName != null
&& pName.endsWith(".class")
- && !pName.contains( "-" );
+ && ( !pName.contains( "-" ) || pName.contains("META-INF/versions/") );
}
public Clazzpath() {
@@ -86,7 +86,7 @@ public boolean removeClazzpathUnit( final ClazzpathUnit pUnit ) {
for (Clazz clazz : unitClazzes) {
clazz.removeClazzpathUnit(pUnit);
if (clazz.getClazzpathUnits().size() == 0) {
- clazzes.remove(clazz.toString());
+ clazzes.remove(clazz.getName());
}
}
@@ -168,7 +168,8 @@ private ClazzpathUnit addClazzpathUnit( final Iterable resources, fina
InputStream inputStream = resource.getInputStream();
try {
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
- final MessageDigestCalculatingInputStream calculatingInputStream = new MessageDigestCalculatingInputStream(inputStream, digest);
+ final MessageDigestInputStream calculatingInputStream =
+ MessageDigestInputStream.builder().setInputStream(inputStream).setMessageDigest(digest).get();
if (versions) {
inputStream = calculatingInputStream;
@@ -190,6 +191,7 @@ private ClazzpathUnit addClazzpathUnit( final Iterable resources, fina
clazz = new Clazz(clazzName);
}
}
+ clazz.addMultiReleaseFile(unit, resource.forJava, resource.fileName);
final String d = Base64.getEncoder().encodeToString(digest.digest());
clazz.addClazzpathUnit(unit, d);
@@ -243,6 +245,10 @@ public Set getClazzes() {
return new HashSet<>(clazzes.values());
}
+ public Map getClazzesMap() {
+ return new TreeMap<>(clazzes);
+ }
+
public Set getClashedClazzes() {
final Set all = new HashSet<>();
for (Clazz clazz : clazzes.values()) {
diff --git a/src/main/java/org/vafer/jdependency/ClazzpathUnit.java b/src/main/java/org/vafer/jdependency/ClazzpathUnit.java
index 0ba835db..7f2d6dac 100644
--- a/src/main/java/org/vafer/jdependency/ClazzpathUnit.java
+++ b/src/main/java/org/vafer/jdependency/ClazzpathUnit.java
@@ -18,6 +18,7 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
public final class ClazzpathUnit {
@@ -36,6 +37,10 @@ public Set getClazzes() {
return new HashSet<>(clazzes.values());
}
+ public Map getClazzesMap() {
+ return new TreeMap<>(clazzes);
+ }
+
public Clazz getClazz( final String pClazzName ) {
return clazzes.get(pClazzName);
}
diff --git a/src/test/java/org/vafer/jdependency/ClazzpathUnitTestCase.java b/src/test/java/org/vafer/jdependency/ClazzpathUnitTestCase.java
index a105035e..e1104838 100644
--- a/src/test/java/org/vafer/jdependency/ClazzpathUnitTestCase.java
+++ b/src/test/java/org/vafer/jdependency/ClazzpathUnitTestCase.java
@@ -19,24 +19,40 @@
import java.io.File;
import java.io.IOException;
+import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
+import java.util.function.Function;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
-
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.vafer.jdependency.Clazz.parseClassFileName;
public class ClazzpathUnitTestCase {
private static Path resourcePath( String filename ) {
- return Paths.get(filename);
+ ClassLoader classLoader = ClazzpathUnitTestCase.class.getClassLoader();
+ URL resource = classLoader.getResource(filename);
+ if (resource == null) {
+ return null;
+ }
+ return Paths.get(resource.getFile());
}
private static File resourceFile( String filename ) {
- return Paths.get(filename).toFile();
+ ClassLoader classLoader = ClazzpathUnitTestCase.class.getClassLoader();
+ URL resource = classLoader.getResource(filename);
+ if (resource == null) {
+ return null;
+ }
+ return new File(resource.getFile());
}
@Test
@@ -69,10 +85,8 @@ public void testIssue47() throws IOException {
.flatMap( i -> i.getClazzpathUnits().stream() )
.map( i -> i.toString() )
.collect(Collectors.toSet());
- final Set unitse = new HashSet(Arrays.asList(
- "woodstox-core-6.2.3.jar"
- ));
- assertEquals(unitse, units);
+ assertEquals(1, units.size());
+ assertTrue(units.iterator().next().endsWith("/woodstox-core-6.2.3.jar"));
}
@Test
@@ -153,13 +167,13 @@ public void testShouldHaveUnitId() throws IOException {
final Clazzpath cp = new Clazzpath();
final ClazzpathUnit u1 = cp.addClazzpathUnit(resourceFile("jar1.jar"));
- assertEquals(u1.toString(), "jar1.jar");
+ assertTrue(u1.toString().endsWith("/jar1.jar"));
final ClazzpathUnit u1e = cp.addClazzpathUnit(resourceFile("jar1.jar"), "jar1");
assertEquals(u1e.toString(), "jar1");
final ClazzpathUnit u2 = cp.addClazzpathUnit(resourcePath("jar2.jar"));
- assertEquals(u2.toString(), "jar2.jar");
+ assertTrue(u2.toString().endsWith("/jar2.jar"));
final ClazzpathUnit u2e = cp.addClazzpathUnit(resourcePath("jar2.jar"), "jar2");
assertEquals(u2e.toString(), "jar2");
@@ -179,4 +193,88 @@ public void testDependencies() throws IOException {
}
+
+ private void verifyFileNameParsing(String fileName, String forJava, String className) {
+ Clazz.ParsedFileName result = parseClassFileName(fileName);
+ assertEquals(className, result.className);
+ assertEquals(forJava, result.forJava);
+ }
+
+ private void verifyFileNameParsingInvalid(String fileName) {
+ assertNull(parseClassFileName(fileName));
+ }
+
+ @Test
+ public void testFileNameCheck(){
+ assertFalse(Clazz.isMultiReleaseClassFile("org/vafer/multijdk/App.class"));
+ assertTrue(Clazz.isMultiReleaseClassFile("META-INF/versions/11/org/vafer/multijdk/App.class"));
+ assertTrue(Clazz.isMultiReleaseClassFile("META-INF/versions/1234/org/vafer/multijdk/App.class"));
+ }
+
+ @Test
+ public void testFileNameParsing(){
+ verifyFileNameParsing("org/vafer/multijdk/App.class", "8", "org.vafer.multijdk.App");
+ verifyFileNameParsing("META-INF/versions/11/org/vafer/multijdk/App.class", "11", "org.vafer.multijdk.App");
+ verifyFileNameParsing("META-INF/versions/1234/org/vafer/multijdk/App.class", "1234", "org.vafer.multijdk.App");
+
+ verifyFileNameParsingInvalid("org/vafer/multijdk/App.classsssss");
+ verifyFileNameParsingInvalid("org/vafer/multijdk/App.txt");
+ verifyFileNameParsingInvalid("META-INF/versions/xxx/org/vafer/multijdk/App.class");
+ }
+
+ private void verifyFileInClazz(Clazz clazz, String expectedFilename, String forJava, boolean mustBePresent) {
+ Clazz.ClazzFile actualClazzFile = clazz.getFileNames().get(forJava);
+ String actualFileName = actualClazzFile == null ? null : actualClazzFile.getFilename();
+ if (mustBePresent) {
+ assertEquals("Incorrect filename for Java "+forJava, expectedFilename, actualFileName);
+ } else {
+ assertNull("Unexpected filename for Java "+forJava, actualFileName);
+ }
+ }
+
+ private void verifyClazzFiles(Clazz clazz, boolean have8, boolean have11, boolean have17) {
+ String expectedFilename = clazz.getName().replace(".", "/")+".class";
+ verifyFileInClazz(clazz, expectedFilename, "8", have8);
+ verifyFileInClazz(clazz, "META-INF/versions/11/" + expectedFilename, "11", have11);
+ verifyFileInClazz(clazz, "META-INF/versions/17/" + expectedFilename, "17", have17);
+ }
+
+ @Test
+ public void testMultiReleaseJar() throws IOException {
+ final Clazzpath cp = new Clazzpath();
+
+ // The Application
+ final ClazzpathUnit app = cp.addClazzpathUnit(resourceFile("uses-multi-jdk-1.0.jar"));
+ Map appClazzes = app.getClazzesMap();
+
+ assertEquals(1, appClazzes.size());
+ verifyClazzFiles(appClazzes.get("nl.example.Main"), true, false, false);
+
+ // The multi release dependency
+ final ClazzpathUnit dependency = cp.addClazzpathUnit(resourceFile("multi-jdk-1.0.0.jar"));
+ Map dependencyClazzes = dependency.getClazzesMap();
+ // Java 8, Java 11, Java 17
+ verifyClazzFiles(dependencyClazzes.get("org.vafer.multijdk.Main"), true, false, false);
+ verifyClazzFiles(dependencyClazzes.get("org.vafer.multijdk.App"), true, true, true);
+ verifyClazzFiles(dependencyClazzes.get("org.vafer.multijdk.AbstractJavaVersion"), true, false, false);
+ verifyClazzFiles(dependencyClazzes.get("org.vafer.multijdk.JavaVersion"), true, true, false);
+ verifyClazzFiles(dependencyClazzes.get("org.vafer.multijdk.Unused"), true, true, true);
+ verifyClazzFiles(dependencyClazzes.get("org.vafer.multijdk.OnlyUsedInJava17"), false, true, false);
+ verifyClazzFiles(dependencyClazzes.get("org.vafer.multijdk.SpecificToJava11"), false, true, false);
+ verifyClazzFiles(dependencyClazzes.get("org.vafer.multijdk.SpecificToJava17"), false, false, true);
+
+ // Check which are obsolete
+ final Set removable = cp.getClazzes();
+ removable.removeAll(appClazzes.values());
+ removable.removeAll(app.getTransitiveDependencies());
+
+ Map removableClazzes = removable
+ .stream()
+ .collect(Collectors.toMap(Clazz::getName, Function.identity()));
+
+ assertEquals(2, removableClazzes.size());
+ assertTrue(removableClazzes.containsKey("org.vafer.multijdk.Main"));
+ assertTrue(removableClazzes.containsKey("org.vafer.multijdk.Unused"));
+ }
+
}
diff --git a/src/test/resources-src/README.md b/src/test/resources-src/README.md
new file mode 100644
index 00000000..eca7f939
--- /dev/null
+++ b/src/test/resources-src/README.md
@@ -0,0 +1,3 @@
+These are the sources for some of the jar files used during testing.
+
+To recreate the jars specified in this subdirectory simply do `mvn clean install` and they will be copied to the `test/resources` directory.
\ No newline at end of file
diff --git a/src/test/resources-src/multi-jdk/pom.xml b/src/test/resources-src/multi-jdk/pom.xml
new file mode 100644
index 00000000..fd69448a
--- /dev/null
+++ b/src/test/resources-src/multi-jdk/pom.xml
@@ -0,0 +1,141 @@
+
+
+
+
+
+ 4.0.0
+
+ Multi Release Jar
+
+ org.vafer
+ multi-jdk
+ 1.0.0
+
+
+ UTF-8
+ UTF-8
+
+
+ 2023-11-11T12:34:56Z
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ java-11
+
+ compile
+
+
+
+ 11
+ 11
+ ${project.basedir}/src/main/java11
+ true
+
+
+
+
+ java-17
+
+ compile
+
+
+
+ 17
+ 17
+ ${project.basedir}/src/main/java17
+ true
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+
+ org.vafer.multijdk.Main
+
+
+ true
+
+
+
+
+
+
+
+
+ maven-resources-plugin
+ 3.3.1
+
+
+ Install jar into resources directory
+ install
+
+ copy-resources
+
+
+
+
+ target
+
+ ${project.artifactId}-${project.version}.jar
+
+
+
+ ${project.basedir}/../../resources/
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-artifact-plugin
+ 3.5.0
+
+
+ Ensure reproducible
+ install
+
+ compare
+
+
+
+
+
+
+
+
diff --git a/src/test/resources-src/multi-jdk/src/main/java/org/vafer/multijdk/AbstractJavaVersion.java b/src/test/resources-src/multi-jdk/src/main/java/org/vafer/multijdk/AbstractJavaVersion.java
new file mode 100644
index 00000000..a23b255a
--- /dev/null
+++ b/src/test/resources-src/multi-jdk/src/main/java/org/vafer/multijdk/AbstractJavaVersion.java
@@ -0,0 +1,32 @@
+package org.vafer.multijdk;
+/*
+ * Copyright 2010-2023 The jdependency developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public abstract class AbstractJavaVersion {
+
+ public abstract String getCodeVersion();
+ public abstract String getJavaVersion();
+
+ public String getJavaMajorVersion() {
+ String version = getJavaVersion();
+ String[] versionElements = version.split("\\.");
+ String majorVersion = versionElements[0];
+ if ("1".equals(majorVersion)) {
+ majorVersion = versionElements[1];
+ }
+ return majorVersion;
+ }
+}
diff --git a/src/test/resources-src/multi-jdk/src/main/java/org/vafer/multijdk/App.java b/src/test/resources-src/multi-jdk/src/main/java/org/vafer/multijdk/App.java
new file mode 100644
index 00000000..250fcd6e
--- /dev/null
+++ b/src/test/resources-src/multi-jdk/src/main/java/org/vafer/multijdk/App.java
@@ -0,0 +1,22 @@
+package org.vafer.multijdk;
+/*
+ * Copyright 2010-2023 The jdependency developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class App {
+ public String doSomething() {
+ return "base";
+ }
+}
diff --git a/src/test/resources-src/multi-jdk/src/main/java/org/vafer/multijdk/JavaVersion.java b/src/test/resources-src/multi-jdk/src/main/java/org/vafer/multijdk/JavaVersion.java
new file mode 100644
index 00000000..a2cbfef5
--- /dev/null
+++ b/src/test/resources-src/multi-jdk/src/main/java/org/vafer/multijdk/JavaVersion.java
@@ -0,0 +1,26 @@
+package org.vafer.multijdk;
+/*
+ * Copyright 2010-2023 The jdependency developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class JavaVersion extends AbstractJavaVersion {
+ public String getCodeVersion() {
+ return "8";
+ }
+
+ public String getJavaVersion() {
+ return System.getProperty("java.version");
+ }
+}
diff --git a/src/test/resources-src/multi-jdk/src/main/java/org/vafer/multijdk/Main.java b/src/test/resources-src/multi-jdk/src/main/java/org/vafer/multijdk/Main.java
new file mode 100644
index 00000000..3e469b64
--- /dev/null
+++ b/src/test/resources-src/multi-jdk/src/main/java/org/vafer/multijdk/Main.java
@@ -0,0 +1,26 @@
+package org.vafer.multijdk;
+/*
+ * Copyright 2010-2023 The jdependency developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+ public static void main(String[] args) {
+ JavaVersion javaVersion = new JavaVersion();
+ System.out.println("Java detect: " + javaVersion.getCodeVersion());
+ System.out.println("Java major : " + javaVersion.getJavaMajorVersion());
+ System.out.println("App code : " + new App().doSomething());
+ System.out.println("Unused : " + new Unused().doSomething());
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources-src/multi-jdk/src/main/java/org/vafer/multijdk/Unused.java b/src/test/resources-src/multi-jdk/src/main/java/org/vafer/multijdk/Unused.java
new file mode 100644
index 00000000..0961fdfd
--- /dev/null
+++ b/src/test/resources-src/multi-jdk/src/main/java/org/vafer/multijdk/Unused.java
@@ -0,0 +1,22 @@
+package org.vafer.multijdk;
+/*
+ * Copyright 2010-2023 The jdependency developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Unused {
+ public String doSomething() {
+ return "base";
+ }
+}
diff --git a/src/test/resources-src/multi-jdk/src/main/java11/org/vafer/multijdk/App.java b/src/test/resources-src/multi-jdk/src/main/java11/org/vafer/multijdk/App.java
new file mode 100644
index 00000000..bbb93303
--- /dev/null
+++ b/src/test/resources-src/multi-jdk/src/main/java11/org/vafer/multijdk/App.java
@@ -0,0 +1,22 @@
+package org.vafer.multijdk;
+/*
+ * Copyright 2010-2023 The jdependency developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class App {
+ public String doSomething() {
+ return new SpecificToJava11().doSomething() + " 11";
+ }
+}
diff --git a/src/test/resources-src/multi-jdk/src/main/java11/org/vafer/multijdk/JavaVersion.java b/src/test/resources-src/multi-jdk/src/main/java11/org/vafer/multijdk/JavaVersion.java
new file mode 100644
index 00000000..3cb84629
--- /dev/null
+++ b/src/test/resources-src/multi-jdk/src/main/java11/org/vafer/multijdk/JavaVersion.java
@@ -0,0 +1,33 @@
+package org.vafer.multijdk;
+/*
+ * Copyright 2010-2023 The jdependency developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.stream.Collectors;
+
+public class JavaVersion extends AbstractJavaVersion {
+ public String getCodeVersion() {
+ return "11";
+ }
+
+ public String getJavaVersion() {
+ return Runtime.version()
+ .version()
+ .stream()
+ .limit(3)
+ .map(Object::toString)
+ .collect(Collectors.joining("."));
+ }
+}
diff --git a/src/test/resources-src/multi-jdk/src/main/java11/org/vafer/multijdk/OnlyUsedInJava17.java b/src/test/resources-src/multi-jdk/src/main/java11/org/vafer/multijdk/OnlyUsedInJava17.java
new file mode 100644
index 00000000..26c2fcb6
--- /dev/null
+++ b/src/test/resources-src/multi-jdk/src/main/java11/org/vafer/multijdk/OnlyUsedInJava17.java
@@ -0,0 +1,22 @@
+package org.vafer.multijdk;
+/*
+ * Copyright 2010-2023 The jdependency developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class OnlyUsedInJava17 {
+ public String doSomething() {
+ return new JavaVersion().getJavaMajorVersion();
+ }
+}
diff --git a/src/test/resources-src/multi-jdk/src/main/java11/org/vafer/multijdk/SpecificToJava11.java b/src/test/resources-src/multi-jdk/src/main/java11/org/vafer/multijdk/SpecificToJava11.java
new file mode 100644
index 00000000..cfaaff71
--- /dev/null
+++ b/src/test/resources-src/multi-jdk/src/main/java11/org/vafer/multijdk/SpecificToJava11.java
@@ -0,0 +1,22 @@
+package org.vafer.multijdk;
+/*
+ * Copyright 2010-2023 The jdependency developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class SpecificToJava11 {
+ public String doSomething() {
+ return "java";
+ }
+}
diff --git a/src/test/resources-src/multi-jdk/src/main/java11/org/vafer/multijdk/Unused.java b/src/test/resources-src/multi-jdk/src/main/java11/org/vafer/multijdk/Unused.java
new file mode 100644
index 00000000..75339a81
--- /dev/null
+++ b/src/test/resources-src/multi-jdk/src/main/java11/org/vafer/multijdk/Unused.java
@@ -0,0 +1,22 @@
+package org.vafer.multijdk;
+/*
+ * Copyright 2010-2023 The jdependency developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Unused {
+ public String doSomething() {
+ return "java 11";
+ }
+}
diff --git a/src/test/resources-src/multi-jdk/src/main/java17/org/vafer/multijdk/App.java b/src/test/resources-src/multi-jdk/src/main/java17/org/vafer/multijdk/App.java
new file mode 100644
index 00000000..8a918662
--- /dev/null
+++ b/src/test/resources-src/multi-jdk/src/main/java17/org/vafer/multijdk/App.java
@@ -0,0 +1,22 @@
+package org.vafer.multijdk;
+/*
+ * Copyright 2010-2023 The jdependency developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class App {
+ public String doSomething() {
+ return new SpecificToJava17().doSomething() + " " + new OnlyUsedInJava17().doSomething();
+ }
+}
diff --git a/src/test/resources-src/multi-jdk/src/main/java17/org/vafer/multijdk/SpecificToJava17.java b/src/test/resources-src/multi-jdk/src/main/java17/org/vafer/multijdk/SpecificToJava17.java
new file mode 100644
index 00000000..d927c18e
--- /dev/null
+++ b/src/test/resources-src/multi-jdk/src/main/java17/org/vafer/multijdk/SpecificToJava17.java
@@ -0,0 +1,22 @@
+package org.vafer.multijdk;
+/*
+ * Copyright 2010-2023 The jdependency developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class SpecificToJava17 {
+ public String doSomething() {
+ return "java";
+ }
+}
diff --git a/src/test/resources-src/multi-jdk/src/main/java17/org/vafer/multijdk/Unused.java b/src/test/resources-src/multi-jdk/src/main/java17/org/vafer/multijdk/Unused.java
new file mode 100644
index 00000000..0c12f545
--- /dev/null
+++ b/src/test/resources-src/multi-jdk/src/main/java17/org/vafer/multijdk/Unused.java
@@ -0,0 +1,21 @@
+package org.vafer.multijdk;
+/*
+ * Copyright 2010-2023 The jdependency developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+public class Unused {
+ public String doSomething() {
+ return "java 17";
+ }
+}
diff --git a/src/test/resources-src/pom.xml b/src/test/resources-src/pom.xml
new file mode 100644
index 00000000..ca0e49dd
--- /dev/null
+++ b/src/test/resources-src/pom.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+ 4.0.0
+ nl.basjes
+ test-jars-parent
+ 1.0
+ pom
+
+ multi-jdk
+ uses-multi-jdk
+
+
\ No newline at end of file
diff --git a/src/test/resources-src/uses-multi-jdk/pom.xml b/src/test/resources-src/uses-multi-jdk/pom.xml
new file mode 100644
index 00000000..49363727
--- /dev/null
+++ b/src/test/resources-src/uses-multi-jdk/pom.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+ 4.0.0
+
+ nl.example
+ uses-multi-jdk
+ 1.0
+
+ App using the Multi Release Jar
+
+
+ UTF-8
+ UTF-8
+
+
+ 2023-11-11T12:34:56Z
+
+
+
+
+ org.vafer
+ multi-jdk
+ 1.0.0
+
+
+
+
+
+
+ maven-resources-plugin
+ 3.3.1
+
+
+ Install jar into resources directory
+ install
+
+ copy-resources
+
+
+
+
+ target
+
+ ${project.artifactId}-${project.version}.jar
+
+
+
+ ${project.basedir}/../../resources/
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-artifact-plugin
+ 3.4.1
+
+
+ Ensure reproducible
+ install
+
+ compare
+
+
+
+
+
+
+
+
+
diff --git a/src/test/resources-src/uses-multi-jdk/src/main/java/nl/example/Main.java b/src/test/resources-src/uses-multi-jdk/src/main/java/nl/example/Main.java
new file mode 100644
index 00000000..0238131e
--- /dev/null
+++ b/src/test/resources-src/uses-multi-jdk/src/main/java/nl/example/Main.java
@@ -0,0 +1,27 @@
+package nl.example;
+/*
+ * Copyright 2010-2023 The jdependency developers.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import org.vafer.multijdk.JavaVersion;
+import org.vafer.multijdk.App;
+
+public class Main {
+ public static void main(String[] args) {
+ JavaVersion javaVersion = new JavaVersion();
+ System.out.println("Java detect: " + javaVersion.getCodeVersion());
+ System.out.println("Java major : " + javaVersion.getJavaMajorVersion());
+ System.out.println("App code : " + new App().doSomething());
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/multi-jdk-1.0.0.jar b/src/test/resources/multi-jdk-1.0.0.jar
new file mode 100644
index 00000000..edde3a96
Binary files /dev/null and b/src/test/resources/multi-jdk-1.0.0.jar differ
diff --git a/src/test/resources/uses-multi-jdk-1.0.jar b/src/test/resources/uses-multi-jdk-1.0.jar
new file mode 100644
index 00000000..9d705f58
Binary files /dev/null and b/src/test/resources/uses-multi-jdk-1.0.jar differ