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