1 /*
2  * Copyright (c) 2014, 2016, 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 
ImageReader(SharedImageReader reader)59     private ImageReader(SharedImageReader reader) {
60         this.reader = reader;
61     }
62 
open(Path imagePath, ByteOrder byteOrder)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 
open(Path imagePath)70     public static ImageReader open(Path imagePath) throws IOException {
71         return open(imagePath, ByteOrder.nativeOrder());
72     }
73 
74     @Override
close()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 
ensureOpen()83     private void ensureOpen() throws IOException {
84         if (closed) {
85             throw new IOException("image file closed");
86         }
87     }
88 
requireOpen()89     private void requireOpen() {
90         if (closed) {
91             throw new IllegalStateException("image file closed");
92         }
93     }
94 
95     // directory management interface
getRootDirectory()96     public Directory getRootDirectory() throws IOException {
97         ensureOpen();
98         return reader.getRootDirectory();
99     }
100 
101 
findNode(String name)102     public Node findNode(String name) throws IOException {
103         ensureOpen();
104         return reader.findNode(name);
105     }
106 
getResource(Node node)107     public byte[] getResource(Node node) throws IOException {
108         ensureOpen();
109         return reader.getResource(node);
110     }
111 
getResource(Resource rs)112     public byte[] getResource(Resource rs) throws IOException {
113         ensureOpen();
114         return reader.getResource(rs);
115     }
116 
getHeader()117     public ImageHeader getHeader() {
118         requireOpen();
119         return reader.getHeader();
120     }
121 
releaseByteBuffer(ByteBuffer buffer)122     public static void releaseByteBuffer(ByteBuffer buffer) {
123         BasicImageReader.releaseByteBuffer(buffer);
124     }
125 
getName()126     public String getName() {
127         requireOpen();
128         return reader.getName();
129     }
130 
getByteOrder()131     public ByteOrder getByteOrder() {
132         requireOpen();
133         return reader.getByteOrder();
134     }
135 
getImagePath()136     public Path getImagePath() {
137         requireOpen();
138         return reader.getImagePath();
139     }
140 
getStrings()141     public ImageStringsReader getStrings() {
142         requireOpen();
143         return reader.getStrings();
144     }
145 
findLocation(String mn, String rn)146     public ImageLocation findLocation(String mn, String rn) {
147         requireOpen();
148         return reader.findLocation(mn, rn);
149     }
150 
findLocation(String name)151     public ImageLocation findLocation(String name) {
152         requireOpen();
153         return reader.findLocation(name);
154     }
155 
getEntryNames()156     public String[] getEntryNames() {
157         requireOpen();
158         return reader.getEntryNames();
159     }
160 
getModuleNames()161     public String[] getModuleNames() {
162         requireOpen();
163         int off = "/modules/".length();
164         return reader.findNode("/modules")
165                      .getChildren()
166                      .stream()
167                      .map(Node::getNameString)
168                      .map(s -> s.substring(off, s.length()))
169                      .toArray(String[]::new);
170     }
171 
getAttributes(int offset)172     public long[] getAttributes(int offset) {
173         requireOpen();
174         return reader.getAttributes(offset);
175     }
176 
getString(int offset)177     public String getString(int offset) {
178         requireOpen();
179         return reader.getString(offset);
180     }
181 
getResource(String name)182     public byte[] getResource(String name) {
183         requireOpen();
184         return reader.getResource(name);
185     }
186 
getResource(ImageLocation loc)187     public byte[] getResource(ImageLocation loc) {
188         requireOpen();
189         return reader.getResource(loc);
190     }
191 
getResourceBuffer(ImageLocation loc)192     public ByteBuffer getResourceBuffer(ImageLocation loc) {
193         requireOpen();
194         return reader.getResourceBuffer(loc);
195     }
196 
getResourceStream(ImageLocation loc)197     public InputStream getResourceStream(ImageLocation loc) {
198         requireOpen();
199         return reader.getResourceStream(loc);
200     }
201 
202     private final static class SharedImageReader extends BasicImageReader {
203         static final int SIZE_OF_OFFSET = Integer.BYTES;
204 
205         static final Map<Path, SharedImageReader> OPEN_FILES = new HashMap<>();
206 
207         // List of openers for this shared image.
208         final Set<ImageReader> openers;
209 
210         // attributes of the .jimage file. jimage file does not contain
211         // attributes for the individual resources (yet). We use attributes
212         // of the jimage file itself (creation, modification, access times).
213         // Iniitalized lazily, see {@link #imageFileAttributes()}.
214         BasicFileAttributes imageFileAttributes;
215 
216         // directory management implementation
217         final HashMap<String, Node> nodes;
218         volatile Directory rootDir;
219 
220         Directory packagesDir;
221         Directory modulesDir;
222 
SharedImageReader(Path imagePath, ByteOrder byteOrder)223         private SharedImageReader(Path imagePath, ByteOrder byteOrder) throws IOException {
224             super(imagePath, byteOrder);
225             this.openers = new HashSet<>();
226             this.nodes = new HashMap<>();
227         }
228 
open(Path imagePath, ByteOrder byteOrder)229         public static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException {
230             Objects.requireNonNull(imagePath);
231             Objects.requireNonNull(byteOrder);
232 
233             synchronized (OPEN_FILES) {
234                 SharedImageReader reader = OPEN_FILES.get(imagePath);
235 
236                 if (reader == null) {
237                     // Will fail with an IOException if wrong byteOrder.
238                     reader =  new SharedImageReader(imagePath, byteOrder);
239                     OPEN_FILES.put(imagePath, reader);
240                 } else if (reader.getByteOrder() != byteOrder) {
241                     throw new IOException("\"" + reader.getName() + "\" is not an image file");
242                 }
243 
244                 ImageReader image = new ImageReader(reader);
245                 reader.openers.add(image);
246 
247                 return image;
248             }
249         }
250 
close(ImageReader image)251         public void close(ImageReader image) throws IOException {
252             Objects.requireNonNull(image);
253 
254             synchronized (OPEN_FILES) {
255                 if (!openers.remove(image)) {
256                     throw new IOException("image file already closed");
257                 }
258 
259                 if (openers.isEmpty()) {
260                     close();
261                     nodes.clear();
262                     rootDir = null;
263 
264                     if (!OPEN_FILES.remove(this.getImagePath(), this)) {
265                         throw new IOException("image file not found in open list");
266                     }
267                 }
268             }
269         }
270 
addOpener(ImageReader reader)271         void addOpener(ImageReader reader) {
272             synchronized (OPEN_FILES) {
273                 openers.add(reader);
274             }
275         }
276 
removeOpener(ImageReader reader)277         boolean removeOpener(ImageReader reader) {
278             synchronized (OPEN_FILES) {
279                 return openers.remove(reader);
280             }
281         }
282 
283         // directory management interface
getRootDirectory()284         Directory getRootDirectory() {
285             return buildRootDirectory();
286         }
287 
288         /**
289          * Lazily build a node from a name.
290         */
buildNode(String name)291         synchronized Node buildNode(String name) {
292             Node n;
293             boolean isPackages = name.startsWith("/packages");
294             boolean isModules = !isPackages && name.startsWith("/modules");
295 
296             if (!(isModules || isPackages)) {
297                 return null;
298             }
299 
300             ImageLocation loc = findLocation(name);
301 
302             if (loc != null) { // A sub tree node
303                 if (isPackages) {
304                     n = handlePackages(name, loc);
305                 } else { // modules sub tree
306                     n = handleModulesSubTree(name, loc);
307                 }
308             } else { // Asking for a resource? /modules/java.base/java/lang/Object.class
309                 if (isModules) {
310                     n = handleResource(name);
311                 } else {
312                     // Possibly ask for /packages/java.lang/java.base
313                     // although /packages/java.base not created
314                     n = handleModuleLink(name);
315                 }
316             }
317             return n;
318         }
319 
buildRootDirectory()320         synchronized Directory buildRootDirectory() {
321             Directory root = rootDir; // volatile read
322             if (root != null) {
323                 return root;
324             }
325 
326             root = newDirectory(null, "/");
327             root.setIsRootDir();
328 
329             // /packages dir
330             packagesDir = newDirectory(root, "/packages");
331             packagesDir.setIsPackagesDir();
332 
333             // /modules dir
334             modulesDir = newDirectory(root, "/modules");
335             modulesDir.setIsModulesDir();
336 
337             root.setCompleted(true);
338             return rootDir = root;
339         }
340 
341         /**
342          * To visit sub tree resources.
343          */
344         interface LocationVisitor {
visit(ImageLocation loc)345             void visit(ImageLocation loc);
346         }
347 
visitLocation(ImageLocation loc, LocationVisitor visitor)348         void visitLocation(ImageLocation loc, LocationVisitor visitor) {
349             byte[] offsets = getResource(loc);
350             ByteBuffer buffer = ByteBuffer.wrap(offsets);
351             buffer.order(getByteOrder());
352             IntBuffer intBuffer = buffer.asIntBuffer();
353             for (int i = 0; i < offsets.length / SIZE_OF_OFFSET; i++) {
354                 int offset = intBuffer.get(i);
355                 ImageLocation pkgLoc = getLocation(offset);
356                 visitor.visit(pkgLoc);
357             }
358         }
359 
visitPackageLocation(ImageLocation loc)360         void visitPackageLocation(ImageLocation loc) {
361             // Retrieve package name
362             String pkgName = getBaseExt(loc);
363             // Content is array of offsets in Strings table
364             byte[] stringsOffsets = getResource(loc);
365             ByteBuffer buffer = ByteBuffer.wrap(stringsOffsets);
366             buffer.order(getByteOrder());
367             IntBuffer intBuffer = buffer.asIntBuffer();
368             // For each module, create a link node.
369             for (int i = 0; i < stringsOffsets.length / SIZE_OF_OFFSET; i++) {
370                 // skip empty state, useless.
371                 intBuffer.get(i);
372                 i++;
373                 int offset = intBuffer.get(i);
374                 String moduleName = getString(offset);
375                 Node targetNode = findNode("/modules/" + moduleName);
376                 if (targetNode != null) {
377                     String pkgDirName = packagesDir.getName() + "/" + pkgName;
378                     Directory pkgDir = (Directory) nodes.get(pkgDirName);
379                     newLinkNode(pkgDir, pkgDir.getName() + "/" + moduleName, targetNode);
380                 }
381             }
382         }
383 
handlePackages(String name, ImageLocation loc)384         Node handlePackages(String name, ImageLocation loc) {
385             long size = loc.getUncompressedSize();
386             Node n = null;
387             // Only possiblities are /packages, /packages/package/module
388             if (name.equals("/packages")) {
389                 visitLocation(loc, (childloc) -> {
390                     findNode(childloc.getFullName());
391                 });
392                 packagesDir.setCompleted(true);
393                 n = packagesDir;
394             } else {
395                 if (size != 0) { // children are offsets to module in StringsTable
396                     String pkgName = getBaseExt(loc);
397                     Directory pkgDir = newDirectory(packagesDir, packagesDir.getName() + "/" + pkgName);
398                     visitPackageLocation(loc);
399                     pkgDir.setCompleted(true);
400                     n = pkgDir;
401                 } else { // Link to module
402                     String pkgName = loc.getParent();
403                     String modName = getBaseExt(loc);
404                     Node targetNode = findNode("/modules/" + modName);
405                     if (targetNode != null) {
406                         String pkgDirName = packagesDir.getName() + "/" + pkgName;
407                         Directory pkgDir = (Directory) nodes.get(pkgDirName);
408                         Node linkNode = newLinkNode(pkgDir, pkgDir.getName() + "/" + modName, targetNode);
409                         n = linkNode;
410                     }
411                 }
412             }
413             return n;
414         }
415 
416         // Asking for /packages/package/module although
417         // /packages/<pkg>/ not yet created, need to create it
418         // prior to return the link to module node.
handleModuleLink(String name)419         Node handleModuleLink(String name) {
420             // eg: unresolved /packages/package/module
421             // Build /packages/package node
422             Node ret = null;
423             String radical = "/packages/";
424             String path = name;
425             if (path.startsWith(radical)) {
426                 int start = radical.length();
427                 int pkgEnd = path.indexOf('/', start);
428                 if (pkgEnd != -1) {
429                     String pkg = path.substring(start, pkgEnd);
430                     String pkgPath = radical + pkg;
431                     Node n = findNode(pkgPath);
432                     // If not found means that this is a symbolic link such as:
433                     // /packages/java.util/java.base/java/util/Vector.class
434                     // and will be done by a retry of the filesystem
435                     for (Node child : n.getChildren()) {
436                         if (child.name.equals(name)) {
437                             ret = child;
438                             break;
439                         }
440                     }
441                 }
442             }
443             return ret;
444         }
445 
handleModulesSubTree(String name, ImageLocation loc)446         Node handleModulesSubTree(String name, ImageLocation loc) {
447             Node n;
448             assert (name.equals(loc.getFullName()));
449             Directory dir = makeDirectories(name);
450             visitLocation(loc, (childloc) -> {
451                 String path = childloc.getFullName();
452                 if (path.startsWith("/modules")) { // a package
453                     makeDirectories(path);
454                 } else { // a resource
455                     makeDirectories(childloc.buildName(true, true, false));
456                     newResource(dir, childloc);
457                 }
458             });
459             dir.setCompleted(true);
460             n = dir;
461             return n;
462         }
463 
handleResource(String name)464         Node handleResource(String name) {
465             Node n = null;
466             String locationPath = name.substring("/modules".length());
467             ImageLocation resourceLoc = findLocation(locationPath);
468             if (resourceLoc != null) {
469                 Directory dir = makeDirectories(resourceLoc.buildName(true, true, false));
470                 Resource res = newResource(dir, resourceLoc);
471                 n = res;
472             }
473             return n;
474         }
475 
getBaseExt(ImageLocation loc)476         String getBaseExt(ImageLocation loc) {
477             String base = loc.getBase();
478             String ext = loc.getExtension();
479             if (ext != null && !ext.isEmpty()) {
480                 base = base + "." + ext;
481             }
482             return base;
483         }
484 
findNode(String name)485         synchronized Node findNode(String name) {
486             buildRootDirectory();
487             Node n = nodes.get(name);
488             if (n == null || !n.isCompleted()) {
489                 n = buildNode(name);
490             }
491             return n;
492         }
493 
494         /**
495          * Returns the file attributes of the image file.
496          */
imageFileAttributes()497         BasicFileAttributes imageFileAttributes() {
498             BasicFileAttributes attrs = imageFileAttributes;
499             if (attrs == null) {
500                 try {
501                     Path file = getImagePath();
502                     attrs = Files.readAttributes(file, BasicFileAttributes.class);
503                 } catch (IOException ioe) {
504                     throw new UncheckedIOException(ioe);
505                 }
506                 imageFileAttributes = attrs;
507             }
508             return attrs;
509         }
510 
newDirectory(Directory parent, String name)511         Directory newDirectory(Directory parent, String name) {
512             Directory dir = Directory.create(parent, name, imageFileAttributes());
513             nodes.put(dir.getName(), dir);
514             return dir;
515         }
516 
newResource(Directory parent, ImageLocation loc)517         Resource newResource(Directory parent, ImageLocation loc) {
518             Resource res = Resource.create(parent, loc, imageFileAttributes());
519             nodes.put(res.getName(), res);
520             return res;
521         }
522 
newLinkNode(Directory dir, String name, Node link)523         LinkNode newLinkNode(Directory dir, String name, Node link) {
524             LinkNode linkNode = LinkNode.create(dir, name, link);
525             nodes.put(linkNode.getName(), linkNode);
526             return linkNode;
527         }
528 
makeDirectories(String parent)529         Directory makeDirectories(String parent) {
530             Directory last = rootDir;
531             for (int offset = parent.indexOf('/', 1);
532                     offset != -1;
533                     offset = parent.indexOf('/', offset + 1)) {
534                 String dir = parent.substring(0, offset);
535                 last = makeDirectory(dir, last);
536             }
537             return makeDirectory(parent, last);
538 
539         }
540 
makeDirectory(String dir, Directory last)541         Directory makeDirectory(String dir, Directory last) {
542             Directory nextDir = (Directory) nodes.get(dir);
543             if (nextDir == null) {
544                 nextDir = newDirectory(last, dir);
545             }
546             return nextDir;
547         }
548 
getResource(Node node)549         byte[] getResource(Node node) throws IOException {
550             if (node.isResource()) {
551                 return super.getResource(node.getLocation());
552             }
553             throw new IOException("Not a resource: " + node);
554         }
555 
getResource(Resource rs)556         byte[] getResource(Resource rs) throws IOException {
557             return super.getResource(rs.getLocation());
558         }
559     }
560 
561     // jimage file does not store directory structure. We build nodes
562     // using the "path" strings found in the jimage file.
563     // Node can be a directory or a resource
564     public abstract static class Node {
565         private static final int ROOT_DIR = 0b0000_0000_0000_0001;
566         private static final int PACKAGES_DIR = 0b0000_0000_0000_0010;
567         private static final int MODULES_DIR = 0b0000_0000_0000_0100;
568 
569         private int flags;
570         private final String name;
571         private final BasicFileAttributes fileAttrs;
572         private boolean completed;
573 
Node(String name, BasicFileAttributes fileAttrs)574         protected Node(String name, BasicFileAttributes fileAttrs) {
575             this.name = Objects.requireNonNull(name);
576             this.fileAttrs = Objects.requireNonNull(fileAttrs);
577         }
578 
579         /**
580          * A node is completed when all its direct children have been built.
581          *
582          * @return
583          */
isCompleted()584         public boolean isCompleted() {
585             return completed;
586         }
587 
setCompleted(boolean completed)588         public void setCompleted(boolean completed) {
589             this.completed = completed;
590         }
591 
setIsRootDir()592         public final void setIsRootDir() {
593             flags |= ROOT_DIR;
594         }
595 
isRootDir()596         public final boolean isRootDir() {
597             return (flags & ROOT_DIR) != 0;
598         }
599 
setIsPackagesDir()600         public final void setIsPackagesDir() {
601             flags |= PACKAGES_DIR;
602         }
603 
isPackagesDir()604         public final boolean isPackagesDir() {
605             return (flags & PACKAGES_DIR) != 0;
606         }
607 
setIsModulesDir()608         public final void setIsModulesDir() {
609             flags |= MODULES_DIR;
610         }
611 
isModulesDir()612         public final boolean isModulesDir() {
613             return (flags & MODULES_DIR) != 0;
614         }
615 
getName()616         public final String getName() {
617             return name;
618         }
619 
getFileAttributes()620         public final BasicFileAttributes getFileAttributes() {
621             return fileAttrs;
622         }
623 
624         // resolve this Node (if this is a soft link, get underlying Node)
resolveLink()625         public final Node resolveLink() {
626             return resolveLink(false);
627         }
628 
resolveLink(boolean recursive)629         public Node resolveLink(boolean recursive) {
630             return this;
631         }
632 
633         // is this a soft link Node?
isLink()634         public boolean isLink() {
635             return false;
636         }
637 
isDirectory()638         public boolean isDirectory() {
639             return false;
640         }
641 
getChildren()642         public List<Node> getChildren() {
643             throw new IllegalArgumentException("not a directory: " + getNameString());
644         }
645 
isResource()646         public boolean isResource() {
647             return false;
648         }
649 
getLocation()650         public ImageLocation getLocation() {
651             throw new IllegalArgumentException("not a resource: " + getNameString());
652         }
653 
size()654         public long size() {
655             return 0L;
656         }
657 
compressedSize()658         public long compressedSize() {
659             return 0L;
660         }
661 
extension()662         public String extension() {
663             return null;
664         }
665 
contentOffset()666         public long contentOffset() {
667             return 0L;
668         }
669 
creationTime()670         public final FileTime creationTime() {
671             return fileAttrs.creationTime();
672         }
673 
lastAccessTime()674         public final FileTime lastAccessTime() {
675             return fileAttrs.lastAccessTime();
676         }
677 
lastModifiedTime()678         public final FileTime lastModifiedTime() {
679             return fileAttrs.lastModifiedTime();
680         }
681 
getNameString()682         public final String getNameString() {
683             return name;
684         }
685 
686         @Override
toString()687         public final String toString() {
688             return getNameString();
689         }
690 
691         @Override
hashCode()692         public final int hashCode() {
693             return name.hashCode();
694         }
695 
696         @Override
equals(Object other)697         public final boolean equals(Object other) {
698             if (this == other) {
699                 return true;
700             }
701 
702             if (other instanceof Node) {
703                 return name.equals(((Node) other).name);
704             }
705 
706             return false;
707         }
708     }
709 
710     // directory node - directory has full path name without '/' at end.
711     static final class Directory extends Node {
712         private final List<Node> children;
713 
Directory(String name, BasicFileAttributes fileAttrs)714         private Directory(String name, BasicFileAttributes fileAttrs) {
715             super(name, fileAttrs);
716             children = new ArrayList<>();
717         }
718 
create(Directory parent, String name, BasicFileAttributes fileAttrs)719         static Directory create(Directory parent, String name, BasicFileAttributes fileAttrs) {
720             Directory d = new Directory(name, fileAttrs);
721             if (parent != null) {
722                 parent.addChild(d);
723             }
724             return d;
725         }
726 
727         @Override
isDirectory()728         public boolean isDirectory() {
729             return true;
730         }
731 
732         @Override
getChildren()733         public List<Node> getChildren() {
734             return Collections.unmodifiableList(children);
735         }
736 
addChild(Node node)737         void addChild(Node node) {
738             children.add(node);
739         }
740 
walk(Consumer<? super Node> consumer)741         public void walk(Consumer<? super Node> consumer) {
742             consumer.accept(this);
743             for ( Node child : children ) {
744                 if (child.isDirectory()) {
745                     ((Directory)child).walk(consumer);
746                 } else {
747                     consumer.accept(child);
748                 }
749             }
750         }
751     }
752 
753     // "resource" is .class or any other resource (compressed/uncompressed) in a jimage.
754     // full path of the resource is the "name" of the resource.
755     static class Resource extends Node {
756         private final ImageLocation loc;
757 
Resource(ImageLocation loc, BasicFileAttributes fileAttrs)758         private Resource(ImageLocation loc, BasicFileAttributes fileAttrs) {
759             super(loc.getFullName(true), fileAttrs);
760             this.loc = loc;
761         }
762 
create(Directory parent, ImageLocation loc, BasicFileAttributes fileAttrs)763         static Resource create(Directory parent, ImageLocation loc, BasicFileAttributes fileAttrs) {
764             Resource rs = new Resource(loc, fileAttrs);
765             parent.addChild(rs);
766             return rs;
767         }
768 
769         @Override
isCompleted()770         public boolean isCompleted() {
771             return true;
772         }
773 
774         @Override
isResource()775         public boolean isResource() {
776             return true;
777         }
778 
779         @Override
getLocation()780         public ImageLocation getLocation() {
781             return loc;
782         }
783 
784         @Override
size()785         public long size() {
786             return loc.getUncompressedSize();
787         }
788 
789         @Override
compressedSize()790         public long compressedSize() {
791             return loc.getCompressedSize();
792         }
793 
794         @Override
extension()795         public String extension() {
796             return loc.getExtension();
797         }
798 
799         @Override
contentOffset()800         public long contentOffset() {
801             return loc.getContentOffset();
802         }
803     }
804 
805     // represents a soft link to another Node
806     static class LinkNode extends Node {
807         private final Node link;
808 
LinkNode(String name, Node link)809         private LinkNode(String name, Node link) {
810             super(name, link.getFileAttributes());
811             this.link = link;
812         }
813 
create(Directory parent, String name, Node link)814         static LinkNode create(Directory parent, String name, Node link) {
815             LinkNode ln = new LinkNode(name, link);
816             parent.addChild(ln);
817             return ln;
818         }
819 
820         @Override
isCompleted()821         public boolean isCompleted() {
822             return true;
823         }
824 
825         @Override
resolveLink(boolean recursive)826         public Node resolveLink(boolean recursive) {
827             return (recursive && link instanceof LinkNode) ? ((LinkNode)link).resolveLink(true) : link;
828         }
829 
830         @Override
isLink()831         public boolean isLink() {
832             return true;
833         }
834     }
835 }
836