bz-62952 Make AntClassLoader multi-release jar aware when it deals with java.util...
authorJaikiran Pai <jaikiran@apache.org>
Wed, 5 Dec 2018 12:56:14 +0000 (18:26 +0530)
committerJaikiran Pai <jaikiran@apache.org>
Wed, 5 Dec 2018 12:57:45 +0000 (18:27 +0530)
WHATSNEW
src/etc/testcases/core/antclassloader.xml
src/main/org/apache/tools/ant/AntClassLoader.java
src/tests/junit/org/apache/tools/ant/AntClassLoaderTest.java

index de54711..a74856e 100644 (file)
--- 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
 =====================================
 
index 045428d..db6f0ff 100644 (file)
@@ -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>
index 3ab7c62..59ee175 100644 (file)
@@ -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});
+    }
 }
index 9adde4a..029d786 100644 (file)
@@ -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;