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.loader.ResourceHelper;
  54 import jdk.internal.misc.SharedSecrets;
  55 import jdk.internal.module.ModuleHashes.HashSupplier;
  56 import jdk.internal.util.jar.VersionedStream;
  57 import sun.net.www.ParseUtil;
  58 
  59 
  60 /**
  61  * A factory for creating ModuleReference implementations where the modules are
  62  * packaged as modular JAR file, JMOD files or where the modules are exploded
  63  * on the file system.
  64  */
  65 
  66 class ModuleReferences {
  67     private ModuleReferences() { }
  68 
  69     /**
  70      * Creates a ModuleReference to a possibly-patched module
  71      */
  72     private static ModuleReference newModule(ModuleInfo.Attributes attrs,
  73                                              URI uri,
  74                                              Supplier<ModuleReader> supplier,
  75                                              ModulePatcher patcher,
  76                                              HashSupplier hasher) {
  77         ModuleReference mref = new ModuleReferenceImpl(attrs.descriptor(),
  78                                                        uri,
  79                                                        supplier,
  80                                                        null,
  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(path.toFile(),
 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             JarEntry entry = jf.getJarEntry(Objects.requireNonNull(name));
 246             return (entry == null || entry.isDirectory()) ? null : entry;
 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          * Throws IOException if the module reader is closed;
 374          */
 375         private void ensureOpen() throws IOException {
 376             if (closed) throw new IOException("ModuleReader is closed");
 377         }
 378 
 379         /**
 380          * Returns a Path to access the given resource. Returns null if the
 381          * resource name does not convert to a file path that locates a regular
 382          * file in the module.
 383          */
 384         private Path toFilePath(String name) {
 385             Path path = ResourceHelper.toFilePath(name);
 386             if (path != null) {
 387                 Path file = dir.resolve(path);
 388                 if (Files.isRegularFile(file)) {
 389                     return file;
 390                 }
 391             }
 392             return null;
 393         }
 394 
 395         @Override
 396         public Optional<URI> find(String name) throws IOException {
 397             ensureOpen();
 398             Path path = toFilePath(name);
 399             if (path != null) {
 400                 try {
 401                     return Optional.of(path.toUri());
 402                 } catch (IOError e) {
 403                     throw (IOException) e.getCause();
 404                 }
 405             } else {
 406                 return Optional.empty();
 407             }
 408         }
 409 
 410         @Override
 411         public Optional<InputStream> open(String name) throws IOException {
 412             ensureOpen();
 413             Path path = toFilePath(name);
 414             if (path != null) {
 415                 return Optional.of(Files.newInputStream(path));
 416             } else {
 417                 return Optional.empty();
 418             }
 419         }
 420 
 421         @Override
 422         public Optional<ByteBuffer> read(String name) throws IOException {
 423             ensureOpen();
 424             Path path = toFilePath(name);
 425             if (path != null) {
 426                 return Optional.of(ByteBuffer.wrap(Files.readAllBytes(path)));
 427             } else {
 428                 return Optional.empty();
 429             }
 430         }
 431 
 432         @Override
 433         public Stream<String> list() throws IOException {
 434             ensureOpen();
 435             // sym links not followed
 436             return Files.find(dir, Integer.MAX_VALUE,
 437                               (path, attrs) -> attrs.isRegularFile())
 438                     .map(f -> dir.relativize(f)
 439                                  .toString()
 440                                  .replace(File.separatorChar, '/'));
 441         }
 442 
 443         @Override
 444         public void close() {
 445             closed = true;
 446         }
 447     }
 448 
 449 }