src/java.base/share/classes/jdk/internal/jimage/ImageReader.java
changeset 47216 71c04702a3d5
parent 43804 96fd6bf9c69b
--- /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<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;
+        }
+    }
+}