<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>
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;
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;
+ }
}
/**
+ 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;
} else {
if (jarFile == null) {
if (file.exists()) {
- jarFile = new JarFile(file);
+ jarFile = newJarFile(file);
jarFiles.put(file, jarFile);
} else {
return null;
log(msg, Project.MSG_WARN);
return null;
}
- jarFile = new JarFile(file);
+ jarFile = newJarFile(file);
jarFiles.put(file, jarFile);
} else {
return null;
}
}
+ /**
+ *
+ * @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});
+ }
}
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;
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;