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.util.List; 40 import java.util.Objects; 41 import java.util.Optional; 42 import java.util.concurrent.locks.Lock; 43 import java.util.concurrent.locks.ReadWriteLock; 44 import java.util.concurrent.locks.ReentrantReadWriteLock; 45 import java.util.function.Supplier; 46 import java.util.jar.JarEntry; 47 import java.util.jar.JarFile; 48 import java.util.stream.Collectors; 49 import java.util.stream.Stream; 50 import java.util.zip.ZipFile; 51 52 import jdk.internal.jmod.JmodFile; 53 import jdk.internal.misc.SharedSecrets; 54 import jdk.internal.module.ModuleHashes.HashSupplier; 55 import jdk.internal.util.jar.VersionedStream; 56 import sun.net.www.ParseUtil; 57 58 59 /** 60 * A factory for creating ModuleReference implementations where the modules are 61 * packaged as modular JAR file, JMOD files or where the modules are exploded 62 * on the file system. 63 */ 64 65 class ModuleReferences { 66 private ModuleReferences() { } 67 68 /** 69 * Creates a ModuleReference to a possibly-patched module 70 */ 71 private static ModuleReference newModule(ModuleInfo.Attributes attrs, 72 URI uri, 73 Supplier<ModuleReader> supplier, 74 ModulePatcher patcher, 75 HashSupplier hasher) { 76 ModuleReference mref = new ModuleReferenceImpl(attrs.descriptor(), 77 uri, 78 supplier, 79 null, 80 attrs.target(), 81 attrs.recordedHashes(), 82 hasher, 83 attrs.moduleResolution()); 84 if (patcher != null) 85 mref = patcher.patchIfNeeded(mref); 86 87 return mref; 88 } 89 90 /** 91 * Creates a ModuleReference to a possibly-patched module in a modular JAR. 92 */ 93 static ModuleReference newJarModule(ModuleInfo.Attributes attrs, 94 ModulePatcher patcher, 95 Path file) { 96 URI uri = file.toUri(); 97 Supplier<ModuleReader> supplier = () -> new JarModuleReader(file, uri); 98 HashSupplier hasher = (a) -> ModuleHashes.computeHash(file, a); 99 return newModule(attrs, uri, supplier, patcher, hasher); 100 } 101 102 /** 103 * Creates a ModuleReference to a module in a JMOD file. 104 */ 105 static ModuleReference newJModModule(ModuleInfo.Attributes attrs, Path file) { 106 URI uri = file.toUri(); 107 Supplier<ModuleReader> supplier = () -> new JModModuleReader(file, uri); 108 HashSupplier hasher = (a) -> ModuleHashes.computeHash(file, a); 109 return newModule(attrs, uri, supplier, null, hasher); 110 } 111 112 /** 113 * Creates a ModuleReference to a possibly-patched exploded module. 114 */ 115 static ModuleReference newExplodedModule(ModuleInfo.Attributes attrs, 116 ModulePatcher patcher, 117 Path dir) { 118 Supplier<ModuleReader> supplier = () -> new ExplodedModuleReader(dir); 119 return newModule(attrs, dir.toUri(), supplier, patcher, null); 120 } 121 122 123 /** 124 * A base module reader that encapsulates machinery required to close the 125 * module reader safely. 126 */ 127 static abstract class SafeCloseModuleReader implements ModuleReader { 128 129 // RW lock to support safe close 130 private final ReadWriteLock lock = new ReentrantReadWriteLock(); 131 private final Lock readLock = lock.readLock(); 132 private final Lock writeLock = lock.writeLock(); 133 private boolean closed; 134 135 SafeCloseModuleReader() { } 136 137 /** 138 * Returns a URL to resource. This method is invoked by the find 139 * method to do the actual work of finding the resource. 140 */ 141 abstract Optional<URI> implFind(String name) throws IOException; 142 143 /** 144 * Returns an input stream for reading a resource. This method is 145 * invoked by the open method to do the actual work of opening 146 * an input stream to the resource. 147 */ 148 abstract Optional<InputStream> implOpen(String name) throws IOException; 149 150 /** 151 * Returns a stream of the names of resources in the module. This 152 * method is invoked by the list method to do the actual work of 153 * creating the stream. 154 */ 155 abstract Stream<String> implList() throws IOException; 156 157 /** 158 * Closes the module reader. This method is invoked by close to do the 159 * actual work of closing the module reader. 160 */ 161 abstract void implClose() throws IOException; 162 163 @Override 164 public final Optional<URI> find(String name) throws IOException { 165 readLock.lock(); 166 try { 167 if (!closed) { 168 return implFind(name); 169 } else { 170 throw new IOException("ModuleReader is closed"); 171 } 172 } finally { 173 readLock.unlock(); 174 } 175 } 176 177 178 @Override 179 public final Optional<InputStream> open(String name) throws IOException { 180 readLock.lock(); 181 try { 182 if (!closed) { 183 return implOpen(name); 184 } else { 185 throw new IOException("ModuleReader is closed"); 186 } 187 } finally { 188 readLock.unlock(); 189 } 190 } 191 192 @Override 193 public final Stream<String> list() throws IOException { 194 readLock.lock(); 195 try { 196 if (!closed) { 197 return implList(); 198 } else { 199 throw new IOException("ModuleReader is closed"); 200 } 201 } finally { 202 readLock.unlock(); 203 } 204 } 205 206 @Override 207 public final void close() throws IOException { 208 writeLock.lock(); 209 try { 210 if (!closed) { 211 closed = true; 212 implClose(); 213 } 214 } finally { 215 writeLock.unlock(); 216 } 217 } 218 } 219 220 221 /** 222 * A ModuleReader for a modular JAR file. 223 */ 224 static class JarModuleReader extends SafeCloseModuleReader { 225 private final JarFile jf; 226 private final URI uri; 227 228 static JarFile newJarFile(Path path) { 229 try { 230 return new JarFile(new File(path.toString()), 231 true, // verify 232 ZipFile.OPEN_READ, 233 JarFile.runtimeVersion()); 234 } catch (IOException ioe) { 235 throw new UncheckedIOException(ioe); 236 } 237 } 238 239 JarModuleReader(Path path, URI uri) { 240 this.jf = newJarFile(path); 241 this.uri = uri; 242 } 243 244 private JarEntry getEntry(String name) { 245 return jf.getJarEntry(Objects.requireNonNull(name)); 246 } 247 248 @Override 249 Optional<URI> implFind(String name) throws IOException { 250 JarEntry je = getEntry(name); 251 if (je != null) { 252 if (jf.isMultiRelease()) 253 name = SharedSecrets.javaUtilJarAccess().getRealName(jf, je); 254 if (je.isDirectory() && !name.endsWith("/")) 255 name += "/"; 256 String encodedPath = ParseUtil.encodePath(name, false); 257 String uris = "jar:" + uri + "!/" + encodedPath; 258 return Optional.of(URI.create(uris)); 259 } else { 260 return Optional.empty(); 261 } 262 } 263 264 @Override 265 Optional<InputStream> implOpen(String name) throws IOException { 266 JarEntry je = getEntry(name); 267 if (je != null) { 268 return Optional.of(jf.getInputStream(je)); 269 } else { 270 return Optional.empty(); 271 } 272 } 273 274 @Override 275 Stream<String> implList() throws IOException { 276 // take snapshot to avoid async close 277 List<String> names = VersionedStream.stream(jf) 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 if (je.isDirectory() && !name.endsWith("/")) 320 name += "/"; 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 @Override 382 public Optional<URI> find(String name) throws IOException { 383 ensureOpen(); 384 Path path = Resources.toFilePath(dir, name); 385 if (path != null) { 386 try { 387 return Optional.of(path.toUri()); 388 } catch (IOError e) { 389 throw (IOException) e.getCause(); 390 } 391 } else { 392 return Optional.empty(); 393 } 394 } 395 396 @Override 397 public Optional<InputStream> open(String name) throws IOException { 398 ensureOpen(); 399 Path path = Resources.toFilePath(dir, name); 400 if (path != null) { 401 return Optional.of(Files.newInputStream(path)); 402 } else { 403 return Optional.empty(); 404 } 405 } 406 407 @Override 408 public Optional<ByteBuffer> read(String name) throws IOException { 409 ensureOpen(); 410 Path path = Resources.toFilePath(dir, name); 411 if (path != null) { 412 return Optional.of(ByteBuffer.wrap(Files.readAllBytes(path))); 413 } else { 414 return Optional.empty(); 415 } 416 } 417 418 @Override 419 public Stream<String> list() throws IOException { 420 ensureOpen(); 421 return Files.walk(dir, Integer.MAX_VALUE) 422 .map(f -> Resources.toResourceName(dir, f)) 423 .filter(s -> s.length() > 0); 424 } 425 426 @Override 427 public void close() { 428 closed = true; 429 } 430 } 431 432 }