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