1 /* 2 * Copyright (c) 2015, 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.jrtfs; 26 27 import java.io.IOException; 28 import java.nio.file.DirectoryStream; 29 import java.nio.file.FileSystem; 30 import java.nio.file.FileSystemException; 31 import java.nio.file.FileSystems; 32 import java.nio.file.Files; 33 import java.nio.file.LinkOption; 34 import java.nio.file.NoSuchFileException; 35 import java.nio.file.NotDirectoryException; 36 import java.nio.file.Path; 37 import java.nio.file.attribute.BasicFileAttributes; 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.HashMap; 41 import java.util.Iterator; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.function.Function; 45 import static java.util.stream.Collectors.toList; 46 import static jdk.internal.jrtfs.AbstractJrtFileSystem.getString; 47 48 /** 49 * A jrt file system built on $JAVA_HOME/modules directory ('exploded modules 50 * build') 51 */ 52 class JrtExplodedFileSystem extends AbstractJrtFileSystem { 53 54 private static final String MODULES = "/modules/"; 55 private static final String PACKAGES = "/packages/"; 56 private static final int PACKAGES_LEN = PACKAGES.length(); 57 58 // root path 59 private final JrtExplodedPath rootPath; 60 private volatile boolean isOpen; 61 private final FileSystem defaultFS; 62 private final String separator; 63 private final Map<String, Node> nodes = Collections.synchronizedMap(new HashMap<>()); 64 private final BasicFileAttributes modulesDirAttrs; 65 66 JrtExplodedFileSystem(JrtFileSystemProvider provider, 67 Map<String, ?> env) 68 throws IOException { 69 70 super(provider, env); 71 checkExists(SystemImages.modulesDirPath); 72 byte[] root = new byte[]{'/'}; 73 rootPath = new JrtExplodedPath(this, root); 74 isOpen = true; 75 defaultFS = FileSystems.getDefault(); 76 String str = defaultFS.getSeparator(); 77 separator = str.equals(getSeparator()) ? null : str; 78 modulesDirAttrs = Files.readAttributes(SystemImages.modulesDirPath, BasicFileAttributes.class); 79 initNodes(); 80 } 81 82 @Override 83 public void close() throws IOException { 84 cleanup(); 85 } 86 87 @Override 88 public boolean isOpen() { 89 return isOpen; 90 } 91 92 @Override 93 protected void finalize() throws Throwable { 94 cleanup(); 95 super.finalize(); 96 } 97 98 private synchronized void cleanup() { 99 isOpen = false; 100 nodes.clear(); 101 } 102 103 @Override 104 JrtExplodedPath getRootPath() { 105 return rootPath; 106 } 107 108 // Base class for Nodes of this file system 109 abstract class Node { 110 111 private final String name; 112 113 Node(String name) { 114 this.name = name; 115 } 116 117 final String getName() { 118 return name; 119 } 120 121 final String getExtension() { 122 if (isFile()) { 123 final int index = name.lastIndexOf("."); 124 if (index != -1) { 125 return name.substring(index + 1); 126 } 127 } 128 129 return null; 130 } 131 132 BasicFileAttributes getBasicAttrs() throws IOException { 133 return modulesDirAttrs; 134 } 135 136 boolean isLink() { 137 return false; 138 } 139 140 boolean isDirectory() { 141 return false; 142 } 143 144 boolean isFile() { 145 return false; 146 } 147 148 byte[] getContent() throws IOException { 149 if (!isFile()) { 150 throw new FileSystemException(name + " is not file"); 151 } 152 153 throw new AssertionError("ShouldNotReachHere"); 154 } 155 156 List<Node> getChildren() throws IOException { 157 if (!isDirectory()) { 158 throw new NotDirectoryException(name); 159 } 160 161 throw new AssertionError("ShouldNotReachHere"); 162 } 163 164 final Node resolveLink() { 165 return resolveLink(false); 166 } 167 168 Node resolveLink(boolean recursive) { 169 return this; 170 } 171 } 172 173 // A Node that is backed by actual default file system Path 174 private final class PathNode extends Node { 175 176 // Path in underlying default file system 177 private final Path path; 178 private final boolean file; 179 // lazily initialized, don't read attributes unless required! 180 private BasicFileAttributes attrs; 181 182 PathNode(String name, Path path) { 183 super(name); 184 this.path = path; 185 this.file = Files.isRegularFile(path); 186 } 187 188 @Override 189 synchronized BasicFileAttributes getBasicAttrs() throws IOException { 190 if (attrs == null) { 191 attrs = Files.readAttributes(path, BasicFileAttributes.class); 192 } 193 return attrs; 194 } 195 196 @Override 197 boolean isDirectory() { 198 return !file; 199 } 200 201 @Override 202 boolean isFile() { 203 return file; 204 } 205 206 @Override 207 byte[] getContent() throws IOException { 208 if (!isFile()) { 209 throw new FileSystemException(getName() + " is not file"); 210 } 211 212 return Files.readAllBytes(path); 213 } 214 215 @Override 216 List<Node> getChildren() throws IOException { 217 if (!isDirectory()) { 218 throw new NotDirectoryException(getName()); 219 } 220 221 List<Node> children = new ArrayList<>(); 222 try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) { 223 for (Path cp : stream) { 224 cp = SystemImages.modulesDirPath.relativize(cp); 225 String cpName = MODULES + nativeSlashToFrontSlash(cp.toString()); 226 try { 227 children.add(findNode(cpName)); 228 } catch (NoSuchFileException nsfe) { 229 // findNode may choose to hide certain files! 230 } 231 } 232 } 233 234 return children; 235 } 236 } 237 238 // A Node that links to another Node 239 private final class LinkNode extends Node { 240 241 // underlying linked Node 242 private final Node link; 243 244 LinkNode(String name, Node link) { 245 super(name); 246 this.link = link; 247 } 248 249 @Override 250 BasicFileAttributes getBasicAttrs() throws IOException { 251 return link.getBasicAttrs(); 252 } 253 254 @Override 255 public boolean isLink() { 256 return true; 257 } 258 259 @Override 260 Node resolveLink(boolean recursive) { 261 return recursive && (link instanceof LinkNode) ? ((LinkNode) link).resolveLink(true) : link; 262 } 263 } 264 265 // A directory Node with it's children Nodes 266 private final class DirNode extends Node { 267 268 // children Nodes of this Node. 269 private final List<Node> children; 270 271 DirNode(String name, List<Node> children) { 272 super(name); 273 this.children = children; 274 } 275 276 @Override 277 boolean isDirectory() { 278 return true; 279 } 280 281 @Override 282 List<Node> getChildren() throws IOException { 283 return children; 284 } 285 } 286 287 private JrtExplodedPath toJrtExplodedPath(String path) { 288 return toJrtExplodedPath(getBytes(path)); 289 } 290 291 private JrtExplodedPath toJrtExplodedPath(byte[] path) { 292 return new JrtExplodedPath(this, path); 293 } 294 295 @Override 296 boolean isSameFile(AbstractJrtPath p1, AbstractJrtPath p2) throws IOException { 297 Node n1 = checkNode(p1.getName()); 298 Node n2 = checkNode(p2.getName()); 299 return n1 == n2; 300 } 301 302 @Override 303 boolean isLink(AbstractJrtPath jrtPath) throws IOException { 304 return checkNode(jrtPath.getName()).isLink(); 305 } 306 307 @Override 308 AbstractJrtPath resolveLink(AbstractJrtPath jrtPath) throws IOException { 309 String name = checkNode(jrtPath.getName()).resolveLink().getName(); 310 return toJrtExplodedPath(name); 311 } 312 313 @Override 314 AbstractJrtFileAttributes getFileAttributes(byte[] path, LinkOption... options) throws IOException { 315 Node node = checkNode(path); 316 if (node.isLink() && followLinks(options)) { 317 node = node.resolveLink(true); 318 } 319 return new JrtExplodedFileAttributes(node); 320 } 321 322 @Override 323 boolean exists(byte[] path) throws IOException { 324 try { 325 checkNode(path); 326 return true; 327 } catch (NoSuchFileException nsfe) { 328 return false; 329 } 330 } 331 332 @Override 333 boolean isDirectory(byte[] path, boolean resolveLinks) throws IOException { 334 Node node = checkNode(path); 335 return resolveLinks && node.isLink() 336 ? node.resolveLink(true).isDirectory() 337 : node.isDirectory(); 338 } 339 340 @Override 341 Iterator<Path> iteratorOf(byte[] path, String childPrefix) throws IOException { 342 Node node = checkNode(path).resolveLink(true); 343 if (!node.isDirectory()) { 344 throw new NotDirectoryException(getString(path)); 345 } 346 347 Function<Node, Path> f = childPrefix == null 348 ? child -> toJrtExplodedPath(child.getName()) 349 : child -> toJrtExplodedPath(childPrefix + child.getName().substring(1)); 350 return node.getChildren().stream().map(f).collect(toList()).iterator(); 351 } 352 353 @Override 354 byte[] getFileContent(byte[] path) throws IOException { 355 return checkNode(path).getContent(); 356 } 357 358 private Node checkNode(byte[] path) throws IOException { 359 ensureOpen(); 360 return findNode(path); 361 } 362 363 synchronized Node findNode(byte[] path) throws IOException { 364 return findNode(getString(path)); 365 } 366 367 // find Node for the given Path 368 synchronized Node findNode(String str) throws IOException { 369 Node node = findModulesNode(str); 370 if (node != null) { 371 return node; 372 } 373 374 // lazily created for paths like /packages/<package>/<module>/xyz 375 // For example /packages/java.lang/java.base/java/lang/ 376 if (str.startsWith(PACKAGES)) { 377 // pkgEndIdx marks end of <package> part 378 int pkgEndIdx = str.indexOf('/', PACKAGES_LEN); 379 if (pkgEndIdx != -1) { 380 // modEndIdx marks end of <module> part 381 int modEndIdx = str.indexOf('/', pkgEndIdx + 1); 382 if (modEndIdx != -1) { 383 // make sure we have such module link! 384 // ie., /packages/<package>/<module> is valid 385 Node linkNode = nodes.get(str.substring(0, modEndIdx)); 386 if (linkNode == null || !linkNode.isLink()) { 387 throw new NoSuchFileException(str); 388 } 389 390 // map to "/modules/zyz" path and return that node 391 // For example, "/modules/java.base/java/lang" for 392 // "/packages/java.lang/java.base/java/lang". 393 String mod = MODULES + str.substring(pkgEndIdx + 1); 394 return findNode(mod); 395 } 396 } 397 } 398 399 throw new NoSuchFileException(str); 400 } 401 402 // find a Node for a path that starts like "/modules/..." 403 synchronized Node findModulesNode(String str) throws IOException { 404 Node node = nodes.get(str); 405 if (node != null) { 406 return node; 407 } 408 409 // lazily created "/modules/xyz/abc/" Node 410 // This is mapped to default file system path "<JDK_MODULES_DIR>/xyz/abc" 411 Path p = underlyingPath(str); 412 if (p != null) { 413 if (Files.isRegularFile(p)) { 414 Path file = p.getFileName(); 415 if (file.toString().startsWith("_the.")) { 416 return null; 417 } 418 } 419 node = new PathNode(str, p); 420 nodes.put(str, node); 421 return node; 422 } 423 424 return null; 425 } 426 427 Path underlyingPath(String str) { 428 if (str.startsWith(MODULES)) { 429 str = frontSlashToNativeSlash(str.substring("/modules".length())); 430 return defaultFS.getPath(SystemImages.modulesDirPath.toString(), str); 431 } 432 return null; 433 } 434 435 // convert "/" to platform path separator 436 private String frontSlashToNativeSlash(String str) { 437 return separator == null ? str : str.replace("/", separator); 438 } 439 440 // convert platform path separator to "/" 441 private String nativeSlashToFrontSlash(String str) { 442 return separator == null ? str : str.replace(separator, "/"); 443 } 444 445 // convert "/"s to "."s 446 private String slashesToDots(String str) { 447 return str.replace(separator != null ? separator : "/", "."); 448 } 449 450 // initialize file system Nodes 451 private void initNodes() throws IOException { 452 // same package prefix may exist in mutliple modules. This Map 453 // is filled by walking "jdk modules" directory recursively! 454 Map<String, List<String>> packageToModules = new HashMap<>(); 455 456 try (DirectoryStream<Path> stream = Files.newDirectoryStream(SystemImages.modulesDirPath)) { 457 for (Path module : stream) { 458 if (Files.isDirectory(module)) { 459 String moduleName = module.getFileName().toString(); 460 // make sure "/modules/<moduleName>" is created 461 findModulesNode(MODULES + moduleName); 462 463 Files.walk(module).filter(Files::isDirectory).forEach((p) -> { 464 p = module.relativize(p); 465 String pkgName = slashesToDots(p.toString()); 466 // skip META-INFO and empty strings 467 if (!pkgName.isEmpty() && !pkgName.startsWith("META-INF")) { 468 List<String> moduleNames = packageToModules.get(pkgName); 469 if (moduleNames == null) { 470 moduleNames = new ArrayList<>(); 471 packageToModules.put(pkgName, moduleNames); 472 } 473 moduleNames.add(moduleName); 474 } 475 }); 476 } 477 } 478 } 479 480 // create "/modules" directory 481 // "nodes" map contains only /modules/<foo> nodes only so far and so add all as children of /modules 482 DirNode modulesDir = new DirNode("/modules", new ArrayList<>(nodes.values())); 483 nodes.put(modulesDir.getName(), modulesDir); 484 485 // create children under "/packages" 486 List<Node> packagesChildren = new ArrayList<>(packageToModules.size()); 487 for (Map.Entry<String, List<String>> entry : packageToModules.entrySet()) { 488 String pkgName = entry.getKey(); 489 List<String> moduleNameList = entry.getValue(); 490 List<Node> moduleLinkNodes = new ArrayList<>(moduleNameList.size()); 491 for (String moduleName : moduleNameList) { 492 Node moduleNode = findModulesNode(MODULES + moduleName); 493 LinkNode linkNode = new LinkNode(PACKAGES + pkgName + "/" + moduleName, moduleNode); 494 nodes.put(linkNode.getName(), linkNode); 495 moduleLinkNodes.add(linkNode); 496 } 497 498 DirNode pkgDir = new DirNode(PACKAGES + pkgName, moduleLinkNodes); 499 nodes.put(pkgDir.getName(), pkgDir); 500 packagesChildren.add(pkgDir); 501 } 502 503 // "/packages" dir 504 DirNode packagesDir = new DirNode("/packages", packagesChildren); 505 nodes.put(packagesDir.getName(), packagesDir); 506 507 // finally "/" dir! 508 List<Node> rootChildren = new ArrayList<>(); 509 rootChildren.add(modulesDir); 510 rootChildren.add(packagesDir); 511 DirNode root = new DirNode("/", rootChildren); 512 nodes.put(root.getName(), root); 513 } 514 }