| @@ -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 | |||
| ===================================== | |||
| @@ -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> | |||
| @@ -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}); | |||
| } | |||
| } | |||
| @@ -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; | |||