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