| /* |
| * 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<Path, SharedImageReader> OPEN_FILES = new HashMap<>(); |
| |
| // List of openers for this shared image. |
| final Set<ImageReader> 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<String, Node> 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/<pkg>/ 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<Node> 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<Node> 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<Node> getChildren() { |
| return Collections.unmodifiableList(children); |
| } |
| |
| void addChild(Node node) { |
| children.add(node); |
| } |
| |
| public void walk(Consumer<? super Node> 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; |
| } |
| } |
| } |