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 */ 25package jdk.internal.jrtfs; 26 27import java.io.IOException; 28import java.io.UncheckedIOException; 29import java.nio.file.DirectoryStream; 30import java.nio.file.FileSystem; 31import java.nio.file.FileSystemException; 32import java.nio.file.FileSystems; 33import java.nio.file.Files; 34import java.nio.file.Path; 35import java.nio.file.attribute.BasicFileAttributes; 36import java.util.ArrayList; 37import java.util.Collections; 38import java.util.HashMap; 39import java.util.List; 40import java.util.Map; 41 42import jdk.internal.jimage.ImageReader.Node; 43 44/** 45 * A jrt file system built on $JAVA_HOME/modules directory ('exploded modules 46 * build') 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 */ 54class ExplodedImage extends SystemImage { 55 56 private static final String MODULES = "/modules/"; 57 private static final String PACKAGES = "/packages/"; 58 private static final int PACKAGES_LEN = PACKAGES.length(); 59 60 private final FileSystem defaultFS; 61 private final String separator; 62 private final Map<String, PathNode> nodes = Collections.synchronizedMap(new HashMap<>()); 63 private final BasicFileAttributes modulesDirAttrs; 64 65 ExplodedImage(Path modulesDir) throws IOException { 66 defaultFS = FileSystems.getDefault(); 67 String str = defaultFS.getSeparator(); 68 separator = str.equals("/") ? null : str; 69 modulesDirAttrs = Files.readAttributes(modulesDir, BasicFileAttributes.class); 70 initNodes(); 71 } 72 73 // A Node that is backed by actual default file system Path 74 private final class PathNode extends Node { 75 76 // Path in underlying default file system 77 private Path path; 78 private PathNode link; 79 private List<Node> children; 80 81 PathNode(String name, Path path, BasicFileAttributes attrs) { // path 82 super(name, attrs); 83 this.path = path; 84 } 85 86 PathNode(String name, Node link) { // link 87 super(name, link.getFileAttributes()); 88 this.link = (PathNode)link; 89 } 90 91 PathNode(String name, List<Node> children) { // dir 92 super(name, modulesDirAttrs); 93 this.children = children; 94 } 95 96 @Override 97 public boolean isDirectory() { 98 return children != null || 99 (link == null && getFileAttributes().isDirectory()); 100 } 101 102 @Override 103 public boolean isLink() { 104 return link != null; 105 } 106 107 @Override 108 public PathNode resolveLink(boolean recursive) { 109 if (link == null) 110 return this; 111 return recursive && link.isLink() ? link.resolveLink(true) : link; 112 } 113 114 byte[] getContent() throws IOException { 115 if (!getFileAttributes().isRegularFile()) 116 throw new FileSystemException(getName() + " is not file"); 117 return Files.readAllBytes(path); 118 } 119 120 @Override 121 public List<Node> getChildren() { 122 if (!isDirectory()) 123 throw new IllegalArgumentException("not a directory: " + getNameString()); 124 if (children == null) { 125 List<Node> list = new ArrayList<>(); 126 try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) { 127 for (Path p : stream) { 128 p = explodedModulesDir.relativize(p); 129 String pName = MODULES + nativeSlashToFrontSlash(p.toString()); 130 Node node = findNode(pName); 131 if (node != null) { // findNode may choose to hide certain files! 132 list.add(node); 133 } 134 } 135 } catch (IOException x) { 136 return null; 137 } 138 children = list; 139 } 140 return children; 141 } 142 143 @Override 144 public long size() { 145 try { 146 return isDirectory() ? 0 : Files.size(path); 147 } catch (IOException ex) { 148 throw new UncheckedIOException(ex); 149 } 150 } 151 } 152 153 @Override 154 public void close() throws IOException { 155 nodes.clear(); 156 } 157 158 @Override 159 public byte[] getResource(Node node) throws IOException { 160 return ((PathNode)node).getContent(); 161 } 162 163 // find Node for the given Path 164 @Override 165 public synchronized Node findNode(String str) { 166 Node node = findModulesNode(str); 167 if (node != null) { 168 return node; 169 } 170 // lazily created for paths like /packages/<package>/<module>/xyz 171 // For example /packages/java.lang/java.base/java/lang/ 172 if (str.startsWith(PACKAGES)) { 173 // pkgEndIdx marks end of <package> part 174 int pkgEndIdx = str.indexOf('/', PACKAGES_LEN); 175 if (pkgEndIdx != -1) { 176 // modEndIdx marks end of <module> part 177 int modEndIdx = str.indexOf('/', pkgEndIdx + 1); 178 if (modEndIdx != -1) { 179 // make sure we have such module link! 180 // ie., /packages/<package>/<module> is valid 181 Node linkNode = nodes.get(str.substring(0, modEndIdx)); 182 if (linkNode == null || !linkNode.isLink()) { 183 return null; 184 } 185 // map to "/modules/zyz" path and return that node 186 // For example, "/modules/java.base/java/lang" for 187 // "/packages/java.lang/java.base/java/lang". 188 String mod = MODULES + str.substring(pkgEndIdx + 1); 189 return findModulesNode(mod); 190 } 191 } 192 } 193 return null; 194 } 195 196 // find a Node for a path that starts like "/modules/..." 197 Node findModulesNode(String str) { 198 PathNode node = nodes.get(str); 199 if (node != null) { 200 return node; 201 } 202 // lazily created "/modules/xyz/abc/" Node 203 // This is mapped to default file system path "<JDK_MODULES_DIR>/xyz/abc" 204 Path p = underlyingPath(str); 205 if (p != null) { 206 try { 207 BasicFileAttributes attrs = Files.readAttributes(p, BasicFileAttributes.class); 208 if (attrs.isRegularFile()) { 209 Path f = p.getFileName(); 210 if (f.toString().startsWith("_the.")) 211 return null; 212 } 213 node = new PathNode(str, p, attrs); 214 nodes.put(str, node); 215 return node; 216 } catch (IOException x) { 217 // does not exists or unable to determine 218 } 219 } 220 return null; 221 } 222 223 Path underlyingPath(String str) { 224 if (str.startsWith(MODULES)) { 225 str = frontSlashToNativeSlash(str.substring("/modules".length())); 226 return defaultFS.getPath(explodedModulesDir.toString(), str); 227 } 228 return null; 229 } 230 231 // convert "/" to platform path separator 232 private String frontSlashToNativeSlash(String str) { 233 return separator == null ? str : str.replace("/", separator); 234 } 235 236 // convert platform path separator to "/" 237 private String nativeSlashToFrontSlash(String str) { 238 return separator == null ? str : str.replace(separator, "/"); 239 } 240 241 // convert "/"s to "."s 242 private String slashesToDots(String str) { 243 return str.replace(separator != null ? separator : "/", "."); 244 } 245 246 // initialize file system Nodes 247 private void initNodes() throws IOException { 248 // same package prefix may exist in mutliple modules. This Map 249 // is filled by walking "jdk modules" directory recursively! 250 Map<String, List<String>> packageToModules = new HashMap<>(); 251 try (DirectoryStream<Path> stream = Files.newDirectoryStream(explodedModulesDir)) { 252 for (Path module : stream) { 253 if (Files.isDirectory(module)) { 254 String moduleName = module.getFileName().toString(); 255 // make sure "/modules/<moduleName>" is created 256 findModulesNode(MODULES + moduleName); 257 Files.walk(module).filter(Files::isDirectory).forEach((p) -> { 258 p = module.relativize(p); 259 String pkgName = slashesToDots(p.toString()); 260 // skip META-INFO and empty strings 261 if (!pkgName.isEmpty() && !pkgName.startsWith("META-INF")) { 262 List<String> moduleNames = packageToModules.get(pkgName); 263 if (moduleNames == null) { 264 moduleNames = new ArrayList<>(); 265 packageToModules.put(pkgName, moduleNames); 266 } 267 moduleNames.add(moduleName); 268 } 269 }); 270 } 271 } 272 } 273 // create "/modules" directory 274 // "nodes" map contains only /modules/<foo> nodes only so far and so add all as children of /modules 275 PathNode modulesDir = new PathNode("/modules", new ArrayList<>(nodes.values())); 276 nodes.put(modulesDir.getName(), modulesDir); 277 278 // create children under "/packages" 279 List<Node> packagesChildren = new ArrayList<>(packageToModules.size()); 280 for (Map.Entry<String, List<String>> entry : packageToModules.entrySet()) { 281 String pkgName = entry.getKey(); 282 List<String> moduleNameList = entry.getValue(); 283 List<Node> moduleLinkNodes = new ArrayList<>(moduleNameList.size()); 284 for (String moduleName : moduleNameList) { 285 Node moduleNode = findModulesNode(MODULES + moduleName); 286 PathNode linkNode = new PathNode(PACKAGES + pkgName + "/" + moduleName, moduleNode); 287 nodes.put(linkNode.getName(), linkNode); 288 moduleLinkNodes.add(linkNode); 289 } 290 PathNode pkgDir = new PathNode(PACKAGES + pkgName, moduleLinkNodes); 291 nodes.put(pkgDir.getName(), pkgDir); 292 packagesChildren.add(pkgDir); 293 } 294 // "/packages" dir 295 PathNode packagesDir = new PathNode("/packages", packagesChildren); 296 nodes.put(packagesDir.getName(), packagesDir); 297 298 // finally "/" dir! 299 List<Node> rootChildren = new ArrayList<>(); 300 rootChildren.add(packagesDir); 301 rootChildren.add(modulesDir); 302 PathNode root = new PathNode("/", rootChildren); 303 nodes.put(root.getName(), root); 304 } 305} 306