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