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