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