< 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");

 183      *
 184      * @throws IOException if the content cannot be returned (including if the
 185      * given node is not a resource node).
 186      */
 187     public byte[] getResource(Node node) throws IOException {
 188         ensureOpen();
 189         return reader.getResource(node);
 190     }
 191 
 192     /**
 193      * Returns the content of a resource node in a newly allocated byte buffer.
 194      */
 195     public ByteBuffer getResourceBuffer(Node node) {
 196         requireOpen();
 197         if (!node.isResource()) {
 198             throw new IllegalArgumentException("Not a resource node: " + node);
 199         }
 200         return reader.getResourceBuffer(node.getLocation());
 201     }
 202 





 203     private static final class SharedImageReader extends BasicImageReader {
 204         private static final Map<Path, SharedImageReader> OPEN_FILES = new HashMap<>();
 205         private static final String MODULES_ROOT = "/modules";
 206         private static final String PACKAGES_ROOT = "/packages";
 207         // There are >30,000 nodes in a complete jimage tree, and even relatively
 208         // common tasks (e.g. starting up javac) load somewhere in the region of
 209         // 1000 classes. Thus, an initial capacity of 2000 is a reasonable guess.
 210         private static final int INITIAL_NODE_CACHE_CAPACITY = 2000;
 211 



























 212         // List of openers for this shared image.
 213         private final Set<ImageReader> openers = new HashSet<>();
 214 
 215         // Attributes of the jimage file. The jimage file does not contain
 216         // attributes for the individual resources (yet). We use attributes
 217         // of the jimage file itself (creation, modification, access times).
 218         private final BasicFileAttributes imageFileAttributes;
 219 
 220         // Cache of all user visible nodes, guarded by synchronizing 'this' instance.
 221         private final Map<String, Node> nodes;
 222         // Used to classify ImageLocation instances without string comparison.
 223         private final int modulesStringOffset;
 224         private final int packagesStringOffset;
 225 
 226         private SharedImageReader(Path imagePath, ByteOrder byteOrder) throws IOException {







 227             super(imagePath, byteOrder);
 228             this.imageFileAttributes = Files.readAttributes(imagePath, BasicFileAttributes.class);
 229             this.nodes = new HashMap<>(INITIAL_NODE_CACHE_CAPACITY);
 230             // Pick stable jimage names from which to extract string offsets (we cannot
 231             // use "/modules" or "/packages", since those have a module offset of zero).
 232             this.modulesStringOffset = getModuleOffset("/modules/java.base");
 233             this.packagesStringOffset = getModuleOffset("/packages/java.lang");
 234 
 235             // Node creation is very lazy, so we can just make the top-level directories
 236             // now without the risk of triggering the building of lots of other nodes.
 237             Directory packages = newDirectory(PACKAGES_ROOT);
 238             nodes.put(packages.getName(), packages);
 239             Directory modules = newDirectory(MODULES_ROOT);
 240             nodes.put(modules.getName(), modules);
 241 
 242             Directory root = newDirectory("/");
 243             root.setChildren(Arrays.asList(packages, modules));
 244             nodes.put(root.getName(), root);







 245         }
 246 
 247         /**
 248          * Returns the offset of the string denoting the leading "module" segment in
 249          * the given path (e.g. {@code <module>/<path>}). We can't just pass in the
 250          * {@code /<module>} string here because that has a module offset of zero.





 251          */
 252         private int getModuleOffset(String path) {
 253             ImageLocation location = findLocation(path);
 254             assert location != null : "Cannot find expected jimage location: " + path;
 255             int offset = location.getModuleOffset();
 256             assert offset != 0 : "Invalid module offset for jimage location: " + path;
 257             return offset;








































































 258         }
 259 
 260         private static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException {
 261             Objects.requireNonNull(imagePath);
 262             Objects.requireNonNull(byteOrder);
 263 
 264             synchronized (OPEN_FILES) {
 265                 SharedImageReader reader = OPEN_FILES.get(imagePath);

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


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

 374          */
 375         boolean containsResource(String moduleName, String resourcePath) {
 376             if (moduleName.indexOf('/') >= 0) {
 377                 throw new IllegalArgumentException("invalid module name: " + moduleName);
 378             }
 379             // If the given module name is 'modules', then 'isResource()'
 380             // returns false to prevent false positives.










 381             ImageLocation loc = findLocation(moduleName, resourcePath);
 382             return loc != null && isResource(loc);
 383         }
 384 
 385         /**
 386          * Builds a node in the "/modules/..." namespace.
 387          *
 388          * <p>Called by {@link #findNode(String)} if a {@code /modules/...} node
 389          * is not present in the cache.
 390          */
 391         private Node buildModulesNode(String name) {
 392             assert name.startsWith(MODULES_ROOT + "/") : "Invalid module node name: " + name;



 393             // Returns null for non-directory resources, since the jimage name does not
 394             // start with "/modules" (e.g. "/java.base/java/lang/Object.class").
 395             ImageLocation loc = findLocation(name);
 396             if (loc != null) {
 397                 assert name.equals(loc.getFullName()) : "Mismatched location for directory: " + name;
 398                 assert isModulesSubdirectory(loc) : "Invalid modules directory: " + name;
 399                 return completeModuleDirectory(newDirectory(name), loc);
 400             }
 401             // Now try the non-prefixed resource name, but be careful to avoid false
 402             // positives for names like "/modules/modules/xxx" which could return a
 403             // location of a directory entry.
 404             loc = findLocation(name.substring(MODULES_ROOT.length()));
 405             return loc != null && isResource(loc) ? newResource(name, loc) : null;


 406         }
 407 
 408         /**
 409          * Builds a node in the "/packages/..." namespace.






























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

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

 461             }
 462             assert dir.isCompleted() : "Directory must be complete by now: " + dir;
 463         }
 464 
 465         /**
 466          * Completes a modules directory by setting the list of child nodes.
 467          *
 468          * <p>The given directory can be the top level {@code /modules} directory,
 469          * so it is NOT safe to use {@code isModulesSubdirectory(loc)} here.
 470          */
 471         private Directory completeModuleDirectory(Directory dir, ImageLocation loc) {
 472             assert dir.getName().equals(loc.getFullName()) : "Mismatched location for directory: " + dir;
 473             List<Node> children = createChildNodes(loc, childLoc -> {
 474                 if (isModulesSubdirectory(childLoc)) {
 475                     return nodes.computeIfAbsent(childLoc.getFullName(), this::newDirectory);








 476                 } else {

 477                     // Add "/modules" prefix to image location paths to get node names.
 478                     String resourceName = childLoc.getFullName(true);
 479                     return nodes.computeIfAbsent(resourceName, n -> newResource(n, childLoc));
 480                 }
 481             });

 482             dir.setChildren(children);
 483             return dir;
 484         }
 485 





















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


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






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





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


 528             IntBuffer offsets = getOffsetBuffer(loc);
 529             int childCount = offsets.capacity();
 530             List<Node> children = new ArrayList<>(childCount);
 531             for (int i = 0; i < childCount; i++) {
 532                 children.add(newChildFn.apply(getLocation(offsets.get(i))));



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

 799         }
 800 
 801         @Override
 802         boolean isCompleted() {
 803             return children != null;
 804         }
 805 
 806         @Override
 807         public boolean isDirectory() {
 808             return true;
 809         }
 810 
 811         @Override
 812         public Stream<String> getChildNames() {
 813             if (children != null) {
 814                 return children.stream().map(Node::getName);
 815             }
 816             throw new IllegalStateException("Cannot get child nodes of an incomplete directory: " + getName());
 817         }
 818 
 819         private void setChildren(List<Node> children) {
 820             assert this.children == null : this + ": Cannot set child nodes twice!";
 821             this.children = Collections.unmodifiableList(children);
 822         }
 823     }

 824     /**
 825      * Resource node (e.g. a ".class" entry, or any other data resource).
 826      *
 827      * <p>Resources are leaf nodes referencing an underlying image location. They
 828      * are lightweight, and do not cache their contents.
 829      *
 830      * <p>Unlike directories (where the node name matches the jimage path for the
 831      * corresponding {@code ImageLocation}), resource node names are NOT the same
 832      * as the corresponding jimage path. The difference is that node names for
 833      * resources are prefixed with "/modules", which is missing from the
 834      * equivalent jimage path.
 835      */
 836     private static class Resource extends Node {
 837         private final ImageLocation loc;
 838 
 839         private Resource(String name, ImageLocation loc, BasicFileAttributes fileAttrs) {
 840             super(name, fileAttrs);
 841             this.loc = loc;
 842         }
 843 

   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");

 200      *
 201      * @throws IOException if the content cannot be returned (including if the
 202      * given node is not a resource node).
 203      */
 204     public byte[] getResource(Node node) throws IOException {
 205         ensureOpen();
 206         return reader.getResource(node);
 207     }
 208 
 209     /**
 210      * Returns the content of a resource node in a newly allocated byte buffer.
 211      */
 212     public ByteBuffer getResourceBuffer(Node node) {
 213         requireOpen();
 214         if (!node.isResource()) {
 215             throw new IllegalArgumentException("Not a resource node: " + node);
 216         }
 217         return reader.getResourceBuffer(node.getLocation());
 218     }
 219 
 220     // Package protected for use only by SystemImageReader.
 221     ResourceEntries getResourceEntries() {
 222         return reader.getResourceEntries();
 223     }
 224 
 225     private static final class SharedImageReader extends BasicImageReader {



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



 268 
 269         // Preview mode support.
 270         private final boolean previewMode;
 271         // A relativized mapping from non-preview name to directories containing
 272         // preview-only nodes. This is used to add preview-only content to
 273         // directories as they are completed.
 274         private final HashMap<String, Directory> previewDirectoriesToMerge;
 275 
 276         private SharedImageReader(Path imagePath, ByteOrder byteOrder, boolean previewMode) throws IOException {
 277             super(imagePath, byteOrder);
 278             this.imageFileAttributes = Files.readAttributes(imagePath, BasicFileAttributes.class);
 279             this.nodes = new HashMap<>(INITIAL_NODE_CACHE_CAPACITY);
 280             this.previewMode = previewMode;



 281 
 282             // Node creation is very lazy, so we can just make the top-level directories
 283             // now without the risk of triggering the building of lots of other nodes.
 284             Directory packages = ensureCached(newDirectory(PACKAGES_PREFIX));
 285             Directory modules = ensureCached(newDirectory(MODULES_PREFIX));


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








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


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





 593                 String dirName = name.substring(0, packageEnd);
 594                 // If no parent exists here, the name cannot be valid.
 595                 Directory parent = (Directory) nodes.get(dirName);
 596                 if (parent != null) {
 597                     if (!parent.isCompleted()) {
 598                         // This caches all child links of the parent directory.
 599                         completePackageSubdirectory(parent, findLocation(dirName));


 600                     }
 601                     return nodes.get(name);
 602                 }
 603             }
 604             return null;
 605         }
 606 
 607         /** Completes a directory by ensuring its child list is populated correctly. */
 608         private void completeDirectory(Directory dir) {
 609             String name = dir.getName();
 610             // Since the node exists, we can assert that its name starts with
 611             // either "/modules" or "/packages", making differentiation easy.
 612             // It also means that the name is valid, so it must yield a location.
 613             assert name.startsWith(MODULES_PREFIX) || name.startsWith(PACKAGES_PREFIX);
 614             ImageLocation loc = findLocation(name);
 615             assert loc != null && name.equals(loc.getFullName()) : "Invalid location for name: " + name;
 616             LocationType type = loc.getType();
 617             if (type == MODULES_DIR || type == MODULES_ROOT) {


 618                 completeModuleDirectory(dir, loc);
 619             } else {
 620                 assert type == PACKAGES_DIR : "Invalid location type: " + loc;
 621                 completePackageSubdirectory(dir, loc);
 622             }
 623             assert dir.isCompleted() : "Directory must be complete by now: " + dir;
 624         }
 625 
 626         /** Completes a modules directory by setting the list of child nodes. */





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

















 686                 }
 687             }
 688             return Collections.emptyList();


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


























 732         /**
 733          * Creates an "incomplete" directory node with no child nodes set.
 734          * Directories need to be "completed" before they are returned by
 735          * {@link #findNode(String)}.
 736          */
 737         private Directory newDirectory(String name) {
 738             return new Directory(name, imageFileAttributes);
 739         }
 740 
 741         /**
 742          * Creates a new resource from an image location. This is the only case
 743          * where the image location name does not match the requested node name.
 744          * In image files, resource locations are NOT prefixed by {@code /modules}.
 745          */
 746         private Resource newResource(String name, ImageLocation loc) {

 747             return new Resource(name, loc, imageFileAttributes);
 748         }
 749 
 750         /**
 751          * Creates a new link node pointing at the given target name.
 752          *
 753          * <p>Note that target node is resolved each time {@code resolve()} is called,
 754          * so if a link node is retained after its reader is closed, it will fail.
 755          */
 756         private LinkNode newLinkNode(String name, String targetName) {
 757             return new LinkNode(name, () -> findNode(targetName), imageFileAttributes);
 758         }
 759 
 760         /** Returns the content of a resource node. */
 761         private byte[] getResource(Node node) throws IOException {
 762             // We could have been given a non-resource node here.
 763             if (node.isResource()) {
 764                 return super.getResource(node.getLocation());
 765             }
 766             throw new IOException("Not a resource: " + node);

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