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 java.lang.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.net.URI; 34 import java.nio.ByteBuffer; 35 import java.nio.file.Files; 36 import java.nio.file.Path; 37 import java.nio.file.Paths; 38 import java.util.Objects; 39 import java.util.Optional; 40 import java.util.concurrent.locks.Lock; 41 import java.util.concurrent.locks.ReadWriteLock; 42 import java.util.concurrent.locks.ReentrantReadWriteLock; 43 import java.util.function.Supplier; 44 import java.util.jar.JarEntry; 45 import java.util.jar.JarFile; 46 import java.util.zip.ZipEntry; 47 import java.util.zip.ZipFile; 48 49 import jdk.internal.misc.JavaLangAccess; 50 import jdk.internal.misc.SharedSecrets; 51 import jdk.internal.module.ModuleHashes; 52 import jdk.internal.module.ModuleHashes.HashSupplier; 53 import jdk.internal.module.ModulePatcher; 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 65 private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); 66 67 private ModuleReferences() { } 68 69 /** 70 * Creates a ModuleReference to a module or to patched module when 71 * creating modules for the boot Layer and -Xpatch is specified. 72 */ 73 private static ModuleReference newModule(ModuleDescriptor md, 74 URI uri, 75 Supplier<ModuleReader> supplier, 76 HashSupplier hasher) { 77 78 ModuleReference mref = new ModuleReference(md, uri, supplier, hasher); 79 80 if (JLA.getBootLayer() == null) 81 mref = ModulePatcher.interposeIfNeeded(mref); 82 83 return mref; 84 } 85 86 /** 87 * Creates a ModuleReference to a module packaged as a modular JAR. 88 */ 89 static ModuleReference newJarModule(ModuleDescriptor md, Path file) { 90 URI uri = file.toUri(); 91 Supplier<ModuleReader> supplier = () -> new JarModuleReader(file, uri); 92 HashSupplier hasher = (a) -> ModuleHashes.computeHashAsString(file, a); 93 return newModule(md, uri, supplier, hasher); 94 } 95 96 /** 97 * Creates a ModuleReference to a module packaged as a JMOD. 98 */ 99 static ModuleReference newJModModule(ModuleDescriptor md, Path file) { 100 URI uri = file.toUri(); 101 Supplier<ModuleReader> supplier = () -> new JModModuleReader(file, uri); 102 HashSupplier hasher = (a) -> ModuleHashes.computeHashAsString(file, a); 103 return newModule(md, file.toUri(), supplier, hasher); 104 } 105 106 /** 107 * Creates a ModuleReference to an exploded module. 108 */ 109 static ModuleReference newExplodedModule(ModuleDescriptor md, Path dir) { 110 Supplier<ModuleReader> supplier = () -> new ExplodedModuleReader(dir); 111 return newModule(md, dir.toUri(), supplier, null); 112 } 113 114 115 /** 116 * A base module reader that encapsulates machinery required to close the 117 * module reader safely. 118 */ 119 static abstract class SafeCloseModuleReader implements ModuleReader { 120 121 // RW lock to support safe close 122 private final ReadWriteLock lock = new ReentrantReadWriteLock(); 123 private final Lock readLock = lock.readLock(); 124 private final Lock writeLock = lock.writeLock(); 125 private boolean closed; 126 127 SafeCloseModuleReader() { } 128 129 /** 130 * Returns a URL to resource. This method is invoked by the find 131 * method to do the actual work of finding the resource. 132 */ 133 abstract Optional<URI> implFind(String name) throws IOException; 134 135 /** 136 * Returns an input stream for reading a resource. This method is 137 * invoked by the open method to do the actual work of opening 138 * an input stream to the resource. 139 */ 140 abstract Optional<InputStream> implOpen(String name) throws IOException; 141 142 /** 143 * Closes the module reader. This method is invoked by close to do the 144 * actual work of closing the module reader. 145 */ 146 abstract void implClose() throws IOException; 147 148 @Override 149 public final Optional<URI> find(String name) throws IOException { 150 readLock.lock(); 151 try { 152 if (!closed) { 153 return implFind(name); 154 } else { 155 throw new IOException("ModuleReader is closed"); 156 } 157 } finally { 158 readLock.unlock(); 159 } 160 } 161 162 163 @Override 164 public final Optional<InputStream> open(String name) throws IOException { 165 readLock.lock(); 166 try { 167 if (!closed) { 168 return implOpen(name); 169 } else { 170 throw new IOException("ModuleReader is closed"); 171 } 172 } finally { 173 readLock.unlock(); 174 } 175 } 176 177 @Override 178 public void close() throws IOException { 179 writeLock.lock(); 180 try { 181 if (!closed) { 182 closed = true; 183 implClose(); 184 } 185 } finally { 186 writeLock.unlock(); 187 } 188 } 189 } 190 191 192 /** 193 * A ModuleReader for a modular JAR file. 194 */ 195 static class JarModuleReader extends SafeCloseModuleReader { 196 private final JarFile jf; 197 private final URI uri; 198 199 static JarFile newJarFile(Path path) { 200 try { 201 return new JarFile(path.toFile(), 202 true, // verify 203 ZipFile.OPEN_READ, 204 JarFile.runtimeVersion()); 205 } catch (IOException ioe) { 206 throw new UncheckedIOException(ioe); 207 } 208 } 209 210 JarModuleReader(Path path, URI uri) { 211 this.jf = newJarFile(path); 212 this.uri = uri; 213 } 214 215 private JarEntry getEntry(String name) { 216 return jf.getJarEntry(Objects.requireNonNull(name)); 217 } 218 219 @Override 220 Optional<URI> implFind(String name) throws IOException { 221 JarEntry je = getEntry(name); 222 if (je != null) { 223 if (jf.isMultiRelease()) 224 name = SharedSecrets.javaUtilJarAccess().getRealName(jf, je); 225 String encodedPath = ParseUtil.encodePath(name, false); 226 String uris = "jar:" + uri + "!/" + encodedPath; 227 return Optional.of(URI.create(uris)); 228 } else { 229 return Optional.empty(); 230 } 231 } 232 233 @Override 234 Optional<InputStream> implOpen(String name) throws IOException { 235 JarEntry je = getEntry(name); 236 if (je != null) { 237 return Optional.of(jf.getInputStream(je)); 238 } else { 239 return Optional.empty(); 240 } 241 } 242 243 @Override 244 void implClose() throws IOException { 245 jf.close(); 246 } 247 } 248 249 250 /** 251 * A ModuleReader for a JMOD file. 252 */ 253 static class JModModuleReader extends SafeCloseModuleReader { 254 private final ZipFile zf; 255 private final URI uri; 256 257 static ZipFile newZipFile(Path path) { 258 try { 259 return new ZipFile(path.toFile()); 260 } catch (IOException ioe) { 261 throw new UncheckedIOException(ioe); 262 } 263 } 264 265 JModModuleReader(Path path, URI uri) { 266 this.zf = newZipFile(path); 267 this.uri = uri; 268 } 269 270 private ZipEntry getEntry(String name) { 271 return zf.getEntry("classes/" + Objects.requireNonNull(name)); 272 } 273 274 @Override 275 Optional<URI> implFind(String name) { 276 ZipEntry ze = getEntry(name); 277 if (ze != null) { 278 String encodedPath = ParseUtil.encodePath(name, false); 279 String uris = "jmod:" + uri + "!/" + encodedPath; 280 return Optional.of(URI.create(uris)); 281 } else { 282 return Optional.empty(); 283 } 284 } 285 286 @Override 287 Optional<InputStream> implOpen(String name) throws IOException { 288 ZipEntry ze = getEntry(name); 289 if (ze != null) { 290 return Optional.of(zf.getInputStream(ze)); 291 } else { 292 return Optional.empty(); 293 } 294 } 295 296 @Override 297 void implClose() throws IOException { 298 zf.close(); 299 } 300 } 301 302 303 /** 304 * A ModuleReader for an exploded module. 305 */ 306 static class ExplodedModuleReader implements ModuleReader { 307 private final Path dir; 308 private volatile boolean closed; 309 310 ExplodedModuleReader(Path dir) { 311 this.dir = dir; 312 313 // when running with a security manager then check that the caller 314 // has access to the directory. 315 SecurityManager sm = System.getSecurityManager(); 316 if (sm != null) { 317 boolean unused = Files.isDirectory(dir); 318 } 319 } 320 321 /** 322 * Returns a Path to access to the given resource. 323 */ 324 private Path toPath(String name) { 325 Path path = Paths.get(name.replace('/', File.separatorChar)); 326 if (path.getRoot() == null) { 327 return dir.resolve(path); 328 } else { 329 // drop the root component so that the resource is 330 // located relative to the module directory 331 int n = path.getNameCount(); 332 return (n > 0) ? dir.resolve(path.subpath(0, n)) : null; 333 } 334 } 335 336 /** 337 * Throws IOException if the module reader is closed; 338 */ 339 private void ensureOpen() throws IOException { 340 if (closed) throw new IOException("ModuleReader is closed"); 341 } 342 343 @Override 344 public Optional<URI> find(String name) throws IOException { 345 ensureOpen(); 346 Path path = toPath(name); 347 if (path != null && Files.isRegularFile(path)) { 348 try { 349 return Optional.of(path.toUri()); 350 } catch (IOError e) { 351 throw (IOException) e.getCause(); 352 } 353 } else { 354 return Optional.empty(); 355 } 356 } 357 358 @Override 359 public Optional<InputStream> open(String name) throws IOException { 360 ensureOpen(); 361 Path path = toPath(name); 362 if (path != null && Files.isRegularFile(path)) { 363 return Optional.of(Files.newInputStream(path)); 364 } else { 365 return Optional.empty(); 366 } 367 } 368 369 @Override 370 public Optional<ByteBuffer> read(String name) throws IOException { 371 ensureOpen(); 372 Path path = toPath(name); 373 if (path != null && Files.isRegularFile(path)) { 374 return Optional.of(ByteBuffer.wrap(Files.readAllBytes(path))); 375 } else { 376 return Optional.empty(); 377 } 378 } 379 380 @Override 381 public void close() { 382 closed = true; 383 } 384 } 385 386 }