< prev index next >

src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java

Print this page
*** 1,7 ***
  /*
!  * Copyright (c) 2015, 2022, 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
--- 1,7 ---
  /*
!  * Copyright (c) 2015, 2025, 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

*** 25,21 ***
  package jdk.internal.jrtfs;
  
  import java.io.IOException;
  import java.io.UncheckedIOException;
  import java.nio.file.DirectoryStream;
- import java.nio.file.FileSystem;
  import java.nio.file.FileSystemException;
- import java.nio.file.FileSystems;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.nio.file.attribute.BasicFileAttributes;
  import java.util.ArrayList;
- import java.util.Collections;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.stream.Stream;
  
  import jdk.internal.jimage.ImageReader.Node;
  
  /**
--- 25,19 ---
  package jdk.internal.jrtfs;
  
  import java.io.IOException;
  import java.io.UncheckedIOException;
  import java.nio.file.DirectoryStream;
  import java.nio.file.FileSystemException;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.nio.file.attribute.BasicFileAttributes;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
+ import java.util.Objects;
  import java.util.stream.Stream;
  
  import jdk.internal.jimage.ImageReader.Node;
  
  /**

*** 54,20 ***
   */
  class ExplodedImage extends SystemImage {
  
      private static final String MODULES = "/modules/";
      private static final String PACKAGES = "/packages/";
-     private static final int PACKAGES_LEN = PACKAGES.length();
  
!     private final FileSystem defaultFS;
      private final String separator;
!     private final Map<String, PathNode> nodes = Collections.synchronizedMap(new HashMap<>());
      private final BasicFileAttributes modulesDirAttrs;
  
      ExplodedImage(Path modulesDir) throws IOException {
!         defaultFS = FileSystems.getDefault();
!         String str = defaultFS.getSeparator();
          separator = str.equals("/") ? null : str;
          modulesDirAttrs = Files.readAttributes(modulesDir, BasicFileAttributes.class);
          initNodes();
      }
  
--- 52,19 ---
   */
  class ExplodedImage extends SystemImage {
  
      private static final String MODULES = "/modules/";
      private static final String PACKAGES = "/packages/";
  
!     private final Path modulesDir;
      private final String separator;
!     private final Map<String, PathNode> nodes = new HashMap<>();
      private final BasicFileAttributes modulesDirAttrs;
  
      ExplodedImage(Path modulesDir) throws IOException {
!         this.modulesDir = modulesDir;
!         String str = modulesDir.getFileSystem().getSeparator();
          separator = str.equals("/") ? null : str;
          modulesDirAttrs = Files.readAttributes(modulesDir, BasicFileAttributes.class);
          initNodes();
      }
  

*** 77,25 ***
          // Path in underlying default file system
          private Path path;
          private PathNode link;
          private List<Node> children;
  
!         PathNode(String name, Path path, BasicFileAttributes attrs) {  // path
              super(name, attrs);
              this.path = path;
          }
  
!         PathNode(String name, Node link) {              // link
              super(name, link.getFileAttributes());
              this.link = (PathNode)link;
          }
  
!         PathNode(String name, List<Node> children) {    // dir
              super(name, modulesDirAttrs);
              this.children = children;
          }
  
          @Override
          public boolean isDirectory() {
              return children != null ||
                     (link == null && getFileAttributes().isDirectory());
          }
--- 74,30 ---
          // Path in underlying default file system
          private Path path;
          private PathNode link;
          private List<Node> children;
  
!         private PathNode(String name, Path path, BasicFileAttributes attrs) {  // path
              super(name, attrs);
              this.path = path;
          }
  
!         private PathNode(String name, Node link) {              // link
              super(name, link.getFileAttributes());
              this.link = (PathNode)link;
          }
  
!         private PathNode(String name, List<Node> children) {    // dir
              super(name, modulesDirAttrs);
              this.children = children;
          }
  
+         @Override
+         public boolean isResource() {
+             return link == null && !getFileAttributes().isDirectory();
+         }
+ 
          @Override
          public boolean isDirectory() {
              return children != null ||
                     (link == null && getFileAttributes().isDirectory());
          }

*** 110,11 ***
              if (link == null)
                  return this;
              return recursive && link.isLink() ? link.resolveLink(true) : link;
          }
  
!         byte[] getContent() throws IOException {
              if (!getFileAttributes().isRegularFile())
                  throw new FileSystemException(getName() + " is not file");
              return Files.readAllBytes(path);
          }
  
--- 112,11 ---
              if (link == null)
                  return this;
              return recursive && link.isLink() ? link.resolveLink(true) : link;
          }
  
