1 /*
2 * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package jdk.internal.jimage;
26
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.UncheckedIOException;
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.attribute.BasicFileAttributes;
35 import java.nio.file.attribute.FileTime;
36 import java.nio.file.Path;
37 import java.util.ArrayList;
38 import java.util.Collections;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Objects;
44 import java.util.Set;
45 import java.util.function.Consumer;
46
47 /**
48 * @implNote This class needs to maintain JDK 8 source compatibility.
49 *
50 * It is used internally in the JDK to implement jimage/jrtfs access,
51 * but also compiled and delivered as part of the jrtfs.jar to support access
52 * to the jimage file provided by the shipped JDK by tools running on JDK 8.
53 */
54 public final class ImageReader implements AutoCloseable {
55 private final SharedImageReader reader;
56
57 private volatile boolean closed;
58
59 private ImageReader(SharedImageReader reader) {
60 this.reader = reader;
61 }
62
63 public static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException {
64 Objects.requireNonNull(imagePath);
65 Objects.requireNonNull(byteOrder);
66
67 return SharedImageReader.open(imagePath, byteOrder);
68 }
69
70 public static ImageReader open(Path imagePath) throws IOException {
71 return open(imagePath, ByteOrder.nativeOrder());
72 }
73
74 @Override
75 public void close() throws IOException {
76 if (closed) {
77 throw new IOException("image file already closed");
78 }
79 reader.close(this);
80 closed = true;
81 }
82
83 private void ensureOpen() throws IOException {
84 if (closed) {
85 throw new IOException("image file closed");
86 }
87 }
88
89 private void requireOpen() {
90 if (closed) {
91 throw new IllegalStateException("image file closed");
92 }
93 }
94
95 // directory management interface
96 public Directory getRootDirectory() throws IOException {
97 ensureOpen();
98 return reader.getRootDirectory();
99 }
100
101
102 public Node findNode(String name) throws IOException {
103 ensureOpen();
104 return reader.findNode(name);
105 }
106
107 public byte[] getResource(Node node) throws IOException {
108 ensureOpen();
109 return reader.getResource(node);
110 }
111
112 public byte[] getResource(Resource rs) throws IOException {
113 ensureOpen();
114 return reader.getResource(rs);
115 }
116
117 public ImageHeader getHeader() {
118 requireOpen();
119 return reader.getHeader();
120 }
121
122 public static void releaseByteBuffer(ByteBuffer buffer) {
123 BasicImageReader.releaseByteBuffer(buffer);
124 }
125
126 public String getName() {
127 requireOpen();
128 return reader.getName();
129 }
130
131 public ByteOrder getByteOrder() {
132 requireOpen();
133 return reader.getByteOrder();
134 }
135
136 public Path getImagePath() {
137 requireOpen();
138 return reader.getImagePath();
139 }
140
141 public ImageStringsReader getStrings() {
142 requireOpen();
143 return reader.getStrings();
144 }
145
146 public ImageLocation findLocation(String mn, String rn) {
147 requireOpen();
148 return reader.findLocation(mn, rn);
149 }
150
151 public boolean verifyLocation(String mn, String rn) {
152 requireOpen();
153 return reader.verifyLocation(mn, rn);
154 }
155
156 public ImageLocation findLocation(String name) {
157 requireOpen();
158 return reader.findLocation(name);
159 }
160
161 public String[] getEntryNames() {
162 requireOpen();
163 return reader.getEntryNames();
164 }
165
166 public String[] getModuleNames() {
167 requireOpen();
168 int off = "/modules/".length();
169 return reader.findNode("/modules")
170 .getChildren()
171 .stream()
172 .map(Node::getNameString)
173 .map(s -> s.substring(off, s.length()))
174 .toArray(String[]::new);
175 }
176
177 public long[] getAttributes(int offset) {
178 requireOpen();
179 return reader.getAttributes(offset);
180 }
181
182 public String getString(int offset) {
183 requireOpen();
184 return reader.getString(offset);
185 }
186
187 public byte[] getResource(String name) {
188 requireOpen();
189 return reader.getResource(name);
190 }
191
192 public byte[] getResource(ImageLocation loc) {
193 requireOpen();
194 return reader.getResource(loc);
195 }
196
197 public ByteBuffer getResourceBuffer(ImageLocation loc) {
198 requireOpen();
199 return reader.getResourceBuffer(loc);
200 }
201
202 public InputStream getResourceStream(ImageLocation loc) {
203 requireOpen();
204 return reader.getResourceStream(loc);
205 }
206
207 private static final class SharedImageReader extends BasicImageReader {
208 static final int SIZE_OF_OFFSET = Integer.BYTES;
209
210 static final Map<Path, SharedImageReader> OPEN_FILES = new HashMap<>();
211
212 // List of openers for this shared image.
213 final Set<ImageReader> openers;
214
215 // attributes of the .jimage file. 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 // Iniitalized lazily, see {@link #imageFileAttributes()}.
219 BasicFileAttributes imageFileAttributes;
220
221 // directory management implementation
222 final HashMap<String, Node> nodes;
223 volatile Directory rootDir;
224
225 Directory packagesDir;
226 Directory modulesDir;
227
228 private SharedImageReader(Path imagePath, ByteOrder byteOrder) throws IOException {
229 super(imagePath, byteOrder);
230 this.openers = new HashSet<>();
231 this.nodes = new HashMap<>();
232 }
233
234 public static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException {
235 Objects.requireNonNull(imagePath);
236 Objects.requireNonNull(byteOrder);
237
238 synchronized (OPEN_FILES) {
239 SharedImageReader reader = OPEN_FILES.get(imagePath);
240
241 if (reader == null) {
242 // Will fail with an IOException if wrong byteOrder.
243 reader = new SharedImageReader(imagePath, byteOrder);
244 OPEN_FILES.put(imagePath, reader);
245 } else if (reader.getByteOrder() != byteOrder) {
246 throw new IOException("\"" + reader.getName() + "\" is not an image file");
247 }
248
249 ImageReader image = new ImageReader(reader);
250 reader.openers.add(image);
251
252 return image;
253 }
254 }
255
256 public void close(ImageReader image) throws IOException {
257 Objects.requireNonNull(image);
258
259 synchronized (OPEN_FILES) {
260 if (!openers.remove(image)) {
261 throw new IOException("image file already closed");
262 }
263
264 if (openers.isEmpty()) {
265 close();
266 nodes.clear();
267 rootDir = null;
268
269 if (!OPEN_FILES.remove(this.getImagePath(), this)) {
270 throw new IOException("image file not found in open list");
271 }
272 }
273 }
274 }
275
276 void addOpener(ImageReader reader) {
277 synchronized (OPEN_FILES) {
278 openers.add(reader);
279 }
280 }
281
282 boolean removeOpener(ImageReader reader) {
283 synchronized (OPEN_FILES) {
284 return openers.remove(reader);
285 }
286 }
287
288 // directory management interface
289 Directory getRootDirectory() {
290 return buildRootDirectory();
291 }
292
293 /**
294 * Lazily build a node from a name.
295 */
296 synchronized Node buildNode(String name) {
297 Node n;
298 boolean isPackages = name.startsWith("/packages");
299 boolean isModules = !isPackages && name.startsWith("/modules");
300
301 if (!(isModules || isPackages)) {
302 return null;
303 }
304
305 ImageLocation loc = findLocation(name);
306
307 if (loc != null) { // A sub tree node
308 if (isPackages) {
309 n = handlePackages(name, loc);
310 } else { // modules sub tree
311 n = handleModulesSubTree(name, loc);
312 }
313 } else { // Asking for a resource? /modules/java.base/java/lang/Object.class
314 if (isModules) {
315 n = handleResource(name);
316 } else {
317 // Possibly ask for /packages/java.lang/java.base
318 // although /packages/java.base not created
319 n = handleModuleLink(name);
320 }
321 }
322 return n;
323 }
324
325 synchronized Directory buildRootDirectory() {
326 Directory root = rootDir; // volatile read
327 if (root != null) {
328 return root;
329 }
330
331 root = newDirectory(null, "/");
332 root.setIsRootDir();
333
334 // /packages dir
335 packagesDir = newDirectory(root, "/packages");
336 packagesDir.setIsPackagesDir();
337
338 // /modules dir
339 modulesDir = newDirectory(root, "/modules");
340 modulesDir.setIsModulesDir();
341
342 root.setCompleted(true);
343 return rootDir = root;
344 }
345
346 /**
347 * To visit sub tree resources.
348 */
349 interface LocationVisitor {
350 void visit(ImageLocation loc);
351 }
352
353 void visitLocation(ImageLocation loc, LocationVisitor visitor) {
354 byte[] offsets = getResource(loc);
355 ByteBuffer buffer = ByteBuffer.wrap(offsets);
356 buffer.order(getByteOrder());
357 IntBuffer intBuffer = buffer.asIntBuffer();
358 for (int i = 0; i < offsets.length / SIZE_OF_OFFSET; i++) {
359 int offset = intBuffer.get(i);
360 ImageLocation pkgLoc = getLocation(offset);
361 visitor.visit(pkgLoc);
362 }
363 }
364
365 void visitPackageLocation(ImageLocation loc) {
366 // Retrieve package name
367 String pkgName = getBaseExt(loc);
368 // Content is array of offsets in Strings table
369 byte[] stringsOffsets = getResource(loc);
370 ByteBuffer buffer = ByteBuffer.wrap(stringsOffsets);
371 buffer.order(getByteOrder());
372 IntBuffer intBuffer = buffer.asIntBuffer();
373 // For each module, create a link node.
374 for (int i = 0; i < stringsOffsets.length / SIZE_OF_OFFSET; i++) {
375 // skip empty state, useless.
376 intBuffer.get(i);
377 i++;
378 int offset = intBuffer.get(i);
379 String moduleName = getString(offset);
380 Node targetNode = findNode("/modules/" + moduleName);
381 if (targetNode != null) {
382 String pkgDirName = packagesDir.getName() + "/" + pkgName;
383 Directory pkgDir = (Directory) nodes.get(pkgDirName);
384 newLinkNode(pkgDir, pkgDir.getName() + "/" + moduleName, targetNode);
385 }
386 }
387 }
388
389 Node handlePackages(String name, ImageLocation loc) {
390 long size = loc.getUncompressedSize();
391 Node n = null;
392 // Only possibilities are /packages, /packages/package/module
393 if (name.equals("/packages")) {
394 visitLocation(loc, (childloc) -> {
395 findNode(childloc.getFullName());
396 });
397 packagesDir.setCompleted(true);
398 n = packagesDir;
399 } else {
400 if (size != 0) { // children are offsets to module in StringsTable
401 String pkgName = getBaseExt(loc);
402 Directory pkgDir = newDirectory(packagesDir, packagesDir.getName() + "/" + pkgName);
403 visitPackageLocation(loc);
404 pkgDir.setCompleted(true);
405 n = pkgDir;
406 } else { // Link to module
407 String pkgName = loc.getParent();
408 String modName = getBaseExt(loc);
409 Node targetNode = findNode("/modules/" + modName);
410 if (targetNode != null) {
411 String pkgDirName = packagesDir.getName() + "/" + pkgName;
412 Directory pkgDir = (Directory) nodes.get(pkgDirName);
413 Node linkNode = newLinkNode(pkgDir, pkgDir.getName() + "/" + modName, targetNode);
414 n = linkNode;
415 }
416 }
417 }
418 return n;
419 }
420
421 // Asking for /packages/package/module although
422 // /packages/<pkg>/ not yet created, need to create it
423 // prior to return the link to module node.
424 Node handleModuleLink(String name) {
425 // eg: unresolved /packages/package/module
426 // Build /packages/package node
427 Node ret = null;
428 String radical = "/packages/";
429 String path = name;
430 if (path.startsWith(radical)) {
431 int start = radical.length();
432 int pkgEnd = path.indexOf('/', start);
433 if (pkgEnd != -1) {
434 String pkg = path.substring(start, pkgEnd);
435 String pkgPath = radical + pkg;
436 Node n = findNode(pkgPath);
437 // If not found means that this is a symbolic link such as:
438 // /packages/java.util/java.base/java/util/Vector.class
439 // and will be done by a retry of the filesystem
440 for (Node child : n.getChildren()) {
441 if (child.name.equals(name)) {
442 ret = child;
443 break;
444 }
445 }
446 }
447 }
448 return ret;
449 }
450
451 Node handleModulesSubTree(String name, ImageLocation loc) {
452 Node n;
453 assert (name.equals(loc.getFullName()));
454 Directory dir = makeDirectories(name);
455 visitLocation(loc, (childloc) -> {
456 String path = childloc.getFullName();
457 if (path.startsWith("/modules")) { // a package
458 makeDirectories(path);
459 } else { // a resource
460 makeDirectories(childloc.buildName(true, true, false));
461 // if we have already created a resource for this name previously, then don't
462 // recreate it
463 if (!nodes.containsKey(childloc.getFullName(true))) {
464 newResource(dir, childloc);
465 }
466 }
467 });
468 dir.setCompleted(true);
469 n = dir;
470 return n;
471 }
472
473 Node handleResource(String name) {
474 Node n = null;
475 if (!name.startsWith("/modules/")) {
476 return null;
477 }
478 // Make sure that the thing that follows "/modules/" is a module name.
479 int moduleEndIndex = name.indexOf('/', "/modules/".length());
480 if (moduleEndIndex == -1) {
481 return null;
482 }
483 ImageLocation moduleLoc = findLocation(name.substring(0, moduleEndIndex));
484 if (moduleLoc == null || moduleLoc.getModuleOffset() == 0) {
485 return null;
486 }
487
488 String locationPath = name.substring("/modules".length());
489 ImageLocation resourceLoc = findLocation(locationPath);
490 if (resourceLoc != null) {
491 Directory dir = makeDirectories(resourceLoc.buildName(true, true, false));
492 Resource res = newResource(dir, resourceLoc);
493 n = res;
494 }
495 return n;
496 }
497
498 String getBaseExt(ImageLocation loc) {
499 String base = loc.getBase();
500 String ext = loc.getExtension();
501 if (ext != null && !ext.isEmpty()) {
502 base = base + "." + ext;
503 }
504 return base;
505 }
506
507 synchronized Node findNode(String name) {
508 buildRootDirectory();
509 Node n = nodes.get(name);
510 if (n == null || !n.isCompleted()) {
511 n = buildNode(name);
512 }
513 return n;
514 }
515
516 /**
517 * Returns the file attributes of the image file.
518 */
519 BasicFileAttributes imageFileAttributes() {
520 BasicFileAttributes attrs = imageFileAttributes;
521 if (attrs == null) {
522 try {
523 Path file = getImagePath();
524 attrs = Files.readAttributes(file, BasicFileAttributes.class);
525 } catch (IOException ioe) {
526 throw new UncheckedIOException(ioe);
527 }
528 imageFileAttributes = attrs;
529 }
530 return attrs;
531 }
532
533 Directory newDirectory(Directory parent, String name) {
534 Directory dir = Directory.create(parent, name, imageFileAttributes());
535 nodes.put(dir.getName(), dir);
536 return dir;
537 }
538
539 Resource newResource(Directory parent, ImageLocation loc) {
540 Resource res = Resource.create(parent, loc, imageFileAttributes());
541 nodes.put(res.getName(), res);
542 return res;
543 }
544
545 LinkNode newLinkNode(Directory dir, String name, Node link) {
546 LinkNode linkNode = LinkNode.create(dir, name, link);
547 nodes.put(linkNode.getName(), linkNode);
548 return linkNode;
549 }
550
551 Directory makeDirectories(String parent) {
552 Directory last = rootDir;
553 for (int offset = parent.indexOf('/', 1);
554 offset != -1;
555 offset = parent.indexOf('/', offset + 1)) {
556 String dir = parent.substring(0, offset);
557 last = makeDirectory(dir, last);
558 }
559 return makeDirectory(parent, last);
560
561 }
562
563 Directory makeDirectory(String dir, Directory last) {
564 Directory nextDir = (Directory) nodes.get(dir);
565 if (nextDir == null) {
566 nextDir = newDirectory(last, dir);
567 }
568 return nextDir;
569 }
570
571 byte[] getResource(Node node) throws IOException {
572 if (node.isResource()) {
573 return super.getResource(node.getLocation());
574 }
575 throw new IOException("Not a resource: " + node);
576 }
577
578 byte[] getResource(Resource rs) throws IOException {
579 return super.getResource(rs.getLocation());
580 }
581 }
582
583 // jimage file does not store directory structure. We build nodes
584 // using the "path" strings found in the jimage file.
585 // Node can be a directory or a resource
586 public abstract static class Node {
587 private static final int ROOT_DIR = 0b0000_0000_0000_0001;
588 private static final int PACKAGES_DIR = 0b0000_0000_0000_0010;
589 private static final int MODULES_DIR = 0b0000_0000_0000_0100;
590
591 private int flags;
592 private final String name;
593 private final BasicFileAttributes fileAttrs;
594 private boolean completed;
595
596 protected Node(String name, BasicFileAttributes fileAttrs) {
597 this.name = Objects.requireNonNull(name);
598 this.fileAttrs = Objects.requireNonNull(fileAttrs);
599 }
600
601 /**
602 * A node is completed when all its direct children have been built.
603 *
604 * @return
605 */
606 public boolean isCompleted() {
607 return completed;
608 }
609
610 public void setCompleted(boolean completed) {
611 this.completed = completed;
612 }
613
614 public final void setIsRootDir() {
615 flags |= ROOT_DIR;
616 }
617
618 public final boolean isRootDir() {
619 return (flags & ROOT_DIR) != 0;
620 }
621
622 public final void setIsPackagesDir() {
623 flags |= PACKAGES_DIR;
624 }
625
626 public final boolean isPackagesDir() {
627 return (flags & PACKAGES_DIR) != 0;
628 }
629
630 public final void setIsModulesDir() {
631 flags |= MODULES_DIR;
632 }
633
634 public final boolean isModulesDir() {
635 return (flags & MODULES_DIR) != 0;
636 }
637
638 public final String getName() {
639 return name;
640 }
641
642 public final BasicFileAttributes getFileAttributes() {
643 return fileAttrs;
644 }
645
646 // resolve this Node (if this is a soft link, get underlying Node)
647 public final Node resolveLink() {
648 return resolveLink(false);
649 }
650
651 public Node resolveLink(boolean recursive) {
652 return this;
653 }
654
655 // is this a soft link Node?
656 public boolean isLink() {
657 return false;
658 }
659
660 public boolean isDirectory() {
661 return false;
662 }
663
664 public List<Node> getChildren() {
665 throw new IllegalArgumentException("not a directory: " + getNameString());
666 }
667
668 public boolean isResource() {
669 return false;
670 }
671
672 public ImageLocation getLocation() {
673 throw new IllegalArgumentException("not a resource: " + getNameString());
674 }
675
676 public long size() {
677 return 0L;
678 }
679
680 public long compressedSize() {
681 return 0L;
682 }
683
684 public String extension() {
685 return null;
686 }
687
688 public long contentOffset() {
689 return 0L;
690 }
691
692 public final FileTime creationTime() {
693 return fileAttrs.creationTime();
694 }
695
696 public final FileTime lastAccessTime() {
697 return fileAttrs.lastAccessTime();
698 }
699
700 public final FileTime lastModifiedTime() {
701 return fileAttrs.lastModifiedTime();
702 }
703
704 public final String getNameString() {
705 return name;
706 }
707
708 @Override
709 public final String toString() {
710 return getNameString();
711 }
712
713 @Override
714 public final int hashCode() {
715 return name.hashCode();
716 }
717
718 @Override
719 public final boolean equals(Object other) {
720 if (this == other) {
721 return true;
722 }
723
724 if (other instanceof Node) {
725 return name.equals(((Node) other).name);
726 }
727
728 return false;
729 }
730 }
731
732 // directory node - directory has full path name without '/' at end.
733 static final class Directory extends Node {
734 private final List<Node> children;
735
736 private Directory(String name, BasicFileAttributes fileAttrs) {
737 super(name, fileAttrs);
738 children = new ArrayList<>();
739 }
740
741 static Directory create(Directory parent, String name, BasicFileAttributes fileAttrs) {
742 Directory d = new Directory(name, fileAttrs);
743 if (parent != null) {
744 parent.addChild(d);
745 }
746 return d;
747 }
748
749 @Override
750 public boolean isDirectory() {
751 return true;
752 }
753
754 @Override
755 public List<Node> getChildren() {
756 return Collections.unmodifiableList(children);
757 }
758
759 void addChild(Node node) {
760 assert !children.contains(node) : "Child " + node + " already added";
761 children.add(node);
762 }
763
764 public void walk(Consumer<? super Node> consumer) {
765 consumer.accept(this);
766 for (Node child : children) {
767 if (child.isDirectory()) {
768 ((Directory)child).walk(consumer);
769 } else {
770 consumer.accept(child);
771 }
772 }
773 }
774 }
775
776 // "resource" is .class or any other resource (compressed/uncompressed) in a jimage.
777 // full path of the resource is the "name" of the resource.
778 static class Resource extends Node {
779 private final ImageLocation loc;
780
781 private Resource(ImageLocation loc, BasicFileAttributes fileAttrs) {
782 super(loc.getFullName(true), fileAttrs);
783 this.loc = loc;
784 }
785
786 static Resource create(Directory parent, ImageLocation loc, BasicFileAttributes fileAttrs) {
787 Resource rs = new Resource(loc, fileAttrs);
788 parent.addChild(rs);
789 return rs;
790 }
791
792 @Override
793 public boolean isCompleted() {
794 return true;
795 }
796
797 @Override
798 public boolean isResource() {
799 return true;
800 }
801
802 @Override
803 public ImageLocation getLocation() {
804 return loc;
805 }
806
807 @Override
808 public long size() {
809 return loc.getUncompressedSize();
810 }
811
812 @Override
813 public long compressedSize() {
814 return loc.getCompressedSize();
815 }
816
817 @Override
818 public String extension() {
819 return loc.getExtension();
820 }
821
822 @Override
823 public long contentOffset() {
824 return loc.getContentOffset();
825 }
826 }
827
828 // represents a soft link to another Node
829 static class LinkNode extends Node {
830 private final Node link;
831
832 private LinkNode(String name, Node link) {
833 super(name, link.getFileAttributes());
834 this.link = link;
835 }
836
837 static LinkNode create(Directory parent, String name, Node link) {
838 LinkNode ln = new LinkNode(name, link);
839 parent.addChild(ln);
840 return ln;
841 }
842
843 @Override
844 public boolean isCompleted() {
845 return true;
846 }
847
848 @Override
849 public Node resolveLink(boolean recursive) {
850 return (recursive && link instanceof LinkNode) ? ((LinkNode)link).resolveLink(true) : link;
851 }
852
853 @Override
854 public boolean isLink() {
855 return true;
856 }
857 }
858 }
|
1 /*
2 * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package jdk.internal.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
67 * parent directory.
68 *
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");
125 }
126 }
127
128 /**
129 * Finds the node with the given name.
130 *
131 * @param name a node name of the form {@code "/modules/<module>/...} or
132 * {@code "/packages/<package>/...}.
133 * @return a node representing a resource, directory or symbolic link.
134 */
135 public Node findNode(String name) throws IOException {
136 ensureOpen();
137 return reader.findNode(name);
138 }
139
140 /**
141 * Returns a copy of the content of a resource node. The buffer returned by
142 * this method is not cached by the node, and each call returns a new array
143 * instance.
144 *
145 * @throws IOException if the content cannot be returned (including if the
146 * given node is not a resource node).
147 */
148 public byte[] getResource(Node node) throws IOException {
149 ensureOpen();
150 return reader.getResource(node);
151 }
152
153 /**
154 * Releases a (possibly cached) {@link ByteBuffer} obtained via
155 * {@link #getResourceBuffer(Node)}.
156 *
157 * <p>Note that no testing is performed to check whether the buffer about
158 * to be released actually came from a call to {@code getResourceBuffer()}.
159 */
160 public static void releaseByteBuffer(ByteBuffer buffer) {
161 BasicImageReader.releaseByteBuffer(buffer);
162 }
163
164 /**
165 * Returns the content of a resource node in a possibly cached byte buffer.
166 * Callers of this method must call {@link #releaseByteBuffer(ByteBuffer)}
167 * when they are finished with it.
168 */
169 public ByteBuffer getResourceBuffer(Node node) {
170 requireOpen();
171 if (!node.isResource()) {
172 throw new IllegalArgumentException("Not a resource node: " + node);
173 }
174 return reader.getResourceBuffer(node.getLocation());
175 }
176
177 private static final class SharedImageReader extends BasicImageReader {
178 private static final Map<Path, SharedImageReader> OPEN_FILES = new HashMap<>();
179 private static final String MODULES_ROOT = "/modules";
180 private static final String PACKAGES_ROOT = "/packages";
181 // There are >30,000 nodes in a complete jimage tree, and even relatively
182 // common tasks (e.g. starting up javac) load somewhere in the region of
183 // 1000 classes. Thus, an initial capacity of 2000 is a reasonable guess.
184 private static final int INITIAL_NODE_CACHE_CAPACITY = 2000;
185
186 // List of openers for this shared image.
187 private final Set<ImageReader> openers = new HashSet<>();
188
189 // Attributes of the jimage file. The jimage file does not contain
190 // attributes for the individual resources (yet). We use attributes
191 // of the jimage file itself (creation, modification, access times).
192 private final BasicFileAttributes imageFileAttributes;
193
194 // Cache of all user visible nodes, guarded by synchronizing 'this' instance.
195 private final Map<String, Node> nodes;
196 // Used to classify ImageLocation instances without string comparison.
197 private final int modulesStringOffset;
198 private final int packagesStringOffset;
199
200 private SharedImageReader(Path imagePath, ByteOrder byteOrder) throws IOException {
201 super(imagePath, byteOrder);
202 this.imageFileAttributes = Files.readAttributes(imagePath, BasicFileAttributes.class);
203 this.nodes = new HashMap<>(INITIAL_NODE_CACHE_CAPACITY);
204 // Pick stable jimage names from which to extract string offsets (we cannot
205 // use "/modules" or "/packages", since those have a module offset of zero).
206 this.modulesStringOffset = getModuleOffset("/modules/java.base");
207 this.packagesStringOffset = getModuleOffset("/packages/java.lang");
208
209 // Node creation is very lazy, so we can just make the top-level directories
210 // now without the risk of triggering the building of lots of other nodes.
211 Directory packages = newDirectory(PACKAGES_ROOT);
212 nodes.put(packages.getName(), packages);
213 Directory modules = newDirectory(MODULES_ROOT);
214 nodes.put(modules.getName(), modules);
215
216 Directory root = newDirectory("/");
217 root.setChildren(Arrays.asList(packages, modules));
218 nodes.put(root.getName(), root);
219 }
220
221 /**
222 * Returns the offset of the string denoting the leading "module" segment in
223 * the given path (e.g. {@code <module>/<path>}). We can't just pass in the
224 * {@code /<module>} string here because that has a module offset of zero.
225 */
226 private int getModuleOffset(String path) {
227 ImageLocation location = findLocation(path);
228 assert location != null : "Cannot find expected jimage location: " + path;
229 int offset = location.getModuleOffset();
230 assert offset != 0 : "Invalid module offset for jimage location: " + path;
231 return offset;
232 }
233
234 private static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException {
235 Objects.requireNonNull(imagePath);
236 Objects.requireNonNull(byteOrder);
237
238 synchronized (OPEN_FILES) {
239 SharedImageReader reader = OPEN_FILES.get(imagePath);
240
241 if (reader == null) {
242 // Will fail with an IOException if wrong byteOrder.
243 reader = new SharedImageReader(imagePath, byteOrder);
244 OPEN_FILES.put(imagePath, reader);
245 } else if (reader.getByteOrder() != byteOrder) {
246 throw new IOException("\"" + reader.getName() + "\" is not an image file");
247 }
248
249 ImageReader image = new ImageReader(reader);
250 reader.openers.add(image);
251
252 return image;
253 }
254 }
255
256 public void close(ImageReader image) throws IOException {
257 Objects.requireNonNull(image);
258
259 synchronized (OPEN_FILES) {
260 if (!openers.remove(image)) {
261 throw new IOException("image file already closed");
262 }
263
264 if (openers.isEmpty()) {
265 close();
266 nodes.clear();
267
268 if (!OPEN_FILES.remove(this.getImagePath(), this)) {
269 throw new IOException("image file not found in open list");
270 }
271 }
272 }
273 }
274
275 /**
276 * Returns a node with the given name, or null if no resource or directory of
277 * that name exists.
278 *
279 * <p>This is the only public API by which anything outside this class can access
280 * {@code Node} instances either directly, or by resolving symbolic links.
281 *
282 * <p>Note also that there is no reentrant calling back to this method from within
283 * the node handling code.
284 *
285 * @param name an absolute, {@code /}-separated path string, prefixed with either
286 * "/modules" or "/packages".
287 */
288 synchronized Node findNode(String name) {
289 Node node = nodes.get(name);
290 if (node == null) {
291 // We cannot get the root paths ("/modules" or "/packages") here
292 // because those nodes are already in the nodes cache.
293 if (name.startsWith(MODULES_ROOT + "/")) {
294 node = buildModulesNode(name);
295 } else if (name.startsWith(PACKAGES_ROOT + "/")) {
296 node = buildPackagesNode(name);
297 }
298 if (node != null) {
299 nodes.put(node.getName(), node);
300 }
301 } else if (!node.isCompleted()) {
302 // Only directories can be incomplete.
303 assert node instanceof Directory : "Invalid incomplete node: " + node;
304 completeDirectory((Directory) node);
305 }
306 assert node == null || node.isCompleted() : "Incomplete node: " + node;
307 return node;
308 }
309
310 /**
311 * Builds a node in the "/modules/..." namespace.
312 *
313 * <p>Called by {@link #findNode(String)} if a {@code /modules/...} node
314 * is not present in the cache.
315 */
316 private Node buildModulesNode(String name) {
317 assert name.startsWith(MODULES_ROOT + "/") : "Invalid module node name: " + name;
318 // Returns null for non-directory resources, since the jimage name does not
319 // start with "/modules" (e.g. "/java.base/java/lang/Object.class").
320 ImageLocation loc = findLocation(name);
321 if (loc != null) {
322 assert name.equals(loc.getFullName()) : "Mismatched location for directory: " + name;
323 assert isModulesSubdirectory(loc) : "Invalid modules directory: " + name;
324 return completeModuleDirectory(newDirectory(name), loc);
325 }
326 // Now try the non-prefixed resource name, but be careful to avoid false
327 // positives for names like "/modules/modules/xxx" which could return a
328 // location of a directory entry.
329 loc = findLocation(name.substring(MODULES_ROOT.length()));
330 return loc != null && isResource(loc) ? newResource(name, loc) : null;
331 }
332
333 /**
334 * Builds a node in the "/packages/..." namespace.
335 *
336 * <p>Called by {@link #findNode(String)} if a {@code /packages/...} node
337 * is not present in the cache.
338 */
339 private Node buildPackagesNode(String name) {
340 // There are only locations for the root "/packages" or "/packages/xxx"
341 // directories, but not the symbolic links below them (the links can be
342 // entirely derived from the name information in the parent directory).
343 // However, unlike resources this means that we do not have a constant
344 // time lookup for link nodes when creating them.
345 int packageStart = PACKAGES_ROOT.length() + 1;
346 int packageEnd = name.indexOf('/', packageStart);
347 if (packageEnd == -1) {
348 ImageLocation loc = findLocation(name);
349 return loc != null ? completePackageDirectory(newDirectory(name), loc) : null;
350 } else {
351 // We cannot assume that the parent directory exists for a link node, since
352 // the given name is untrusted and could reference a non-existent link.
353 // However, if the parent directory is present, we can conclude that the
354 // given name was not a valid link (or else it would already be cached).
355 String dirName = name.substring(0, packageEnd);
356 if (!nodes.containsKey(dirName)) {
357 ImageLocation loc = findLocation(dirName);
358 // If the parent location doesn't exist, the link node cannot exist.
359 if (loc != null) {
360 nodes.put(dirName, completePackageDirectory(newDirectory(dirName), loc));
361 // When the parent is created its child nodes are created and cached,
362 // but this can still return null if given name wasn't a valid link.
363 return nodes.get(name);
364 }
365 }
366 }
367 return null;
368 }
369
370 /** Completes a directory by ensuring its child list is populated correctly. */
371 private void completeDirectory(Directory dir) {
372 String name = dir.getName();
373 // Since the node exists, we can assert that its name starts with
374 // either "/modules" or "/packages", making differentiation easy.
375 // It also means that the name is valid, so it must yield a location.
376 assert name.startsWith(MODULES_ROOT) || name.startsWith(PACKAGES_ROOT);
377 ImageLocation loc = findLocation(name);
378 assert loc != null && name.equals(loc.getFullName()) : "Invalid location for name: " + name;
379 // We cannot use 'isXxxSubdirectory()' methods here since we could
380 // be given a top-level directory (for which that test doesn't work).
381 // The string MUST start "/modules" or "/packages" here.
382 if (name.charAt(1) == 'm') {
383 completeModuleDirectory(dir, loc);
384 } else {
385 completePackageDirectory(dir, loc);
386 }
387 assert dir.isCompleted() : "Directory must be complete by now: " + dir;
388 }
389
390 /**
391 * Completes a modules directory by setting the list of child nodes.
392 *
393 * <p>The given directory can be the top level {@code /modules} directory,
394 * so it is NOT safe to use {@code isModulesSubdirectory(loc)} here.
395 */
396 private Directory completeModuleDirectory(Directory dir, ImageLocation loc) {
397 assert dir.getName().equals(loc.getFullName()) : "Mismatched location for directory: " + dir;
398 List<Node> children = createChildNodes(loc, childLoc -> {
399 if (isModulesSubdirectory(childLoc)) {
400 return nodes.computeIfAbsent(childLoc.getFullName(), this::newDirectory);
401 } else {
402 // Add "/modules" prefix to image location paths to get node names.
403 String resourceName = childLoc.getFullName(true);
404 return nodes.computeIfAbsent(resourceName, n -> newResource(n, childLoc));
405 }
406 });
407 dir.setChildren(children);
408 return dir;
409 }
410
411 /**
412 * Completes a package directory by setting the list of child nodes.
413 *
414 * <p>The given directory can be the top level {@code /packages} directory,
415 * so it is NOT safe to use {@code isPackagesSubdirectory(loc)} here.
416 */
417 private Directory completePackageDirectory(Directory dir, ImageLocation loc) {
418 assert dir.getName().equals(loc.getFullName()) : "Mismatched location for directory: " + dir;
419 // The only directories in the "/packages" namespace are "/packages" or
420 // "/packages/<package>". However, unlike "/modules" directories, the
421 // location offsets mean different things.
422 List<Node> children;
423 if (dir.getName().equals(PACKAGES_ROOT)) {
424 // Top-level directory just contains a list of subdirectories.
425 children = createChildNodes(loc, c -> nodes.computeIfAbsent(c.getFullName(), this::newDirectory));
426 } else {
427 // A package directory's content is array of offset PAIRS in the
428 // Strings table, but we only need the 2nd value of each pair.
429 IntBuffer intBuffer = getOffsetBuffer(loc);
430 int offsetCount = intBuffer.capacity();
431 assert (offsetCount & 0x1) == 0 : "Offset count must be even: " + offsetCount;
432 children = new ArrayList<>(offsetCount / 2);
433 // Iterate the 2nd offset in each pair (odd indices).
434 for (int i = 1; i < offsetCount; i += 2) {
435 String moduleName = getString(intBuffer.get(i));
436 children.add(nodes.computeIfAbsent(
437 dir.getName() + "/" + moduleName,
438 n -> newLinkNode(n, MODULES_ROOT + "/" + moduleName)));
439 }
440 }
441 // This only happens once and "completes" the directory.
442 dir.setChildren(children);
443 return dir;
444 }
445
446 /**
447 * Creates the list of child nodes for a {@code Directory} based on a given
448 *
449 * <p>Note: This cannot be used for package subdirectories as they have
450 * child offsets stored differently to other directories.
451 */
452 private List<Node> createChildNodes(ImageLocation loc, Function<ImageLocation, Node> newChildFn) {
453 IntBuffer offsets = getOffsetBuffer(loc);
454 int childCount = offsets.capacity();
455 List<Node> children = new ArrayList<>(childCount);
456 for (int i = 0; i < childCount; i++) {
457 children.add(newChildFn.apply(getLocation(offsets.get(i))));
458 }
459 return children;
460 }
461
462 /** Helper to extract the integer offset buffer from a directory location. */
463 private IntBuffer getOffsetBuffer(ImageLocation dir) {
464 assert !isResource(dir) : "Not a directory: " + dir.getFullName();
465 byte[] offsets = getResource(dir);
466 ByteBuffer buffer = ByteBuffer.wrap(offsets);
467 buffer.order(getByteOrder());
468 return buffer.asIntBuffer();
469 }
470
471 /**
472 * Efficiently determines if an image location is a resource.
473 *
474 * <p>A resource must have a valid module associated with it, so its
475 * module offset must be non-zero, and not equal to the offsets for
476 * "/modules/..." or "/packages/..." entries.
477 */
478 private boolean isResource(ImageLocation loc) {
479 int moduleOffset = loc.getModuleOffset();
480 return moduleOffset != 0
481 && moduleOffset != modulesStringOffset
482 && moduleOffset != packagesStringOffset;
483 }
484
485 /**
486 * Determines if an image location is a directory in the {@code /modules}
487 * namespace (if so, the location name is the node name).
488 *
489 * <p>In jimage, every {@code ImageLocation} under {@code /modules/} is a
490 * directory and has the same value for {@code getModule()}, and {@code
491 * getModuleOffset()}.
492 */
493 private boolean isModulesSubdirectory(ImageLocation loc) {
494 return loc.getModuleOffset() == modulesStringOffset;
495 }
496
497 /**
498 * Creates an "incomplete" directory node with no child nodes set.
499 * Directories need to be "completed" before they are returned by
500 * {@link #findNode(String)}.
501 */
502 private Directory newDirectory(String name) {
503 return new Directory(name, imageFileAttributes);
504 }
505
506 /**
507 * Creates a new resource from an image location. This is the only case
508 * where the image location name does not match the requested node name.
509 * In image files, resource locations are NOT prefixed by {@code /modules}.
510 */
511 private Resource newResource(String name, ImageLocation loc) {
512 assert name.equals(loc.getFullName(true)) : "Mismatched location for resource: " + name;
513 return new Resource(name, loc, imageFileAttributes);
514 }
515
516 /**
517 * Creates a new link node pointing at the given target name.
518 *
519 * <p>Note that target node is resolved each time {@code resolve()} is called,
520 * so if a link node is retained after its reader is closed, it will fail.
521 */
522 private LinkNode newLinkNode(String name, String targetName) {
523 return new LinkNode(name, () -> findNode(targetName), imageFileAttributes);
524 }
525
526 /** Returns the content of a resource node. */
527 private byte[] getResource(Node node) throws IOException {
528 // We could have been given a non-resource node here.
529 if (node.isResource()) {
530 return super.getResource(node.getLocation());
531 }
532 throw new IOException("Not a resource: " + node);
533 }
534 }
535
536 /**
537 * A directory, resource or symbolic link.
538 *
539 * <h3 id="node_equality">Node Equality</h3>
540 *
541 * Nodes are identified solely by their name, and it is not valid to attempt
542 * to compare nodes from different reader instances. Different readers may
543 * produce nodes with the same names, but different contents.
544 *
545 * <p>Furthermore, since a {@link ImageReader} provides "perfect" caching of
546 * nodes, equality of nodes from the same reader is equivalent to instance
547 * identity.
548 */
549 public abstract static class Node {
550 private final String name;
551 private final BasicFileAttributes fileAttrs;
552
553 /**
554 * Creates an abstract {@code Node}, which is either a resource, directory
555 * or symbolic link.
556 *
557 * <p>This constructor is only non-private so it can be used by the
558 * {@code ExplodedImage} class, and must not be used otherwise.
559 */
560 protected Node(String name, BasicFileAttributes fileAttrs) {
561 this.name = Objects.requireNonNull(name);
562 this.fileAttrs = Objects.requireNonNull(fileAttrs);
563 }
564
565 // A node is completed when all its direct children have been built.
566 // As such, non-directory nodes are always complete.
567 boolean isCompleted() {
568 return true;
569 }
570
571 // Only resources can return a location.
572 ImageLocation getLocation() {
573 throw new IllegalStateException("not a resource: " + getName());
574 }
575
576 /**
577 * Returns the name of this node (e.g. {@code
578 * "/modules/java.base/java/lang/Object.class"} or {@code
579 * "/packages/java.lang"}).
580 *
581 * <p>Note that for resource nodes this is NOT the underlying jimage
582 * resource name (it is prefixed with {@code "/modules"}).
583 */
584 public final String getName() {
585 return name;
586 }
587
588 /**
589 * Returns file attributes for this node. The value returned may be the
590 * same for all nodes, and should not be relied upon for accuracy.
591 */
592 public final BasicFileAttributes getFileAttributes() {
593 return fileAttrs;
594 }
595
596 /**
597 * Resolves a symbolic link to its target node. If this code is not a
598 * symbolic link, then it resolves to itself.
599 */
600 public final Node resolveLink() {
601 return resolveLink(false);
602 }
603
604 /**
605 * Resolves a symbolic link to its target node. If this code is not a
606 * symbolic link, then it resolves to itself.
607 */
608 public Node resolveLink(boolean recursive) {
609 return this;
610 }
611
612 /** Returns whether this node is a symbolic link. */
613 public boolean isLink() {
614 return false;
615 }
616
617 /**
618 * Returns whether this node is a directory. Directory nodes can have
619 * {@link #getChildNames()} invoked to get the fully qualified names
620 * of any child nodes.
621 */
622 public boolean isDirectory() {
623 return false;
624 }
625
626 /**
627 * Returns whether this node is a resource. Resource nodes can have
628 * their contents obtained via {@link ImageReader#getResource(Node)}
629 * or {@link ImageReader#getResourceBuffer(Node)}.
630 */
631 public boolean isResource() {
632 return false;
633 }
634
635 /**
636 * Returns the fully qualified names of any child nodes for a directory.
637 *
638 * <p>By default, this method throws {@link IllegalStateException} and
639 * is overridden for directories.
640 */
641 public Stream<String> getChildNames() {
642 throw new IllegalStateException("not a directory: " + getName());
643 }
644
645 /**
646 * Returns the uncompressed size of this node's content. If this node is
647 * not a resource, this method returns zero.
648 */
649 public long size() {
650 return 0L;
651 }
652
653 /**
654 * Returns the compressed size of this node's content. If this node is
655 * not a resource, this method returns zero.
656 */
657 public long compressedSize() {
658 return 0L;
659 }
660
661 /**
662 * Returns the extension string of a resource node. If this node is not
663 * a resource, this method returns null.
664 */
665 public String extension() {
666 return null;
667 }
668
669 @Override
670 public final String toString() {
671 return getName();
672 }
673
674 /** See <a href="#node_equality">Node Equality</a>. */
675 @Override
676 public final int hashCode() {
677 return name.hashCode();
678 }
679
680 /** See <a href="#node_equality">Node Equality</a>. */
681 @Override
682 public final boolean equals(Object other) {
683 if (this == other) {
684 return true;
685 }
686
687 if (other instanceof Node) {
688 return name.equals(((Node) other).name);
689 }
690
691 return false;
692 }
693 }
694
695 /**
696 * Directory node (referenced from a full path, without a trailing '/').
697 *
698 * <p>Directory nodes have two distinct states:
699 * <ul>
700 * <li>Incomplete: The child list has not been set.
701 * <li>Complete: The child list has been set.
702 * </ul>
703 *
704 * <p>When a directory node is returned by {@link ImageReader#findNode(String)}
705 * it is always complete, but this DOES NOT mean that its child nodes are
706 * complete yet.
707 *
708 * <p>To avoid users being able to access incomplete child nodes, the
709 * {@code Node} API offers only a way to obtain child node names, forcing
710 * callers to invoke {@code findNode()} if they need to access the child
711 * node itself.
712 *
713 * <p>This approach allows directories to be implemented lazily with respect
714 * to child nodes, while retaining efficiency when child nodes are accessed
715 * (since any incomplete nodes will be created and placed in the node cache
716 * when the parent was first returned to the user).
717 */
718 private static final class Directory extends Node {
719 // Monotonic reference, will be set to the unmodifiable child list exactly once.
720 private List<Node> children = null;
721
722 private Directory(String name, BasicFileAttributes fileAttrs) {
723 super(name, fileAttrs);
724 }
725
726 @Override
727 boolean isCompleted() {
728 return children != null;
729 }
730
731 @Override
732 public boolean isDirectory() {
733 return true;
734 }
735
736 @Override
737 public Stream<String> getChildNames() {
738 if (children != null) {
739 return children.stream().map(Node::getName);
740 }
741 throw new IllegalStateException("Cannot get child nodes of an incomplete directory: " + getName());
742 }
743
744 private void setChildren(List<Node> children) {
745 assert this.children == null : this + ": Cannot set child nodes twice!";
746 this.children = Collections.unmodifiableList(children);
747 }
748 }
749 /**
750 * Resource node (e.g. a ".class" entry, or any other data resource).
751 *
752 * <p>Resources are leaf nodes referencing an underlying image location. They
753 * are lightweight, and do not cache their contents.
754 *
755 * <p>Unlike directories (where the node name matches the jimage path for the
756 * corresponding {@code ImageLocation}), resource node names are NOT the same
757 * as the corresponding jimage path. The difference is that node names for
758 * resources are prefixed with "/modules", which is missing from the
759 * equivalent jimage path.
760 */
761 private static class Resource extends Node {
762 private final ImageLocation loc;
763
764 private Resource(String name, ImageLocation loc, BasicFileAttributes fileAttrs) {
765 super(name, fileAttrs);
766 this.loc = loc;
767 }
768
769 @Override
770 ImageLocation getLocation() {
771 return loc;
772 }
773
774 @Override
775 public boolean isResource() {
776 return true;
777 }
778
779 @Override
780 public long size() {
781 return loc.getUncompressedSize();
782 }
783
784 @Override
785 public long compressedSize() {
786 return loc.getCompressedSize();
787 }
788
789 @Override
790 public String extension() {
791 return loc.getExtension();
792 }
793 }
794
795 /**
796 * Link node (a symbolic link to a top-level modules directory).
797 *
798 * <p>Link nodes resolve their target by invoking a given supplier, and do
799 * not cache the result. Since nodes are cached by the {@code ImageReader},
800 * this means that only the first call to {@link #resolveLink(boolean)}
801 * could do any significant work.
802 */
803 private static class LinkNode extends Node {
804 private final Supplier<Node> link;
805
806 private LinkNode(String name, Supplier<Node> link, BasicFileAttributes fileAttrs) {
807 super(name, fileAttrs);
808 this.link = link;
809 }
810
811 @Override
812 public Node resolveLink(boolean recursive) {
813 // No need to use or propagate the recursive flag, since the target
814 // cannot possibly be a link node (links only point to directories).
815 return link.get();
816 }
817
818 @Override
819 public boolean isLink() {
820 return true;
821 }
822 }
823 }
|