1 /* 2 * Copyright (c) 2014, 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.UncheckedIOException; 29 import java.nio.ByteBuffer; 30 import java.nio.ByteOrder; 31 import java.nio.IntBuffer; 32 import java.nio.file.Files; 33 import java.nio.file.attribute.BasicFileAttributes; 34 import java.nio.file.attribute.FileTime; 35 import java.nio.file.Paths; 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.List; 41 import java.util.Map; 42 import java.util.function.Consumer; 43 import static jdk.internal.jimage.UTF8String.*; 44 45 public class ImageReader extends BasicImageReader { 46 // well-known strings needed for image file system. 47 static final UTF8String ROOT_STRING = UTF8String.SLASH_STRING; 48 49 // attributes of the .jimage file. jimage file does not contain 50 // attributes for the individual resources (yet). We use attributes 51 // of the jimage file itself (creation, modification, access times). 52 // Iniitalized lazily, see {@link #imageFileAttributes()}. 53 private BasicFileAttributes imageFileAttributes; 54 55 private final ImageModuleData moduleData; 56 57 // directory management implementation 58 private final Map<UTF8String, Node> nodes; 59 private volatile Directory rootDir; 60 61 private Directory packagesDir; 62 private Directory modulesDir; 63 64 ImageReader(String imagePath, ByteOrder byteOrder) throws IOException { 65 super(imagePath, byteOrder); 66 this.moduleData = new ImageModuleData(this); 67 this.nodes = Collections.synchronizedMap(new HashMap<>()); 68 } 69 70 ImageReader(String imagePath) throws IOException { 71 this(imagePath, ByteOrder.nativeOrder()); 72 } 73 74 public static ImageReader open(String imagePath, ByteOrder byteOrder) throws IOException { 75 return new ImageReader(imagePath, byteOrder); 76 } 77 78 /** 79 * Opens the given file path as an image file, returning an {@code ImageReader}. 80 */ 81 public static ImageReader open(String imagePath) throws IOException { 82 return open(imagePath, ByteOrder.nativeOrder()); 83 } 84 85 @Override 86 public synchronized void close() throws IOException { 87 super.close(); 88 clearNodes(); 89 } 90 91 @Override 92 public ImageLocation findLocation(UTF8String name) { 93 ImageLocation location = super.findLocation(name); 94 95 // NOTE: This should be removed when module system is up in full. 96 if (location == null) { 97 int index = name.lastIndexOf('/'); 98 99 if (index != -1) { 100 UTF8String packageName = name.substring(0, index); 101 UTF8String moduleName = moduleData.packageToModule(packageName); 102 103 if (moduleName != null) { 104 UTF8String fullName = UTF8String.SLASH_STRING.concat(moduleName, 105 UTF8String.SLASH_STRING, name); 106 location = super.findLocation(fullName); 107 } 108 } else { 109 // No package, try all modules. 110 for (String mod : moduleData.allModuleNames()) { 111 location = super.findLocation("/" + mod + "/" + name); 112 if (location != null) { 113 break; 114 } 115 } 116 } 117 } 118 119 return location; 120 } 121 122 /** 123 * Return the module name that contains the given package name. 124 */ 125 public String getModule(String packageName) { 126 return moduleData.packageToModule(packageName); 127 } 128 129 // jimage file does not store directory structure. We build nodes 130 // using the "path" strings found in the jimage file. 131 // Node can be a directory or a resource 132 public static abstract class Node { 133 private static final int ROOT_DIR = 0b0000_0000_0000_0001; 134 private static final int PACKAGES_DIR = 0b0000_0000_0000_0010; 135 private static final int MODULES_DIR = 0b0000_0000_0000_0100; 136 137 private int flags; 138 private final UTF8String name; 139 private final BasicFileAttributes fileAttrs; 140 private boolean completed; 141 142 Node(UTF8String name, BasicFileAttributes fileAttrs) { 143 assert name != null; 144 assert fileAttrs != null; 145 this.name = name; 146 this.fileAttrs = fileAttrs; 147 } 148 149 /** 150 * A node is completed when all its direct children have been built. 151 * 152 * @return 153 */ 154 public boolean isCompleted() { 155 return completed; 156 } 157 158 public void setCompleted(boolean completed) { 159 this.completed = completed; 160 } 161 162 public final void setIsRootDir() { 163 flags |= ROOT_DIR; 164 } 165 166 public final boolean isRootDir() { 167 return (flags & ROOT_DIR) != 0; 168 } 169 170 public final void setIsPackagesDir() { 171 flags |= PACKAGES_DIR; 172 } 173 174 public final boolean isPackagesDir() { 175 return (flags & PACKAGES_DIR) != 0; 176 } 177 178 public final void setIsModulesDir() { 179 flags |= MODULES_DIR; 180 } 181 182 public final boolean isModulesDir() { 183 return (flags & MODULES_DIR) != 0; 184 } 185 186 public final UTF8String getName() { 187 return name; 188 } 189 190 public final BasicFileAttributes getFileAttributes() { 191 return fileAttrs; 192 } 193 194 // resolve this Node (if this is a soft link, get underlying Node) 195 public final Node resolveLink() { 196 return resolveLink(false); 197 } 198 199 public Node resolveLink(boolean recursive) { 200 return this; 201 } 202 203 // is this a soft link Node? 204 public boolean isLink() { 205 return false; 206 } 207 208 public boolean isDirectory() { 209 return false; 210 } 211 212 public List<Node> getChildren() { 213 throw new IllegalArgumentException("not a directory: " + getNameString()); 214 } 215 216 public boolean isResource() { 217 return false; 218 } 219 220 public ImageLocation getLocation() { 221 throw new IllegalArgumentException("not a resource: " + getNameString()); 222 } 223 224 public long size() { 225 return 0L; 226 } 227 228 public long compressedSize() { 229 return 0L; 230 } 231 232 public String extension() { 233 return null; 234 } 235 236 public long contentOffset() { 237 return 0L; 238 } 239 240 public final FileTime creationTime() { 241 return fileAttrs.creationTime(); 242 } 243 244 public final FileTime lastAccessTime() { 245 return fileAttrs.lastAccessTime(); 246 } 247 248 public final FileTime lastModifiedTime() { 249 return fileAttrs.lastModifiedTime(); 250 } 251 252 public final String getNameString() { 253 return name.toString(); 254 } 255 256 @Override 257 public final String toString() { 258 return getNameString(); 259 } 260 261 @Override 262 public final int hashCode() { 263 return name.hashCode(); 264 } 265 266 @Override 267 public final boolean equals(Object other) { 268 if (this == other) { 269 return true; 270 } 271 272 if (other instanceof Node) { 273 return name.equals(((Node) other).name); 274 } 275 276 return false; 277 } 278 } 279 280 // directory node - directory has full path name without '/' at end. 281 static final class Directory extends Node { 282 private final List<Node> children; 283 284 private Directory(Directory parent, UTF8String name, BasicFileAttributes fileAttrs) { 285 super(name, fileAttrs); 286 children = new ArrayList<>(); 287 } 288 289 static Directory create(Directory parent, UTF8String name, BasicFileAttributes fileAttrs) { 290 Directory dir = new Directory(parent, name, fileAttrs); 291 if (parent != null) { 292 parent.addChild(dir); 293 } 294 return dir; 295 } 296 297 @Override 298 public boolean isDirectory() { 299 return true; 300 } 301 302 @Override 303 public List<Node> getChildren() { 304 return Collections.unmodifiableList(children); 305 } 306 307 void addChild(Node node) { 308 children.add(node); 309 } 310 311 public void walk(Consumer<? super Node> consumer) { 312 consumer.accept(this); 313 for ( Node child : children ) { 314 if (child.isDirectory()) { 315 ((Directory)child).walk(consumer); 316 } else { 317 consumer.accept(child); 318 } 319 } 320 } 321 } 322 323 // "resource" is .class or any other resource (compressed/uncompressed) in a jimage. 324 // full path of the resource is the "name" of the resource. 325 static class Resource extends Node { 326 private final ImageLocation loc; 327 328 private Resource(Directory parent, ImageLocation loc, BasicFileAttributes fileAttrs) { 329 this(parent, loc.getFullName(true), loc, fileAttrs); 330 } 331 332 private Resource(Directory parent, UTF8String name, ImageLocation loc, BasicFileAttributes fileAttrs) { 333 super(name, fileAttrs); 334 this.loc = loc; 335 } 336 337 static Resource create(Directory parent, ImageLocation loc, BasicFileAttributes fileAttrs) { 338 Resource resource = new Resource(parent, loc, fileAttrs); 339 parent.addChild(resource); 340 return resource; 341 } 342 343 static Resource create(Directory parent, UTF8String name, ImageLocation loc, BasicFileAttributes fileAttrs) { 344 Resource resource = new Resource(parent, name, loc, fileAttrs); 345 parent.addChild(resource); 346 return resource; 347 } 348 349 @Override 350 public boolean isCompleted() { 351 return true; 352 } 353 354 @Override 355 public boolean isResource() { 356 return true; 357 } 358 359 @Override 360 public ImageLocation getLocation() { 361 return loc; 362 } 363 364 @Override 365 public long size() { 366 return loc.getUncompressedSize(); 367 } 368 369 @Override 370 public long compressedSize() { 371 return loc.getCompressedSize(); 372 } 373 374 @Override 375 public String extension() { 376 return loc.getExtensionString(); 377 } 378 379 @Override 380 public long contentOffset() { 381 return loc.getContentOffset(); 382 } 383 } 384 385 // represents a soft link to another Node 386 static class LinkNode extends Node { 387 private final Node link; 388 389 private LinkNode(Directory parent, UTF8String name, Node link) { 390 super(name, link.getFileAttributes()); 391 this.link = link; 392 } 393 394 static LinkNode create(Directory parent, UTF8String name, Node link) { 395 LinkNode linkNode = new LinkNode(parent, name, link); 396 parent.addChild(linkNode); 397 return linkNode; 398 } 399 400 @Override 401 public boolean isCompleted() { 402 return true; 403 } 404 405 @Override 406 public Node resolveLink(boolean recursive) { 407 return recursive && (link instanceof LinkNode)? ((LinkNode)link).resolveLink(true) : link; 408 } 409 410 @Override 411 public boolean isLink() { 412 return true; 413 } 414 } 415 416 // directory management interface 417 public Directory getRootDirectory() { 418 return buildRootDirectory(); 419 } 420 421 public Node findNode(String name) { 422 return findNode(new UTF8String(name)); 423 } 424 425 public Node findNode(byte[] name) { 426 return findNode(new UTF8String(name)); 427 } 428 429 /** 430 * To visit sub tree resources. 431 */ 432 interface LocationVisitor { 433 434 void visit(ImageLocation loc); 435 } 436 437 /** 438 * Lazily build a node from a name. 439 */ 440 private final class NodeBuilder { 441 442 private static final int SIZE_OF_OFFSET = 4; 443 444 private final UTF8String name; 445 446 private NodeBuilder(UTF8String name) { 447 this.name = name; 448 } 449 450 private Node buildNode() { 451 Node n = null; 452 boolean isPackages = false; 453 boolean isModules = false; 454 String strName = name.toString(); 455 if (strName.startsWith("" + PACKAGES_STRING)) { 456 isPackages = true; 457 } else { 458 if (strName.startsWith("" + MODULES_STRING)) { 459 isModules = true; 460 } 461 } 462 if (!isModules && !isPackages) { 463 return null; 464 } 465 466 ImageLocation loc = findLocation(name); 467 468 if (loc != null) { // A sub tree node 469 if (isPackages) { 470 n = handlePackages(strName, loc); 471 } else { // modules sub tree 472 n = handleModulesSubTree(strName, loc); 473 } 474 } else { // Asking for a resource? /modules/java.base/java/lang/Object.class 475 if (isModules) { 476 n = handleResource(strName, loc); 477 } 478 } 479 return n; 480 } 481 482 private void visitLocation(ImageLocation loc, LocationVisitor visitor) { 483 byte[] offsets = getResource(loc); 484 ByteBuffer buffer = ByteBuffer.wrap(offsets); 485 buffer.order(getByteOrder()); 486 IntBuffer intBuffer = buffer.asIntBuffer(); 487 for (int i = 0; i < offsets.length / SIZE_OF_OFFSET; i++) { 488 int offset = intBuffer.get(i); 489 ImageLocation pkgLoc = getLocation(offset); 490 visitor.visit(pkgLoc); 491 } 492 } 493 494 private Node handlePackages(String name, ImageLocation loc) { 495 long size = loc.getUncompressedSize(); 496 Node n = null; 497 // Only possiblities are /packages, /packages/package/module 498 if (name.equals("" + PACKAGES_STRING)) { 499 visitLocation(loc, (childloc) -> { 500 findNode(childloc.getFullName()); 501 }); 502 packagesDir.setCompleted(true); 503 n = packagesDir; 504 } else { 505 if (size != 0) { // children are links to module 506 String pkgName = getBaseExt(loc); 507 Directory pkgDir = newDirectory(packagesDir, 508 packagesDir.getName().concat(SLASH_STRING, new UTF8String(pkgName))); 509 visitLocation(loc, (childloc) -> { 510 findNode(childloc.getFullName()); 511 }); 512 pkgDir.setCompleted(true); 513 n = pkgDir; 514 } else { // Link to module 515 String pkgName = loc.getParentString(); 516 String modName = getBaseExt(loc); 517 Node targetNode = findNode(MODULES_STRING + "/" + modName); 518 if (targetNode != null) { 519 UTF8String pkgDirName = packagesDir.getName().concat(SLASH_STRING, new UTF8String(pkgName)); 520 Directory pkgDir = (Directory) nodes.get(pkgDirName); 521 Node linkNode = newLinkNode(pkgDir, 522 pkgDir.getName().concat(SLASH_STRING, new UTF8String(modName)), targetNode); 523 n = linkNode; 524 } 525 } 526 } 527 return n; 528 } 529 530 private Node handleModulesSubTree(String name, ImageLocation loc) { 531 Node n; 532 Directory dir = makeDirectories(loc.getFullName()); 533 visitLocation(loc, (childloc) -> { 534 String path = childloc.getFullNameString(); 535 if (path.startsWith(MODULES_STRING.toString())) { // a package 536 makeDirectories(childloc.getFullName()); 537 } else { // a resource 538 makeDirectories(childloc.buildName(true, true, false)); 539 newResource(dir, childloc); 540 } 541 }); 542 dir.setCompleted(true); 543 n = dir; 544 return n; 545 } 546 547 private Node handleResource(String name, ImageLocation loc) { 548 Node n = null; 549 String locationPath = name.substring((MODULES_STRING).length()); 550 ImageLocation resourceLoc = findLocation(locationPath); 551 if (resourceLoc != null) { 552 Directory dir = makeDirectories(resourceLoc.buildName(true, true, false)); 553 Resource res = newResource(dir, resourceLoc); 554 n = res; 555 } 556 return n; 557 } 558 559 private String getBaseExt(ImageLocation loc) { 560 String base = loc.getBaseString(); 561 String ext = loc.getExtensionString(); 562 if (ext != null && !ext.isEmpty()) { 563 base = base + "." + ext; 564 } 565 return base; 566 } 567 } 568 569 public synchronized Node findNode(UTF8String name) { 570 buildRootDirectory(); 571 Node n = nodes.get(name); 572 if (n == null || !n.isCompleted()) { 573 NodeBuilder builder = new NodeBuilder(name); 574 n = builder.buildNode(); 575 } 576 return n; 577 } 578 579 private synchronized void clearNodes() { 580 nodes.clear(); 581 rootDir = null; 582 } 583 584 /** 585 * Returns the file attributes of the image file. 586 */ 587 private BasicFileAttributes imageFileAttributes() { 588 BasicFileAttributes attrs = imageFileAttributes; 589 if (attrs == null) { 590 try { 591 Path file = Paths.get(imagePath()); 592 attrs = Files.readAttributes(file, BasicFileAttributes.class); 593 } catch (IOException ioe) { 594 throw new UncheckedIOException(ioe); 595 } 596 imageFileAttributes = attrs; 597 } 598 return attrs; 599 } 600 601 private synchronized Directory buildRootDirectory() { 602 if (rootDir != null) { 603 return rootDir; 604 } 605 606 // FIXME no time information per resource in jimage file (yet?) 607 // we use file attributes of jimage itself. 608 // root directory 609 rootDir = newDirectory(null, ROOT_STRING); 610 rootDir.setIsRootDir(); 611 612 // /packages dir 613 packagesDir = newDirectory(rootDir, PACKAGES_STRING); 614 packagesDir.setIsPackagesDir(); 615 616 // /modules dir 617 modulesDir = newDirectory(rootDir, MODULES_STRING); 618 modulesDir.setIsModulesDir(); 619 620 rootDir.setCompleted(true); 621 return rootDir; 622 } 623 624 private Directory newDirectory(Directory parent, UTF8String name) { 625 Directory dir = Directory.create(parent, name, imageFileAttributes()); 626 nodes.put(dir.getName(), dir); 627 return dir; 628 } 629 630 private Resource newResource(Directory parent, ImageLocation loc) { 631 Resource res = Resource.create(parent, loc, imageFileAttributes()); 632 nodes.put(res.getName(), res); 633 return res; 634 } 635 636 private LinkNode newLinkNode(Directory dir, UTF8String name, Node link) { 637 LinkNode linkNode = LinkNode.create(dir, name, link); 638 nodes.put(linkNode.getName(), linkNode); 639 return linkNode; 640 } 641 642 private List<UTF8String> dirs(UTF8String parent) { 643 List<UTF8String> splits = new ArrayList<>(); 644 645 for (int i = 1; i < parent.length(); i++) { 646 if (parent.byteAt(i) == '/') { 647 splits.add(parent.substring(0, i)); 648 } 649 } 650 651 splits.add(parent); 652 653 return splits; 654 } 655 656 private Directory makeDirectories(UTF8String parent) { 657 Directory last = rootDir; 658 List<UTF8String> dirs = dirs(parent); 659 660 for (UTF8String dir : dirs) { 661 Directory nextDir = (Directory) nodes.get(dir); 662 if (nextDir == null) { 663 nextDir = newDirectory(last, dir); 664 } 665 last = nextDir; 666 } 667 668 return last; 669 } 670 671 public byte[] getResource(Node node) throws IOException { 672 if (node.isResource()) { 673 return super.getResource(node.getLocation()); 674 } 675 throw new IOException("Not a resource: " + node); 676 } 677 678 public byte[] getResource(Resource rs) throws IOException { 679 return super.getResource(rs.getLocation()); 680 } 681 }