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 }