diff -r 4ebc2e2fb97c -r 71c04702a3d5 src/java.base/share/classes/jdk/internal/jimage/ImageReader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java Tue Sep 12 19:03:39 2017 +0200 @@ -0,0 +1,835 @@ +/* + * Copyright (c) 2014, 2016, 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.internal.jimage; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.nio.file.Files; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; + +/** + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public final class ImageReader implements AutoCloseable { + private final SharedImageReader reader; + + private volatile boolean closed; + + private ImageReader(SharedImageReader reader) { + this.reader = reader; + } + + public static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException { + Objects.requireNonNull(imagePath); + Objects.requireNonNull(byteOrder); + + return SharedImageReader.open(imagePath, byteOrder); + } + + public static ImageReader open(Path imagePath) throws IOException { + return open(imagePath, ByteOrder.nativeOrder()); + } + + @Override + public void close() throws IOException { + if (closed) { + throw new IOException("image file already closed"); + } + reader.close(this); + closed = true; + } + + private void ensureOpen() throws IOException { + if (closed) { + throw new IOException("image file closed"); + } + } + + private void requireOpen() { + if (closed) { + throw new IllegalStateException("image file closed"); + } + } + + // directory management interface + public Directory getRootDirectory() throws IOException { + ensureOpen(); + return reader.getRootDirectory(); + } + + + public Node findNode(String name) throws IOException { + ensureOpen(); + return reader.findNode(name); + } + + public byte[] getResource(Node node) throws IOException { + ensureOpen(); + return reader.getResource(node); + } + + public byte[] getResource(Resource rs) throws IOException { + ensureOpen(); + return reader.getResource(rs); + } + + public ImageHeader getHeader() { + requireOpen(); + return reader.getHeader(); + } + + public static void releaseByteBuffer(ByteBuffer buffer) { + BasicImageReader.releaseByteBuffer(buffer); + } + + public String getName() { + requireOpen(); + return reader.getName(); + } + + public ByteOrder getByteOrder() { + requireOpen(); + return reader.getByteOrder(); + } + + public Path getImagePath() { + requireOpen(); + return reader.getImagePath(); + } + + public ImageStringsReader getStrings() { + requireOpen(); + return reader.getStrings(); + } + + public ImageLocation findLocation(String mn, String rn) { + requireOpen(); + return reader.findLocation(mn, rn); + } + + public ImageLocation findLocation(String name) { + requireOpen(); + return reader.findLocation(name); + } + + public String[] getEntryNames() { + requireOpen(); + return reader.getEntryNames(); + } + + public String[] getModuleNames() { + requireOpen(); + int off = "/modules/".length(); + return reader.findNode("/modules") + .getChildren() + .stream() + .map(Node::getNameString) + .map(s -> s.substring(off, s.length())) + .toArray(String[]::new); + } + + public long[] getAttributes(int offset) { + requireOpen(); + return reader.getAttributes(offset); + } + + public String getString(int offset) { + requireOpen(); + return reader.getString(offset); + } + + public byte[] getResource(String name) { + requireOpen(); + return reader.getResource(name); + } + + public byte[] getResource(ImageLocation loc) { + requireOpen(); + return reader.getResource(loc); + } + + public ByteBuffer getResourceBuffer(ImageLocation loc) { + requireOpen(); + return reader.getResourceBuffer(loc); + } + + public InputStream getResourceStream(ImageLocation loc) { + requireOpen(); + return reader.getResourceStream(loc); + } + + private final static class SharedImageReader extends BasicImageReader { + static final int SIZE_OF_OFFSET = Integer.BYTES; + + static final Map OPEN_FILES = new HashMap<>(); + + // List of openers for this shared image. + final Set openers; + + // attributes of the .jimage file. jimage file does not contain + // attributes for the individual resources (yet). We use attributes + // of the jimage file itself (creation, modification, access times). + // Iniitalized lazily, see {@link #imageFileAttributes()}. + BasicFileAttributes imageFileAttributes; + + // directory management implementation + final HashMap nodes; + volatile Directory rootDir; + + Directory packagesDir; + Directory modulesDir; + + private SharedImageReader(Path imagePath, ByteOrder byteOrder) throws IOException { + super(imagePath, byteOrder); + this.openers = new HashSet<>(); + this.nodes = new HashMap<>(); + } + + public static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException { + Objects.requireNonNull(imagePath); + Objects.requireNonNull(byteOrder); + + synchronized (OPEN_FILES) { + SharedImageReader reader = OPEN_FILES.get(imagePath); + + if (reader == null) { + // Will fail with an IOException if wrong byteOrder. + reader = new SharedImageReader(imagePath, byteOrder); + OPEN_FILES.put(imagePath, reader); + } else if (reader.getByteOrder() != byteOrder) { + throw new IOException("\"" + reader.getName() + "\" is not an image file"); + } + + ImageReader image = new ImageReader(reader); + reader.openers.add(image); + + return image; + } + } + + public void close(ImageReader image) throws IOException { + Objects.requireNonNull(image); + + synchronized (OPEN_FILES) { + if (!openers.remove(image)) { + throw new IOException("image file already closed"); + } + + if (openers.isEmpty()) { + close(); + nodes.clear(); + rootDir = null; + + if (!OPEN_FILES.remove(this.getImagePath(), this)) { + throw new IOException("image file not found in open list"); + } + } + } + } + + void addOpener(ImageReader reader) { + synchronized (OPEN_FILES) { + openers.add(reader); + } + } + + boolean removeOpener(ImageReader reader) { + synchronized (OPEN_FILES) { + return openers.remove(reader); + } + } + + // directory management interface + Directory getRootDirectory() { + return buildRootDirectory(); + } + + /** + * Lazily build a node from a name. + */ + synchronized Node buildNode(String name) { + Node n; + boolean isPackages = name.startsWith("/packages"); + boolean isModules = !isPackages && name.startsWith("/modules"); + + if (!(isModules || isPackages)) { + return null; + } + + ImageLocation loc = findLocation(name); + + if (loc != null) { // A sub tree node + if (isPackages) { + n = handlePackages(name, loc); + } else { // modules sub tree + n = handleModulesSubTree(name, loc); + } + } else { // Asking for a resource? /modules/java.base/java/lang/Object.class + if (isModules) { + n = handleResource(name); + } else { + // Possibly ask for /packages/java.lang/java.base + // although /packages/java.base not created + n = handleModuleLink(name); + } + } + return n; + } + + synchronized Directory buildRootDirectory() { + Directory root = rootDir; // volatile read + if (root != null) { + return root; + } + + root = newDirectory(null, "/"); + root.setIsRootDir(); + + // /packages dir + packagesDir = newDirectory(root, "/packages"); + packagesDir.setIsPackagesDir(); + + // /modules dir + modulesDir = newDirectory(root, "/modules"); + modulesDir.setIsModulesDir(); + + root.setCompleted(true); + return rootDir = root; + } + + /** + * To visit sub tree resources. + */ + interface LocationVisitor { + void visit(ImageLocation loc); + } + + void visitLocation(ImageLocation loc, LocationVisitor visitor) { + byte[] offsets = getResource(loc); + ByteBuffer buffer = ByteBuffer.wrap(offsets); + buffer.order(getByteOrder()); + IntBuffer intBuffer = buffer.asIntBuffer(); + for (int i = 0; i < offsets.length / SIZE_OF_OFFSET; i++) { + int offset = intBuffer.get(i); + ImageLocation pkgLoc = getLocation(offset); + visitor.visit(pkgLoc); + } + } + + void visitPackageLocation(ImageLocation loc) { + // Retrieve package name + String pkgName = getBaseExt(loc); + // Content is array of offsets in Strings table + byte[] stringsOffsets = getResource(loc); + ByteBuffer buffer = ByteBuffer.wrap(stringsOffsets); + buffer.order(getByteOrder()); + IntBuffer intBuffer = buffer.asIntBuffer(); + // For each module, create a link node. + for (int i = 0; i < stringsOffsets.length / SIZE_OF_OFFSET; i++) { + // skip empty state, useless. + intBuffer.get(i); + i++; + int offset = intBuffer.get(i); + String moduleName = getString(offset); + Node targetNode = findNode("/modules/" + moduleName); + if (targetNode != null) { + String pkgDirName = packagesDir.getName() + "/" + pkgName; + Directory pkgDir = (Directory) nodes.get(pkgDirName); + newLinkNode(pkgDir, pkgDir.getName() + "/" + moduleName, targetNode); + } + } + } + + Node handlePackages(String name, ImageLocation loc) { + long size = loc.getUncompressedSize(); + Node n = null; + // Only possiblities are /packages, /packages/package/module + if (name.equals("/packages")) { + visitLocation(loc, (childloc) -> { + findNode(childloc.getFullName()); + }); + packagesDir.setCompleted(true); + n = packagesDir; + } else { + if (size != 0) { // children are offsets to module in StringsTable + String pkgName = getBaseExt(loc); + Directory pkgDir = newDirectory(packagesDir, packagesDir.getName() + "/" + pkgName); + visitPackageLocation(loc); + pkgDir.setCompleted(true); + n = pkgDir; + } else { // Link to module + String pkgName = loc.getParent(); + String modName = getBaseExt(loc); + Node targetNode = findNode("/modules/" + modName); + if (targetNode != null) { + String pkgDirName = packagesDir.getName() + "/" + pkgName; + Directory pkgDir = (Directory) nodes.get(pkgDirName); + Node linkNode = newLinkNode(pkgDir, pkgDir.getName() + "/" + modName, targetNode); + n = linkNode; + } + } + } + return n; + } + + // Asking for /packages/package/module although + // /packages// not yet created, need to create it + // prior to return the link to module node. + Node handleModuleLink(String name) { + // eg: unresolved /packages/package/module + // Build /packages/package node + Node ret = null; + String radical = "/packages/"; + String path = name; + if (path.startsWith(radical)) { + int start = radical.length(); + int pkgEnd = path.indexOf('/', start); + if (pkgEnd != -1) { + String pkg = path.substring(start, pkgEnd); + String pkgPath = radical + pkg; + Node n = findNode(pkgPath); + // If not found means that this is a symbolic link such as: + // /packages/java.util/java.base/java/util/Vector.class + // and will be done by a retry of the filesystem + for (Node child : n.getChildren()) { + if (child.name.equals(name)) { + ret = child; + break; + } + } + } + } + return ret; + } + + Node handleModulesSubTree(String name, ImageLocation loc) { + Node n; + assert (name.equals(loc.getFullName())); + Directory dir = makeDirectories(name); + visitLocation(loc, (childloc) -> { + String path = childloc.getFullName(); + if (path.startsWith("/modules")) { // a package + makeDirectories(path); + } else { // a resource + makeDirectories(childloc.buildName(true, true, false)); + newResource(dir, childloc); + } + }); + dir.setCompleted(true); + n = dir; + return n; + } + + Node handleResource(String name) { + Node n = null; + String locationPath = name.substring("/modules".length()); + ImageLocation resourceLoc = findLocation(locationPath); + if (resourceLoc != null) { + Directory dir = makeDirectories(resourceLoc.buildName(true, true, false)); + Resource res = newResource(dir, resourceLoc); + n = res; + } + return n; + } + + String getBaseExt(ImageLocation loc) { + String base = loc.getBase(); + String ext = loc.getExtension(); + if (ext != null && !ext.isEmpty()) { + base = base + "." + ext; + } + return base; + } + + synchronized Node findNode(String name) { + buildRootDirectory(); + Node n = nodes.get(name); + if (n == null || !n.isCompleted()) { + n = buildNode(name); + } + return n; + } + + /** + * Returns the file attributes of the image file. + */ + BasicFileAttributes imageFileAttributes() { + BasicFileAttributes attrs = imageFileAttributes; + if (attrs == null) { + try { + Path file = getImagePath(); + attrs = Files.readAttributes(file, BasicFileAttributes.class); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + imageFileAttributes = attrs; + } + return attrs; + } + + Directory newDirectory(Directory parent, String name) { + Directory dir = Directory.create(parent, name, imageFileAttributes()); + nodes.put(dir.getName(), dir); + return dir; + } + + Resource newResource(Directory parent, ImageLocation loc) { + Resource res = Resource.create(parent, loc, imageFileAttributes()); + nodes.put(res.getName(), res); + return res; + } + + LinkNode newLinkNode(Directory dir, String name, Node link) { + LinkNode linkNode = LinkNode.create(dir, name, link); + nodes.put(linkNode.getName(), linkNode); + return linkNode; + } + + Directory makeDirectories(String parent) { + Directory last = rootDir; + for (int offset = parent.indexOf('/', 1); + offset != -1; + offset = parent.indexOf('/', offset + 1)) { + String dir = parent.substring(0, offset); + last = makeDirectory(dir, last); + } + return makeDirectory(parent, last); + + } + + Directory makeDirectory(String dir, Directory last) { + Directory nextDir = (Directory) nodes.get(dir); + if (nextDir == null) { + nextDir = newDirectory(last, dir); + } + return nextDir; + } + + byte[] getResource(Node node) throws IOException { + if (node.isResource()) { + return super.getResource(node.getLocation()); + } + throw new IOException("Not a resource: " + node); + } + + byte[] getResource(Resource rs) throws IOException { + return super.getResource(rs.getLocation()); + } + } + + // jimage file does not store directory structure. We build nodes + // using the "path" strings found in the jimage file. + // Node can be a directory or a resource + public abstract static class Node { + private static final int ROOT_DIR = 0b0000_0000_0000_0001; + private static final int PACKAGES_DIR = 0b0000_0000_0000_0010; + private static final int MODULES_DIR = 0b0000_0000_0000_0100; + + private int flags; + private final String name; + private final BasicFileAttributes fileAttrs; + private boolean completed; + + protected Node(String name, BasicFileAttributes fileAttrs) { + this.name = Objects.requireNonNull(name); + this.fileAttrs = Objects.requireNonNull(fileAttrs); + } + + /** + * A node is completed when all its direct children have been built. + * + * @return + */ + public boolean isCompleted() { + return completed; + } + + public void setCompleted(boolean completed) { + this.completed = completed; + } + + public final void setIsRootDir() { + flags |= ROOT_DIR; + } + + public final boolean isRootDir() { + return (flags & ROOT_DIR) != 0; + } + + public final void setIsPackagesDir() { + flags |= PACKAGES_DIR; + } + + public final boolean isPackagesDir() { + return (flags & PACKAGES_DIR) != 0; + } + + public final void setIsModulesDir() { + flags |= MODULES_DIR; + } + + public final boolean isModulesDir() { + return (flags & MODULES_DIR) != 0; + } + + public final String getName() { + return name; + } + + public final BasicFileAttributes getFileAttributes() { + return fileAttrs; + } + + // resolve this Node (if this is a soft link, get underlying Node) + public final Node resolveLink() { + return resolveLink(false); + } + + public Node resolveLink(boolean recursive) { + return this; + } + + // is this a soft link Node? + public boolean isLink() { + return false; + } + + public boolean isDirectory() { + return false; + } + + public List getChildren() { + throw new IllegalArgumentException("not a directory: " + getNameString()); + } + + public boolean isResource() { + return false; + } + + public ImageLocation getLocation() { + throw new IllegalArgumentException("not a resource: " + getNameString()); + } + + public long size() { + return 0L; + } + + public long compressedSize() { + return 0L; + } + + public String extension() { + return null; + } + + public long contentOffset() { + return 0L; + } + + public final FileTime creationTime() { + return fileAttrs.creationTime(); + } + + public final FileTime lastAccessTime() { + return fileAttrs.lastAccessTime(); + } + + public final FileTime lastModifiedTime() { + return fileAttrs.lastModifiedTime(); + } + + public final String getNameString() { + return name; + } + + @Override + public final String toString() { + return getNameString(); + } + + @Override + public final int hashCode() { + return name.hashCode(); + } + + @Override + public final boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other instanceof Node) { + return name.equals(((Node) other).name); + } + + return false; + } + } + + // directory node - directory has full path name without '/' at end. + static final class Directory extends Node { + private final List children; + + private Directory(String name, BasicFileAttributes fileAttrs) { + super(name, fileAttrs); + children = new ArrayList<>(); + } + + static Directory create(Directory parent, String name, BasicFileAttributes fileAttrs) { + Directory d = new Directory(name, fileAttrs); + if (parent != null) { + parent.addChild(d); + } + return d; + } + + @Override + public boolean isDirectory() { + return true; + } + + @Override + public List getChildren() { + return Collections.unmodifiableList(children); + } + + void addChild(Node node) { + children.add(node); + } + + public void walk(Consumer consumer) { + consumer.accept(this); + for ( Node child : children ) { + if (child.isDirectory()) { + ((Directory)child).walk(consumer); + } else { + consumer.accept(child); + } + } + } + } + + // "resource" is .class or any other resource (compressed/uncompressed) in a jimage. + // full path of the resource is the "name" of the resource. + static class Resource extends Node { + private final ImageLocation loc; + + private Resource(ImageLocation loc, BasicFileAttributes fileAttrs) { + super(loc.getFullName(true), fileAttrs); + this.loc = loc; + } + + static Resource create(Directory parent, ImageLocation loc, BasicFileAttributes fileAttrs) { + Resource rs = new Resource(loc, fileAttrs); + parent.addChild(rs); + return rs; + } + + @Override + public boolean isCompleted() { + return true; + } + + @Override + public boolean isResource() { + return true; + } + + @Override + public ImageLocation getLocation() { + return loc; + } + + @Override + public long size() { + return loc.getUncompressedSize(); + } + + @Override + public long compressedSize() { + return loc.getCompressedSize(); + } + + @Override + public String extension() { + return loc.getExtension(); + } + + @Override + public long contentOffset() { + return loc.getContentOffset(); + } + } + + // represents a soft link to another Node + static class LinkNode extends Node { + private final Node link; + + private LinkNode(String name, Node link) { + super(name, link.getFileAttributes()); + this.link = link; + } + + static LinkNode create(Directory parent, String name, Node link) { + LinkNode ln = new LinkNode(name, link); + parent.addChild(ln); + return ln; + } + + @Override + public boolean isCompleted() { + return true; + } + + @Override + public Node resolveLink(boolean recursive) { + return (recursive && link instanceof LinkNode) ? ((LinkNode)link).resolveLink(true) : link; + } + + @Override + public boolean isLink() { + return true; + } + } +}