1 /* 2 * Copyright (c) 2015, 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 26 package jdk.internal.module; 27 28 import java.io.File; 29 import java.io.IOError; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.io.UncheckedIOException; 33 import java.lang.module.ModuleReader; 34 import java.lang.module.ModuleReference; 35 import java.net.URI; 36 import java.nio.ByteBuffer; 37 import java.nio.file.Files; 38 import java.nio.file.Path; 39 import java.nio.file.Paths; 40 import java.util.List; 41 import java.util.Objects; 42 import java.util.Optional; 43 import java.util.concurrent.locks.Lock; 44 import java.util.concurrent.locks.ReadWriteLock; 45 import java.util.concurrent.locks.ReentrantReadWriteLock; 46 import java.util.function.Supplier; 47 import java.util.jar.JarEntry; 48 import java.util.jar.JarFile; 49 import java.util.stream.Collectors; 50 import java.util.stream.Stream; 51 import java.util.zip.ZipFile; 52 53 import jdk.internal.jmod.JmodFile; 54 import jdk.internal.misc.JavaLangAccess; 55 import jdk.internal.misc.SharedSecrets; 56 import jdk.internal.module.ModuleHashes.HashSupplier; 57 import jdk.internal.util.jar.VersionedStream; 58 import sun.net.www.ParseUtil; 59 60 61 /** 62 * A factory for creating ModuleReference implementations where the modules are 63 * packaged as modular JAR file, JMOD files or where the modules are exploded 64 * on the file system. 65 */ 66 67 class ModuleReferences { 68 69 private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); 70 71 private ModuleReferences() { } 72 73 /** 74 * Creates a ModuleReference to a module or to patched module when 75 * creating modules for the boot Layer and --patch-module is specified. 76 */ 77 private static ModuleReference newModule(ModuleInfo.Attributes attrs, 78 URI uri, 79 Supplier<ModuleReader> supplier, 80 HashSupplier hasher) { 81 82 ModuleReference mref = new ModuleReferenceImpl(attrs.descriptor(), 83 uri, 84 supplier, 85 null, 86 attrs.recordedHashes(), 87 hasher, 88 attrs.moduleResolution()); 89 if (JLA.getBootLayer() == null) 90 mref = ModuleBootstrap.patcher().patchIfNeeded(mref); 91 92 return mref; 93 } 94 95 /** 96 * Creates a ModuleReference to a module packaged as a modular JAR. 97 */ 98 static ModuleReference newJarModule(ModuleInfo.Attributes attrs, Path file) { 99 URI uri = file.toUri(); 100 Supplier<ModuleReader> supplier = () -> new JarModuleReader(file, uri); 101 HashSupplier hasher = (a) -> ModuleHashes.computeHash(file, a); 102 return newModule(attrs, uri, supplier, hasher); 103 } 104 105 /** 106 * Creates a ModuleReference to a module packaged as a JMOD. 107 */ 108 static ModuleReference newJModModule(ModuleInfo.Attributes attrs, Path file) { 109 URI uri = file.toUri(); 110 Supplier<ModuleReader> supplier = () -> new JModModuleReader(file, uri); 111 HashSupplier hasher = (a) -> ModuleHashes.computeHash(file, a); 112 return newModule(attrs, uri, supplier, hasher); 113 } 114 115 /** 116 * Creates a ModuleReference to an exploded module. 117 */ 118 static ModuleReference newExplodedModule(ModuleInfo.Attributes attrs, Path dir) { 119 Supplier<ModuleReader> supplier = () -> new ExplodedModuleReader(dir); 120 return newModule(attrs, dir.toUri(), supplier, null); 121 } 122 123 124 /** 125 * A base module reader that encapsulates machinery required to close the 126 * module reader safely. 127 */ 128 static abstract class SafeCloseModuleReader implements ModuleReader { 129 130 // RW lock to support safe close 131 private final ReadWriteLock lock = new ReentrantReadWriteLock(); 132 private final Lock readLock = lock.readLock(); 133 private final Lock writeLock = lock.writeLock(); 134 private boolean closed; 135 136 SafeCloseModuleReader() { } 137 138 /** 139 * Returns a URL to resource. This method is invoked by the find 140 * method to do the actual work of finding the resource. 141 */ 142 abstract Optional<URI> implFind(String name) throws IOException; 143 144 /** 145 * Returns an input stream for reading a resource. This method is 146 * invoked by the open method to do the actual work of opening 147 * an input stream to the resource. 148 */ 149 abstract Optional<InputStream> implOpen(String name) throws IOException; 150 151 /** 152 * Returns a stream of the names of resources in the module. This 153 * method is invoked by the list method to do the actual work of 154 * creating the stream. 155 */ 156 abstract Stream<String> implList() throws IOException; 157 158 /** 159 * Closes the module reader. This method is invoked by close to do the 160 * actual work of closing the module reader. 161 */ 162 abstract void implClose() throws IOException; 163 164 @Override 165 public final Optional<URI> find(String name) throws IOException { 166 readLock.lock(); 167 try { 168 if (!closed) { 169 return implFind(name); 170 } else { 171 throw new IOException("ModuleReader is closed"); 172 } 173 } finally { 174 readLock.unlock(); 175 } 176 } 177 178 179 @Override 180 public final Optional<InputStream> open(String name) throws IOException { 181 readLock.lock(); 182 try { 183 if (!closed) { 184 return implOpen(name); 185 } else { 186 throw new IOException("ModuleReader is closed"); 187 } 188 } finally { 189 readLock.unlock(); 190 } 191 } 192 193 @Override 194 public final Stream<String> list() throws IOException { 195 readLock.lock(); 196 try { 197 if (!closed) { 198 return implList(); 199 } else { 200 throw new IOException("ModuleReader is closed"); 201 } 202 } finally { 203 readLock.unlock(); 204 } 205 } 206 207 @Override 208 public final void close() throws IOException { 209 writeLock.lock(); 210 try { 211 if (!closed) { 212 closed = true; 213 implClose(); 214 } 215 } finally { 216 writeLock.unlock(); 217 } 218 } 219 } 220 221 222 /** 223 * A ModuleReader for a modular JAR file. 224 */ 225 static class JarModuleReader extends SafeCloseModuleReader { 226 private final JarFile jf; 227 private final URI uri; 228 229 static JarFile newJarFile(Path path) { 230 try { 231 return new JarFile(path.toFile(), 232 true, // verify 233 ZipFile.OPEN_READ, 234 JarFile.runtimeVersion()); 235 } catch (IOException ioe) { 236 throw new UncheckedIOException(ioe); 237 } 238 } 239 240 JarModuleReader(Path path, URI uri) { 241 this.jf = newJarFile(path); 242 this.uri = uri; 243 } 244 245 private JarEntry getEntry(String name) { 246 return jf.getJarEntry(Objects.requireNonNull(name)); 247 } 248 249 @Override 250 Optional<URI> implFind(String name) throws IOException { 251 JarEntry je = getEntry(name); 252 if (je != null) { 253 if (jf.isMultiRelease()) 254 name = SharedSecrets.javaUtilJarAccess().getRealName(jf, je); 255 String encodedPath = ParseUtil.encodePath(name, false); 256 String uris = "jar:" + uri + "!/" + encodedPath; 257 return Optional.of(URI.create(uris)); 258 } else { 259 return Optional.empty(); 260 } 261 } 262 263 @Override 264 Optional<InputStream> implOpen(String name) throws IOException { 265 JarEntry je = getEntry(name); 266 if (je != null) { 267 return Optional.of(jf.getInputStream(je)); 268 } else { 269 return Optional.empty(); 270 } 271 } 272 273 @Override 274 Stream<String> implList() throws IOException { 275 // take snapshot to avoid async close 276 List<String> names = VersionedStream.stream(jf) 277 .filter(e -> !e.isDirectory()) 278 .map(JarEntry::getName) 279 .collect(Collectors.toList()); 280 return names.stream(); 281 } 282 283 @Override 284 void implClose() throws IOException { 285 jf.close(); 286 } 287 } 288 289 290 /** 291 * A ModuleReader for a JMOD file. 292 */ 293 static class JModModuleReader extends SafeCloseModuleReader { 294 private final JmodFile jf; 295 private final URI uri; 296 297 static JmodFile newJmodFile(Path path) { 298 try { 299 return new JmodFile(path); 300 } catch (IOException ioe) { 301 throw new UncheckedIOException(ioe); 302 } 303 } 304 305 JModModuleReader(Path path, URI uri) { 306 this.jf = newJmodFile(path); 307 this.uri = uri; 308 } 309 310 private JmodFile.Entry getEntry(String name) { 311 Objects.requireNonNull(name); 312 return jf.getEntry(JmodFile.Section.CLASSES, name); 313 } 314 315 @Override 316 Optional<URI> implFind(String name) { 317 JmodFile.Entry je = getEntry(name); 318 if (je != null) { 319 String encodedPath = ParseUtil.encodePath(name, false); 320 String uris = "jmod:" + uri + "!/" + encodedPath; 321 return Optional.of(URI.create(uris)); 322 } else { 323 return Optional.empty(); 324 } 325 } 326 327 @Override 328 Optional<InputStream> implOpen(String name) throws IOException { 329 JmodFile.Entry je = getEntry(name); 330 if (je != null) { 331 return Optional.of(jf.getInputStream(je)); 332 } else { 333 return Optional.empty(); 334 } 335 } 336 337 @Override 338 Stream<String> implList() throws IOException { 339 // take snapshot to avoid async close 340 List<String> names = jf.stream() 341 .filter(e -> e.section() == JmodFile.Section.CLASSES) 342 .map(JmodFile.Entry::name) 343 .collect(Collectors.toList()); 344 return names.stream(); 345 } 346 347 @Override 348 void implClose() throws IOException { 349 jf.close(); 350 } 351 } 352 353 354 /** 355 * A ModuleReader for an exploded module. 356 */ 357 static class ExplodedModuleReader implements ModuleReader { 358 private final Path dir; 359 private volatile boolean closed; 360 361 ExplodedModuleReader(Path dir) { 362 this.dir = dir; 363 364 // when running with a security manager then check that the caller 365 // has access to the directory. 366 SecurityManager sm = System.getSecurityManager(); 367 if (sm != null) { 368 boolean unused = Files.isDirectory(dir); 369 } 370 } 371 372 /** 373 * Returns a Path to access to the given resource. 374 */ 375 private Path toPath(String name) { 376 Path path = Paths.get(name.replace('/', File.separatorChar)); 377 if (path.getRoot() == null) { 378 return dir.resolve(path); 379 } else { 380 // drop the root component so that the resource is 381 // located relative to the module directory 382 int n = path.getNameCount(); 383 return (n > 0) ? dir.resolve(path.subpath(0, n)) : null; 384 } 385 } 386 387 /** 388 * Throws IOException if the module reader is closed; 389 */ 390 private void ensureOpen() throws IOException { 391 if (closed) throw new IOException("ModuleReader is closed"); 392 } 393 394 @Override 395 public Optional<URI> find(String name) throws IOException { 396 ensureOpen(); 397 Path path = toPath(name); 398 if (path != null && Files.isRegularFile(path)) { 399 try { 400 return Optional.of(path.toUri()); 401 } catch (IOError e) { 402 throw (IOException) e.getCause(); 403 } 404 } else { 405 return Optional.empty(); 406 } 407 } 408 409 @Override 410 public Optional<InputStream> open(String name) throws IOException { 411 ensureOpen(); 412 Path path = toPath(name); 413 if (path != null && Files.isRegularFile(path)) { 414 return Optional.of(Files.newInputStream(path)); 415 } else { 416 return Optional.empty(); 417 } 418 } 419 420 @Override 421 public Optional<ByteBuffer> read(String name) throws IOException { 422 ensureOpen(); 423 Path path = toPath(name); 424 if (path != null && Files.isRegularFile(path)) { 425 return Optional.of(ByteBuffer.wrap(Files.readAllBytes(path))); 426 } else { 427 return Optional.empty(); 428 } 429 } 430 431 @Override 432 public Stream<String> list() throws IOException { 433 ensureOpen(); 434 // sym links not followed 435 return Files.find(dir, Integer.MAX_VALUE, 436 (path, attrs) -> attrs.isRegularFile()) 437 .map(f -> dir.relativize(f) 438 .toString() 439 .replace(File.separatorChar, '/')); 440 } 441 442 @Override 443 public void close() { 444 closed = true; 445 } 446 } 447 448 }