< prev index next >

src/java.base/share/classes/jdk/internal/jimage/ImageReader.java

Print this page

   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.jimage;
  26 


  27 import java.io.IOException;
  28 import java.nio.ByteBuffer;
  29 import java.nio.ByteOrder;
  30 import java.nio.IntBuffer;
  31 import java.nio.file.Files;
  32 import java.nio.file.Path;
  33 import java.nio.file.attribute.BasicFileAttributes;
  34 import java.util.ArrayList;
  35 import java.util.Arrays;
  36 import java.util.Collections;

  37 import java.util.HashMap;
  38 import java.util.HashSet;
  39 import java.util.List;
  40 import java.util.Map;
  41 import java.util.Objects;
  42 import java.util.Set;

  43 import java.util.function.Function;
  44 import java.util.function.Supplier;
  45 import java.util.stream.Stream;
  46 








  47 /**
  48  * A view over the entries of a jimage file with a unified namespace suitable
  49  * for file system use. The jimage entries (resources, module and package
  50  * information) are mapped into a unified hierarchy of named nodes, which serve
  51  * as the underlying structure for {@code JrtFileSystem} and other utilities.
  52  *
  53  * <p>Entries in jimage are expressed as one of three {@link Node} types;
  54  * resource nodes, directory nodes and link nodes.
  55  *
  56  * <p>When remapping jimage entries, jimage location names (e.g. {@code
  57  * "/java.base/java/lang/Integer.class"}) are prefixed with {@code "/modules"}
  58  * to form the names of resource nodes. This aligns with the naming of module
  59  * entries in jimage (e.g. "/modules/java.base/java/lang"), which appear as
  60  * directory nodes in {@code ImageReader}.
  61  *
  62  * <p>Package entries (e.g. {@code "/packages/java.lang"} appear as directory
  63  * nodes containing link nodes, which resolve back to the root directory of the
  64  * module in which that package exists (e.g. {@code "/modules/java.base"}).
  65  * Unlike other nodes, the jimage file does not contain explicit entries for
  66  * link nodes, and their existence is derived only from the contents of the

  69  * <p>While similar to {@code BasicImageReader}, this class is not a conceptual
  70  * subtype of it, and deliberately hides types such as {@code ImageLocation} to
  71  * give a focused API based only on nodes.
  72  *
  73  * @implNote This class needs to maintain JDK 8 source compatibility.
  74  *
  75  * It is used internally in the JDK to implement jimage/jrtfs access,
  76  * but also compiled and delivered as part of the jrtfs.jar to support access
  77  * to the jimage file provided by the shipped JDK by tools running on JDK 8.
  78  */
  79 public final class ImageReader implements AutoCloseable {
  80     private final SharedImageReader reader;
  81 
  82     private volatile boolean closed;
  83 
  84     private ImageReader(SharedImageReader reader) {
  85         this.reader = reader;
  86     }
  87 
  88     /**
  89      * Opens an image reader for a jimage file at the specified path, using the
  90      * given byte order.


  91      */
  92     public static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException {
  93         Objects.requireNonNull(imagePath);
  94         Objects.requireNonNull(byteOrder);
  95 
  96         return SharedImageReader.open(imagePath, byteOrder);
  97     }
  98 
  99     /**
 100      * Opens an image reader for a jimage file at the specified path, using the
 101      * platform native byte order.



 102      */
 103     public static ImageReader open(Path imagePath) throws IOException {
 104         return open(imagePath, ByteOrder.nativeOrder());



 105     }
 106 
 107     @Override
 108     public void close() throws IOException {
 109         if (closed) {
 110             throw new IOException("image file already closed");
 111         }
 112         reader.close(this);
 113         closed = true;
 114     }
 115 
 116     private void ensureOpen() throws IOException {
 117         if (closed) {
 118             throw new IOException("image file closed");
 119         }
 120     }
 121 
 122     private void requireOpen() {
 123         if (closed) {
 124             throw new IllegalStateException("image file closed");

 196      * <p>Note that no testing is performed to check whether the buffer about
 197      * to be released actually came from a call to {@code getResourceBuffer()}.
 198      */
 199     public static void releaseByteBuffer(ByteBuffer buffer) {
 200         BasicImageReader.releaseByteBuffer(buffer);
 201     }
 202 
 203     /**
 204      * Returns the content of a resource node in a possibly cached byte buffer.
 205      * Callers of this method must call {@link #releaseByteBuffer(ByteBuffer)}
 206      * when they are finished with it.
 207      */
 208     public ByteBuffer getResourceBuffer(Node node) {
 209         requireOpen();
 210         if (!node.isResource()) {
 211             throw new IllegalArgumentException("Not a resource node: " + node);
 212         }
 213         return reader.getResourceBuffer(node.getLocation());
 214     }
 215 





 216     private static final class SharedImageReader extends BasicImageReader {
 217         private static final Map<Path, SharedImageReader> OPEN_FILES = new HashMap<>();
 218         private static final String MODULES_ROOT = "/modules";
 219         private static final String PACKAGES_ROOT = "/packages";
 220         // There are >30,000 nodes in a complete jimage tree, and even relatively
 221         // common tasks (e.g. starting up javac) load somewhere in the region of
 222         // 1000 classes. Thus, an initial capacity of 2000 is a reasonable guess.
 223         private static final int INITIAL_NODE_CACHE_CAPACITY = 2000;
 224 



























 225         // List of openers for this shared image.
 226         private final Set<ImageReader> openers = new HashSet<>();
 227 
 228         // Attributes of the jimage file. The jimage file does not contain
 229         // attributes for the individual resources (yet). We use attributes
 230         // of the jimage file itself (creation, modification, access times).
 231         private final BasicFileAttributes imageFileAttributes;
 232 
 233         // Cache of all user visible nodes, guarded by synchronizing 'this' instance.
 234         private final Map<String, Node> nodes;
 235         // Used to classify ImageLocation instances without string comparison.
 236         private final int modulesStringOffset;
 237         private final int packagesStringOffset;
 238 
 239         private SharedImageReader(Path imagePath, ByteOrder byteOrder) throws IOException {







 240             super(imagePath, byteOrder);
 241             this.imageFileAttributes = Files.readAttributes(imagePath, BasicFileAttributes.class);
 242             this.nodes = new HashMap<>(INITIAL_NODE_CACHE_CAPACITY);
 243             // Pick stable jimage names from which to extract string offsets (we cannot
 244             // use "/modules" or "/packages", since those have a module offset of zero).
 245             this.modulesStringOffset = getModuleOffset("/modules/java.base");
 246             this.packagesStringOffset = getModuleOffset("/packages/java.lang");
 247 
 248             // Node creation is very lazy, so we can just make the top-level directories
 249             // now without the risk of triggering the building of lots of other nodes.
 250             Directory packages = newDirectory(PACKAGES_ROOT);
 251             nodes.put(packages.getName(), packages);
 252             Directory modules = newDirectory(MODULES_ROOT);
 253             nodes.put(modules.getName(), modules);
 254 
 255             Directory root = newDirectory("/");
 256             root.setChildren(Arrays.asList(packages, modules));
 257             nodes.put(root.getName(), root);







 258         }
 259 
 260         /**
 261          * Returns the offset of the string denoting the leading "module" segment in
 262          * the given path (e.g. {@code <module>/<path>}). We can't just pass in the
 263          * {@code /<module>} string here because that has a module offset of zero.





 264          */
 265         private int getModuleOffset(String path) {
 266             ImageLocation location = findLocation(path);
 267             assert location != null : "Cannot find expected jimage location: " + path;
 268             int offset = location.getModuleOffset();
 269             assert offset != 0 : "Invalid module offset for jimage location: " + path;
 270             return offset;








































































 271         }
 272 
 273         private static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException {
 274             Objects.requireNonNull(imagePath);
 275             Objects.requireNonNull(byteOrder);
 276 
 277             synchronized (OPEN_FILES) {
 278                 SharedImageReader reader = OPEN_FILES.get(imagePath);

 279 
 280                 if (reader == null) {
 281                     // Will fail with an IOException if wrong byteOrder.
 282                     reader =  new SharedImageReader(imagePath, byteOrder);
 283                     OPEN_FILES.put(imagePath, reader);
 284                 } else if (reader.getByteOrder() != byteOrder) {
 285                     throw new IOException("\"" + reader.getName() + "\" is not an image file");
 286                 }
 287 
 288                 ImageReader image = new ImageReader(reader);
 289                 reader.openers.add(image);
 290 
 291                 return image;
 292             }
 293         }
 294 
 295         public void close(ImageReader image) throws IOException {
 296             Objects.requireNonNull(image);
 297 
 298             synchronized (OPEN_FILES) {
 299                 if (!openers.remove(image)) {
 300                     throw new IOException("image file already closed");
 301                 }
 302 
 303                 if (openers.isEmpty()) {
 304                     close();
 305                     nodes.clear();
 306 
 307                     if (!OPEN_FILES.remove(this.getImagePath(), this)) {
 308                         throw new IOException("image file not found in open list");
 309                     }
 310                 }
 311             }
 312         }
 313 
 314         /**
 315          * Returns a node with the given name, or null if no resource or directory of
 316          * that name exists.
 317          *
 318          * <p>Note that there is no reentrant calling back to this method from within
 319          * the node handling code.
 320          *
 321          * @param name an absolute, {@code /}-separated path string, prefixed with either
 322          *     "/modules" or "/packages".
 323          */
 324         synchronized Node findNode(String name) {


 325             Node node = nodes.get(name);
 326             if (node == null) {
 327                 // We cannot get the root paths ("/modules" or "/packages") here
 328                 // because those nodes are already in the nodes cache.
 329                 if (name.startsWith(MODULES_ROOT + "/")) {
 330                     // This may perform two lookups, one for a directory (in
 331                     // "/modules/...") and one for a non-prefixed resource
 332                     // (with "/modules" removed).
 333                     node = buildModulesNode(name);
 334                 } else if (name.startsWith(PACKAGES_ROOT + "/")) {
 335                     node = buildPackagesNode(name);
 336                 }
 337                 if (node != null) {
 338                     nodes.put(node.getName(), node);
 339                 }
 340             } else if (!node.isCompleted()) {
 341                 // Only directories can be incomplete.
 342                 assert node instanceof Directory : "Invalid incomplete node: " + node;
 343                 completeDirectory((Directory) node);
 344             }
 345             assert node == null || node.isCompleted() : "Incomplete node: " + node;
 346             return node;
 347         }
 348 
 349         /**
 350          * Returns a resource node in the given module, or null if no resource of
 351          * that name exists.
 352          *
 353          * <p>Note that there is no reentrant calling back to this method from within
 354          * the node handling code.
 355          */
 356         Node findResourceNode(String moduleName, String resourcePath) {
 357             // Unlike findNode(), this method makes only one lookup in the
 358             // underlying jimage, but can only reliably return resource nodes.
 359             if (moduleName.indexOf('/') >= 0) {
 360                 throw new IllegalArgumentException("invalid module name: " + moduleName);
 361             }
 362             String nodeName = MODULES_ROOT + "/" + moduleName + "/" + resourcePath;
 363             // Synchronize as tightly as possible to reduce locking contention.
 364             synchronized (this) {
 365                 Node node = nodes.get(nodeName);
 366                 if (node == null) {
 367                     ImageLocation loc = findLocation(moduleName, resourcePath);
 368                     if (loc != null && isResource(loc)) {
 369                         node = newResource(nodeName, loc);
 370                         nodes.put(node.getName(), node);
 371                     }
 372                     return node;
 373                 } else {
 374                     return node.isResource() ? node : null;
 375                 }
 376             }
 377         }
 378 
 379         /**
 380          * Returns whether a resource exists in the given module.
 381          *
 382          * <p>This method is expected to be called frequently for resources
 383          * which do not exist in the given module (e.g. as part of classpath
 384          * search). As such, it skips checking the nodes cache and only checks
 385          * for an entry in the jimage file, as this is faster if the resource
 386          * is not present. This also means it doesn't need synchronization.

 387          */
 388         boolean containsResource(String moduleName, String resourcePath) {
 389             if (moduleName.indexOf('/') >= 0) {
 390                 throw new IllegalArgumentException("invalid module name: " + moduleName);
 391             }
 392             // If the given module name is 'modules', then 'isResource()'
 393             // returns false to prevent false positives.










 394             ImageLocation loc = findLocation(moduleName, resourcePath);
 395             return loc != null && isResource(loc);
 396         }
 397 
 398         /**
 399          * Builds a node in the "/modules/..." namespace.
 400          *
 401          * <p>Called by {@link #findNode(String)} if a {@code /modules/...} node
 402          * is not present in the cache.
 403          */
 404         private Node buildModulesNode(String name) {
 405             assert name.startsWith(MODULES_ROOT + "/") : "Invalid module node name: " + name;



 406             // Returns null for non-directory resources, since the jimage name does not
 407             // start with "/modules" (e.g. "/java.base/java/lang/Object.class").
 408             ImageLocation loc = findLocation(name);
 409             if (loc != null) {
 410                 assert name.equals(loc.getFullName()) : "Mismatched location for directory: " + name;
 411                 assert isModulesSubdirectory(loc) : "Invalid modules directory: " + name;
 412                 return completeModuleDirectory(newDirectory(name), loc);
 413             }
 414             // Now try the non-prefixed resource name, but be careful to avoid false
 415             // positives for names like "/modules/modules/xxx" which could return a
 416             // location of a directory entry.
 417             loc = findLocation(name.substring(MODULES_ROOT.length()));
 418             return loc != null && isResource(loc) ? newResource(name, loc) : null;


 419         }
 420 
 421         /**
 422          * Builds a node in the "/packages/..." namespace.






























 423          *
 424          * <p>Called by {@link #findNode(String)} if a {@code /packages/...} node
 425          * is not present in the cache.
 426          */
 427         private Node buildPackagesNode(String name) {
 428             // There are only locations for the root "/packages" or "/packages/xxx"
 429             // directories, but not the symbolic links below them (the links can be
 430             // entirely derived from the name information in the parent directory).
 431             // However, unlike resources this means that we do not have a constant
 432             // time lookup for link nodes when creating them.
 433             int packageStart = PACKAGES_ROOT.length() + 1;
 434             int packageEnd = name.indexOf('/', packageStart);
 435             if (packageEnd == -1) {
 436                 ImageLocation loc = findLocation(name);
 437                 return loc != null ? completePackageDirectory(newDirectory(name), loc) : null;
 438             } else {
 439                 // We cannot assume that the parent directory exists for a link node, since
 440                 // the given name is untrusted and could reference a non-existent link.
 441                 // However, if the parent directory is present, we can conclude that the
 442                 // given name was not a valid link (or else it would already be cached).
 443                 String dirName = name.substring(0, packageEnd);
 444                 if (!nodes.containsKey(dirName)) {
 445                     ImageLocation loc = findLocation(dirName);
 446                     // If the parent location doesn't exist, the link node cannot exist.
 447                     if (loc != null) {
 448                         nodes.put(dirName, completePackageDirectory(newDirectory(dirName), loc));
 449                         // When the parent is created its child nodes are created and cached,
 450                         // but this can still return null if given name wasn't a valid link.
 451                         return nodes.get(name);
 452                     }

 453                 }
 454             }
 455             return null;
 456         }
 457 
 458         /** Completes a directory by ensuring its child list is populated correctly. */
 459         private void completeDirectory(Directory dir) {
 460             String name = dir.getName();
 461             // Since the node exists, we can assert that its name starts with
 462             // either "/modules" or "/packages", making differentiation easy.
 463             // It also means that the name is valid, so it must yield a location.
 464             assert name.startsWith(MODULES_ROOT) || name.startsWith(PACKAGES_ROOT);
 465             ImageLocation loc = findLocation(name);
 466             assert loc != null && name.equals(loc.getFullName()) : "Invalid location for name: " + name;
 467             // We cannot use 'isXxxSubdirectory()' methods here since we could
 468             // be given a top-level directory (for which that test doesn't work).
 469             // The string MUST start "/modules" or "/packages" here.
 470             if (name.charAt(1) == 'm') {
 471                 completeModuleDirectory(dir, loc);
 472             } else {
 473                 completePackageDirectory(dir, loc);

 474             }
 475             assert dir.isCompleted() : "Directory must be complete by now: " + dir;
 476         }
 477 
 478         /**
 479          * Completes a modules directory by setting the list of child nodes.
 480          *
 481          * <p>The given directory can be the top level {@code /modules} directory,
 482          * so it is NOT safe to use {@code isModulesSubdirectory(loc)} here.
 483          */
 484         private Directory completeModuleDirectory(Directory dir, ImageLocation loc) {
 485             assert dir.getName().equals(loc.getFullName()) : "Mismatched location for directory: " + dir;
 486             List<Node> children = createChildNodes(loc, childLoc -> {
 487                 if (isModulesSubdirectory(childLoc)) {
 488                     return nodes.computeIfAbsent(childLoc.getFullName(), this::newDirectory);








 489                 } else {

 490                     // Add "/modules" prefix to image location paths to get node names.
 491                     String resourceName = childLoc.getFullName(true);
 492                     return nodes.computeIfAbsent(resourceName, n -> newResource(n, childLoc));
 493                 }
 494             });

 495             dir.setChildren(children);
 496             return dir;
 497         }
 498 





















 499         /**
 500          * Completes a package directory by setting the list of child nodes.
 501          *
 502          * <p>The given directory can be the top level {@code /packages} directory,
 503          * so it is NOT safe to use {@code isPackagesSubdirectory(loc)} here.


 504          */
 505         private Directory completePackageDirectory(Directory dir, ImageLocation loc) {
 506             assert dir.getName().equals(loc.getFullName()) : "Mismatched location for directory: " + dir;
 507             // The only directories in the "/packages" namespace are "/packages" or
 508             // "/packages/<package>". However, unlike "/modules" directories, the
 509             // location offsets mean different things.
 510             List<Node> children;
 511             if (dir.getName().equals(PACKAGES_ROOT)) {
 512                 // Top-level directory just contains a list of subdirectories.
 513                 children = createChildNodes(loc, c -> nodes.computeIfAbsent(c.getFullName(), this::newDirectory));
 514             } else {
 515                 // A package directory's content is array of offset PAIRS in the
 516                 // Strings table, but we only need the 2nd value of each pair.
 517                 IntBuffer intBuffer = getOffsetBuffer(loc);
 518                 int offsetCount = intBuffer.capacity();
 519                 assert (offsetCount & 0x1) == 0 : "Offset count must be even: " + offsetCount;
 520                 children = new ArrayList<>(offsetCount / 2);
 521                 // Iterate the 2nd offset in each pair (odd indices).
 522                 for (int i = 1; i < offsetCount; i += 2) {
 523                     String moduleName = getString(intBuffer.get(i));
 524                     children.add(nodes.computeIfAbsent(
 525                             dir.getName() + "/" + moduleName,
 526                             n -> newLinkNode(n, MODULES_ROOT + "/" + moduleName)));
 527                 }
 528             }
 529             // This only happens once and "completes" the directory.
 530             dir.setChildren(children);
 531             return dir;
 532         }
 533 
 534         /**
 535          * Creates the list of child nodes for a {@code Directory} based on a given






 536          *
 537          * <p>Note: This cannot be used for package subdirectories as they have
 538          * child offsets stored differently to other directories.





 539          */
 540         private List<Node> createChildNodes(ImageLocation loc, Function<ImageLocation, Node> newChildFn) {


 541             IntBuffer offsets = getOffsetBuffer(loc);
 542             int childCount = offsets.capacity();
 543             List<Node> children = new ArrayList<>(childCount);
 544             for (int i = 0; i < childCount; i++) {
 545                 children.add(newChildFn.apply(getLocation(offsets.get(i))));



 546             }
 547             return children;
 548         }
 549 
 550         /** Helper to extract the integer offset buffer from a directory location. */
 551         private IntBuffer getOffsetBuffer(ImageLocation dir) {
 552             assert !isResource(dir) : "Not a directory: " + dir.getFullName();
 553             byte[] offsets = getResource(dir);
 554             ByteBuffer buffer = ByteBuffer.wrap(offsets);
 555             buffer.order(getByteOrder());
 556             return buffer.asIntBuffer();
 557         }
 558 
 559         /**
 560          * Efficiently determines if an image location is a resource.
 561          *
 562          * <p>A resource must have a valid module associated with it, so its
 563          * module offset must be non-zero, and not equal to the offsets for
 564          * "/modules/..." or "/packages/..." entries.
 565          */
 566         private boolean isResource(ImageLocation loc) {
 567             int moduleOffset = loc.getModuleOffset();
 568             return moduleOffset != 0
 569                     && moduleOffset != modulesStringOffset
 570                     && moduleOffset != packagesStringOffset;
 571         }
 572 
 573         /**
 574          * Determines if an image location is a directory in the {@code /modules}
 575          * namespace (if so, the location name is the node name).
 576          *
 577          * <p>In jimage, every {@code ImageLocation} under {@code /modules/} is a
 578          * directory and has the same value for {@code getModule()}, and {@code
 579          * getModuleOffset()}.
 580          */
 581         private boolean isModulesSubdirectory(ImageLocation loc) {
 582             return loc.getModuleOffset() == modulesStringOffset;
 583         }
 584 
 585         /**
 586          * Creates an "incomplete" directory node with no child nodes set.
 587          * Directories need to be "completed" before they are returned by
 588          * {@link #findNode(String)}.
 589          */
 590         private Directory newDirectory(String name) {
 591             return new Directory(name, imageFileAttributes);
 592         }
 593 
 594         /**
 595          * Creates a new resource from an image location. This is the only case
 596          * where the image location name does not match the requested node name.
 597          * In image files, resource locations are NOT prefixed by {@code /modules}.
 598          */
 599         private Resource newResource(String name, ImageLocation loc) {
 600             assert name.equals(loc.getFullName(true)) : "Mismatched location for resource: " + name;
 601             return new Resource(name, loc, imageFileAttributes);
 602         }
 603 
 604         /**
 605          * Creates a new link node pointing at the given target name.
 606          *
 607          * <p>Note that target node is resolved each time {@code resolve()} is called,
 608          * so if a link node is retained after its reader is closed, it will fail.
 609          */
 610         private LinkNode newLinkNode(String name, String targetName) {
 611             return new LinkNode(name, () -> findNode(targetName), imageFileAttributes);
 612         }
 613 
 614         /** Returns the content of a resource node. */
 615         private byte[] getResource(Node node) throws IOException {
 616             // We could have been given a non-resource node here.
 617             if (node.isResource()) {
 618                 return super.getResource(node.getLocation());
 619             }
 620             throw new IOException("Not a resource: " + node);

 812         }
 813 
 814         @Override
 815         boolean isCompleted() {
 816             return children != null;
 817         }
 818 
 819         @Override
 820         public boolean isDirectory() {
 821             return true;
 822         }
 823 
 824         @Override
 825         public Stream<String> getChildNames() {
 826             if (children != null) {
 827                 return children.stream().map(Node::getName);
 828             }
 829             throw new IllegalStateException("Cannot get child nodes of an incomplete directory: " + getName());
 830         }
 831 
 832         private void setChildren(List<Node> children) {
 833             assert this.children == null : this + ": Cannot set child nodes twice!";
 834             this.children = Collections.unmodifiableList(children);
 835         }
 836     }

 837     /**
 838      * Resource node (e.g. a ".class" entry, or any other data resource).
 839      *
 840      * <p>Resources are leaf nodes referencing an underlying image location. They
 841      * are lightweight, and do not cache their contents.
 842      *
 843      * <p>Unlike directories (where the node name matches the jimage path for the
 844      * corresponding {@code ImageLocation}), resource node names are NOT the same
 845      * as the corresponding jimage path. The difference is that node names for
 846      * resources are prefixed with "/modules", which is missing from the
 847      * equivalent jimage path.
 848      */
 849     private static class Resource extends Node {
 850         private final ImageLocation loc;
 851 
 852         private Resource(String name, ImageLocation loc, BasicFileAttributes fileAttrs) {
 853             super(name, fileAttrs);
 854             this.loc = loc;
 855         }
 856 

   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.jimage;
  26 
  27 import jdk.internal.jimage.ImageLocation.LocationType;
  28 
  29 import java.io.IOException;
  30 import java.nio.ByteBuffer;
  31 import java.nio.ByteOrder;
  32 import java.nio.IntBuffer;
  33 import java.nio.file.Files;
  34 import java.nio.file.Path;
  35 import java.nio.file.attribute.BasicFileAttributes;
  36 import java.util.ArrayList;
  37 import java.util.Arrays;
  38 import java.util.Collections;
  39 import java.util.Comparator;
  40 import java.util.HashMap;
  41 import java.util.HashSet;
  42 import java.util.List;
  43 import java.util.Map;
  44 import java.util.Objects;
  45 import java.util.Set;
  46 import java.util.TreeMap;
  47 import java.util.function.Function;
  48 import java.util.function.Supplier;
  49 import java.util.stream.Stream;
  50 
  51 import static jdk.internal.jimage.ImageLocation.LocationType.MODULES_DIR;
  52 import static jdk.internal.jimage.ImageLocation.LocationType.MODULES_ROOT;
  53 import static jdk.internal.jimage.ImageLocation.LocationType.PACKAGES_DIR;
  54 import static jdk.internal.jimage.ImageLocation.LocationType.RESOURCE;
  55 import static jdk.internal.jimage.ImageLocation.MODULES_PREFIX;
  56 import static jdk.internal.jimage.ImageLocation.PACKAGES_PREFIX;
  57 import static jdk.internal.jimage.ImageLocation.PREVIEW_INFIX;
  58 
  59 /**
  60  * A view over the entries of a jimage file with a unified namespace suitable
  61  * for file system use. The jimage entries (resources, module and package
  62  * information) are mapped into a unified hierarchy of named nodes, which serve
  63  * as the underlying structure for {@code JrtFileSystem} and other utilities.
  64  *
  65  * <p>Entries in jimage are expressed as one of three {@link Node} types;
  66  * resource nodes, directory nodes and link nodes.
  67  *
  68  * <p>When remapping jimage entries, jimage location names (e.g. {@code
  69  * "/java.base/java/lang/Integer.class"}) are prefixed with {@code "/modules"}
  70  * to form the names of resource nodes. This aligns with the naming of module
  71  * entries in jimage (e.g. "/modules/java.base/java/lang"), which appear as
  72  * directory nodes in {@code ImageReader}.
  73  *
  74  * <p>Package entries (e.g. {@code "/packages/java.lang"} appear as directory
  75  * nodes containing link nodes, which resolve back to the root directory of the
  76  * module in which that package exists (e.g. {@code "/modules/java.base"}).
  77  * Unlike other nodes, the jimage file does not contain explicit entries for
  78  * link nodes, and their existence is derived only from the contents of the

  81  * <p>While similar to {@code BasicImageReader}, this class is not a conceptual
  82  * subtype of it, and deliberately hides types such as {@code ImageLocation} to
  83  * give a focused API based only on nodes.
  84  *
  85  * @implNote This class needs to maintain JDK 8 source compatibility.
  86  *
  87  * It is used internally in the JDK to implement jimage/jrtfs access,
  88  * but also compiled and delivered as part of the jrtfs.jar to support access
  89  * to the jimage file provided by the shipped JDK by tools running on JDK 8.
  90  */
  91 public final class ImageReader implements AutoCloseable {
  92     private final SharedImageReader reader;
  93 
  94     private volatile boolean closed;
  95 
  96     private ImageReader(SharedImageReader reader) {
  97         this.reader = reader;
  98     }
  99 
 100     /**
 101      * Opens an image reader for a jimage file at the specified path.
 102      *
 103      * @param imagePath file system path of the jimage file.
 104      * @param mode whether to return preview resources.
 105      */
 106     public static ImageReader open(Path imagePath, PreviewMode mode) throws IOException {
 107         return open(imagePath, ByteOrder.nativeOrder(), mode);



 108     }
 109 
 110     /**
 111      * Opens an image reader for a jimage file at the specified path.
 112      *
 113      * @param imagePath file system path of the jimage file.
 114      * @param byteOrder the byte-order to be used when reading the jimage file.
 115      * @param mode controls whether preview resources are visible.
 116      */
 117     public static ImageReader open(Path imagePath, ByteOrder byteOrder, PreviewMode mode)
 118             throws IOException {
 119         Objects.requireNonNull(imagePath);
 120         Objects.requireNonNull(byteOrder);
 121         return SharedImageReader.open(imagePath, byteOrder, mode.isPreviewModeEnabled());
 122     }
 123 
 124     @Override
 125     public void close() throws IOException {
 126         if (closed) {
 127             throw new IOException("image file already closed");
 128         }
 129         reader.close(this);
 130         closed = true;
 131     }
 132 
 133     private void ensureOpen() throws IOException {
 134         if (closed) {
 135             throw new IOException("image file closed");
 136         }
 137     }
 138 
 139     private void requireOpen() {
 140         if (closed) {
 141             throw new IllegalStateException("image file closed");

 213      * <p>Note that no testing is performed to check whether the buffer about
 214      * to be released actually came from a call to {@code getResourceBuffer()}.
 215      */
 216     public static void releaseByteBuffer(ByteBuffer buffer) {
 217         BasicImageReader.releaseByteBuffer(buffer);
 218     }
 219 
 220     /**
 221      * Returns the content of a resource node in a possibly cached byte buffer.
 222      * Callers of this method must call {@link #releaseByteBuffer(ByteBuffer)}
 223      * when they are finished with it.
 224      */
 225     public ByteBuffer getResourceBuffer(Node node) {
 226         requireOpen();
 227         if (!node.isResource()) {
 228             throw new IllegalArgumentException("Not a resource node: " + node);
 229         }
 230         return reader.getResourceBuffer(node.getLocation());
 231     }
 232 
 233     // Package protected for use only by SystemImageReader.
 234     ResourceEntries getResourceEntries() {
 235         return reader.getResourceEntries();
 236     }
 237 
 238     private static final class SharedImageReader extends BasicImageReader {



 239         // There are >30,000 nodes in a complete jimage tree, and even relatively
 240         // common tasks (e.g. starting up javac) load somewhere in the region of
 241         // 1000 classes. Thus, an initial capacity of 2000 is a reasonable guess.
 242         private static final int INITIAL_NODE_CACHE_CAPACITY = 2000;
 243 
 244         static final class ReaderKey {
 245             private final Path imagePath;
 246             private final boolean previewMode;
 247 
 248             public ReaderKey(Path imagePath, boolean previewMode) {
 249                 this.imagePath = imagePath;
 250                 this.previewMode = previewMode;
 251             }
 252 
 253             @Override
 254             public boolean equals(Object obj) {
 255                 // No pattern variables here (Java 8 compatible source).
 256                 if (obj instanceof ReaderKey) {
 257                     ReaderKey other = (ReaderKey) obj;
 258                     return this.imagePath.equals(other.imagePath) && this.previewMode == other.previewMode;
 259                 }
 260                 return false;
 261             }
 262 
 263             @Override
 264             public int hashCode() {
 265                 return imagePath.hashCode() ^ Boolean.hashCode(previewMode);
 266             }
 267         }
 268 
 269         private static final Map<ReaderKey, SharedImageReader> OPEN_FILES = new HashMap<>();
 270 
 271         // List of openers for this shared image.
 272         private final Set<ImageReader> openers = new HashSet<>();
 273 
 274         // Attributes of the jimage file. The jimage file does not contain
 275         // attributes for the individual resources (yet). We use attributes
 276         // of the jimage file itself (creation, modification, access times).
 277         private final BasicFileAttributes imageFileAttributes;
 278 
 279         // Cache of all user visible nodes, guarded by synchronizing 'this' instance.
 280         private final Map<String, Node> nodes;



 281 
 282         // Preview mode support.
 283         private final boolean previewMode;
 284         // A relativized mapping from non-preview name to directories containing
 285         // preview-only nodes. This is used to add preview-only content to
 286         // directories as they are completed.
 287         private final HashMap<String, Directory> previewDirectoriesToMerge;
 288 
 289         private SharedImageReader(Path imagePath, ByteOrder byteOrder, boolean previewMode) throws IOException {
 290             super(imagePath, byteOrder);
 291             this.imageFileAttributes = Files.readAttributes(imagePath, BasicFileAttributes.class);
 292             this.nodes = new HashMap<>(INITIAL_NODE_CACHE_CAPACITY);
 293             this.previewMode = previewMode;



 294 
 295             // Node creation is very lazy, so we can just make the top-level directories
 296             // now without the risk of triggering the building of lots of other nodes.
 297             Directory packages = ensureCached(newDirectory(PACKAGES_PREFIX));
 298             Directory modules = ensureCached(newDirectory(MODULES_PREFIX));


 299 
 300             Directory root = newDirectory("/");
 301             root.setChildren(Arrays.asList(packages, modules));
 302             ensureCached(root);
 303 
 304             // By scanning the /packages directory information early we can determine
 305             // which module/package pairs have preview resources, and build the (small)
 306             // set of preview nodes early. This also ensures that preview-only entries
 307             // in the /packages directory are not present in non-preview mode.
 308             this.previewDirectoriesToMerge = previewMode ? new HashMap<>() : null;
 309             packages.setChildren(processPackagesDirectory(previewMode));
 310         }
 311 
 312         /**
 313          * Process {@code "/packages/xxx"} entries to build the child nodes for the
 314          * root {@code "/packages"} node. Preview-only entries will be skipped if
 315          * {@code previewMode == false}.
 316          *
 317          * <p>If {@code previewMode == true}, this method also populates the {@link
 318          * #previewDirectoriesToMerge} map with any preview-only nodes, to be merged
 319          * into directories as they are completed. It also caches preview resources
 320          * and preview-only directories for direct lookup.
 321          */
 322         private ArrayList<Node> processPackagesDirectory(boolean previewMode) {
 323             ImageLocation pkgRoot = findLocation(PACKAGES_PREFIX);
 324             assert pkgRoot != null : "Invalid jimage file";
 325             IntBuffer offsets = getOffsetBuffer(pkgRoot);
 326             ArrayList<Node> pkgDirs = new ArrayList<>(offsets.capacity());
 327             // Package path to module map, sorted in reverse order so that
 328             // longer child paths get processed first.
 329             Map<String, List<String>> previewPackagesToModules =
 330                     new TreeMap<>(Comparator.reverseOrder());
 331             for (int i = 0; i < offsets.capacity(); i++) {
 332                 ImageLocation pkgDir = getLocation(offsets.get(i));
 333                 int flags = pkgDir.getFlags();
 334                 // A package subdirectory is "preview only" if all the modules
 335                 // it references have that package marked as preview only.
 336                 // Skipping these entries avoids empty package subdirectories.
 337                 if (previewMode || !ImageLocation.isPreviewOnly(flags)) {
 338                     pkgDirs.add(ensureCached(newDirectory(pkgDir.getFullName())));
 339                 }
 340                 if (previewMode && ImageLocation.hasPreviewVersion(flags)) {
 341                     // Only do this in preview mode for the small set of packages with
 342                     // preview versions (the number of preview entries should be small).
 343                     List<String> moduleNames = new ArrayList<>();
 344                     ModuleReference.readNameOffsets(getOffsetBuffer(pkgDir), /*normal*/ false, /*preview*/ true)
 345                             .forEachRemaining(n -> moduleNames.add(getString(n)));
 346                     previewPackagesToModules.put(pkgDir.getBase().replace('.', '/'), moduleNames);
 347                 }
 348             }
 349             // Reverse sorted map means child directories are processed first.
 350             previewPackagesToModules.forEach((pkgPath, modules) ->
 351                     modules.forEach(modName -> processPreviewDir(MODULES_PREFIX + "/" + modName, pkgPath)));
 352             // We might have skipped some preview-only package entries.
 353             pkgDirs.trimToSize();
 354             return pkgDirs;
 355         }
 356 
 357         void processPreviewDir(String namePrefix, String pkgPath) {
 358             String previewDirName = namePrefix + PREVIEW_INFIX + "/" + pkgPath;
 359             ImageLocation previewLoc = findLocation(previewDirName);
 360             assert previewLoc != null : "Missing preview directory location: " + previewDirName;
 361             String nonPreviewDirName = namePrefix + "/" + pkgPath;
 362             List<Node> previewOnlyChildren = createChildNodes(previewLoc, 0, childLoc -> {
 363                 String baseName = getBaseName(childLoc);
 364                 String nonPreviewChildName = nonPreviewDirName + "/" + baseName;
 365                 boolean isPreviewOnly = ImageLocation.isPreviewOnly(childLoc.getFlags());
 366                 LocationType type = childLoc.getType();
 367                 if (type == RESOURCE) {
 368                     // Preview resources are cached to override non-preview versions.
 369                     Node childNode = ensureCached(newResource(nonPreviewChildName, childLoc));
 370                     return isPreviewOnly ? childNode : null;
 371                 } else {
 372                     // Child directories are not cached here (they are either cached
 373                     // already or have been added to previewDirectoriesToMerge).
 374                     assert type == MODULES_DIR : "Invalid location type: " + childLoc;
 375                     Node childNode = nodes.get(nonPreviewChildName);
 376                     assert isPreviewOnly == (childNode != null) :
 377                             "Inconsistent child node: " + nonPreviewChildName;
 378                     return childNode;
 379                 }
 380             });
 381             Directory previewDir = newDirectory(nonPreviewDirName);
 382             previewDir.setChildren(previewOnlyChildren);
 383             if (ImageLocation.isPreviewOnly(previewLoc.getFlags())) {
 384                 // If we are preview-only, our children are also preview-only, so
 385                 // this directory is a complete hierarchy and should be cached.
 386                 assert !previewOnlyChildren.isEmpty() : "Invalid empty preview-only directory: " + nonPreviewDirName;
 387                 ensureCached(previewDir);
 388             } else if (!previewOnlyChildren.isEmpty()) {
 389                 // A partial directory containing extra preview-only nodes
 390                 // to be merged when the non-preview directory is completed.
 391                 previewDirectoriesToMerge.put(nonPreviewDirName, previewDir);
 392             }
 393         }
 394 
 395         // Adds a node to the cache, ensuring that no matching entry already existed.
 396         private <T extends Node> T ensureCached(T node) {
 397             Node existingNode = nodes.put(node.getName(), node);
 398             assert existingNode == null : "Unexpected node already cached for: " + node;
 399             return node;
 400         }
 401 
 402         private static ImageReader open(Path imagePath, ByteOrder byteOrder, boolean previewMode) throws IOException {
 403             Objects.requireNonNull(imagePath);
 404             Objects.requireNonNull(byteOrder);
 405 
 406             synchronized (OPEN_FILES) {
 407                 ReaderKey key = new ReaderKey(imagePath, previewMode);
 408                 SharedImageReader reader = OPEN_FILES.get(key);
 409 
 410                 if (reader == null) {
 411                     // Will fail with an IOException if wrong byteOrder.
 412                     reader = new SharedImageReader(imagePath, byteOrder, previewMode);
 413                     OPEN_FILES.put(key, reader);
 414                 } else if (reader.getByteOrder() != byteOrder) {
 415                     throw new IOException("\"" + reader.getName() + "\" is not an image file");
 416                 }
 417 
 418                 ImageReader image = new ImageReader(reader);
 419                 reader.openers.add(image);
 420 
 421                 return image;
 422             }
 423         }
 424 
 425         public void close(ImageReader image) throws IOException {
 426             Objects.requireNonNull(image);
 427 
 428             synchronized (OPEN_FILES) {
 429                 if (!openers.remove(image)) {
 430                     throw new IOException("image file already closed");
 431                 }
 432 
 433                 if (openers.isEmpty()) {
 434                     close();
 435                     nodes.clear();
 436 
 437                     if (!OPEN_FILES.remove(new ReaderKey(getImagePath(), previewMode), this)) {
 438                         throw new IOException("image file not found in open list");
 439                     }
 440                 }
 441             }
 442         }
 443 
 444         /**
 445          * Returns a node with the given name, or null if no resource or directory of
 446          * that name exists.
 447          *
 448          * <p>Note that there is no reentrant calling back to this method from within
 449          * the node handling code.
 450          *
 451          * @param name an absolute, {@code /}-separated path string, prefixed with either
 452          *     "/modules" or "/packages".
 453          */
 454         synchronized Node findNode(String name) {
 455             // Root directories "/", "/modules" and "/packages", as well
 456             // as all "/packages/xxx" subdirectories are already cached.
 457             Node node = nodes.get(name);
 458             if (node == null) {
 459                 if (name.startsWith(MODULES_PREFIX + "/")) {
 460                     node = buildAndCacheModulesNode(name);
 461                 } else if (name.startsWith(PACKAGES_PREFIX + "/")) {
 462                     node = buildAndCacheLinkNode(name);








 463                 }
 464             } else if (!node.isCompleted()) {
 465                 // Only directories can be incomplete.
 466                 assert node instanceof Directory : "Invalid incomplete node: " + node;
 467                 completeDirectory((Directory) node);
 468             }
 469             assert node == null || node.isCompleted() : "Incomplete node: " + node;
 470             return node;
 471         }
 472 
 473         /**
 474          * Returns a resource node in the given module, or null if no resource of
 475          * that name exists.
 476          *
 477          * <p>Note that there is no reentrant calling back to this method from within
 478          * the node handling code.
 479          */
 480         Node findResourceNode(String moduleName, String resourcePath) {
 481             // Unlike findNode(), this method makes only one lookup in the
 482             // underlying jimage, but can only reliably return resource nodes.
 483             if (moduleName.indexOf('/') >= 0) {
 484                 throw new IllegalArgumentException("invalid module name: " + moduleName);
 485             }
 486             String nodeName = MODULES_PREFIX + "/" + moduleName + "/" + resourcePath;
 487             // Synchronize as tightly as possible to reduce locking contention.
 488             synchronized (this) {
 489                 Node node = nodes.get(nodeName);
 490                 if (node == null) {
 491                     ImageLocation loc = findLocation(moduleName, resourcePath);
 492                     if (loc != null && loc.getType() == RESOURCE) {
 493                         node = newResource(nodeName, loc);
 494                         nodes.put(node.getName(), node);
 495                     }
 496                     return node;
 497                 } else {
 498                     return node.isResource() ? node : null;
 499                 }
 500             }
 501         }
 502 
 503         /**
 504          * Returns whether a resource exists in the given module.
 505          *
 506          * <p>This method is expected to be called frequently for resources
 507          * which do not exist in the given module (e.g. as part of classpath
 508          * search). As such, it skips checking the nodes cache if possible, and
 509          * only checks for an entry in the jimage file, as this is faster if the
 510          * resource is not present. This also means it doesn't need
 511          * synchronization most of the time.
 512          */
 513         boolean containsResource(String moduleName, String resourcePath) {
 514             if (moduleName.indexOf('/') >= 0) {
 515                 throw new IllegalArgumentException("invalid module name: " + moduleName);
 516             }
 517             // In preview mode, preview-only resources are eagerly added to the
 518             // cache, so we must check that first.
 519             if (previewMode) {
 520                 String nodeName = MODULES_PREFIX + "/" + moduleName + "/" + resourcePath;
 521                 // Synchronize as tightly as possible to reduce locking contention.
 522                 synchronized (this) {
 523                     Node node = nodes.get(nodeName);
 524                     if (node != null) {
 525                         return node.isResource();
 526                     }
 527                 }
 528             }
 529             ImageLocation loc = findLocation(moduleName, resourcePath);
 530             return loc != null && loc.getType() == RESOURCE;
 531         }
 532 
 533         /**
 534          * Builds a node in the "/modules/..." namespace.
 535          *
 536          * <p>Called by {@link #findNode(String)} if a {@code /modules/...} node
 537          * is not present in the cache.
 538          */
 539         private Node buildAndCacheModulesNode(String name) {
 540             assert name.startsWith(MODULES_PREFIX + "/") : "Invalid module node name: " + name;
 541             if (isPreviewName(name)) {
 542                 return null;
 543             }
 544             // Returns null for non-directory resources, since the jimage name does not
 545             // start with "/modules" (e.g. "/java.base/java/lang/Object.class").
 546             ImageLocation loc = findLocation(name);
 547             if (loc != null) {
 548                 assert name.equals(loc.getFullName()) : "Mismatched location for directory: " + name;
 549                 assert loc.getType() == MODULES_DIR : "Invalid modules directory: " + name;
 550                 return ensureCached(completeModuleDirectory(newDirectory(name), loc));
 551             }
 552             // Now try the non-prefixed resource name, but be careful to avoid false
 553             // positives for names like "/modules/modules/xxx" which could return a
 554             // location of a directory entry.
 555             loc = findLocation(name.substring(MODULES_PREFIX.length()));
 556             return loc != null && loc.getType() == RESOURCE
 557                     ? ensureCached(newResource(name, loc))
 558                     : null;
 559         }
 560 
 561         /**
 562          * Returns whether a directory name in the "/modules/" directory could be referencing
 563          * the "META-INF" directory".
 564          */
 565         private boolean isMetaInf(Directory dir) {
 566             String name = dir.getName();
 567             int pathStart = name.indexOf('/', MODULES_PREFIX.length() + 1);
 568             return name.length() == pathStart + "/META-INF".length()
 569                     && name.endsWith("/META-INF");
 570         }
 571 
 572         /**
 573          * Returns whether a node name in the "/modules/" directory could be referencing
 574          * a preview resource or directory under "META-INF/preview".
 575          */
 576         private boolean isPreviewName(String name) {
 577             int pathStart = name.indexOf('/', MODULES_PREFIX.length() + 1);
 578             int previewEnd = pathStart + PREVIEW_INFIX.length();
 579             return pathStart > 0
 580                     && name.regionMatches(pathStart, PREVIEW_INFIX, 0, PREVIEW_INFIX.length())
 581                     && (name.length() == previewEnd || name.charAt(previewEnd) == '/');
 582         }
 583 
 584         private String getBaseName(ImageLocation loc) {
 585             // Matches logic in ImageLocation#getFullName() regarding extensions.
 586             String trailingParts = loc.getBase()
 587                     + ((loc.getExtensionOffset() != 0) ? "." + loc.getExtension() : "");
 588             return trailingParts.substring(trailingParts.lastIndexOf('/') + 1);
 589         }
 590 
 591         /**
 592          * Builds a link node of the form "/packages/xxx/yyy".
 593          *
 594          * <p>Called by {@link #findNode(String)} if a {@code /packages/...}
 595          * node is not present in the cache (the name is not trusted).
 596          */
 597         private Node buildAndCacheLinkNode(String name) {
 598             // There are only locations for "/packages" or "/packages/xxx"
 599             // directories, but not the symbolic links below them (links are
 600             // derived from the name information in the parent directory).
 601             int packageStart = PACKAGES_PREFIX.length() + 1;


 602             int packageEnd = name.indexOf('/', packageStart);
 603             // We already built the 2-level "/packages/xxx" directories,
 604             // so if this is a 2-level name, it cannot reference a node.
 605             if (packageEnd >= 0) {





 606                 String dirName = name.substring(0, packageEnd);
 607                 // If no parent exists here, the name cannot be valid.
 608                 Directory parent = (Directory) nodes.get(dirName);
 609                 if (parent != null) {
 610                     if (!parent.isCompleted()) {
 611                         // This caches all child links of the parent directory.
 612                         completePackageSubdirectory(parent, findLocation(dirName));


 613                     }
 614                     return nodes.get(name);
 615                 }
 616             }
 617             return null;
 618         }
 619 
 620         /** Completes a directory by ensuring its child list is populated correctly. */
 621         private void completeDirectory(Directory dir) {
 622             String name = dir.getName();
 623             // Since the node exists, we can assert that its name starts with
 624             // either "/modules" or "/packages", making differentiation easy.
 625             // It also means that the name is valid, so it must yield a location.
 626             assert name.startsWith(MODULES_PREFIX) || name.startsWith(PACKAGES_PREFIX);
 627             ImageLocation loc = findLocation(name);
 628             assert loc != null && name.equals(loc.getFullName()) : "Invalid location for name: " + name;
 629             LocationType type = loc.getType();
 630             if (type == MODULES_DIR || type == MODULES_ROOT) {


 631                 completeModuleDirectory(dir, loc);
 632             } else {
 633                 assert type == PACKAGES_DIR : "Invalid location type: " + loc;
 634                 completePackageSubdirectory(dir, loc);
 635             }
 636             assert dir.isCompleted() : "Directory must be complete by now: " + dir;
 637         }
 638 
 639         /** Completes a modules directory by setting the list of child nodes. */





 640         private Directory completeModuleDirectory(Directory dir, ImageLocation loc) {
 641             assert dir.getName().equals(loc.getFullName()) : "Mismatched location for directory: " + dir;
 642             List<Node> previewOnlyNodes = getPreviewNodesToMerge(dir);
 643             // We hide preview names from direct lookup, but must also prevent
 644             // the preview directory from appearing in any META-INF directories.
 645             boolean parentIsMetaInfDir = isMetaInf(dir);
 646             List<Node> children = createChildNodes(loc, previewOnlyNodes.size(), childLoc -> {
 647                 LocationType type = childLoc.getType();
 648                 if (type == MODULES_DIR) {
 649                     String name = childLoc.getFullName();
 650                     return parentIsMetaInfDir && name.endsWith("/preview")
 651                             ? null
 652                             : nodes.computeIfAbsent(name, this::newDirectory);
 653                 } else {
 654                     assert type == RESOURCE : "Invalid location type: " + loc;
 655                     // Add "/modules" prefix to image location paths to get node names.
 656                     String resourceName = childLoc.getFullName(true);
 657                     return nodes.computeIfAbsent(resourceName, n -> newResource(n, childLoc));
 658                 }
 659             });
 660             children.addAll(previewOnlyNodes);
 661             dir.setChildren(children);
 662             return dir;
 663         }
 664 
 665         /** Completes a package directory by setting the list of child nodes. */
 666         private void completePackageSubdirectory(Directory dir, ImageLocation loc) {
 667             assert dir.getName().equals(loc.getFullName()) : "Mismatched location for directory: " + dir;
 668             assert !dir.isCompleted() : "Directory already completed: " + dir;
 669             assert loc.getType() == PACKAGES_DIR : "Invalid location type: " + loc.getType();
 670 
 671             // In non-preview mode we might skip a very small number of preview-only
 672             // entries, but it's not worth "right-sizing" the array for that.
 673             IntBuffer offsets = getOffsetBuffer(loc);
 674             List<Node> children = new ArrayList<>(offsets.capacity() / 2);
 675             ModuleReference.readNameOffsets(offsets, /*normal*/ true, previewMode)
 676                     .forEachRemaining(n -> {
 677                         String modName = getString(n);
 678                         Node link = newLinkNode(dir.getName() + "/" + modName, MODULES_PREFIX + "/" + modName);
 679                         children.add(ensureCached(link));
 680                     });
 681             // If the parent directory exists, there must be at least one child node.
 682             assert !children.isEmpty() : "Invalid empty package directory: " + dir;
 683             dir.setChildren(children);
 684         }
 685 
 686         /**
 687          * Returns the list of child preview nodes to be merged into the given directory.
 688          *
 689          * <p>Because this is only called once per-directory (since the result is cached
 690          * indefinitely) we can remove any entries we find from the cache. If ever the
 691          * node cache allowed entries to expire, this would have to be changed so that
 692          * directories could be completed more than once.
 693          */
 694         List<Node> getPreviewNodesToMerge(Directory dir) {
 695             if (previewDirectoriesToMerge != null) {
 696                 Directory mergeDir = previewDirectoriesToMerge.remove(dir.getName());
 697                 if (mergeDir != null) {
 698                     return mergeDir.children;

















 699                 }
 700             }
 701             return Collections.emptyList();


 702         }
 703 
 704         /**
 705          * Creates the list of child nodes for a modules {@code Directory} from
 706          * its parent location.
 707          *
 708          * <p>The {@code getChildFn} may return existing cached nodes rather
 709          * than creating them, and if newly created nodes are to be cached,
 710          * it is the job of {@code getChildFn}, or the caller of this method,
 711          * to do that.
 712          *
 713          * @param loc a location relating to a "/modules" directory.
 714          * @param extraNodesCount a known number of preview-only child nodes
 715          *     which will be merged onto the end of the returned list later.
 716          * @param getChildFn a function to return a node for each child location
 717          *     (or null to skip putting anything in the list).
 718          * @return the list of the non-null child nodes, returned by
 719          *     {@code getChildFn}, in the order of the locations entries.
 720          */
 721         private List<Node> createChildNodes(ImageLocation loc, int extraNodesCount, Function<ImageLocation, Node> getChildFn) {
 722             LocationType type = loc.getType();
 723             assert type == MODULES_DIR || type == MODULES_ROOT : "Invalid location type: " + loc;
 724             IntBuffer offsets = getOffsetBuffer(loc);
 725             int childCount = offsets.capacity();
 726             List<Node> children = new ArrayList<>(childCount + extraNodesCount);
 727             for (int i = 0; i < childCount; i++) {
 728                 Node childNode = getChildFn.apply(getLocation(offsets.get(i)));
 729                 if (childNode != null) {
 730                     children.add(childNode);
 731                 }
 732             }
 733             return children;
 734         }
 735 
 736         /** Helper to extract the integer offset buffer from a directory location. */
 737         private IntBuffer getOffsetBuffer(ImageLocation dir) {
 738             assert dir.getType() != RESOURCE : "Not a directory: " + dir.getFullName();
 739             byte[] offsets = getResource(dir);
 740             ByteBuffer buffer = ByteBuffer.wrap(offsets);
 741             buffer.order(getByteOrder());
 742             return buffer.asIntBuffer();
 743         }
 744 


























 745         /**
 746          * Creates an "incomplete" directory node with no child nodes set.
 747          * Directories need to be "completed" before they are returned by
 748          * {@link #findNode(String)}.
 749          */
 750         private Directory newDirectory(String name) {
 751             return new Directory(name, imageFileAttributes);
 752         }
 753 
 754         /**
 755          * Creates a new resource from an image location. This is the only case
 756          * where the image location name does not match the requested node name.
 757          * In image files, resource locations are NOT prefixed by {@code /modules}.
 758          */
 759         private Resource newResource(String name, ImageLocation loc) {

 760             return new Resource(name, loc, imageFileAttributes);
 761         }
 762 
 763         /**
 764          * Creates a new link node pointing at the given target name.
 765          *
 766          * <p>Note that target node is resolved each time {@code resolve()} is called,
 767          * so if a link node is retained after its reader is closed, it will fail.
 768          */
 769         private LinkNode newLinkNode(String name, String targetName) {
 770             return new LinkNode(name, () -> findNode(targetName), imageFileAttributes);
 771         }
 772 
 773         /** Returns the content of a resource node. */
 774         private byte[] getResource(Node node) throws IOException {
 775             // We could have been given a non-resource node here.
 776             if (node.isResource()) {
 777                 return super.getResource(node.getLocation());
 778             }
 779             throw new IOException("Not a resource: " + node);

 971         }
 972 
 973         @Override
 974         boolean isCompleted() {
 975             return children != null;
 976         }
 977 
 978         @Override
 979         public boolean isDirectory() {
 980             return true;
 981         }
 982 
 983         @Override
 984         public Stream<String> getChildNames() {
 985             if (children != null) {
 986                 return children.stream().map(Node::getName);
 987             }
 988             throw new IllegalStateException("Cannot get child nodes of an incomplete directory: " + getName());
 989         }
 990 
 991         private void setChildren(List<? extends Node> children) {
 992             assert this.children == null : this + ": Cannot set child nodes twice!";
 993             this.children = Collections.unmodifiableList(children);
 994         }
 995     }
 996 
 997     /**
 998      * Resource node (e.g. a ".class" entry, or any other data resource).
 999      *
1000      * <p>Resources are leaf nodes referencing an underlying image location. They
1001      * are lightweight, and do not cache their contents.
1002      *
1003      * <p>Unlike directories (where the node name matches the jimage path for the
1004      * corresponding {@code ImageLocation}), resource node names are NOT the same
1005      * as the corresponding jimage path. The difference is that node names for
1006      * resources are prefixed with "/modules", which is missing from the
1007      * equivalent jimage path.
1008      */
1009     private static class Resource extends Node {
1010         private final ImageLocation loc;
1011 
1012         private Resource(String name, ImageLocation loc, BasicFileAttributes fileAttrs) {
1013             super(name, fileAttrs);
1014             this.loc = loc;
1015         }
1016 
< prev index next >