You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by mi...@apache.org on 2024/06/01 19:40:46 UTC
(maven-shared-jar) branch master updated: [MSHARED-1256] Add support for Multi-Release JARs
This is an automated email from the ASF dual-hosted git repository.
michaelo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-shared-jar.git
The following commit(s) were added to refs/heads/master by this push:
new 0095819 [MSHARED-1256] Add support for Multi-Release JARs
0095819 is described below
commit 009581918bd86d9bcff1b3db155f7589a9e91c16
Author: Gabriel Belingueres <be...@gmail.com>
AuthorDate: Thu May 25 00:25:00 2023 -0300
[MSHARED-1256] Add support for Multi-Release JARs
This closes #114
---
.../org/apache/maven/shared/jar/JarAnalyzer.java | 25 ++-
.../java/org/apache/maven/shared/jar/JarData.java | 45 +++++-
.../shared/jar/classes/JarClassesAnalysis.java | 176 +++++++++++++++-----
.../shared/jar/classes/JarVersionedRuntime.java | 51 ++++++
.../shared/jar/classes/JarVersionedRuntimes.java | 110 +++++++++++++
.../apache/maven/shared/jar/JarAnalyzerTest.java | 12 ++
.../shared/jar/classes/JarClassesAnalyzerTest.java | 180 +++++++++++++++++++++
.../resources/jars/module-info-only-test-0.0.1.jar | Bin 0 -> 3340 bytes
.../resources/jars/multi-release-test-0.0.1.jar | Bin 0 -> 10201 bytes
src/test/resources/jars/tomcat-jni-9.0.75.jar | Bin 0 -> 37906 bytes
10 files changed, 548 insertions(+), 51 deletions(-)
diff --git a/src/main/java/org/apache/maven/shared/jar/JarAnalyzer.java b/src/main/java/org/apache/maven/shared/jar/JarAnalyzer.java
index 6c9a92c..74ac4f9 100644
--- a/src/main/java/org/apache/maven/shared/jar/JarAnalyzer.java
+++ b/src/main/java/org/apache/maven/shared/jar/JarAnalyzer.java
@@ -151,9 +151,20 @@ public class JarAnalyzer {
* @return the list of files found, in {@link java.util.jar.JarEntry} elements
*/
public List<JarEntry> filterEntries(Pattern pattern) {
+ return filterEntries(pattern, getEntries());
+ }
+
+ /**
+ * Filter a list of JAR entries against the pattern.
+ *
+ * @param pattern the pattern to filter against.
+ * @param entryList the list of entries to filter from.
+ * @return the filtered list of JarEntry.
+ */
+ private List<JarEntry> filterEntries(Pattern pattern, List<JarEntry> entryList) {
List<JarEntry> ret = new ArrayList<>();
- for (JarEntry entry : getEntries()) {
+ for (JarEntry entry : entryList) {
Matcher mat = pattern.matcher(entry.getName());
if (mat.find()) {
ret.add(entry);
@@ -171,6 +182,18 @@ public class JarAnalyzer {
return filterEntries(CLASS_FILTER);
}
+ /**
+ * Get all the classes in the entry list.
+ *
+ * @param entryList the entry list.
+ * @return the filtered entry list.
+ *
+ * @since 3.0.1
+ */
+ public List<JarEntry> getClassEntries(List<JarEntry> entryList) {
+ return filterEntries(CLASS_FILTER, entryList);
+ }
+
/**
* Get all the Maven POM entries in the JAR.
*
diff --git a/src/main/java/org/apache/maven/shared/jar/JarData.java b/src/main/java/org/apache/maven/shared/jar/JarData.java
index 32cbad8..3ffa13e 100644
--- a/src/main/java/org/apache/maven/shared/jar/JarData.java
+++ b/src/main/java/org/apache/maven/shared/jar/JarData.java
@@ -22,16 +22,21 @@ import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.jar.Attributes;
+import java.util.jar.Attributes.Name;
import java.util.jar.JarEntry;
import java.util.jar.Manifest;
import org.apache.maven.shared.jar.classes.JarClasses;
+import org.apache.maven.shared.jar.classes.JarVersionedRuntimes;
import org.apache.maven.shared.jar.identification.JarIdentification;
/**
* Class that contains details of a single JAR file and it's entries.
*/
public final class JarData {
+
+ private static final Name ATTR_MULTI_RELEASE = new Attributes.Name("Multi-Release");
+
/**
* The JAR file.
*/
@@ -42,6 +47,11 @@ public final class JarData {
*/
private final boolean aSealed;
+ /**
+ * Whether the JAR file is Multi-Release.
+ */
+ private boolean multiRelease;
+
/**
* The hashcode for the entire file's contents.
*/
@@ -72,6 +82,11 @@ public final class JarData {
*/
private JarIdentification jarIdentification;
+ /**
+ * Information about the JAR's Multi-Release entries
+ */
+ private JarVersionedRuntimes versionedRuntimes;
+
/**
* Constructor.
*
@@ -86,14 +101,8 @@ public final class JarData {
this.entries = Collections.unmodifiableList(entries);
- boolean aSealed = false;
- if (this.manifest != null) {
- String sval = this.manifest.getMainAttributes().getValue(Attributes.Name.SEALED);
- if (sval != null && !sval.isEmpty()) {
- aSealed = "true".equalsIgnoreCase(sval.trim());
- }
- }
- this.aSealed = aSealed;
+ this.aSealed = isAttributePresent(Attributes.Name.SEALED);
+ this.multiRelease = isAttributePresent(ATTR_MULTI_RELEASE);
}
public List<JarEntry> getEntries() {
@@ -112,6 +121,10 @@ public final class JarData {
return aSealed;
}
+ public boolean isMultiRelease() {
+ return multiRelease;
+ }
+
public void setFileHash(String fileHash) {
this.fileHash = fileHash;
}
@@ -163,4 +176,20 @@ public final class JarData {
public JarClasses getJarClasses() {
return jarClasses;
}
+
+ public void setVersionedRuntimes(JarVersionedRuntimes versionedRuntimess) {
+ this.versionedRuntimes = versionedRuntimess;
+ }
+
+ public JarVersionedRuntimes getVersionedRuntimes() {
+ return this.versionedRuntimes;
+ }
+
+ private boolean isAttributePresent(Attributes.Name attrName) {
+ if (this.manifest != null) {
+ String sval = this.manifest.getMainAttributes().getValue(attrName);
+ return sval != null && "true".equalsIgnoreCase(sval.trim());
+ }
+ return false;
+ }
}
diff --git a/src/main/java/org/apache/maven/shared/jar/classes/JarClassesAnalysis.java b/src/main/java/org/apache/maven/shared/jar/classes/JarClassesAnalysis.java
index 68e297a..bd9cb22 100644
--- a/src/main/java/org/apache/maven/shared/jar/classes/JarClassesAnalysis.java
+++ b/src/main/java/org/apache/maven/shared/jar/classes/JarClassesAnalysis.java
@@ -26,8 +26,13 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.NavigableMap;
import java.util.Optional;
+import java.util.TreeMap;
import java.util.jar.JarEntry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
import org.apache.bcel.classfile.ClassFormatException;
import org.apache.bcel.classfile.ClassParser;
@@ -36,6 +41,7 @@ import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.LineNumberTable;
import org.apache.bcel.classfile.Method;
import org.apache.maven.shared.jar.JarAnalyzer;
+import org.apache.maven.shared.jar.JarData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -51,8 +57,17 @@ import org.slf4j.LoggerFactory;
@Named
@SuppressWarnings("checkstyle:MagicNumber")
public class JarClassesAnalysis {
+
private final Logger logger = LoggerFactory.getLogger(getClass());
+ /**
+ * Constant representing the classes in the root of a Multi-Release JAR file.
+ * Meaning outside of any given META-INF/versions/NN/... entry.
+ */
+ private static final Integer ROOT = 0;
+
+ private static final Pattern ENTRY_FILTER_MULTI_RELEASE = Pattern.compile("^META-INF/versions/(\\d{1,2})/.+$");
+
private static final Map<Double, String> JAVA_CLASS_VERSIONS;
static {
@@ -90,68 +105,145 @@ public class JarClassesAnalysis {
* @return the details of the classes found
*/
public JarClasses analyze(JarAnalyzer jarAnalyzer) {
- JarClasses classes = jarAnalyzer.getJarData().getJarClasses();
+ JarData jarData = jarAnalyzer.getJarData();
+ JarClasses classes = jarData.getJarClasses();
if (classes == null) {
- String jarfilename = jarAnalyzer.getFile().getAbsolutePath();
- classes = new JarClasses();
+ if (jarData.isMultiRelease()) {
+ classes = analyzeMultiRelease(jarAnalyzer);
+ } else {
+ classes = analyzeRoot(jarAnalyzer);
+ }
+ }
+ return classes;
+ }
- List<JarEntry> classList = jarAnalyzer.getClassEntries();
+ private Integer jarEntryVersion(JarEntry entry) {
+ Matcher matcher = ENTRY_FILTER_MULTI_RELEASE.matcher(entry.getName());
+ if (matcher.matches()) {
+ return Integer.valueOf(matcher.group(1));
+ }
+ return ROOT;
+ }
- classes.setDebugPresent(false);
+ private JarClasses analyzeMultiRelease(JarAnalyzer jarAnalyzer) {
+ String jarFilename = jarAnalyzer.getFile().getAbsolutePath();
- double maxVersion = 0.0;
+ Map<Integer, List<JarEntry>> mapEntries =
+ jarAnalyzer.getEntries().stream().collect(Collectors.groupingBy(this::jarEntryVersion));
- for (JarEntry entry : classList) {
- String classname = entry.getName();
+ // ordered by increasing Java version
+ NavigableMap<Integer, JarVersionedRuntime> runtimeVersionsMap = new TreeMap<>();
- try {
- ClassParser classParser = new ClassParser(jarfilename, classname);
+ for (Map.Entry<Integer, List<JarEntry>> mapEntry : mapEntries.entrySet()) {
+ Integer runtimeVersion = mapEntry.getKey();
+ List<JarEntry> runtimeVersionEntryList = mapEntry.getValue();
- JavaClass javaClass = classParser.parse();
+ List<JarEntry> classList = jarAnalyzer.getClassEntries(runtimeVersionEntryList);
- String classSignature = javaClass.getClassName();
+ JarClasses classes = analyze(jarFilename, classList);
- if (!classes.isDebugPresent()) {
- if (hasDebugSymbols(javaClass)) {
- classes.setDebugPresent(true);
- }
- }
+ runtimeVersionsMap.put(runtimeVersion, new JarVersionedRuntime(runtimeVersionEntryList, classes));
+ }
- double classVersion = javaClass.getMajor();
- if (javaClass.getMinor() > 0) {
- classVersion = classVersion + javaClass.getMinor() / 10.0;
- }
+ JarVersionedRuntime baseJarRelease = runtimeVersionsMap.remove(ROOT);
+ JarClasses baseJarClasses = baseJarRelease.getJarClasses();
- if (classVersion > maxVersion) {
- maxVersion = classVersion;
- }
+ jarAnalyzer.getJarData().setJarClasses(baseJarClasses);
- Method[] methods = javaClass.getMethods();
- for (Method method : methods) {
- classes.addMethod(classSignature + "." + method.getName() + method.getSignature());
- }
+ // Paranoid?
+ for (Map.Entry<Integer, JarVersionedRuntime> runtimeVersionEntry : runtimeVersionsMap.entrySet()) {
+ Integer version = runtimeVersionEntry.getKey();
+ String jdkRevision = runtimeVersionEntry.getValue().getJarClasses().getJdkRevision();
+ if (!version.equals(Integer.valueOf(jdkRevision))) {
+ logger.warn(
+ "Multi-release version {} in JAR file '{}' has some class compiled for Jdk revision {}",
+ version,
+ jarFilename,
+ jdkRevision);
+ }
+ }
+
+ jarAnalyzer.getJarData().setVersionedRuntimes(new JarVersionedRuntimes(runtimeVersionsMap));
+
+ return baseJarClasses;
+ }
+
+ private JarClasses analyzeRoot(JarAnalyzer jarAnalyzer) {
+ String jarFilename = jarAnalyzer.getFile().getAbsolutePath();
+
+ List<JarEntry> classList = jarAnalyzer.getClassEntries();
- String classPackageName = javaClass.getPackageName();
+ JarClasses classes = analyze(jarFilename, classList);
- classes.addClassName(classSignature);
- classes.addPackage(classPackageName);
+ jarAnalyzer.getJarData().setJarClasses(classes);
+ return classes;
+ }
+
+ private JarClasses analyze(String jarFilename, List<JarEntry> classList) {
+ JarClasses classes = new JarClasses();
+
+ classes.setDebugPresent(false);
+
+ double maxVersion = 0.0;
+ double moduleInfoVersion = 0.0;
+
+ for (JarEntry entry : classList) {
+ String classname = entry.getName();
- ImportVisitor importVisitor = new ImportVisitor(javaClass);
- DescendingVisitor descVisitor = new DescendingVisitor(javaClass, importVisitor);
- javaClass.accept(descVisitor);
+ try {
+ ClassParser classParser = new ClassParser(jarFilename, classname);
- classes.addImports(importVisitor.getImports());
- } catch (ClassFormatException e) {
- logger.warn("Unable to process class " + classname + " in JarAnalyzer File " + jarfilename, e);
- } catch (IOException e) {
- logger.warn("Unable to process JarAnalyzer File " + jarfilename, e);
+ JavaClass javaClass = classParser.parse();
+
+ String classSignature = javaClass.getClassName();
+
+ if (!classes.isDebugPresent()) {
+ if (hasDebugSymbols(javaClass)) {
+ classes.setDebugPresent(true);
+ }
}
- }
- Optional.ofNullable(JAVA_CLASS_VERSIONS.get(maxVersion)).ifPresent(classes::setJdkRevision);
+ double classVersion = javaClass.getMajor();
+ if (javaClass.getMinor() > 0) {
+ classVersion = classVersion + javaClass.getMinor() / 10.0;
+ }
- jarAnalyzer.getJarData().setJarClasses(classes);
+ if ("module-info".equals(classSignature)) {
+ // ignore the module-info.class for computing the maxVersion, since it will always be >= 9
+ moduleInfoVersion = classVersion;
+ } else if (classVersion > maxVersion) {
+ maxVersion = classVersion;
+ }
+
+ Method[] methods = javaClass.getMethods();
+ for (Method method : methods) {
+ classes.addMethod(classSignature + "." + method.getName() + method.getSignature());
+ }
+
+ String classPackageName = javaClass.getPackageName();
+
+ classes.addClassName(classSignature);
+ classes.addPackage(classPackageName);
+
+ ImportVisitor importVisitor = new ImportVisitor(javaClass);
+ DescendingVisitor descVisitor = new DescendingVisitor(javaClass, importVisitor);
+ javaClass.accept(descVisitor);
+
+ classes.addImports(importVisitor.getImports());
+ } catch (ClassFormatException e) {
+ logger.warn("Unable to process class " + classname + " in JarAnalyzer File " + jarFilename, e);
+ } catch (IOException e) {
+ logger.warn("Unable to process JarAnalyzer File " + jarFilename, e);
+ }
+ }
+
+ if (maxVersion == 0.0 && moduleInfoVersion > 0.0) {
+ // the one and only class file was module-info.class
+ maxVersion = moduleInfoVersion;
}
+
+ Optional.ofNullable(JAVA_CLASS_VERSIONS.get(maxVersion)).ifPresent(classes::setJdkRevision);
+
return classes;
}
diff --git a/src/main/java/org/apache/maven/shared/jar/classes/JarVersionedRuntime.java b/src/main/java/org/apache/maven/shared/jar/classes/JarVersionedRuntime.java
new file mode 100644
index 0000000..f76cd30
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/classes/JarVersionedRuntime.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.maven.shared.jar.classes;
+
+import java.util.List;
+import java.util.jar.JarEntry;
+
+public class JarVersionedRuntime {
+
+ /**
+ * The JAR entries.
+ */
+ private final List<JarEntry> entries;
+
+ private final JarClasses jarClasses;
+
+ public JarVersionedRuntime(List<JarEntry> entries, JarClasses jarClasses) {
+ this.entries = entries;
+ this.jarClasses = jarClasses;
+ }
+
+ /**
+ * @return the entries
+ */
+ public List<JarEntry> getEntries() {
+ return entries;
+ }
+
+ /**
+ * @return the jarClasses
+ */
+ public JarClasses getJarClasses() {
+ return jarClasses;
+ }
+}
diff --git a/src/main/java/org/apache/maven/shared/jar/classes/JarVersionedRuntimes.java b/src/main/java/org/apache/maven/shared/jar/classes/JarVersionedRuntimes.java
new file mode 100644
index 0000000..7afa7bd
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/jar/classes/JarVersionedRuntimes.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+package org.apache.maven.shared.jar.classes;
+
+import java.util.Map.Entry;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Gathered facts about the runtime versions contained within a Multi-Release JAR file.
+ *
+ * @see org.apache.maven.shared.jar.classes.JarClassesAnalysis#analyze(org.apache.maven.shared.jar.JarAnalyzer)
+ */
+public class JarVersionedRuntimes {
+
+ /**
+ * Information about the JAR's Multi-Release entries
+ */
+ private NavigableMap<Integer, JarVersionedRuntime> versionedRuntimeMap;
+
+ public JarVersionedRuntimes(NavigableMap<Integer, JarVersionedRuntime> versionedRuntimeMap) {
+ this.versionedRuntimeMap = versionedRuntimeMap;
+ }
+
+ /**
+ * @return the versionedRuntimeMap
+ */
+ public NavigableMap<Integer, JarVersionedRuntime> getVersionedRuntimeMap() {
+ return versionedRuntimeMap;
+ }
+
+ public JarVersionedRuntime getJarVersionedRuntime(Integer version) {
+ return versionedRuntimeMap.get(version);
+ }
+
+ /**
+ * Return the JarClasses associated to the release.
+ * @param version the release version.
+ * @return the JarClasses.
+ */
+ public JarClasses getJarClasses(Integer version) {
+ return versionedRuntimeMap.get(version).getJarClasses();
+ }
+
+ /**
+ * Get a set of release versions included in the JAR file.
+ * @return a set with the Java versions as Strings.
+ */
+ public Set<Integer> getRuntimeVersionsAsSet() {
+ return versionedRuntimeMap.keySet();
+ }
+
+ /**
+ * Return the highest the JarClasses of the Jdk version that would be executed if they would be executed on a JVM given by the release parameter.
+ * @param version the Jdk version number of the executing JVM.
+ * @return The fittest JarClasses object matching if found one, or null otherwise.
+ * @throws NullPointerException if release is null.
+ */
+ public JarVersionedRuntime getBestFitJarVersionedRuntime(Integer version) {
+ Objects.requireNonNull(version, "version cannot be null");
+ Entry<Integer, JarVersionedRuntime> entry = versionedRuntimeMap.floorEntry(version);
+ if (entry == null) {
+ return null;
+ }
+ return entry.getValue();
+ }
+
+ /**
+ * Return the highest the JarClasses of the Jdk version that would be executed if they would be executed given a System property.
+ * Example values: "java.version.specification" or "java.vm.specification.version".
+ * @param key the System property.
+ * @return The best fitting JarClasses object matching if found one, or null otherwise.
+ * @throws NullPointerException if key is null
+ * @throws IllegalStateException if system property value of key is null
+ * @throws IllegalStateException if system property cannot be converted to Integer
+ */
+ public JarVersionedRuntime getBestFitJarVersionedRuntimeBySystemProperty(String key) {
+ Objects.requireNonNull(key, "key cannot null");
+ String property = System.getProperty(key);
+ if (property == null) {
+ throw new IllegalStateException("The value of the system property '" + key + "' is null");
+ }
+ try {
+ Integer release = Integer.parseInt(property);
+ return getBestFitJarVersionedRuntime(release);
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException(
+ "The value of the system property '" + key + "' [" + property
+ + "] cannot be converted to an Integer",
+ e);
+ }
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/jar/JarAnalyzerTest.java b/src/test/java/org/apache/maven/shared/jar/JarAnalyzerTest.java
index 8cac275..f342573 100644
--- a/src/test/java/org/apache/maven/shared/jar/JarAnalyzerTest.java
+++ b/src/test/java/org/apache/maven/shared/jar/JarAnalyzerTest.java
@@ -64,6 +64,18 @@ class JarAnalyzerTest extends AbstractJarAnalyzerTestCase {
assertFalse(jarData.isSealed());
}
+ @Test
+ void testMultiRelease() throws Exception {
+ JarData jarData = getJarData("multi-release-test-0.0.1.jar");
+ assertTrue(jarData.isMultiRelease());
+ }
+
+ @Test
+ void testNotMultiRelease() throws Exception {
+ JarData jarData = getJarData("codec.jar");
+ assertFalse(jarData.isMultiRelease());
+ }
+
@Test
void testMissingFile() {
assertThrows(IOException.class, () -> new JarAnalyzer(new File("foo-bar-this-should-not-exist.jar")));
diff --git a/src/test/java/org/apache/maven/shared/jar/classes/JarClassesAnalyzerTest.java b/src/test/java/org/apache/maven/shared/jar/classes/JarClassesAnalyzerTest.java
index 8c7bb2d..dc7c1db 100644
--- a/src/test/java/org/apache/maven/shared/jar/classes/JarClassesAnalyzerTest.java
+++ b/src/test/java/org/apache/maven/shared/jar/classes/JarClassesAnalyzerTest.java
@@ -21,9 +21,13 @@ package org.apache.maven.shared.jar.classes;
import javax.inject.Inject;
import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.JarEntry;
import org.apache.maven.shared.jar.AbstractJarAnalyzerTestCase;
import org.apache.maven.shared.jar.JarAnalyzer;
+import org.apache.maven.shared.jar.JarData;
import org.codehaus.plexus.testing.PlexusTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -34,6 +38,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
/**
* JarAnalyzer Classes Test Case
@@ -137,6 +142,181 @@ class JarClassesAnalyzerTest extends AbstractJarAnalyzerTestCase {
assertEquals(expectedRevision, jclass.getJdkRevision());
}
+ @Test
+ void testAnalyzeJarWithModuleInfoClass() throws Exception {
+ JarData jarData = getJarData("tomcat-jni-9.0.75.jar");
+ JarClasses jclass = jarData.getJarClasses();
+ assertEquals("1.8", jclass.getJdkRevision());
+ }
+
+ @Test
+ void testAnalyzeJarWithOnlyModuleInfoClass() throws Exception {
+ JarData jarData = getJarData("module-info-only-test-0.0.1.jar");
+ // root level classes
+ JarClasses jclass = jarData.getJarClasses();
+ assertTrue(jclass.getImports().isEmpty());
+ assertTrue(jclass.getPackages().isEmpty());
+ assertTrue(jclass.getClassNames().isEmpty());
+ assertTrue(jclass.getMethods().isEmpty());
+ assertNull(jclass.getJdkRevision());
+
+ JarVersionedRuntimes jarVersionedRuntimes = jarData.getVersionedRuntimes();
+ assertNotNull(jarVersionedRuntimes);
+ Map<Integer, JarVersionedRuntime> jarVersionedRuntimeMap = jarVersionedRuntimes.getVersionedRuntimeMap();
+ assertNotNull(jarVersionedRuntimeMap);
+ assertEquals(1, jarVersionedRuntimeMap.size()); // 11
+
+ JarVersionedRuntime jarVersionedRuntime11 = jarVersionedRuntimes.getJarVersionedRuntime(11);
+ JarClasses jarClasses11 = jarVersionedRuntime11.getJarClasses();
+ assertEquals("11", jarClasses11.getJdkRevision());
+ assertTrue(jarClasses11.getImports().isEmpty());
+ assertEquals(1, jarClasses11.getPackages().size());
+ assertEquals("", jarClasses11.getPackages().get(0));
+ assertEquals(1, jarClasses11.getClassNames().size());
+ assertTrue(jarClasses11.getMethods().isEmpty());
+ assertEquals(1, jarVersionedRuntime11.getEntries().size());
+ assertEntriesContains(jarVersionedRuntime11.getEntries(), "META-INF/versions/11/module-info.class");
+ }
+
+ @Test
+ void testAnalyzeMultiReleaseJarVersion() throws Exception {
+ JarData jarData = getJarData("multi-release-test-0.0.1.jar");
+ JarClasses jclass = jarData.getJarClasses();
+
+ assertEquals("1.8", jclass.getJdkRevision());
+ assertFalse(jclass.getImports().isEmpty());
+ assertEquals(1, jclass.getPackages().size());
+ assertEquals(1, jclass.getClassNames().size());
+ assertFalse(jclass.getMethods().isEmpty());
+ assertEntriesContains(jarData.getEntries(), "resource.txt");
+
+ JarVersionedRuntimes jarVersionedRuntimes = jarData.getVersionedRuntimes();
+ assertNotNull(jarVersionedRuntimes);
+ Map<Integer, JarVersionedRuntime> jarVersionedRuntimeMap = jarVersionedRuntimes.getVersionedRuntimeMap();
+ assertNotNull(jarVersionedRuntimeMap);
+ assertEquals(2, jarVersionedRuntimeMap.size()); // 9 and 11
+
+ JarVersionedRuntime jarVersionedRuntime9 = jarVersionedRuntimes.getJarVersionedRuntime(9);
+ JarClasses jarClasses9 = jarVersionedRuntime9.getJarClasses();
+ assertEquals("9", jarClasses9.getJdkRevision());
+ assertFalse(jarClasses9.getImports().isEmpty());
+ assertEquals(1, jarClasses9.getPackages().size());
+ assertEquals(1, jarClasses9.getClassNames().size());
+ assertFalse(jarClasses9.getMethods().isEmpty());
+ assertEntriesContains(jarVersionedRuntime9.getEntries(), "META-INF/versions/9/resource.txt");
+
+ JarVersionedRuntime jarVersionedRuntime11 = jarVersionedRuntimes.getJarVersionedRuntime(11);
+ JarClasses jarClasses11 = jarVersionedRuntime11.getJarClasses();
+ assertEquals("11", jarClasses11.getJdkRevision());
+ assertFalse(jarClasses11.getImports().isEmpty());
+ assertEquals(1, jarClasses11.getPackages().size());
+ assertEquals(1, jarClasses11.getClassNames().size());
+ assertFalse(jarClasses11.getMethods().isEmpty());
+ assertEntriesContains(jarVersionedRuntime11.getEntries(), "META-INF/versions/11/resource.txt");
+
+ // test ordering
+ assertEquals("[9, 11]", jarVersionedRuntimeMap.keySet().toString());
+
+ // test best fit
+ try {
+ jarVersionedRuntimes.getBestFitJarVersionedRuntime(null);
+ fail("It should throw an NPE");
+ } catch (NullPointerException e) {
+ assertTrue(true);
+ }
+ assertNull(jarVersionedRuntimes.getBestFitJarVersionedRuntime(8)); // unreal value but good just for testing
+ assertEquals(
+ "9",
+ jarVersionedRuntimes
+ .getBestFitJarVersionedRuntime(9)
+ .getJarClasses()
+ .getJdkRevision());
+ assertEquals(
+ "9",
+ jarVersionedRuntimes
+ .getBestFitJarVersionedRuntime(10)
+ .getJarClasses()
+ .getJdkRevision());
+ assertEquals(
+ "11",
+ jarVersionedRuntimes
+ .getBestFitJarVersionedRuntime(11)
+ .getJarClasses()
+ .getJdkRevision());
+ assertEquals(
+ "11",
+ jarVersionedRuntimes
+ .getBestFitJarVersionedRuntime(20)
+ .getJarClasses()
+ .getJdkRevision());
+
+ try {
+ jarVersionedRuntimes.getBestFitJarVersionedRuntimeBySystemProperty(null);
+ fail("It should throw an NPE");
+ } catch (NullPointerException e) {
+ assertTrue(true);
+ }
+
+ try {
+ getBestFitReleaseBySystemProperty(jarVersionedRuntimes, null);
+ fail("It should throw an NPE");
+ } catch (NullPointerException e) {
+ assertTrue(true);
+ }
+
+ try {
+ getBestFitReleaseBySystemProperty(jarVersionedRuntimes, "xxx");
+ fail("It should throw an ISE");
+ } catch (IllegalStateException e) {
+ assertTrue(true);
+ }
+
+ assertNull(getBestFitReleaseBySystemProperty(jarVersionedRuntimes, "8"));
+ assertEquals(
+ "9",
+ getBestFitReleaseBySystemProperty(jarVersionedRuntimes, "9")
+ .getJarClasses()
+ .getJdkRevision());
+ assertEquals(
+ "9",
+ getBestFitReleaseBySystemProperty(jarVersionedRuntimes, "10")
+ .getJarClasses()
+ .getJdkRevision());
+ assertEquals(
+ "11",
+ getBestFitReleaseBySystemProperty(jarVersionedRuntimes, "11")
+ .getJarClasses()
+ .getJdkRevision());
+ assertEquals(
+ "11",
+ getBestFitReleaseBySystemProperty(jarVersionedRuntimes, "20")
+ .getJarClasses()
+ .getJdkRevision());
+ }
+
+ private void assertEntriesContains(List<JarEntry> list, final String entryToFind) {
+ assertTrue(list.stream().anyMatch(entry -> entry.getName().equals(entryToFind)));
+ }
+
+ private JarVersionedRuntime getBestFitReleaseBySystemProperty(
+ JarVersionedRuntimes jarVersionedRuntimes, String value) {
+ String key = "maven.shared.jar.test.vm";
+ System.setProperty(key, value);
+ return jarVersionedRuntimes.getBestFitJarVersionedRuntimeBySystemProperty(key);
+ }
+
+ private JarData getJarData(String filename) throws Exception {
+ File file = getSampleJar(filename);
+
+ JarAnalyzer jarAnalyzer = new JarAnalyzer(file);
+ JarClasses jclass = analyzer.analyze(jarAnalyzer);
+ JarData jarData = jarAnalyzer.getJarData();
+ assertNotNull(jclass, "JarClasses");
+ assertNotNull(jarData, "JarData");
+
+ return jarData;
+ }
+
private JarClasses getJarClasses(String filename) throws Exception {
File file = getSampleJar(filename);
diff --git a/src/test/resources/jars/module-info-only-test-0.0.1.jar b/src/test/resources/jars/module-info-only-test-0.0.1.jar
new file mode 100644
index 0000000..c6f5752
Binary files /dev/null and b/src/test/resources/jars/module-info-only-test-0.0.1.jar differ
diff --git a/src/test/resources/jars/multi-release-test-0.0.1.jar b/src/test/resources/jars/multi-release-test-0.0.1.jar
new file mode 100644
index 0000000..b0a03bc
Binary files /dev/null and b/src/test/resources/jars/multi-release-test-0.0.1.jar differ
diff --git a/src/test/resources/jars/tomcat-jni-9.0.75.jar b/src/test/resources/jars/tomcat-jni-9.0.75.jar
new file mode 100644
index 0000000..93d0d6b
Binary files /dev/null and b/src/test/resources/jars/tomcat-jni-9.0.75.jar differ