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.net.URI; 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.FileSystem; 35 import java.nio.file.attribute.BasicFileAttributes; 36 import java.nio.file.attribute.FileTime; 37 import java.nio.file.Paths; 38 import java.nio.file.Path; 39 import java.util.ArrayList; 40 import java.util.Collections; 41 import java.util.HashMap; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.function.Supplier; 45 46 public class ImageReader extends BasicImageReader { 47 // well-known strings needed for image file system. 48 static final UTF8String ROOT_STRING = new UTF8String("/"); 49 static final UTF8String META_INF_STRING = new UTF8String("/META-INF/"); 50 51 // attributes of the .jimage file. jimage file does not contain 52 // attributes for the individual resources (yet). We use attributes 53 // of the jimage file itself (creation, modification, access times). 54 // Iniitalized lazily, see {@link #imageFileAttributes()}. 55 private BasicFileAttributes imageFileAttributes; 56 57 private final Map<String, String> packageMap; 58 59 // directory management implementation 60 private final Map<UTF8String, Node> nodes; 61 private volatile Directory rootDir; 62 63 ImageReader(String imagePath, ByteOrder byteOrder) throws IOException { 64 super(imagePath, byteOrder); 65 this.packageMap = PackageModuleMap.readFrom(this); 66 this.nodes = Collections.synchronizedMap(new HashMap<>()); 67 } 68 69 ImageReader(String imagePath) throws IOException { 70 this(imagePath, ByteOrder.nativeOrder()); 71 } 72 73 public static ImageReader open(String imagePath, ByteOrder byteOrder) throws IOException { 74 return new ImageReader(imagePath, byteOrder); 75 } 76 77 /** 78 * Opens the given file path as an image file, returning an {@code ImageReader}. 79 */ 80 public static ImageReader open(String imagePath) throws IOException { 81 return open(imagePath, ByteOrder.nativeOrder()); 82 } 83 84 @Override 85 public synchronized void close() throws IOException { 86 super.close(); 87 clearNodes(); 88 } 89 90 /** 91 * Return the module name that contains the given package name. 92 */ 93 public String getModule(String pkg) { 94 return packageMap.get(pkg); 95 } 96 97 // jimage file does not store directory structure. We build nodes 98 // using the "path" strings found in the jimage file. 99 // Node can be a directory or a resource 100 public static abstract class Node { 101 private static final int ROOT_DIR = 0b0000_0000_0000_0001; 102 private static final int MODULE_DIR = 0b0000_0000_0000_0010; 103 private static final int METAINF_DIR = 0b0000_0000_0000_0100; 104 private static final int TOPLEVEL_PKG_DIR = 0b0000_0000_0000_1000; 105 106 private int flags; 107 private final UTF8String name; 108 private final BasicFileAttributes fileAttrs; 109 110 Node(UTF8String name, BasicFileAttributes fileAttrs) { 111 assert name != null; 112 assert fileAttrs != null; 113 this.name = name; 114 this.fileAttrs = fileAttrs; 115 } 116 117 public void setIsRootDir() { 118 flags |= ROOT_DIR; 119 } 120 121 public boolean isRootDir() { 122 return (flags & ROOT_DIR) != 0; 123 } 124 125 public void setIsModuleDir() { 126 flags |= MODULE_DIR; 127 } 128 129 public boolean isModuleDir() { 130 return (flags & MODULE_DIR) != 0; 131 } 132 133 public void setIsMetaInfDir() { 134 flags |= METAINF_DIR; 135 } 136 137 public boolean isMetaInfDir() { 138 return (flags & METAINF_DIR) != 0; 139 } 140 141 public void setIsTopLevelPackageDir() { 142 flags |= TOPLEVEL_PKG_DIR; 143 } 144 145 public boolean isTopLevelPackageDir() { 146 return (flags & TOPLEVEL_PKG_DIR) != 0; 147 } 148 149 public final UTF8String getName() { 150 return name; 151 } 152 153 public final BasicFileAttributes getFileAttributes() { 154 return fileAttrs; 155 } 156 157 public boolean isDirectory() { 158 return false; 159 } 160 161 public List<Node> getChildren() { 162 throw new IllegalArgumentException("not a directory: " + getNameString()); 163 } 164 165 public boolean isResource() { 166 return false; 167 } 168 169 public ImageLocation getLocation() { 170 throw new IllegalArgumentException("not a resource: " + getNameString()); 171 } 172 173 public long size() { 174 return 0L; 175 } 176 177 public long compressedSize() { 178 return 0L; 179 } 180 181 public String extension() { 182 return null; 183 } 184 185 public long contentOffset() { 186 return 0L; 187 } 188 189 public final FileTime creationTime() { 190 return fileAttrs.creationTime(); 191 } 192 193 public final FileTime lastAccessTime() { 194 return fileAttrs.lastAccessTime(); 195 } 196 197 public final FileTime lastModifiedTime() { 198 return fileAttrs.lastModifiedTime(); 199 } 200 201 public final String getNameString() { 202 return name.toString(); 203 } 204 205 @Override 206 public final String toString() { 207 return getNameString(); 208 } 209 210 @Override 211 public final int hashCode() { 212 return name.hashCode(); 213 } 214 215 @Override 216 public final boolean equals(Object other) { 217 if (this == other) { 218 return true; 219 } 220 221 if (other instanceof Node) { 222 return name.equals(((Node) other).name); 223 } 224 225 return false; 226 } 227 } 228 229 // directory node - directory has full path name without '/' at end. 230 public static final class Directory extends Node { 231 private final List<Node> children; 232 233 @SuppressWarnings("LeakingThisInConstructor") 234 Directory(Directory parent, UTF8String name, BasicFileAttributes fileAttrs) { 235 super(name, fileAttrs); 236 children = new ArrayList<>(); 237 if (parent != null) { 238 parent.addChild(this); 239 } 240 } 241 242 @Override 243 public boolean isDirectory() { 244 return true; 245 } 246 247 public List<Node> getChildren() { 248 return Collections.unmodifiableList(children); 249 } 250 251 void addChild(Node node) { 252 children.add(node); 253 } 254 } 255 256 // "resource" is .class or any other resource (compressed/uncompressed) in a jimage. 257 // full path of the resource is the "name" of the resource. 258 public static class Resource extends Node { 259 private final ImageLocation loc; 260 261 @SuppressWarnings("LeakingThisInConstructor") 262 Resource(Directory parent, ImageLocation loc, BasicFileAttributes fileAttrs) { 263 this(parent, ROOT_STRING.concat(loc.getFullname()), loc, fileAttrs); 264 } 265 266 @SuppressWarnings("LeakingThisInConstructor") 267 Resource(Directory parent, UTF8String name, ImageLocation loc, BasicFileAttributes fileAttrs) { 268 super(name, fileAttrs); 269 this.loc = loc; 270 parent.addChild(this); 271 } 272 273 @Override 274 public boolean isResource() { 275 return true; 276 } 277 278 @Override 279 public ImageLocation getLocation() { 280 return loc; 281 } 282 283 @Override 284 public long size() { 285 return loc.getUncompressedSize(); 286 } 287 288 @Override 289 public long compressedSize() { 290 return loc.getCompressedSize(); 291 } 292 293 @Override 294 public String extension() { 295 return loc.getExtensionString(); 296 } 297 298 @Override 299 public long contentOffset() { 300 return loc.getContentOffset(); 301 } 302 } 303 304 // directory management interface 305 public Directory getRootDirectory() { 306 return buildRootDirectory(); 307 } 308 309 public Node findNode(String name) { 310 return findNode(new UTF8String(name)); 311 } 312 313 public Node findNode(byte[] name) { 314 return findNode(new UTF8String(name)); 315 } 316 317 public synchronized Node findNode(UTF8String name) { 318 buildRootDirectory(); 319 Node node = nodes.get(name); 320 if (node != null) { 321 return node; 322 } 323 // Try with front '/' 324 if (name.length() > 0 && name.charAt(0) != '/') { 325 name = ROOT_STRING.concat(name); 326 } 327 node = nodes.get(name); 328 if (node != null) { 329 return node; 330 } 331 // remove '/' at the end and try again 332 if (name.charAt(name.length() - 1) == '/') { 333 name = name.substring(0, name.length() - 1); 334 } 335 return nodes.get(name); 336 } 337 338 private synchronized void clearNodes() { 339 nodes.clear(); 340 rootDir = null; 341 } 342 343 /** 344 * Returns the file attributes of the image file. 345 */ 346 private BasicFileAttributes imageFileAttributes() { 347 BasicFileAttributes attrs = imageFileAttributes; 348 if (attrs == null) { 349 try { 350 Path file = Paths.get(imagePath()); 351 attrs = Files.readAttributes(file, BasicFileAttributes.class); 352 } catch (IOException ioe) { 353 throw new UncheckedIOException(ioe); 354 } 355 imageFileAttributes = attrs; 356 } 357 return attrs; 358 } 359 360 private synchronized Directory buildRootDirectory() { 361 if (rootDir != null) { 362 return rootDir; 363 } 364 365 // FIXME no time information per resource in jimage file (yet?) 366 // we use file attributes of jimage itself. 367 // root directory 368 rootDir = new Directory(null, ROOT_STRING, imageFileAttributes()); 369 rootDir.setIsRootDir(); 370 nodes.put(rootDir.getName(), rootDir); 371 nodes.put(UTF8String.EMPTY_STRING, rootDir); 372 373 ImageLocation[] locs = getAllLocations(true); 374 for (ImageLocation loc : locs) { 375 UTF8String parent = loc.getParent(); 376 // directory where this location goes as child 377 Directory dir; 378 if (parent == null || parent.isEmpty()) { 379 // top level entry under root 380 dir = rootDir; 381 } else { 382 int idx = parent.lastIndexOf('/'); 383 assert idx != -1 : "invalid parent string"; 384 UTF8String name = ROOT_STRING.concat(parent.substring(0, idx)); 385 dir = (Directory) nodes.get(name); 386 if (dir == null) { 387 // make all parent directories (as needed) 388 dir = makeDirectories(parent); 389 } 390 } 391 Resource entry = new Resource(dir, loc, imageFileAttributes()); 392 nodes.put(entry.getName(), entry); 393 } 394 395 Node metaInf = nodes.get(META_INF_STRING); 396 if (metaInf instanceof Directory) { 397 metaInf.setIsMetaInfDir(); 398 } 399 400 fillPackageModuleInfo(); 401 402 return rootDir; 403 } 404 405 private Directory newDirectory(Directory parent, UTF8String name) { 406 Directory dir = new Directory(parent, name, imageFileAttributes()); 407 nodes.put(dir.getName(), dir); 408 return dir; 409 } 410 411 private Directory makeDirectories(UTF8String parent) { 412 assert !parent.isEmpty() : "non empty parent expected"; 413 414 int idx = parent.indexOf('/'); 415 assert idx != -1 : "invalid parent string"; 416 UTF8String name = ROOT_STRING.concat(parent.substring(0, idx)); 417 Directory top = (Directory) nodes.get(name); 418 if (top == null) { 419 top = newDirectory(rootDir, name); 420 } 421 Directory last = top; 422 while ((idx = parent.indexOf('/', idx + 1)) != -1) { 423 name = ROOT_STRING.concat(parent.substring(0, idx)); 424 Directory nextDir = (Directory) nodes.get(name); 425 if (nextDir == null) { 426 nextDir = newDirectory(last, name); 427 } 428 last = nextDir; 429 } 430 431 return last; 432 } 433 434 private void fillPackageModuleInfo() { 435 assert rootDir != null; 436 437 packageMap.entrySet().stream().sorted((x, y)->x.getKey().compareTo(y.getKey())).forEach((entry) -> { 438 UTF8String moduleName = new UTF8String("/" + entry.getValue()); 439 UTF8String fullName = moduleName.concat(new UTF8String(entry.getKey() + "/")); 440 if (! nodes.containsKey(fullName)) { 441 Directory module = (Directory) nodes.get(moduleName); 442 assert module != null : "module directory missing " + moduleName; 443 module.setIsModuleDir(); 444 // package name without front '/' 445 UTF8String pkgName = new UTF8String(entry.getKey() + "/"); 446 int idx = -1; 447 Directory moduleSubDir = module; 448 while ((idx = pkgName.indexOf('/', idx + 1)) != -1) { 449 UTF8String subPkg = pkgName.substring(0, idx); 450 UTF8String moduleSubDirName = moduleName.concat(ROOT_STRING, subPkg); 451 Directory tmp = (Directory) nodes.get(moduleSubDirName); 452 if (tmp == null) { 453 moduleSubDir = newDirectory(moduleSubDir, moduleSubDirName); 454 } else { 455 moduleSubDir = tmp; 456 } 457 } 458 // copy pkgDir "resources" 459 Directory pkgDir = (Directory) nodes.get(ROOT_STRING.concat(pkgName.substring(0, pkgName.length() - 1))); 460 pkgDir.setIsTopLevelPackageDir(); 461 for (Node child : pkgDir.getChildren()) { 462 if (child.isResource()) { 463 ImageLocation loc = child.getLocation(); 464 BasicFileAttributes imageFileAttrs = child.getFileAttributes(); 465 UTF8String rsName = moduleName.concat(child.getName()); 466 Resource rs = new Resource(moduleSubDir, rsName, loc, imageFileAttrs); 467 nodes.put(rs.getName(), rs); 468 } 469 } 470 } 471 }); 472 } 473 474 public byte[] getResource(Node node) throws IOException { 475 if (node.isResource()) { 476 return super.getResource(node.getLocation()); 477 } 478 throw new IOException("Not a resource: " + node); 479 } 480 481 public byte[] getResource(Resource rs) throws IOException { 482 return super.getResource(rs.getLocation()); 483 } 484 }