jdk/src/java.base/share/classes/jdk/internal/jrtfs/JrtExplodedFileSystem.java
changeset 37365 9cc4eb4d7491
parent 37364 80be215c8c51
child 37366 2a9763cab068
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.LinkOption;
       
    34 import java.nio.file.NoSuchFileException;
       
    35 import java.nio.file.NotDirectoryException;
       
    36 import java.nio.file.Path;
       
    37 import java.nio.file.attribute.BasicFileAttributes;
       
    38 import java.util.ArrayList;
       
    39 import java.util.Collections;
       
    40 import java.util.HashMap;
       
    41 import java.util.Iterator;
       
    42 import java.util.List;
       
    43 import java.util.Map;
       
    44 import java.util.function.Function;
       
    45 import static java.util.stream.Collectors.toList;
       
    46 import static jdk.internal.jrtfs.AbstractJrtFileSystem.getString;
       
    47 
       
    48 /**
       
    49  * A jrt file system built on $JAVA_HOME/modules directory ('exploded modules
       
    50  * build')
       
    51  *
       
    52  * @implNote This class needs to maintain JDK 8 source compatibility.
       
    53  *
       
    54  * It is used internally in the JDK to implement jimage/jrtfs access,
       
    55  * but also compiled and delivered as part of the jrtfs.jar to support access
       
    56  * to the jimage file provided by the shipped JDK by tools running on JDK 8.
       
    57  */
       
    58 class JrtExplodedFileSystem extends AbstractJrtFileSystem {
       
    59 
       
    60     private static final String MODULES = "/modules/";
       
    61     private static final String PACKAGES = "/packages/";
       
    62     private static final int PACKAGES_LEN = PACKAGES.length();
       
    63 
       
    64     // root path
       
    65     private final JrtExplodedPath rootPath;
       
    66     private volatile boolean isOpen;
       
    67     private final FileSystem defaultFS;
       
    68     private final String separator;
       
    69     private final Map<String, Node> nodes = Collections.synchronizedMap(new HashMap<>());
       
    70     private final BasicFileAttributes modulesDirAttrs;
       
    71 
       
    72     JrtExplodedFileSystem(JrtFileSystemProvider provider,
       
    73             Map<String, ?> env)
       
    74             throws IOException {
       
    75 
       
    76         super(provider, env);
       
    77         checkExists(SystemImages.explodedModulesDir());
       
    78         byte[] root = new byte[]{'/'};
       
    79         rootPath = new JrtExplodedPath(this, root);
       
    80         isOpen = true;
       
    81         defaultFS = FileSystems.getDefault();
       
    82         String str = defaultFS.getSeparator();
       
    83         separator = str.equals(getSeparator()) ? null : str;
       
    84         modulesDirAttrs = Files.readAttributes(SystemImages.explodedModulesDir(), BasicFileAttributes.class);
       
    85         initNodes();
       
    86     }
       
    87 
       
    88     @Override
       
    89     public void close() throws IOException {
       
    90         cleanup();
       
    91     }
       
    92 
       
    93     @Override
       
    94     public boolean isOpen() {
       
    95         return isOpen;
       
    96     }
       
    97 
       
    98     @Override
       
    99     protected void finalize() throws Throwable {
       
   100         cleanup();
       
   101         super.finalize();
       
   102     }
       
   103 
       
   104     private synchronized void cleanup() {
       
   105         isOpen = false;
       
   106         nodes.clear();
       
   107     }
       
   108 
       
   109     @Override
       
   110     JrtExplodedPath getRootPath() {
       
   111         return rootPath;
       
   112     }
       
   113 
       
   114     // Base class for Nodes of this file system
       
   115     abstract class Node {
       
   116 
       
   117         private final String name;
       
   118 
       
   119         Node(String name) {
       
   120             this.name = name;
       
   121         }
       
   122 
       
   123         final String getName() {
       
   124             return name;
       
   125         }
       
   126 
       
   127         final String getExtension() {
       
   128             if (isFile()) {
       
   129                 final int index = name.lastIndexOf(".");
       
   130                 if (index != -1) {
       
   131                     return name.substring(index + 1);
       
   132                 }
       
   133             }
       
   134 
       
   135             return null;
       
   136         }
       
   137 
       
   138         BasicFileAttributes getBasicAttrs() throws IOException {
       
   139             return modulesDirAttrs;
       
   140         }
       
   141 
       
   142         boolean isLink() {
       
   143             return false;
       
   144         }
       
   145 
       
   146         boolean isDirectory() {
       
   147             return false;
       
   148         }
       
   149 
       
   150         boolean isFile() {
       
   151             return false;
       
   152         }
       
   153 
       
   154         byte[] getContent() throws IOException {
       
   155             if (!isFile()) {
       
   156                 throw new FileSystemException(name + " is not file");
       
   157             }
       
   158 
       
   159             throw new AssertionError("ShouldNotReachHere");
       
   160         }
       
   161 
       
   162         List<Node> getChildren() throws IOException {
       
   163             if (!isDirectory()) {
       
   164                 throw new NotDirectoryException(name);
       
   165             }
       
   166 
       
   167             throw new AssertionError("ShouldNotReachHere");
       
   168         }
       
   169 
       
   170         final Node resolveLink() {
       
   171             return resolveLink(false);
       
   172         }
       
   173 
       
   174         Node resolveLink(boolean recursive) {
       
   175             return this;
       
   176         }
       
   177     }
       
   178 
       
   179     // A Node that is backed by actual default file system Path
       
   180     private final class PathNode extends Node {
       
   181 
       
   182         // Path in underlying default file system
       
   183         private final Path path;
       
   184         private final boolean file;
       
   185         // lazily initialized, don't read attributes unless required!
       
   186         private BasicFileAttributes attrs;
       
   187 
       
   188         PathNode(String name, Path path) {
       
   189             super(name);
       
   190             this.path = path;
       
   191             this.file = Files.isRegularFile(path);
       
   192         }
       
   193 
       
   194         @Override
       
   195         synchronized BasicFileAttributes getBasicAttrs() throws IOException {
       
   196             if (attrs == null) {
       
   197                 attrs = Files.readAttributes(path, BasicFileAttributes.class);
       
   198             }
       
   199             return attrs;
       
   200         }
       
   201 
       
   202         @Override
       
   203         boolean isDirectory() {
       
   204             return !file;
       
   205         }
       
   206 
       
   207         @Override
       
   208         boolean isFile() {
       
   209             return file;
       
   210         }
       
   211 
       
   212         @Override
       
   213         byte[] getContent() throws IOException {
       
   214             if (!isFile()) {
       
   215                 throw new FileSystemException(getName() + " is not file");
       
   216             }
       
   217 
       
   218             return Files.readAllBytes(path);
       
   219         }
       
   220 
       
   221         @Override
       
   222         List<Node> getChildren() throws IOException {
       
   223             if (!isDirectory()) {
       
   224                 throw new NotDirectoryException(getName());
       
   225             }
       
   226 
       
   227             List<Node> children = new ArrayList<>();
       
   228             try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
       
   229                 for (Path cp : stream) {
       
   230                     cp = SystemImages.explodedModulesDir().relativize(cp);
       
   231                     String cpName = MODULES + nativeSlashToFrontSlash(cp.toString());
       
   232                     try {
       
   233                         children.add(findNode(cpName));
       
   234                     } catch (NoSuchFileException nsfe) {
       
   235                         // findNode may choose to hide certain files!
       
   236                     }
       
   237                 }
       
   238             }
       
   239 
       
   240             return children;
       
   241         }
       
   242     }
       
   243 
       
   244     // A Node that links to another Node
       
   245     private final class LinkNode extends Node {
       
   246 
       
   247         // underlying linked Node
       
   248         private final Node link;
       
   249 
       
   250         LinkNode(String name, Node link) {
       
   251             super(name);
       
   252             this.link = link;
       
   253         }
       
   254 
       
   255         @Override
       
   256         BasicFileAttributes getBasicAttrs() throws IOException {
       
   257             return link.getBasicAttrs();
       
   258         }
       
   259 
       
   260         @Override
       
   261         public boolean isLink() {
       
   262             return true;
       
   263         }
       
   264 
       
   265         @Override
       
   266         Node resolveLink(boolean recursive) {
       
   267             return recursive && (link instanceof LinkNode) ? ((LinkNode) link).resolveLink(true) : link;
       
   268         }
       
   269     }
       
   270 
       
   271     // A directory Node with it's children Nodes
       
   272     private final class DirNode extends Node {
       
   273 
       
   274         // children Nodes of this Node.
       
   275         private final List<Node> children;
       
   276 
       
   277         DirNode(String name, List<Node> children) {
       
   278             super(name);
       
   279             this.children = children;
       
   280         }
       
   281 
       
   282         @Override
       
   283         boolean isDirectory() {
       
   284             return true;
       
   285         }
       
   286 
       
   287         @Override
       
   288         List<Node> getChildren() throws IOException {
       
   289             return children;
       
   290         }
       
   291     }
       
   292 
       
   293     private JrtExplodedPath toJrtExplodedPath(String path) {
       
   294         return toJrtExplodedPath(getBytes(path));
       
   295     }
       
   296 
       
   297     private JrtExplodedPath toJrtExplodedPath(byte[] path) {
       
   298         return new JrtExplodedPath(this, path);
       
   299     }
       
   300 
       
   301     @Override
       
   302     boolean isSameFile(AbstractJrtPath jrtPath1, AbstractJrtPath jrtPath2) throws IOException {
       
   303         Node n1 = checkNode(jrtPath1);
       
   304         Node n2 = checkNode(jrtPath2);
       
   305         return n1 == n2;
       
   306     }
       
   307 
       
   308     @Override
       
   309     boolean isLink(AbstractJrtPath jrtPath) throws IOException {
       
   310         return checkNode(jrtPath).isLink();
       
   311     }
       
   312 
       
   313     @Override
       
   314     AbstractJrtPath resolveLink(AbstractJrtPath jrtPath) throws IOException {
       
   315         String name = checkNode(jrtPath).resolveLink().getName();
       
   316         return toJrtExplodedPath(name);
       
   317     }
       
   318 
       
   319     @Override
       
   320     AbstractJrtFileAttributes getFileAttributes(AbstractJrtPath jrtPath, LinkOption... options) throws IOException {
       
   321         Node node = checkNode(jrtPath);
       
   322         if (node.isLink() && followLinks(options)) {
       
   323             node = node.resolveLink(true);
       
   324         }
       
   325         return new JrtExplodedFileAttributes(node);
       
   326     }
       
   327 
       
   328     @Override
       
   329     boolean exists(AbstractJrtPath jrtPath) throws IOException {
       
   330         try {
       
   331             checkNode(jrtPath);
       
   332             return true;
       
   333         } catch (NoSuchFileException nsfe) {
       
   334             return false;
       
   335         }
       
   336     }
       
   337 
       
   338     @Override
       
   339     boolean isDirectory(AbstractJrtPath jrtPath, boolean resolveLinks) throws IOException {
       
   340         Node node = checkNode(jrtPath);
       
   341         return resolveLinks && node.isLink()
       
   342                 ? node.resolveLink(true).isDirectory()
       
   343                 : node.isDirectory();
       
   344     }
       
   345 
       
   346     @Override
       
   347     Iterator<Path> iteratorOf(AbstractJrtPath dir) throws IOException {
       
   348         Node node = checkNode(dir).resolveLink(true);
       
   349         if (!node.isDirectory()) {
       
   350             throw new NotDirectoryException(getString(dir.getName()));
       
   351         }
       
   352 
       
   353         Function<Node, Path> nodeToPath =
       
   354             child -> dir.resolve(
       
   355                 toJrtExplodedPath(child.getName()).
       
   356                 getFileName());
       
   357 
       
   358         return node.getChildren().stream().
       
   359                    map(nodeToPath).collect(toList()).
       
   360                    iterator();
       
   361     }
       
   362 
       
   363     @Override
       
   364     byte[] getFileContent(AbstractJrtPath jrtPath) throws IOException {
       
   365         return checkNode(jrtPath).getContent();
       
   366     }
       
   367 
       
   368     private Node checkNode(AbstractJrtPath jrtPath) throws IOException {
       
   369         return checkNode(jrtPath.getResolvedPath());
       
   370     }
       
   371 
       
   372     private Node checkNode(byte[] path) throws IOException {
       
   373         ensureOpen();
       
   374         return findNode(path);
       
   375     }
       
   376 
       
   377     synchronized Node findNode(byte[] path) throws IOException {
       
   378         return findNode(getString(path));
       
   379     }
       
   380 
       
   381     // find Node for the given Path
       
   382     synchronized Node findNode(String str) throws IOException {
       
   383         Node node = findModulesNode(str);
       
   384         if (node != null) {
       
   385             return node;
       
   386         }
       
   387 
       
   388         // lazily created for paths like /packages/<package>/<module>/xyz
       
   389         // For example /packages/java.lang/java.base/java/lang/
       
   390         if (str.startsWith(PACKAGES)) {
       
   391             // pkgEndIdx marks end of <package> part
       
   392             int pkgEndIdx = str.indexOf('/', PACKAGES_LEN);
       
   393             if (pkgEndIdx != -1) {
       
   394                 // modEndIdx marks end of <module> part
       
   395                 int modEndIdx = str.indexOf('/', pkgEndIdx + 1);
       
   396                 if (modEndIdx != -1) {
       
   397                     // make sure we have such module link!
       
   398                     // ie., /packages/<package>/<module> is valid
       
   399                     Node linkNode = nodes.get(str.substring(0, modEndIdx));
       
   400                     if (linkNode == null || !linkNode.isLink()) {
       
   401                         throw new NoSuchFileException(str);
       
   402                     }
       
   403 
       
   404                     // map to "/modules/zyz" path and return that node
       
   405                     // For example, "/modules/java.base/java/lang" for
       
   406                     // "/packages/java.lang/java.base/java/lang".
       
   407                     String mod = MODULES + str.substring(pkgEndIdx + 1);
       
   408                     return findNode(mod);
       
   409                 }
       
   410             }
       
   411         }
       
   412 
       
   413         throw new NoSuchFileException(str);
       
   414     }
       
   415 
       
   416     // find a Node for a path that starts like "/modules/..."
       
   417     synchronized Node findModulesNode(String str) throws IOException {
       
   418         Node node = nodes.get(str);
       
   419         if (node != null) {
       
   420             return node;
       
   421         }
       
   422 
       
   423         // lazily created "/modules/xyz/abc/" Node
       
   424         // This is mapped to default file system path "<JDK_MODULES_DIR>/xyz/abc"
       
   425         Path p = underlyingPath(str);
       
   426         if (p != null) {
       
   427             if (Files.isRegularFile(p)) {
       
   428                 Path file = p.getFileName();
       
   429                 if (file.toString().startsWith("_the.")) {
       
   430                     return null;
       
   431                 }
       
   432             }
       
   433             node = new PathNode(str, p);
       
   434             nodes.put(str, node);
       
   435             return node;
       
   436         }
       
   437 
       
   438         return null;
       
   439     }
       
   440 
       
   441     Path underlyingPath(String str) {
       
   442         if (str.startsWith(MODULES)) {
       
   443             str = frontSlashToNativeSlash(str.substring("/modules".length()));
       
   444             return defaultFS.getPath(SystemImages.explodedModulesDir().toString(), str);
       
   445         }
       
   446         return null;
       
   447     }
       
   448 
       
   449     // convert "/" to platform path separator
       
   450     private String frontSlashToNativeSlash(String str) {
       
   451         return separator == null ? str : str.replace("/", separator);
       
   452     }
       
   453 
       
   454     // convert platform path separator to "/"
       
   455     private String nativeSlashToFrontSlash(String str) {
       
   456         return separator == null ? str : str.replace(separator, "/");
       
   457     }
       
   458 
       
   459     // convert "/"s to "."s
       
   460     private String slashesToDots(String str) {
       
   461         return str.replace(separator != null ? separator : "/", ".");
       
   462     }
       
   463 
       
   464     // initialize file system Nodes
       
   465     private void initNodes() throws IOException {
       
   466         // same package prefix may exist in mutliple modules. This Map
       
   467         // is filled by walking "jdk modules" directory recursively!
       
   468         Map<String, List<String>> packageToModules = new HashMap<>();
       
   469 
       
   470         try (DirectoryStream<Path> stream = Files.newDirectoryStream(SystemImages.explodedModulesDir())) {
       
   471             for (Path module : stream) {
       
   472                 if (Files.isDirectory(module)) {
       
   473                     String moduleName = module.getFileName().toString();
       
   474                     // make sure "/modules/<moduleName>" is created
       
   475                     findModulesNode(MODULES + moduleName);
       
   476 
       
   477                     Files.walk(module).filter(Files::isDirectory).forEach((p) -> {
       
   478                         p = module.relativize(p);
       
   479                         String pkgName = slashesToDots(p.toString());
       
   480                         // skip META-INFO and empty strings
       
   481                         if (!pkgName.isEmpty() && !pkgName.startsWith("META-INF")) {
       
   482                             List<String> moduleNames = packageToModules.get(pkgName);
       
   483                             if (moduleNames == null) {
       
   484                                 moduleNames = new ArrayList<>();
       
   485                                 packageToModules.put(pkgName, moduleNames);
       
   486                             }
       
   487                             moduleNames.add(moduleName);
       
   488                         }
       
   489                     });
       
   490                 }
       
   491             }
       
   492         }
       
   493 
       
   494         // create "/modules" directory
       
   495         // "nodes" map contains only /modules/<foo> nodes only so far and so add all as children of /modules
       
   496         DirNode modulesDir = new DirNode("/modules", new ArrayList<>(nodes.values()));
       
   497         nodes.put(modulesDir.getName(), modulesDir);
       
   498 
       
   499         // create children under "/packages"
       
   500         List<Node> packagesChildren = new ArrayList<>(packageToModules.size());
       
   501         for (Map.Entry<String, List<String>> entry : packageToModules.entrySet()) {
       
   502             String pkgName = entry.getKey();
       
   503             List<String> moduleNameList = entry.getValue();
       
   504             List<Node> moduleLinkNodes = new ArrayList<>(moduleNameList.size());
       
   505             for (String moduleName : moduleNameList) {
       
   506                 Node moduleNode = findModulesNode(MODULES + moduleName);
       
   507                 LinkNode linkNode = new LinkNode(PACKAGES + pkgName + "/" + moduleName, moduleNode);
       
   508                 nodes.put(linkNode.getName(), linkNode);
       
   509                 moduleLinkNodes.add(linkNode);
       
   510             }
       
   511 
       
   512             DirNode pkgDir = new DirNode(PACKAGES + pkgName, moduleLinkNodes);
       
   513             nodes.put(pkgDir.getName(), pkgDir);
       
   514             packagesChildren.add(pkgDir);
       
   515         }
       
   516 
       
   517         // "/packages" dir
       
   518         DirNode packagesDir = new DirNode("/packages", packagesChildren);
       
   519         nodes.put(packagesDir.getName(), packagesDir);
       
   520 
       
   521         // finally "/" dir!
       
   522         List<Node> rootChildren = new ArrayList<>();
       
   523         rootChildren.add(modulesDir);
       
   524         rootChildren.add(packagesDir);
       
   525         DirNode root = new DirNode("/", rootChildren);
       
   526         nodes.put(root.getName(), root);
       
   527     }
       
   528 }