8144355: JDK 9 changes to ZipFileSystem to support multi-release jar files
Summary: JEP 238 Multi-Release JarFileSystem implementation
Reviewed-by: alanb, psandoz, sherman
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/JarFileSystem.java Wed Dec 30 16:15:21 2015 +0000
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nio.zipfs;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+/**
+ * Adds aliasing to ZipFileSystem to support multi-release jar files. An alias map
+ * is created by {@link JarFileSystem#createVersionedLinks(int)}. The map is then
+ * consulted when an entry is looked up in {@link JarFileSystem#getEntry(byte[])}
+ * to determine if the entry has a corresponding versioned entry. If so, the
+ * versioned entry is returned.
+ *
+ * @author Steve Drach
+ */
+
+class JarFileSystem extends ZipFileSystem {
+ private Function<byte[],byte[]> lookup;
+
+ @Override
+ Entry getEntry(byte[] path) throws IOException {
+ // check for an alias to a versioned entry
+ byte[] versionedPath = lookup.apply(path);
+ return versionedPath == null ? super.getEntry(path) : super.getEntry(versionedPath);
+ }
+
+ JarFileSystem(ZipFileSystemProvider provider, Path zfpath, Map<String,?> env)
+ throws IOException {
+ super(provider, zfpath, env);
+ lookup = path -> path; // lookup needs to be set before isMultiReleaseJar is called
+ // because it eventually calls getEntry
+ if (isMultiReleaseJar()) {
+ int version;
+ Object o = env.get("multi-release");
+ if (o instanceof String) {
+ String s = (String)o;
+ if (s.equals("runtime")) {
+ version = sun.misc.Version.jdkMajorVersion(); // fixme waiting for jdk.util.Version
+ } else {
+ version = Integer.parseInt(s);
+ }
+ } else if (o instanceof Integer) {
+ version = (Integer)o;
+ } else if (false /*o instanceof Version*/) { // fixme waiting for jdk.util.Version
+// version = ((Version)o).major();
+ } else {
+ throw new IllegalArgumentException("env parameter must be String, Integer, "
+ + "or Version");
+ }
+ lookup = createVersionedLinks(version < 0 ? 0 : version);
+ setReadOnly();
+ }
+ }
+
+ private boolean isMultiReleaseJar() {
+ try (InputStream is = newInputStream(getBytes("META-INF/MANIFEST.MF"))) {
+ return (new Manifest(is)).getMainAttributes()
+ .containsKey(new Attributes.Name("Multi-Release"));
+ // fixme change line above after JarFile integration to contain Attributes.Name.MULTI_RELEASE
+ } catch (IOException x) {
+ return false;
+ }
+ }
+
+ /**
+ * create a map of aliases for versioned entries, for example:
+ * version/PackagePrivate.class -> META-INF/versions/9/version/PackagePrivate.class
+ * version/PackagePrivate.java -> META-INF/versions/9/version/PackagePrivate.java
+ * version/Version.class -> META-INF/versions/10/version/Version.class
+ * version/Version.java -> META-INF/versions/10/version/Version.java
+ *
+ * then wrap the map in a function that getEntry can use to override root
+ * entry lookup for entries that have corresponding versioned entries
+ */
+ private Function<byte[],byte[]> createVersionedLinks(int version) {
+ HashMap<IndexNode,byte[]> aliasMap = new HashMap<>();
+ getVersionMap(version, getInode(getBytes("META-INF/versions"))).values()
+ .forEach(versionNode -> { // for each META-INF/versions/{n} directory
+ // put all the leaf inodes, i.e. entries, into the alias map
+ // possibly shadowing lower versioned entries
+ walk(versionNode, entryNode -> {
+ byte[] rootName = getRootName(versionNode, entryNode);
+ if (rootName != null) {
+ IndexNode rootNode = getInode(rootName);
+ if (rootNode == null) { // no matching root node, make a virtual one
+ rootNode = IndexNode.keyOf(rootName);
+ }
+ aliasMap.put(rootNode, entryNode.name);
+ }
+ });
+ });
+ return path -> aliasMap.get(IndexNode.keyOf(path));
+ }
+
+ /**
+ * create a sorted version map of version -> inode, for inodes <= max version
+ * 9 -> META-INF/versions/9
+ * 10 -> META-INF/versions/10
+ */
+ private TreeMap<Integer, IndexNode> getVersionMap(int version, IndexNode metaInfVersions) {
+ TreeMap<Integer,IndexNode> map = new TreeMap<>();
+ IndexNode child = metaInfVersions.child;
+ while (child != null) {
+ Integer key = getVersion(child.name, metaInfVersions.name.length);
+ if (key != null && key <= version) {
+ map.put(key, child);
+ }
+ child = child.sibling;
+ }
+ return map;
+ }
+
+ /**
+ * extract the integer version number -- META-INF/versions/9 returns 9
+ */
+ private Integer getVersion(byte[] name, int offset) {
+ try {
+ return Integer.parseInt(getString(Arrays.copyOfRange(name, offset, name.length-1)));
+ } catch (NumberFormatException x) {
+ // ignore this even though it might indicate issues with the JAR structure
+ return null;
+ }
+ }
+
+ /**
+ * walk the IndexNode tree processing all leaf nodes
+ */
+ private void walk(IndexNode inode, Consumer<IndexNode> process) {
+ if (inode == null) return;
+ if (inode.isDir()) {
+ walk(inode.child, process);
+ } else {
+ process.accept(inode);
+ walk(inode.sibling, process);
+ }
+ }
+
+ /**
+ * extract the root name from a versioned entry name
+ * given inode for META-INF/versions/9/foo/bar.class
+ * and prefix META-INF/versions/9/
+ * returns foo/bar.class
+ */
+ private byte[] getRootName(IndexNode prefix, IndexNode inode) {
+ int offset = prefix.name.length;
+ byte[] fullName = inode.name;
+ return Arrays.copyOfRange(fullName, offset, fullName.length);
+ }
+}
--- a/jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java Mon Dec 28 19:03:18 2015 -0800
+++ b/jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java Wed Dec 30 16:15:21 2015 +0000
@@ -155,6 +155,10 @@
throw new ReadOnlyFileSystemException();
}
+ void setReadOnly() {
+ this.readOnly = true;
+ }
+
@Override
public Iterable<Path> getRootDirectories() {
ArrayList<Path> pathArr = new ArrayList<>();
@@ -320,7 +324,7 @@
beginRead();
try {
ensureOpen();
- e = getEntry0(path);
+ e = getEntry(path);
if (e == null) {
IndexNode inode = getInode(path);
if (inode == null)
@@ -342,7 +346,7 @@
beginWrite();
try {
ensureOpen();
- Entry e = getEntry0(path); // ensureOpen checked
+ Entry e = getEntry(path); // ensureOpen checked
if (e == null)
throw new NoSuchFileException(getString(path));
if (e.type == Entry.CEN)
@@ -445,7 +449,7 @@
beginWrite();
try {
ensureOpen();
- Entry eSrc = getEntry0(src); // ensureOpen checked
+ Entry eSrc = getEntry(src); // ensureOpen checked
if (eSrc == null)
throw new NoSuchFileException(getString(src));
if (eSrc.isDir()) { // spec says to create dst dir
@@ -460,7 +464,7 @@
else if (opt == COPY_ATTRIBUTES)
hasCopyAttrs = true;
}
- Entry eDst = getEntry0(dst);
+ Entry eDst = getEntry(dst);
if (eDst != null) {
if (!hasReplace)
throw new FileAlreadyExistsException(getString(dst));
@@ -521,7 +525,7 @@
beginRead(); // only need a readlock, the "update()" will
try { // try to obtain a writelock when the os is
ensureOpen(); // being closed.
- Entry e = getEntry0(path);
+ Entry e = getEntry(path);
if (e != null) {
if (e.isDir() || hasCreateNew)
throw new FileAlreadyExistsException(getString(path));
@@ -550,7 +554,7 @@
beginRead();
try {
ensureOpen();
- Entry e = getEntry0(path);
+ Entry e = getEntry(path);
if (e == null)
throw new NoSuchFileException(getString(path));
if (e.isDir())
@@ -592,7 +596,7 @@
newOutputStream(path, options.toArray(new OpenOption[0])));
long leftover = 0;
if (options.contains(StandardOpenOption.APPEND)) {
- Entry e = getEntry0(path);
+ Entry e = getEntry(path);
if (e != null && e.size >= 0)
leftover = e.size;
}
@@ -644,7 +648,7 @@
beginRead();
try {
ensureOpen();
- Entry e = getEntry0(path);
+ Entry e = getEntry(path);
if (e == null || e.isDir())
throw new NoSuchFileException(getString(path));
final ReadableByteChannel rbc =
@@ -714,7 +718,7 @@
beginRead();
try {
ensureOpen();
- Entry e = getEntry0(path);
+ Entry e = getEntry(path);
if (forWrite) {
checkWritable();
if (e == null) {
@@ -855,7 +859,7 @@
private Path getTempPathForEntry(byte[] path) throws IOException {
Path tmpPath = createTempFileInSameDirectoryAs(zfpath);
if (path != null) {
- Entry e = getEntry0(path);
+ Entry e = getEntry(path);
if (e != null) {
try (InputStream is = newInputStream(path)) {
Files.copy(is, tmpPath, REPLACE_EXISTING);
@@ -939,7 +943,7 @@
private long getDataPos(Entry e) throws IOException {
if (e.locoff == -1) {
- Entry e2 = getEntry0(e.name);
+ Entry e2 = getEntry(e.name);
if (e2 == null)
throw new ZipException("invalid loc for entry <" + e.name + ">");
e.locoff = e2.locoff;
@@ -1325,7 +1329,7 @@
//System.out.printf("->sync(%s) done!%n", toString());
}
- private IndexNode getInode(byte[] path) {
+ IndexNode getInode(byte[] path) {
if (path == null)
throw new NullPointerException("path");
IndexNode key = IndexNode.keyOf(path);
@@ -1340,7 +1344,7 @@
return inode;
}
- private Entry getEntry0(byte[] path) throws IOException {
+ Entry getEntry(byte[] path) throws IOException {
IndexNode inode = getInode(path);
if (inode instanceof Entry)
return (Entry)inode;
@@ -2096,7 +2100,7 @@
pos += (LOCHDR + nlen + elen);
if ((flag & FLAG_DATADESCR) != 0) {
// Data Descriptor
- Entry e = zipfs.getEntry0(name); // get the size/csize from cen
+ Entry e = zipfs.getEntry(name); // get the size/csize from cen
if (e == null)
throw new ZipException("loc: name not found in cen");
size = e.size;
--- a/jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystemProvider.java Mon Dec 28 19:03:18 2015 -0800
+++ b/jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystemProvider.java Wed Dec 30 16:15:21 2015 +0000
@@ -100,7 +100,11 @@
}
ZipFileSystem zipfs = null;
try {
- zipfs = new ZipFileSystem(this, path, env);
+ if (env.containsKey("multi-release")) {
+ zipfs = new JarFileSystem(this, path, env);
+ } else {
+ zipfs = new ZipFileSystem(this, path, env);
+ }
} catch (ZipException ze) {
String pname = path.toString();
if (pname.endsWith(".zip") || pname.endsWith(".jar"))
@@ -124,8 +128,14 @@
throw new UnsupportedOperationException();
}
ensureFile(path);
- try {
- return new ZipFileSystem(this, path, env);
+ try {
+ ZipFileSystem zipfs;
+ if (env.containsKey("multi-release")) {
+ zipfs = new JarFileSystem(this, path, env);
+ } else {
+ zipfs = new ZipFileSystem(this, path, env);
+ }
+ return zipfs;
} catch (ZipException ze) {
String pname = path.toString();
if (pname.endsWith(".zip") || pname.endsWith(".jar"))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/jdk/nio/zipfs/MultiReleaseJarTest.java Wed Dec 30 16:15:21 2015 +0000
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8144355
+ * @summary Test aliasing additions to ZipFileSystem for multi-release jar files
+ * @library /lib/testlibrary/java/util/jar
+ * @build Compiler JarBuilder CreateMultiReleaseTestJars
+ * @run testng MultiReleaseJarTest
+ */
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.net.URI;
+import java.nio.file.*;
+import java.util.HashMap;
+import java.util.Map;
+
+import static sun.misc.Version.jdkMajorVersion;
+
+import org.testng.Assert;
+import org.testng.annotations.*;
+
+public class MultiReleaseJarTest {
+ final private String userdir = System.getProperty("user.dir",".");
+ final private Map<String,String> stringEnv = new HashMap<>();
+ final private Map<String,Integer> integerEnv = new HashMap<>();
+ final private String className = "version.Version";
+ final private MethodType mt = MethodType.methodType(int.class);
+
+ private String entryName;
+ private URI uvuri;
+ private URI mruri;
+ private URI smruri;
+
+ @BeforeClass
+ public void initialize() throws Exception {
+ CreateMultiReleaseTestJars creator = new CreateMultiReleaseTestJars();
+ creator.compileEntries();
+ creator.buildUnversionedJar();
+ creator.buildMultiReleaseJar();
+ creator.buildShortMultiReleaseJar();
+ String ssp = Paths.get(userdir, "unversioned.jar").toUri().toString();
+ uvuri = new URI("jar", ssp , null);
+ ssp = Paths.get(userdir, "multi-release.jar").toUri().toString();
+ mruri = new URI("jar", ssp, null);
+ ssp = Paths.get(userdir, "short-multi-release.jar").toUri().toString();
+ smruri = new URI("jar", ssp, null);
+ entryName = className.replace('.', '/') + ".class";
+ }
+
+ public void close() throws IOException {
+ Files.delete(Paths.get(userdir, "unversioned.jar"));
+ Files.delete(Paths.get(userdir, "multi-release.jar"));
+ Files.delete(Paths.get(userdir, "short-multi-release.jar"));
+ }
+
+ @DataProvider(name="strings")
+ public Object[][] createStrings() {
+ return new Object[][]{
+ {"runtime", jdkMajorVersion()},
+ {"-20", 8},
+ {"0", 8},
+ {"8", 8},
+ {"9", 9},
+ {"10", 10},
+ {"11", 10},
+ {"50", 10}
+ };
+ }
+
+ @DataProvider(name="integers")
+ public Object[][] createIntegers() {
+ return new Object[][] {
+ {new Integer(-5), 8},
+ {new Integer(0), 8},
+ {new Integer(8), 8},
+ {new Integer(9), 9},
+ {new Integer(10), 10},
+ {new Integer(11), 10},
+ {new Integer(100), 10}
+ };
+ }
+
+ // Not the best test but all I can do since ZipFileSystem and JarFileSystem
+ // are not public, so I can't use (fs instanceof ...)
+ @Test
+ public void testNewFileSystem() throws Exception {
+ Map<String,String> env = new HashMap<>();
+ // no configuration, treat multi-release jar as unversioned
+ try (FileSystem fs = FileSystems.newFileSystem(mruri, env)) {
+ Assert.assertTrue(readAndCompare(fs, 8));
+ }
+ env.put("multi-release", "runtime");
+ // a configuration and jar file is multi-release
+ try (FileSystem fs = FileSystems.newFileSystem(mruri, env)) {
+ Assert.assertTrue(readAndCompare(fs, jdkMajorVersion()));
+ }
+ // a configuration but jar file is unversioned
+ try (FileSystem fs = FileSystems.newFileSystem(uvuri, env)) {
+ Assert.assertTrue(readAndCompare(fs, 8));
+ }
+ }
+
+ private boolean readAndCompare(FileSystem fs, int expected) throws IOException {
+ Path path = fs.getPath("version/Version.java");
+ String src = new String(Files.readAllBytes(path));
+ return src.contains("return " + expected);
+ }
+
+ @Test(dataProvider="strings")
+ public void testStrings(String value, int expected) throws Throwable {
+ stringEnv.put("multi-release", value);
+ runTest(stringEnv, expected);
+ }
+
+ @Test(dataProvider="integers")
+ public void testIntegers(Integer value, int expected) throws Throwable {
+ integerEnv.put("multi-release", value);
+ runTest(integerEnv, expected);
+ }
+
+ @Test
+ public void testShortJar() throws Throwable {
+ integerEnv.put("multi-release", Integer.valueOf(10));
+ runTest(smruri, integerEnv, 10);
+ integerEnv.put("multi-release", Integer.valueOf(9));
+ runTest(smruri, integerEnv, 8);
+ }
+
+ private void runTest(Map<String,?> env, int expected) throws Throwable {
+ runTest(mruri, env, expected);
+ }
+
+ private void runTest(URI uri, Map<String,?> env, int expected) throws Throwable {
+ try (FileSystem fs = FileSystems.newFileSystem(uri, env)) {
+ Path version = fs.getPath(entryName);
+ byte [] bytes = Files.readAllBytes(version);
+ Class<?> vcls = (new ByteArrayClassLoader(fs)).defineClass(className, bytes);
+ MethodHandle mh = MethodHandles.lookup().findVirtual(vcls, "getVersion", mt);
+ Assert.assertEquals((int)mh.invoke(vcls.newInstance()), expected);
+ }
+ }
+
+ private static class ByteArrayClassLoader extends ClassLoader {
+ final private FileSystem fs;
+
+ ByteArrayClassLoader(FileSystem fs) {
+ super(null);
+ this.fs = fs;
+ }
+
+ @Override
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ try {
+ return super.loadClass(name);
+ } catch (ClassNotFoundException x) {}
+ Path cls = fs.getPath(name.replace('.', '/') + ".class");
+ try {
+ byte[] bytes = Files.readAllBytes(cls);
+ return defineClass(name, bytes);
+ } catch (IOException x) {
+ throw new ClassNotFoundException(x.getMessage());
+ }
+ }
+
+ public Class<?> defineClass(String name, byte[] bytes) throws ClassNotFoundException {
+ if (bytes == null) throw new ClassNotFoundException("No bytes for " + name);
+ return defineClass(name, bytes, 0, bytes.length);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/lib/testlibrary/java/util/jar/Compiler.java Wed Dec 30 16:15:21 2015 +0000
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import javax.tools.*;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+class Compiler {
+ final private Map<String,String> input;
+ private List<String> options;
+
+ Compiler(Map<String,String> input) {
+ this.input = input;
+ }
+
+ Compiler setRelease(int release) {
+ // Setting the -release option does not work for some reason
+ // so do it the old fashioned way
+ // options = Arrays.asList("-release", String.valueOf(release));
+ String target = String.valueOf(release);
+ options = Arrays.asList("-source", target, "-target", target);
+ return this;
+ }
+
+ Map<String,byte[]> compile() {
+ List<SourceFileObject> cunits = createCompilationUnits();
+ Map<String,ClassFileObject> cfos = createClassFileObjects();
+ JavaCompiler jc = ToolProvider.getSystemJavaCompiler();
+ JavaFileManager jfm = new CustomFileManager(jc.getStandardFileManager(null, null, null), cfos);
+ jc.getTask(null, jfm, null, options, null, cunits).call();
+ return createOutput(cfos);
+ }
+
+ private List<SourceFileObject> createCompilationUnits() {
+ return input.entrySet().stream()
+ .map(e -> new SourceFileObject(e.getKey(), e.getValue())).collect(Collectors.toList());
+ }
+
+ private Map<String,ClassFileObject> createClassFileObjects() {
+ return input.keySet().stream()
+ .collect(Collectors.toMap(k -> k, k -> new ClassFileObject(k)));
+ }
+
+ private Map<String,byte[]> createOutput(Map<String,ClassFileObject> cfos) {
+ return cfos.keySet().stream().collect(Collectors.toMap(k -> k, k -> cfos.get(k).getBytes()));
+ }
+
+ private static class SourceFileObject extends SimpleJavaFileObject {
+ private final String source;
+
+ SourceFileObject(String name, String source) {
+ super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
+ this.source = source;
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+ return source;
+ }
+ }
+
+ private static class ClassFileObject extends SimpleJavaFileObject {
+ private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ ClassFileObject(String className) {
+ super(URI.create(className), Kind.CLASS);
+ }
+
+ @Override
+ public OutputStream openOutputStream() throws IOException {
+ return baos;
+ }
+
+ public byte[] getBytes() {
+ return baos.toByteArray();
+ }
+ }
+
+ private static class CustomFileManager extends ForwardingJavaFileManager<JavaFileManager> {
+ private final Map<String,ClassFileObject> cfos;
+
+ CustomFileManager(JavaFileManager jfm, Map<String,ClassFileObject> cfos) {
+ super(jfm);
+ this.cfos = cfos;
+ }
+
+ @Override
+ public JavaFileObject getJavaFileForOutput(JavaFileManager.Location loc, String name,
+ JavaFileObject.Kind kind, FileObject sibling) throws IOException {
+ ClassFileObject cfo = cfos.get(name);
+ return cfo;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/lib/testlibrary/java/util/jar/CreateMultiReleaseTestJars.java Wed Dec 30 16:15:21 2015 +0000
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+
+public class CreateMultiReleaseTestJars {
+ final private String main =
+ "package version;\n\n"
+ + "public class Main {\n"
+ + " public static void main(String[] args) {\n"
+ + " Version v = new Version();\n"
+ + " System.out.println(\"I am running on version \" + v.getVersion());\n"
+ + " }\n"
+ + "}\n";
+ final private String java8 =
+ "package version;\n\n"
+ + "public class Version {\n"
+ + " public int getVersion() {\n"
+ + " return 8;\n"
+ + " }\n"
+ + "}\n";
+ final private String java9 =
+ "package version;\n\n"
+ + "public class Version {\n"
+ + " public int getVersion() {\n"
+ + " int version = (new PackagePrivate()).getVersion();\n"
+ + " if (version == 9) return 9;\n" // strange I know, but easy to test
+ + " return version;\n"
+ + " }\n"
+ + "}\n";
+ final private String ppjava9 =
+ "package version;\n\n"
+ + "class PackagePrivate {\n"
+ + " int getVersion() {\n"
+ + " return 9;\n"
+ + " }\n"
+ + "}\n";
+ final private String java10 = java8.replace("8", "10");
+ final String readme8 = "This is the root readme file";
+ final String readme9 = "This is the version nine readme file";
+ final String readme10 = "This is the version ten readme file";
+ private Map<String,byte[]> rootClasses;
+ private Map<String,byte[]> version9Classes;
+ private Map<String,byte[]> version10Classes;
+
+ public void buildUnversionedJar() throws IOException {
+ JarBuilder jb = new JarBuilder("unversioned.jar");
+ jb.addEntry("README", readme8.getBytes());
+ jb.addEntry("version/Main.java", main.getBytes());
+ jb.addEntry("version/Main.class", rootClasses.get("version.Main"));
+ jb.addEntry("version/Version.java", java8.getBytes());
+ jb.addEntry("version/Version.class", rootClasses.get("version.Version"));
+ jb.build();
+ }
+
+ public void buildMultiReleaseJar() throws IOException {
+ JarBuilder jb = new JarBuilder("multi-release.jar");
+ jb.addAttribute("Multi-Release", "true");
+ jb.addEntry("README", readme8.getBytes());
+ jb.addEntry("version/Main.java", main.getBytes());
+ jb.addEntry("version/Main.class", rootClasses.get("version.Main"));
+ jb.addEntry("version/Version.java", java8.getBytes());
+ jb.addEntry("version/Version.class", rootClasses.get("version.Version"));
+ jb.addEntry("META-INF/versions/9/README", readme9.getBytes());
+ jb.addEntry("META-INF/versions/9/version/Version.java", java9.getBytes());
+ jb.addEntry("META-INF/versions/9/version/PackagePrivate.java", ppjava9.getBytes());
+ jb.addEntry("META-INF/versions/9/version/Version.class", version9Classes.get("version.Version"));
+ jb.addEntry("META-INF/versions/9/version/PackagePrivate.class", version9Classes.get("version.PackagePrivate"));
+ jb.addEntry("META-INF/versions/10/README", readme10.getBytes());
+ jb.addEntry("META-INF/versions/10/version/Version.java", java10.getBytes());
+ jb.addEntry("META-INF/versions/10/version/Version.class", version10Classes.get("version.Version"));
+ jb.build();
+ }
+
+ public void buildShortMultiReleaseJar() throws IOException {
+ JarBuilder jb = new JarBuilder("short-multi-release.jar");
+ jb.addAttribute("Multi-Release", "true");
+ jb.addEntry("README", readme8.getBytes());
+ jb.addEntry("version/Main.java", main.getBytes());
+ jb.addEntry("version/Main.class", rootClasses.get("version.Main"));
+ jb.addEntry("version/Version.java", java8.getBytes());
+ jb.addEntry("version/Version.class", rootClasses.get("version.Version"));
+ jb.addEntry("META-INF/versions/9/README", readme9.getBytes());
+ jb.addEntry("META-INF/versions/9/version/Version.java", java9.getBytes());
+ jb.addEntry("META-INF/versions/9/version/PackagePrivate.java", ppjava9.getBytes());
+ // no entry for META-INF/versions/9/version/Version.class
+ jb.addEntry("META-INF/versions/9/version/PackagePrivate.class", version9Classes.get("version.PackagePrivate"));
+ jb.addEntry("META-INF/versions/10/README", readme10.getBytes());
+ jb.addEntry("META-INF/versions/10/version/Version.java", java10.getBytes());
+ jb.addEntry("META-INF/versions/10/version/Version.class", version10Classes.get("version.Version"));
+ jb.build();
+ }
+
+ public void buildSignedMultiReleaseJar() throws Exception {
+ String testsrc = System.getProperty("test.src",".");
+ String testdir = findTestDir(testsrc);
+ String keystore = testdir + "/sun/security/tools/jarsigner/JarSigning.keystore";
+ String[] jsArgs = {
+ "-keystore", keystore,
+ "-storepass", "bbbbbb",
+ "-signedJar", "signed-multi-release.jar",
+ "multi-release.jar", "b"
+ };
+ sun.security.tools.jarsigner.Main.main(jsArgs);
+
+ }
+
+ String findTestDir(String dir) throws IOException {
+ Path path = Paths.get(dir).toAbsolutePath();
+ while (path != null && !path.endsWith("test")) {
+ path = path.getParent();
+ }
+ if (path == null) {
+ throw new IllegalArgumentException(dir + " is not in a test directory");
+ }
+ if (!Files.isDirectory(path)) {
+ throw new IOException(path.toString() + " is not a directory");
+ }
+ return path.toString();
+ }
+
+ void compileEntries() {
+ Map<String,String> input = new HashMap<>();
+ input.put("version.Main", main);
+ input.put("version.Version", java8);
+ rootClasses = (new Compiler(input)).setRelease(8).compile();
+ input.clear();
+ input.put("version.Version", java9);
+ input.put("version.PackagePrivate", ppjava9);
+ version9Classes = (new Compiler(input)).setRelease(9).compile();
+ input.clear();
+ input.put("version.Version", java10);
+ version10Classes = (new Compiler(input)).setRelease(9).compile(); // fixme in JDK 10
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/lib/testlibrary/java/util/jar/JarBuilder.java Wed Dec 30 16:15:21 2015 +0000
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+public class JarBuilder {
+ final private String name;
+ final private Attributes attributes = new Attributes();
+ final private List<Entry> entries = new ArrayList<>();
+
+ public JarBuilder(String name) {
+ this.name = name;
+ attributes.putValue("Manifest-Version", "1.0");
+ attributes.putValue("Created-By", "1.9.0-internal (Oracle Corporation)");
+ }
+
+ public JarBuilder addAttribute(String name, String value) {
+ attributes.putValue(name, value);
+ return this;
+ }
+
+ public JarBuilder addEntry(String name, byte[] bytes) {
+ entries.add(new Entry(name, bytes));
+ return this;
+ }
+
+ public void build() throws IOException {
+ try (OutputStream os = Files.newOutputStream(Paths.get(name));
+ JarOutputStream jos = new JarOutputStream(os)) {
+ JarEntry me = new JarEntry("META-INF/MANIFEST.MF");
+ jos.putNextEntry(me);
+ Manifest manifest = new Manifest();
+ manifest.getMainAttributes().putAll(attributes);
+ manifest.write(jos);
+ jos.closeEntry();
+ entries.forEach(e -> {
+ JarEntry je = new JarEntry(e.name);
+ try {
+ jos.putNextEntry(je);
+ jos.write(e.bytes);
+ jos.closeEntry();
+ } catch (IOException iox) {
+ throw new RuntimeException(iox);
+ }
+ });
+ } catch (RuntimeException x) {
+ Throwable t = x.getCause();
+ if (t instanceof IOException) {
+ IOException iox = (IOException)t;
+ throw iox;
+ }
+ throw x;
+ }
+ }
+
+ private static class Entry {
+ String name;
+ byte[] bytes;
+
+ Entry(String name, byte[] bytes) {
+ this.name = name;
+ this.bytes = bytes;
+ }
+ }
+
+ public static void main(String[] args) throws IOException {
+ JarBuilder jb = new JarBuilder("version.jar");
+ jb.addAttribute("Multi-Release", "true");
+ String s = "something to say";
+ byte[] bytes = s.getBytes();
+ jb.addEntry("version/Version.class", bytes);
+ jb.addEntry("README", bytes);
+ jb.addEntry("version/Version.java", bytes);
+ jb.build();
+ }
+}