8144355: JDK 9 changes to ZipFileSystem to support multi-release jar files
JEP 238 Multi-Release JarFileSystem implementation Reviewed-by: alanb, psandoz, sherman
This commit is contained in:
parent
bac6aa4f18
commit
cdc0735c60
182
jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/JarFileSystem.java
Normal file
182
jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/JarFileSystem.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -155,6 +155,10 @@ class ZipFileSystem extends FileSystem {
|
||||
throw new ReadOnlyFileSystemException();
|
||||
}
|
||||
|
||||
void setReadOnly() {
|
||||
this.readOnly = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Path> getRootDirectories() {
|
||||
ArrayList<Path> pathArr = new ArrayList<>();
|
||||
@ -320,7 +324,7 @@ class ZipFileSystem extends FileSystem {
|
||||
beginRead();
|
||||
try {
|
||||
ensureOpen();
|
||||
e = getEntry0(path);
|
||||
e = getEntry(path);
|
||||
if (e == null) {
|
||||
IndexNode inode = getInode(path);
|
||||
if (inode == null)
|
||||
@ -342,7 +346,7 @@ class ZipFileSystem extends FileSystem {
|
||||
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 @@ class ZipFileSystem extends FileSystem {
|
||||
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 @@ class ZipFileSystem extends FileSystem {
|
||||
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 @@ class ZipFileSystem extends FileSystem {
|
||||
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 @@ class ZipFileSystem extends FileSystem {
|
||||
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 @@ class ZipFileSystem extends FileSystem {
|
||||
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 @@ class ZipFileSystem extends FileSystem {
|
||||
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 @@ class ZipFileSystem extends FileSystem {
|
||||
beginRead();
|
||||
try {
|
||||
ensureOpen();
|
||||
Entry e = getEntry0(path);
|
||||
Entry e = getEntry(path);
|
||||
if (forWrite) {
|
||||
checkWritable();
|
||||
if (e == null) {
|
||||
@ -855,7 +859,7 @@ class ZipFileSystem extends FileSystem {
|
||||
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 @@ class ZipFileSystem extends FileSystem {
|
||||
|
||||
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 @@ class ZipFileSystem extends FileSystem {
|
||||
//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 @@ class ZipFileSystem extends FileSystem {
|
||||
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 @@ class ZipFileSystem extends FileSystem {
|
||||
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;
|
||||
|
@ -100,7 +100,11 @@ public class ZipFileSystemProvider extends FileSystemProvider {
|
||||
}
|
||||
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 @@ public class ZipFileSystemProvider extends FileSystemProvider {
|
||||
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"))
|
||||
|
195
jdk/test/jdk/nio/zipfs/MultiReleaseJarTest.java
Normal file
195
jdk/test/jdk/nio/zipfs/MultiReleaseJarTest.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
120
jdk/test/lib/testlibrary/java/util/jar/Compiler.java
Normal file
120
jdk/test/lib/testlibrary/java/util/jar/Compiler.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
105
jdk/test/lib/testlibrary/java/util/jar/JarBuilder.java
Normal file
105
jdk/test/lib/testlibrary/java/util/jar/JarBuilder.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user