Repository: ant Updated Branches: refs/heads/master ac46ff190 -> 593aff2d2
bz-62952 Make AntClassLoader multi-release jar aware when it deals with java.util.jar.JarFile Project: http://git-wip-us.apache.org/repos/asf/ant/repo Commit: http://git-wip-us.apache.org/repos/asf/ant/commit/593aff2d Tree: http://git-wip-us.apache.org/repos/asf/ant/tree/593aff2d Diff: http://git-wip-us.apache.org/repos/asf/ant/diff/593aff2d Branch: refs/heads/master Commit: 593aff2d2ea52a025cfe7da32155216719029a7d Parents: ac46ff1 Author: Jaikiran Pai <jaiki...@apache.org> Authored: Wed Dec 5 18:26:14 2018 +0530 Committer: Jaikiran Pai <jaiki...@apache.org> Committed: Wed Dec 5 18:27:45 2018 +0530 ---------------------------------------------------------------------- WHATSNEW | 7 +++ src/etc/testcases/core/antclassloader.xml | 48 ++++++++++++++++++++ .../org/apache/tools/ant/AntClassLoader.java | 44 ++++++++++++++++-- .../apache/tools/ant/AntClassLoaderTest.java | 22 +++++++++ 4 files changed, 118 insertions(+), 3 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ant/blob/593aff2d/WHATSNEW ---------------------------------------------------------------------- diff --git a/WHATSNEW b/WHATSNEW index de54711..a74856e 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 ===================================== http://git-wip-us.apache.org/repos/asf/ant/blob/593aff2d/src/etc/testcases/core/antclassloader.xml ---------------------------------------------------------------------- diff --git a/src/etc/testcases/core/antclassloader.xml b/src/etc/testcases/core/antclassloader.xml index 045428d..db6f0ff 100644 --- a/src/etc/testcases/core/antclassloader.xml +++ b/src/etc/testcases/core/antclassloader.xml @@ -73,4 +73,52 @@ public class Foo {} <target name="createNonJar"> <touch file="${tmp.dir}/foo.jar"/> </target> + + <target name="testMRJar" description="tests AntClassLoader support for multi-release jars. + see https://bz.apache.org/bugzilla/show_bug.cgi?id=62952"> + <mkdir dir="${tmp.dir}/mrjar/org/example"/> + <mkdir dir="${tmp.dir}/mrjar-9/org/example"/> + <!-- default version of the class --> + <echo file="${tmp.dir}/mrjar/org/example/MRJarTest.java"><![CDATA[ + package org.example; + public class MRJarTest { + public static void main(String[] args) { + System.out.println("mrjar test result = default"); + } + } + ]]> + </echo> + <!-- Java runtime version 9 of the class --> + <echo file="${tmp.dir}/mrjar-9/org/example/MRJarTest.java"><![CDATA[ + package org.example; + public class MRJarTest { + public static void main(String[] args) { + System.out.println("mrjar test result = 9"); + } + } + ]]> + </echo> + <!-- compile these classes --> + <javac srcdir="${tmp.dir}/mrjar" destdir="${tmp.dir}/mrjar"/> + <javac srcdir="${tmp.dir}/mrjar-9" destdir="${tmp.dir}/mrjar-9"/> + + <!-- create multi-release jar file --> + <jar destfile="${tmp.dir}/mrjar.jar"> + <manifest> + <attribute name="Multi-Release" value="true"/> + </manifest> + <!-- default classes --> + <fileset dir="${tmp.dir}/mrjar" includes="**/*.class"/> + <!-- Java 9 specific classes --> + <zipfileset prefix="META-INF/versions/9/" dir="${tmp.dir}/mrjar-9" includes="**/*.class"/> + </jar> + + <!-- now run the class present in the multi-release jar --> + <java classname="org.example.MRJarTest" failonerror="true"> + <classpath> + <pathelement location="${tmp.dir}/mrjar.jar"/> + </classpath> + </java> + + </target> </project> http://git-wip-us.apache.org/repos/asf/ant/blob/593aff2d/src/main/org/apache/tools/ant/AntClassLoader.java ---------------------------------------------------------------------- diff --git a/src/main/org/apache/tools/ant/AntClassLoader.java b/src/main/org/apache/tools/ant/AntClassLoader.java index 3ab7c62..59ee175 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}); + } } http://git-wip-us.apache.org/repos/asf/ant/blob/593aff2d/src/tests/junit/org/apache/tools/ant/AntClassLoaderTest.java ---------------------------------------------------------------------- diff --git a/src/tests/junit/org/apache/tools/ant/AntClassLoaderTest.java b/src/tests/junit/org/apache/tools/ant/AntClassLoaderTest.java index 9adde4a..029d786 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 <a href="bz-62952">https://bz.apache.org/bugzilla/show_bug.cgi?id=62952</a> + */ + @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;