!         private byte[] getContent() throws IOException {
              if (!getFileAttributes().isRegularFile())
                  throw new FileSystemException(getName() + " is not file");
              return Files.readAllBytes(path);
          }
  

*** 124,11 ***
                  throw new IllegalArgumentException("not a directory: " + getName());
              if (children == null) {
                  List<Node> list = new ArrayList<>();
                  try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
                      for (Path p : stream) {
!                         p = explodedModulesDir.relativize(p);
                          String pName = MODULES + nativeSlashToFrontSlash(p.toString());
                          Node node = findNode(pName);
                          if (node != null) {  // findNode may choose to hide certain files!
                              list.add(node);
                          }
--- 126,11 ---
                  throw new IllegalArgumentException("not a directory: " + getName());
              if (children == null) {
                  List<Node> list = new ArrayList<>();
                  try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
                      for (Path p : stream) {
!                         p = modulesDir.relativize(p);
                          String pName = MODULES + nativeSlashToFrontSlash(p.toString());
                          Node node = findNode(pName);
                          if (node != null) {  // findNode may choose to hide certain files!
                              list.add(node);
                          }

*** 150,87 ***
              }
          }
      }
  
      @Override
!     public void close() throws IOException {
          nodes.clear();
      }
  
      @Override
      public byte[] getResource(Node node) throws IOException {
          return ((PathNode)node).getContent();
      }
  
-     // find Node for the given Path
      @Override
!     public synchronized Node findNode(String str) {
!         Node node = findModulesNode(str);
          if (node != null) {
              return node;
          }
!         // lazily created for paths like /packages/<package>/<module>/xyz
!         // For example /packages/java.lang/java.base/java/lang/
!         if (str.startsWith(PACKAGES)) {
!             // pkgEndIdx marks end of <package> part
!             int pkgEndIdx = str.indexOf('/', PACKAGES_LEN);
!             if (pkgEndIdx != -1) {
-                 // modEndIdx marks end of <module> part
-                 int modEndIdx = str.indexOf('/', pkgEndIdx + 1);
-                 if (modEndIdx != -1) {
-                     // make sure we have such module link!
-                     // ie., /packages/<package>/<module> is valid
-                     Node linkNode = nodes.get(str.substring(0, modEndIdx));
-                     if (linkNode == null || !linkNode.isLink()) {
-                         return null;
-                     }
-                     // map to "/modules/zyz" path and return that node
-                     // For example, "/modules/java.base/java/lang" for
-                     // "/packages/java.lang/java.base/java/lang".
-                     String mod = MODULES + str.substring(pkgEndIdx + 1);
-                     return findModulesNode(mod);
-                 }
-             }
          }
!         return null;
      }
  
!     // find a Node for a path that starts like "/modules/..."
!     Node findModulesNode(String str) {
!         PathNode node = nodes.get(str);
!         if (node != null) {
!             return node;
!         }
!         // lazily created "/modules/xyz/abc/" Node
!         // This is mapped to default file system path "<JDK_MODULES_DIR>/xyz/abc"
!         Path p = underlyingPath(str);
!         if (p != null) {
!             try {
!                 BasicFileAttributes attrs = Files.readAttributes(p, BasicFileAttributes.class);
!                 if (attrs.isRegularFile()) {
!                     Path f = p.getFileName();
!                     if (f.toString().startsWith("_the."))
!                         return null;
                  }
!                 node = new PathNode(str, p, attrs);
!                 nodes.put(str, node);
-                 return node;
-             } catch (IOException x) {
-                 // does not exists or unable to determine
              }
          }
-         return null;
      }
  
!     Path underlyingPath(String str) {
!         if (str.startsWith(MODULES)) {
!             str = frontSlashToNativeSlash(str.substring("/modules".length()));
!             return defaultFS.getPath(explodedModulesDir.toString(), str);
          }
          return null;
      }
  
      // convert "/" to platform path separator
      private String frontSlashToNativeSlash(String str) {
          return separator == null ? str : str.replace("/", separator);
      }
  
--- 152,91 ---
              }
          }
      }
  
      @Override
