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