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
|