diff --git a/WHATSNEW b/WHATSNEW
index de54711f9..a74856e45 100644
--- a/WHATSNEW
+++ b/WHATSNEW
@@ -65,6 +65,13 @@ Other changes:
tasks that must deal with different character encodings in files,
file names and other string resources.
+ * org.apache.tools.ant.AntClassLoader is now multi-release jar aware.
+ Starting Java 9, jar files can be packaged as multi-release jars,
+ AntClassLoader now recognizes such multi-release jar files while
+ loading resources at runtime in Java 9+ runtime environments.
+ Bugzilla Report 62952
+
+
Changes from Ant 1.10.4 TO Ant 1.10.5
=====================================
diff --git a/src/etc/testcases/core/antclassloader.xml b/src/etc/testcases/core/antclassloader.xml
index 045428d22..db6f0ffb5 100644
--- a/src/etc/testcases/core/antclassloader.xml
+++ b/src/etc/testcases/core/antclassloader.xml
@@ -73,4 +73,52 @@ public class Foo {}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/org/apache/tools/ant/AntClassLoader.java b/src/main/org/apache/tools/ant/AntClassLoader.java
index 3ab7c62a5..59ee1756d 100644
--- a/src/main/org/apache/tools/ant/AntClassLoader.java
+++ b/src/main/org/apache/tools/ant/AntClassLoader.java
@@ -45,12 +45,14 @@ import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import java.util.zip.ZipFile;
import org.apache.tools.ant.launch.Locator;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.JavaEnvUtils;
import org.apache.tools.ant.util.LoaderUtils;
+import org.apache.tools.ant.util.ReflectUtil;
import org.apache.tools.ant.util.StringUtils;
import org.apache.tools.ant.util.VectorSet;
import org.apache.tools.zip.ZipLong;
@@ -76,8 +78,29 @@ public class AntClassLoader extends ClassLoader implements SubBuildListener, Clo
private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
+ private static final boolean IS_ATLEAST_JAVA9 = JavaEnvUtils.isAtLeastJavaVersion(JavaEnvUtils.JAVA_9);
+ // constructs needed to create (via reflection) a java.util.jar.JarFile instance when Java runtime version is >= 9
+ private static final Class[] MR_JARFILE_CTOR_ARGS;
+ private static final Object MR_JARFILE_CTOR_RUNTIME_VERSION_VAL;
+
static {
registerAsParallelCapable();
+ if (IS_ATLEAST_JAVA9) {
+ Class[] ctorArgs = null;
+ Object runtimeVersionVal = null;
+ try {
+ final Class> runtimeVersionClass = Class.forName("java.lang.Runtime$Version");
+ ctorArgs = new Class[] {File.class, boolean.class, int.class, runtimeVersionClass};
+ runtimeVersionVal = Runtime.class.getDeclaredMethod("version").invoke(null);
+ } catch (Exception e) {
+ // ignore - we consider this as multi-release jar unsupported
+ }
+ MR_JARFILE_CTOR_ARGS = ctorArgs;
+ MR_JARFILE_CTOR_RUNTIME_VERSION_VAL = runtimeVersionVal;
+ } else {
+ MR_JARFILE_CTOR_ARGS = null;
+ MR_JARFILE_CTOR_RUNTIME_VERSION_VAL = null;
+ }
}
/**
@@ -500,7 +523,7 @@ public class AntClassLoader extends ClassLoader implements SubBuildListener, Clo
+ pathComponent.lastModified() + "-" + pathComponent.length();
String classpath = pathMap.get(absPathPlusTimeAndLength);
if (classpath == null) {
- try (JarFile jarFile = new JarFile(pathComponent)) {
+ try (JarFile jarFile = newJarFile(pathComponent)) {
final Manifest manifest = jarFile.getManifest();
if (manifest == null) {
return;
@@ -785,7 +808,7 @@ public class AntClassLoader extends ClassLoader implements SubBuildListener, Clo
} else {
if (jarFile == null) {
if (file.exists()) {
- jarFile = new JarFile(file);
+ jarFile = newJarFile(file);
jarFiles.put(file, jarFile);
} else {
return null;
@@ -1005,7 +1028,7 @@ public class AntClassLoader extends ClassLoader implements SubBuildListener, Clo
log(msg, Project.MSG_WARN);
return null;
}
- jarFile = new JarFile(file);
+ jarFile = newJarFile(file);
jarFiles.put(file, jarFile);
} else {
return null;
@@ -1570,4 +1593,19 @@ public class AntClassLoader extends ClassLoader implements SubBuildListener, Clo
}
}
+ /**
+ *
+ * @param file The file representing the jar
+ * @return Returns a {@link JarFile} instance, which is constructed based upon the Java runtime version.
+ * Depending on the Java runtime version, the returned instance may or may not be {@code multi-release}
+ * aware (a feature introduced in Java 9)
+ * @throws IOException
+ */
+ private static JarFile newJarFile(final File file) throws IOException {
+ if (!IS_ATLEAST_JAVA9 || MR_JARFILE_CTOR_ARGS == null || MR_JARFILE_CTOR_RUNTIME_VERSION_VAL == null) {
+ return new JarFile(file);
+ }
+ return ReflectUtil.newInstance(JarFile.class, MR_JARFILE_CTOR_ARGS,
+ new Object[] {file, true, ZipFile.OPEN_READ, MR_JARFILE_CTOR_RUNTIME_VERSION_VAL});
+ }
}
diff --git a/src/tests/junit/org/apache/tools/ant/AntClassLoaderTest.java b/src/tests/junit/org/apache/tools/ant/AntClassLoaderTest.java
index 9adde4a89..029d7861c 100644
--- a/src/tests/junit/org/apache/tools/ant/AntClassLoaderTest.java
+++ b/src/tests/junit/org/apache/tools/ant/AntClassLoaderTest.java
@@ -33,7 +33,9 @@ import java.util.Enumeration;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.util.FileUtils;
+import org.apache.tools.ant.util.JavaEnvUtils;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -233,6 +235,26 @@ public class AntClassLoaderTest {
assertFalse(acl.getResources("META-INF/MANIFEST.MF").hasMoreElements());
}
+ /**
+ * Tests that {@link AntClassLoader} supports multi-release jar files while dealing with
+ * runtime resources in Java 9+ runtime environments.
+ *
+ * @see https://bz.apache.org/bugzilla/show_bug.cgi?id=62952
+ */
+ @Test
+ public void testMultiReleaseJar() {
+ buildRule.executeTarget("testMRJar");
+ final boolean atleastJava9 = JavaEnvUtils.isAtLeastJavaVersion(JavaEnvUtils.JAVA_9);
+ final String targetOutput = buildRule.getOutput();
+ Assert.assertNotNull("Multi-release jar test did not generate any output", targetOutput);
+ if (atleastJava9) {
+ Assert.assertTrue("Unexpected output from multi-release jar test for Java runtime >= 9",
+ targetOutput.contains("mrjar test result = 9"));
+ } else {
+ Assert.assertTrue("Unexpected output from multi-release jar test", targetOutput.contains("mrjar test result = default"));
+ }
+ }
+
private static class EmptyLoader extends ClassLoader {
public URL getResource(String n) {
return null;