Path: blob/master/src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java
41159 views
/*1* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package jdk.internal.jrtfs;2526import java.io.IOException;27import java.io.UncheckedIOException;28import java.nio.file.DirectoryStream;29import java.nio.file.FileSystem;30import java.nio.file.FileSystemException;31import java.nio.file.FileSystems;32import java.nio.file.Files;33import java.nio.file.Path;34import java.nio.file.attribute.BasicFileAttributes;35import java.util.ArrayList;36import java.util.Collections;37import java.util.HashMap;38import java.util.List;39import java.util.Map;4041import jdk.internal.jimage.ImageReader.Node;4243/**44* A jrt file system built on $JAVA_HOME/modules directory ('exploded modules45* build')46*47* @implNote This class needs to maintain JDK 8 source compatibility.48*49* It is used internally in the JDK to implement jimage/jrtfs access,50* but also compiled and delivered as part of the jrtfs.jar to support access51* to the jimage file provided by the shipped JDK by tools running on JDK 8.52*/53class ExplodedImage extends SystemImage {5455private static final String MODULES = "/modules/";56private static final String PACKAGES = "/packages/";57private static final int PACKAGES_LEN = PACKAGES.length();5859private final FileSystem defaultFS;60private final String separator;61private final Map<String, PathNode> nodes = Collections.synchronizedMap(new HashMap<>());62private final BasicFileAttributes modulesDirAttrs;6364ExplodedImage(Path modulesDir) throws IOException {65defaultFS = FileSystems.getDefault();66String str = defaultFS.getSeparator();67separator = str.equals("/") ? null : str;68modulesDirAttrs = Files.readAttributes(modulesDir, BasicFileAttributes.class);69initNodes();70}7172// A Node that is backed by actual default file system Path73private final class PathNode extends Node {7475// Path in underlying default file system76private Path path;77private PathNode link;78private List<Node> children;7980PathNode(String name, Path path, BasicFileAttributes attrs) { // path81super(name, attrs);82this.path = path;83}8485PathNode(String name, Node link) { // link86super(name, link.getFileAttributes());87this.link = (PathNode)link;88}8990PathNode(String name, List<Node> children) { // dir91super(name, modulesDirAttrs);92this.children = children;93}9495@Override96public boolean isDirectory() {97return children != null ||98(link == null && getFileAttributes().isDirectory());99}100101@Override102public boolean isLink() {103return link != null;104}105106@Override107public PathNode resolveLink(boolean recursive) {108if (link == null)109return this;110return recursive && link.isLink() ? link.resolveLink(true) : link;111}112113byte[] getContent() throws IOException {114if (!getFileAttributes().isRegularFile())115throw new FileSystemException(getName() + " is not file");116return Files.readAllBytes(path);117}118119@Override120public List<Node> getChildren() {121if (!isDirectory())122throw new IllegalArgumentException("not a directory: " + getNameString());123if (children == null) {124List<Node> list = new ArrayList<>();125try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {126for (Path p : stream) {127p = explodedModulesDir.relativize(p);128String pName = MODULES + nativeSlashToFrontSlash(p.toString());129Node node = findNode(pName);130if (node != null) { // findNode may choose to hide certain files!131list.add(node);132}133}134} catch (IOException x) {135return null;136}137children = list;138}139return children;140}141142@Override143public long size() {144try {145return isDirectory() ? 0 : Files.size(path);146} catch (IOException ex) {147throw new UncheckedIOException(ex);148}149}150}151152@Override153public void close() throws IOException {154nodes.clear();155}156157@Override158public byte[] getResource(Node node) throws IOException {159return ((PathNode)node).getContent();160}161162// find Node for the given Path163@Override164public synchronized Node findNode(String str) {165Node node = findModulesNode(str);166if (node != null) {167return node;168}169// lazily created for paths like /packages/<package>/<module>/xyz170// For example /packages/java.lang/java.base/java/lang/171if (str.startsWith(PACKAGES)) {172// pkgEndIdx marks end of <package> part173int pkgEndIdx = str.indexOf('/', PACKAGES_LEN);174if (pkgEndIdx != -1) {175// modEndIdx marks end of <module> part176int modEndIdx = str.indexOf('/', pkgEndIdx + 1);177if (modEndIdx != -1) {178// make sure we have such module link!179// ie., /packages/<package>/<module> is valid180Node linkNode = nodes.get(str.substring(0, modEndIdx));181if (linkNode == null || !linkNode.isLink()) {182return null;183}184// map to "/modules/zyz" path and return that node185// For example, "/modules/java.base/java/lang" for186// "/packages/java.lang/java.base/java/lang".187String mod = MODULES + str.substring(pkgEndIdx + 1);188return findModulesNode(mod);189}190}191}192return null;193}194195// find a Node for a path that starts like "/modules/..."196Node findModulesNode(String str) {197PathNode node = nodes.get(str);198if (node != null) {199return node;200}201// lazily created "/modules/xyz/abc/" Node202// This is mapped to default file system path "<JDK_MODULES_DIR>/xyz/abc"203Path p = underlyingPath(str);204if (p != null) {205try {206BasicFileAttributes attrs = Files.readAttributes(p, BasicFileAttributes.class);207if (attrs.isRegularFile()) {208Path f = p.getFileName();209if (f.toString().startsWith("_the."))210return null;211}212node = new PathNode(str, p, attrs);213nodes.put(str, node);214return node;215} catch (IOException x) {216// does not exists or unable to determine217}218}219return null;220}221222Path underlyingPath(String str) {223if (str.startsWith(MODULES)) {224str = frontSlashToNativeSlash(str.substring("/modules".length()));225return defaultFS.getPath(explodedModulesDir.toString(), str);226}227return null;228}229230// convert "/" to platform path separator231private String frontSlashToNativeSlash(String str) {232return separator == null ? str : str.replace("/", separator);233}234235// convert platform path separator to "/"236private String nativeSlashToFrontSlash(String str) {237return separator == null ? str : str.replace(separator, "/");238}239240// convert "/"s to "."s241private String slashesToDots(String str) {242return str.replace(separator != null ? separator : "/", ".");243}244245// initialize file system Nodes246private void initNodes() throws IOException {247// same package prefix may exist in mutliple modules. This Map248// is filled by walking "jdk modules" directory recursively!249Map<String, List<String>> packageToModules = new HashMap<>();250try (DirectoryStream<Path> stream = Files.newDirectoryStream(explodedModulesDir)) {251for (Path module : stream) {252if (Files.isDirectory(module)) {253String moduleName = module.getFileName().toString();254// make sure "/modules/<moduleName>" is created255findModulesNode(MODULES + moduleName);256Files.walk(module).filter(Files::isDirectory).forEach((p) -> {257p = module.relativize(p);258String pkgName = slashesToDots(p.toString());259// skip META-INFO and empty strings260if (!pkgName.isEmpty() && !pkgName.startsWith("META-INF")) {261List<String> moduleNames = packageToModules.get(pkgName);262if (moduleNames == null) {263moduleNames = new ArrayList<>();264packageToModules.put(pkgName, moduleNames);265}266moduleNames.add(moduleName);267}268});269}270}271}272// create "/modules" directory273// "nodes" map contains only /modules/<foo> nodes only so far and so add all as children of /modules274PathNode modulesDir = new PathNode("/modules", new ArrayList<>(nodes.values()));275nodes.put(modulesDir.getName(), modulesDir);276277// create children under "/packages"278List<Node> packagesChildren = new ArrayList<>(packageToModules.size());279for (Map.Entry<String, List<String>> entry : packageToModules.entrySet()) {280String pkgName = entry.getKey();281List<String> moduleNameList = entry.getValue();282List<Node> moduleLinkNodes = new ArrayList<>(moduleNameList.size());283for (String moduleName : moduleNameList) {284Node moduleNode = findModulesNode(MODULES + moduleName);285PathNode linkNode = new PathNode(PACKAGES + pkgName + "/" + moduleName, moduleNode);286nodes.put(linkNode.getName(), linkNode);287moduleLinkNodes.add(linkNode);288}289PathNode pkgDir = new PathNode(PACKAGES + pkgName, moduleLinkNodes);290nodes.put(pkgDir.getName(), pkgDir);291packagesChildren.add(pkgDir);292}293// "/packages" dir294PathNode packagesDir = new PathNode("/packages", packagesChildren);295nodes.put(packagesDir.getName(), packagesDir);296297// finally "/" dir!298List<Node> rootChildren = new ArrayList<>();299rootChildren.add(packagesDir);300rootChildren.add(modulesDir);301PathNode root = new PathNode("/", rootChildren);302nodes.put(root.getName(), root);303}304}305306307