/*
 * 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;

Implementation Note: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.
/** * @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.
/** * 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.
/** * 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.
/** * 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.
Returns:
/** * 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; } } }