!     public synchronized void close() throws IOException {
          nodes.clear();
      }
  
      @Override
      public byte[] getResource(Node node) throws IOException {
          return ((PathNode)node).getContent();
      }
  
      @Override
!     public synchronized Node findNode(String name) {
!         PathNode node = nodes.get(name);
          if (node != null) {
              return node;
          }
!         // If null, this was not the name of "/modules/..." node, and since all
!         // "/packages/..." nodes were created and cached in advance, the name
!         // cannot reference a valid node.
!         Path path = underlyingModulesPath(name);
!         if (path == null) {
!             return null;
          }
!         // This can still return null for hidden files.
+         return createModulesNode(name, path);
      }
  
!     /**
!      * Lazily creates and caches a {@code Node} for the given "/modules/..." name
!      * and corresponding path to a file or directory.
!      *
!      * @param name a resource or directory node name, of the form "/modules/...".
!      * @param path the path of a file for a resource or directory.
!      * @return the newly created and cached node, or {@code null} if the given
!      *     path references a file which must be hidden in the node hierarchy.
!      */
!     private Node createModulesNode(String name, Path path) {
!         assert !nodes.containsKey(name) : "Node must not already exist: " + name;
!         assert isNonEmptyModulesPath(name) : "Invalid modules name: " + name;
! 
!         try {
!             // We only know if we're creating a resource of directory when we
!             // look up file attributes, and we only do that once. Thus, we can
+             // only reject "marker files" here, rather than by inspecting the
+             // given name string, since it doesn't apply to directories.
+             BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
+             if (attrs.isRegularFile()) {
+                 Path f = path.getFileName();
+                 if (f.toString().startsWith("_the.")) {
+                     return null;
                  }
!             } else if (!attrs.isDirectory()) {
!                 return null;
              }
+             PathNode node = new PathNode(name, path, attrs);
+             nodes.put(name, node);
+             return node;
+         } catch (IOException x) {
+             // Since the path reference a file, any errors should not be ignored.
+             throw new UncheckedIOException(x);
          }
      }
  
!     /**
!      * Returns the expected file path for name in the "/modules/..." namespace,
!      * or {@code null} if the name is not in the "/modules/..." namespace or the
!      * path does not reference a file.
+      */
+     private Path underlyingModulesPath(String name) {
+         if (isNonEmptyModulesPath(name)) {
+             Path path = modulesDir.resolve(frontSlashToNativeSlash(name.substring(MODULES.length())));
+             return Files.exists(path) ? path : null;
          }
          return null;
      }
  
+     private static boolean isNonEmptyModulesPath(String name) {
+         // Don't just check the prefix, there must be something after it too
+         // (otherwise you end up with an empty string after trimming).
+         return name.startsWith(MODULES) && name.length() > MODULES.length();
+     }
+ 
      // convert "/" to platform path separator
      private String frontSlashToNativeSlash(String str) {
          return separator == null ? str : str.replace("/", separator);
      }
  

*** 247,62 ***
      // initialize file system Nodes
      private void initNodes() throws IOException {
          // same package prefix may exist in multiple modules. This Map
          // is filled by walking "jdk modules" directory recursively!
          Map<String, List<String>> packageToModules = new HashMap<>();
!         try (DirectoryStream<Path> stream = Files.newDirectoryStream(explodedModulesDir)) {
              for (Path module : stream) {
                  if (Files.isDirectory(module)) {
                      String moduleName = module.getFileName().toString();
                      // make sure "/modules/<moduleName>" is created
!                     findModulesNode(MODULES + moduleName);
                      try (Stream<Path> contentsStream = Files.walk(module)) {
                          contentsStream.filter(Files::isDirectory).forEach((p) -> {
                              p = module.relativize(p);
                              String pkgName = slashesToDots(p.toString());
                              // skip META-INF and empty strings
                              if (!pkgName.isEmpty() && !pkgName.startsWith("META-INF")) {
!                                 List<String> moduleNames = packageToModules.get(pkgName);
!                                 if (moduleNames == null) {
!                                     moduleNames = new ArrayList<>();
-                                     packageToModules.put(pkgName, moduleNames);
-                                 }
-                                 moduleNames.add(moduleName);
                              }
                          });
                      }
                  }
              }
          }
          // create "/modules" directory
          // "nodes" map contains only /modules/<foo> nodes only so far and so add all as children of /modules
!         PathNode modulesDir = new PathNode("/modules", new ArrayList<>(nodes.values()));
!         nodes.put(modulesDir.getName(), modulesDir);
  
          // create children under "/packages"
          List<Node> packagesChildren = new ArrayList<>(packageToModules.size());
          for (Map.Entry<String, List<String>> entry : packageToModules.entrySet()) {
              String pkgName = entry.getKey();
              List<String> moduleNameList = entry.getValue();
              List<Node> moduleLinkNodes = new ArrayList<>(moduleNameList.size());
              for (String moduleName : moduleNameList) {
!                 Node moduleNode = findModulesNode(MODULES + moduleName);
                  PathNode linkNode = new PathNode(PACKAGES + pkgName + "/" + moduleName, moduleNode);
                  nodes.put(linkNode.getName(), linkNode);
                  moduleLinkNodes.add(linkNode);
              }
              PathNode pkgDir = new PathNode(PACKAGES + pkgName, moduleLinkNodes);
              nodes.put(pkgDir.getName(), pkgDir);
              packagesChildren.add(pkgDir);
          }
          // "/packages" dir
!         PathNode packagesDir = new PathNode("/packages", packagesChildren);
!         nodes.put(packagesDir.getName(), packagesDir);
  
          // finally "/" dir!
          List<Node> rootChildren = new ArrayList<>();
!         rootChildren.add(packagesDir);
!         rootChildren.add(modulesDir);
          PathNode root = new PathNode("/", rootChildren);
          nodes.put(root.getName(), root);
      }
  }
