blob: 6082c21bee324de4413674963654cf53786ccd21 [file] [log] [blame]
/*
* 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;
}
}
}