jdk/src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java
changeset 37365 9cc4eb4d7491
parent 36511 9d0388c6b336
child 39155 61d7981116d9
equal deleted inserted replaced
37364:80be215c8c51 37365:9cc4eb4d7491
       
     1 /*
       
     2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 package jdk.internal.jrtfs;
       
    26 
       
    27 import java.io.IOException;
       
    28 import java.nio.file.DirectoryStream;
       
    29 import java.nio.file.FileSystem;
       
    30 import java.nio.file.FileSystemException;
       
    31 import java.nio.file.FileSystems;
       
    32 import java.nio.file.Files;
       
    33 import java.nio.file.Path;
       
    34 import java.nio.file.attribute.BasicFileAttributes;
       
    35 import java.util.ArrayList;
       
    36 import java.util.Collections;
       
    37 import java.util.HashMap;
       
    38 import java.util.List;
       
    39 import java.util.Map;
       
    40 
       
    41 import jdk.internal.jimage.ImageReader.Node;
       
    42 
       
    43 /**
       
    44  * A jrt file system built on $JAVA_HOME/modules directory ('exploded modules
       
    45  * 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 access
       
    51  * to the jimage file provided by the shipped JDK by tools running on JDK 8.
       
    52  */
       
    53 class ExplodedImage extends SystemImage {
       
    54 
       
    55     private static final String MODULES = "/modules/";
       
    56     private static final String PACKAGES = "/packages/";
       
    57     private static final int PACKAGES_LEN = PACKAGES.length();
       
    58 
       
    59     private final FileSystem defaultFS;
       
    60     private final String separator;
       
    61     private final Map<String, PathNode> nodes = Collections.synchronizedMap(new HashMap<>());
       
    62     private final BasicFileAttributes modulesDirAttrs;
       
    63 
       
    64     ExplodedImage(Path modulesDir) throws IOException {
       
    65         defaultFS = FileSystems.getDefault();
       
    66         String str = defaultFS.getSeparator();
       
    67         separator = str.equals("/") ? null : str;
       
    68         modulesDirAttrs = Files.readAttributes(modulesDir, BasicFileAttributes.class);
       
    69         initNodes();
       
    70     }
       
    71 
       
    72     // A Node that is backed by actual default file system Path
       
    73     private final class PathNode extends Node {
       
    74 
       
    75         // Path in underlying default file system
       
    76         private Path path;
       
    77         private PathNode link;
       
    78         private List<Node> children;
       
    79 
       
    80         PathNode(String name, Path path, BasicFileAttributes attrs) {  // path
       
    81             super(name, attrs);
       
    82             this.path = path;
       
    83         }
       
    84 
       
    85         PathNode(String name, Node link) {              // link
       
    86             super(name, link.getFileAttributes());
       
    87             this.link = (PathNode)link;
       
    88         }
       
    89 
       
    90         PathNode(String name, List<Node> children) {    // dir
       
    91             super(name, modulesDirAttrs);
       
    92             this.children = children;
       
    93         }
       
    94 
       
    95         @Override
       
    96         public boolean isDirectory() {
       
    97             return children != null ||
       
    98                    (link == null && getFileAttributes().isDirectory());
       
    99         }
       
   100 
       
   101         @Override
       
   102         public boolean isLink() {
       
   103             return link != null;
       
   104         }
       
   105 
       
   106         @Override
       
   107         public PathNode resolveLink(boolean recursive) {
       
   108             if (link == null)
       
   109                 return this;
       
   110             return recursive && link.isLink() ? link.resolveLink(true) : link;
       
   111         }
       
   112 
       
   113         byte[] getContent() throws IOException {
       
   114             if (!getFileAttributes().isRegularFile())
       
   115                 throw new FileSystemException(getName() + " is not file");
       
   116             return Files.readAllBytes(path);
       
   117         }
       
   118 
       
   119         @Override
       
   120         public List<Node> getChildren() {
       
   121             if (!isDirectory())
       
   122                 throw new IllegalArgumentException("not a directory: " + getNameString());
       
   123             if (children == null) {
       
   124                 List<Node> list = new ArrayList<>();
       
   125                 try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
       
   126                     for (Path p : stream) {
       
   127                         p = explodedModulesDir.relativize(p);
       
   128                         String pName = MODULES + nativeSlashToFrontSlash(p.toString());
       
   129                         Node node = findNode(pName);
       
   130                         if (node != null) {  // findNode may choose to hide certain files!
       
   131                             list.add(node);
       
   132                         }
       
   133                     }
       
   134                 } catch (IOException x) {
       
   135                     return null;
       
   136                 }
       
   137                 children = list;
       
   138             }
       
   139             return children;
       
   140         }
       
   141     }
       
   142 
       
   143     @Override
       
   144     public void close() throws IOException {
       
   145         nodes.clear();
       
   146     }
       
   147 
       
   148     @Override
       
   149     public byte[] getResource(Node node) throws IOException {
       
   150         return ((PathNode)node).getContent();
       
   151     }
       
   152 
       
   153     // find Node for the given Path
       
   154     @Override
       
   155     public synchronized Node findNode(String str) {
       
   156         Node node = findModulesNode(str);
       
   157         if (node != null) {
       
   158             return node;
       
   159         }
       
   160         // lazily created for paths like /packages/<package>/<module>/xyz
       
   161         // For example /packages/java.lang/java.base/java/lang/
       
   162         if (str.startsWith(PACKAGES)) {
       
   163             // pkgEndIdx marks end of <package> part
       
   164             int pkgEndIdx = str.indexOf('/', PACKAGES_LEN);
       
   165             if (pkgEndIdx != -1) {
       
   166                 // modEndIdx marks end of <module> part
       
   167                 int modEndIdx = str.indexOf('/', pkgEndIdx + 1);
       
   168                 if (modEndIdx != -1) {
       
   169                     // make sure we have such module link!
       
   170                     // ie., /packages/<package>/<module> is valid
       
   171                     Node linkNode = nodes.get(str.substring(0, modEndIdx));
       
   172                     if (linkNode == null || !linkNode.isLink()) {
       
   173                         return null;
       
   174                     }
       
   175                     // map to "/modules/zyz" path and return that node
       
   176                     // For example, "/modules/java.base/java/lang" for
       
   177                     // "/packages/java.lang/java.base/java/lang".
       
   178                     String mod = MODULES + str.substring(pkgEndIdx + 1);
       
   179                     return findModulesNode(mod);
       
   180                 }
       
   181             }
       
   182         }
       
   183         return null;
       
   184     }
       
   185 
       
   186     // find a Node for a path that starts like "/modules/..."
       
   187     Node findModulesNode(String str) {
       
   188         PathNode node = nodes.get(str);
       
   189         if (node != null) {
       
   190             return node;
       
   191         }
       
   192         // lazily created "/modules/xyz/abc/" Node
       
   193         // This is mapped to default file system path "<JDK_MODULES_DIR>/xyz/abc"
       
   194         Path p = underlyingPath(str);
       
   195         if (p != null) {
       
   196             try {
       
   197                 BasicFileAttributes attrs = Files.readAttributes(p, BasicFileAttributes.class);
       
   198                 if (attrs.isRegularFile()) {
       
   199                     Path f = p.getFileName();
       
   200                     if (f.toString().startsWith("_the."))
       
   201                         return null;
       
   202                 }
       
   203                 node = new PathNode(str, p, attrs);
       
   204                 nodes.put(str, node);
       
   205                 return node;
       
   206             } catch (IOException x) {
       
   207                 // does not exists or unable to determine
       
   208             }
       
   209         }
       
   210         return null;
       
   211     }
       
   212 
       
   213     Path underlyingPath(String str) {
       
   214         if (str.startsWith(MODULES)) {
       
   215             str = frontSlashToNativeSlash(str.substring("/modules".length()));
       
   216             return defaultFS.getPath(explodedModulesDir.toString(), str);
       
   217         }
       
   218         return null;
       
   219     }
       
   220 
       
   221     // convert "/" to platform path separator
       
   222     private String frontSlashToNativeSlash(String str) {
       
   223         return separator == null ? str : str.replace("/", separator);
       
   224     }
       
   225 
       
   226     // convert platform path separator to "/"
       
   227     private String nativeSlashToFrontSlash(String str) {
       
   228         return separator == null ? str : str.replace(separator, "/");
       
   229     }
       
   230 
       
   231     // convert "/"s to "."s
       
   232     private String slashesToDots(String str) {
       
   233         return str.replace(separator != null ? separator : "/", ".");
       
   234     }
       
   235 
       
   236     // initialize file system Nodes
       
   237     private void initNodes() throws IOException {
       
   238         // same package prefix may exist in mutliple modules. This Map
       
   239         // is filled by walking "jdk modules" directory recursively!
       
   240         Map<String, List<String>> packageToModules = new HashMap<>();
       
   241         try (DirectoryStream<Path> stream = Files.newDirectoryStream(explodedModulesDir)) {
       
   242             for (Path module : stream) {
       
   243                 if (Files.isDirectory(module)) {
       
   244                     String moduleName = module.getFileName().toString();
       
   245                     // make sure "/modules/<moduleName>" is created
       
   246                     findModulesNode(MODULES + moduleName);
       
   247                     Files.walk(module).filter(Files::isDirectory).forEach((p) -> {
       
   248                         p = module.relativize(p);
       
   249                         String pkgName = slashesToDots(p.toString());
       
   250                         // skip META-INFO and empty strings
       
   251                         if (!pkgName.isEmpty() && !pkgName.startsWith("META-INF")) {
       
   252                             List<String> moduleNames = packageToModules.get(pkgName);
       
   253                             if (moduleNames == null) {
       
   254                                 moduleNames = new ArrayList<>();
       
   255                                 packageToModules.put(pkgName, moduleNames);
       
   256                             }
       
   257                             moduleNames.add(moduleName);
       
   258                         }
       
   259                     });
       
   260                 }
       
   261             }
       
   262         }
       
   263         // create "/modules" directory
       
   264         // "nodes" map contains only /modules/<foo> nodes only so far and so add all as children of /modules
       
   265         PathNode modulesDir = new PathNode("/modules", new ArrayList<>(nodes.values()));
       
   266         nodes.put(modulesDir.getName(), modulesDir);
       
   267 
       
   268         // create children under "/packages"
       
   269         List<Node> packagesChildren = new ArrayList<>(packageToModules.size());
       
   270         for (Map.Entry<String, List<String>> entry : packageToModules.entrySet()) {
       
   271             String pkgName = entry.getKey();
       
   272             List<String> moduleNameList = entry.getValue();
       
   273             List<Node> moduleLinkNodes = new ArrayList<>(moduleNameList.size());
       
   274             for (String moduleName : moduleNameList) {
       
   275                 Node moduleNode = findModulesNode(MODULES + moduleName);
       
   276                 PathNode linkNode = new PathNode(PACKAGES + pkgName + "/" + moduleName, moduleNode);
       
   277                 nodes.put(linkNode.getName(), linkNode);
       
   278                 moduleLinkNodes.add(linkNode);
       
   279             }
       
   280             PathNode pkgDir = new PathNode(PACKAGES + pkgName, moduleLinkNodes);
       
   281             nodes.put(pkgDir.getName(), pkgDir);
       
   282             packagesChildren.add(pkgDir);
       
   283         }
       
   284         // "/packages" dir
       
   285         PathNode packagesDir = new PathNode("/packages", packagesChildren);
       
   286         nodes.put(packagesDir.getName(), packagesDir);
       
   287 
       
   288         // finally "/" dir!
       
   289         List<Node> rootChildren = new ArrayList<>();
       
   290         rootChildren.add(packagesDir);
       
   291         rootChildren.add(modulesDir);
       
   292         PathNode root = new PathNode("/", rootChildren);
       
   293         nodes.put(root.getName(), root);
       
   294     }
       
   295 }