< prev index next >

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

Print this page

  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 }
< prev index next >