--- 253,59 ---
      // initialize file system Nodes
      private void initNodes() throws IOException {
          // same package prefix may exist in multiple modules. This Map
          // is filled by walking "jdk modules" directory recursively!
          Map<String, List<String>> packageToModules = new HashMap<>();
!         try (DirectoryStream<Path> stream = Files.newDirectoryStream(modulesDir)) {
              for (Path module : stream) {
                  if (Files.isDirectory(module)) {
                      String moduleName = module.getFileName().toString();
                      // make sure "/modules/<moduleName>" is created
!                     Objects.requireNonNull(createModulesNode(MODULES + moduleName, module));
                      try (Stream<Path> contentsStream = Files.walk(module)) {
                          contentsStream.filter(Files::isDirectory).forEach((p) -> {
                              p = module.relativize(p);
                              String pkgName = slashesToDots(p.toString());
                              // skip META-INF and empty strings
                              if (!pkgName.isEmpty() && !pkgName.startsWith("META-INF")) {
!                                 packageToModules
!                                         .computeIfAbsent(pkgName, k -> new ArrayList<>())
!                                         .add(moduleName);
                              }
                          });
                      }
                  }
              }
          }
          // create "/modules" directory
          // "nodes" map contains only /modules/<foo> nodes only so far and so add all as children of /modules
!         PathNode modulesRootNode = new PathNode("/modules", new ArrayList<>(nodes.values()));
!         nodes.put(modulesRootNode.getName(), modulesRootNode);
  
          // create children under "/packages"
          List<Node> packagesChildren = new ArrayList<>(packageToModules.size());
          for (Map.Entry<String, List<String>> entry : packageToModules.entrySet()) {
              String pkgName = entry.getKey();
              List<String> moduleNameList = entry.getValue();
              List<Node> moduleLinkNodes = new ArrayList<>(moduleNameList.size());
              for (String moduleName : moduleNameList) {
!                 Node moduleNode = Objects.requireNonNull(nodes.get(MODULES + moduleName));
                  PathNode linkNode = new PathNode(PACKAGES + pkgName + "/" + moduleName, moduleNode);
                  nodes.put(linkNode.getName(), linkNode);
                  moduleLinkNodes.add(linkNode);
              }
              PathNode pkgDir = new PathNode(PACKAGES + pkgName, moduleLinkNodes);
              nodes.put(pkgDir.getName(), pkgDir);
              packagesChildren.add(pkgDir);
          }
          // "/packages" dir
!         PathNode packagesRootNode = new PathNode("/packages", packagesChildren);
!         nodes.put(packagesRootNode.getName(), packagesRootNode);
  
          // finally "/" dir!
          List<Node> rootChildren = new ArrayList<>();
!         rootChildren.add(packagesRootNode);
!         rootChildren.add(modulesRootNode);
          PathNode root = new PathNode("/", rootChildren);
          nodes.put(root.getName(), root);
      }
  }
< prev index next >