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.IOError;
  29 import java.io.IOException;
  30 import java.io.InputStream;
  31 import java.io.UncheckedIOException;
  32 import java.lang.module.ModuleReader;
  33 import java.lang.module.ModuleReference;
  34 import java.net.URI;
  35 import java.nio.ByteBuffer;
  36 import java.nio.file.Files;
  37 import java.nio.file.Path;
  38 import java.util.List;
  39 import java.util.Objects;
  40 import java.util.Optional;
  41 import java.util.concurrent.locks.Lock;
  42 import java.util.concurrent.locks.ReadWriteLock;
  43 import java.util.concurrent.locks.ReentrantReadWriteLock;
  44 import java.util.function.Supplier;
  45 import java.util.jar.JarEntry;
  46 import java.util.jar.JarFile;
  47 import java.util.stream.Collectors;
  48 import java.util.stream.Stream;
  49 import java.util.zip.ZipFile;
  50 
  51 import jdk.internal.jmod.JmodFile;
  52 import jdk.internal.misc.SharedSecrets;
  53 import jdk.internal.module.ModuleHashes.HashSupplier;
  54 import jdk.internal.util.jar.VersionedStream;
  55 import sun.net.www.ParseUtil;
  56 
  57 
  58 /**
  59  * A factory for creating ModuleReference implementations where the modules are
  60  * packaged as modular JAR file, JMOD files or where the modules are exploded
  61  * on the file system.
  62  */
  63 
  64 class ModuleReferences {
  65     private ModuleReferences() { }
  66 
  67     /**
  68      * Creates a ModuleReference to a possibly-patched module
  69      */
  70     private static ModuleReference newModule(ModuleInfo.Attributes attrs,
  71                                              URI uri,
  72                                              Supplier<ModuleReader> supplier,
  73                                              ModulePatcher patcher,
  74                                              HashSupplier hasher) {
  75         ModuleReference mref = new ModuleReferenceImpl(attrs.descriptor(),
  76                                                        uri,
  77                                                        supplier,
  78                                                        null,
  79                                                        attrs.target(),
  80                                                        attrs.recordedHashes(),
  81                                                        hasher,
  82                                                        attrs.moduleResolution());
  83         if (patcher != null)
  84             mref = patcher.patchIfNeeded(mref);
  85 
  86         return mref;
  87     }
  88 
  89     /**
  90      * Creates a ModuleReference to a possibly-patched module in a modular JAR.
  91      */
  92     static ModuleReference newJarModule(ModuleInfo.Attributes attrs,
  93                                         ModulePatcher patcher,
  94                                         Path file) {
  95         URI uri = file.toUri();
  96         Supplier<ModuleReader> supplier = () -> new JarModuleReader(file, uri);
  97         HashSupplier hasher = (a) -> ModuleHashes.computeHash(file, a);
  98         return newModule(attrs, uri, supplier, patcher, hasher);
  99     }
 100 
 101     /**
 102      * Creates a ModuleReference to a module in a JMOD file.
 103      */
 104     static ModuleReference newJModModule(ModuleInfo.Attributes attrs, Path file) {
 105         URI uri = file.toUri();
 106         Supplier<ModuleReader> supplier = () -> new JModModuleReader(file, uri);
 107         HashSupplier hasher = (a) -> ModuleHashes.computeHash(file, a);
 108         return newModule(attrs, uri, supplier, null, hasher);
 109     }
 110 
 111     /**
 112      * Creates a ModuleReference to a possibly-patched exploded module.
 113      */
 114     static ModuleReference newExplodedModule(ModuleInfo.Attributes attrs,
 115                                              ModulePatcher patcher,
 116                                              Path dir) {
 117         Supplier<ModuleReader> supplier = () -> new ExplodedModuleReader(dir);
 118         return newModule(attrs, dir.toUri(), supplier, patcher, null);
 119     }
 120 
 121 
 122     /**
 123      * A base module reader that encapsulates machinery required to close the
 124      * module reader safely.
 125      */
 126     static abstract class SafeCloseModuleReader implements ModuleReader {
 127 
 128         // RW lock to support safe close
 129         private final ReadWriteLock lock = new ReentrantReadWriteLock();
 130         private final Lock readLock = lock.readLock();
 131         private final Lock writeLock = lock.writeLock();
 132         private boolean closed;
 133 
 134         SafeCloseModuleReader() { }
 135 
 136         /**
 137          * Returns a URL to  resource. This method is invoked by the find
 138          * method to do the actual work of finding the resource.
 139          */
 140         abstract Optional<URI> implFind(String name) throws IOException;
 141 
 142         /**
 143          * Returns an input stream for reading a resource. This method is
 144          * invoked by the open method to do the actual work of opening
 145          * an input stream to the resource.
 146          */
 147         abstract Optional<InputStream> implOpen(String name) throws IOException;
 148 
 149         /**
 150          * Returns a stream of the names of resources in the module. This
 151          * method is invoked by the list method to do the actual work of
 152          * creating the stream.
 153          */
 154         abstract Stream<String> implList() throws IOException;
 155 
 156         /**
 157          * Closes the module reader. This method is invoked by close to do the
 158          * actual work of closing the module reader.
 159          */
 160         abstract void implClose() throws IOException;
 161 
 162         @Override
 163         public final Optional<URI> find(String name) throws IOException {
 164             readLock.lock();
 165             try {
 166                 if (!closed) {
 167                     return implFind(name);
 168                 } else {
 169                     throw new IOException("ModuleReader is closed");
 170                 }
 171             } finally {
 172                 readLock.unlock();
 173             }
 174         }
 175 
 176 
 177         @Override
 178         public final Optional<InputStream> open(String name) throws IOException {
 179             readLock.lock();
 180             try {
 181                 if (!closed) {
 182                     return implOpen(name);
 183                 } else {
 184                     throw new IOException("ModuleReader is closed");
 185                 }
 186             } finally {
 187                 readLock.unlock();
 188             }
 189         }
 190 
 191         @Override
 192         public final Stream<String> list() throws IOException {
 193             readLock.lock();
 194             try {
 195                 if (!closed) {
 196                     return implList();
 197                 } else {
 198                     throw new IOException("ModuleReader is closed");
 199                 }
 200             } finally {
 201                 readLock.unlock();
 202             }
 203         }
 204 
 205         @Override
 206         public final void close() throws IOException {
 207             writeLock.lock();
 208             try {
 209                 if (!closed) {
 210                     closed = true;
 211                     implClose();
 212                 }
 213             } finally {
 214                 writeLock.unlock();
 215             }
 216         }
 217     }
 218 
 219 
 220     /**
 221      * A ModuleReader for a modular JAR file.
 222      */
 223     static class JarModuleReader extends SafeCloseModuleReader {
 224         private final JarFile jf;
 225         private final URI uri;
 226 
 227         static JarFile newJarFile(Path path) {
 228             try {
 229                 return new JarFile(path.toFile(),
 230                                    true,               // verify
 231                                    ZipFile.OPEN_READ,
 232                                    JarFile.runtimeVersion());
 233             } catch (IOException ioe) {
 234                 throw new UncheckedIOException(ioe);
 235             }
 236         }
 237 
 238         JarModuleReader(Path path, URI uri) {
 239             this.jf = newJarFile(path);
 240             this.uri = uri;
 241         }
 242 
 243         private JarEntry getEntry(String name) {
 244             return jf.getJarEntry(Objects.requireNonNull(name));
 245         }
 246 
 247         @Override
 248         Optional<URI> implFind(String name) throws IOException {
 249             JarEntry je = getEntry(name);
 250             if (je != null) {
 251                 if (jf.isMultiRelease())
 252                     name = SharedSecrets.javaUtilJarAccess().getRealName(jf, je);
 253                 if (je.isDirectory() && !name.endsWith("/"))
 254                     name += "/";
 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                     .map(JarEntry::getName)
 278                     .collect(Collectors.toList());
 279             return names.stream();
 280         }
 281 
 282         @Override
 283         void implClose() throws IOException {
 284             jf.close();
 285         }
 286     }
 287 
 288 
 289     /**
 290      * A ModuleReader for a JMOD file.
 291      */
 292     static class JModModuleReader extends SafeCloseModuleReader {
 293         private final JmodFile jf;
 294         private final URI uri;
 295 
 296         static JmodFile newJmodFile(Path path) {
 297             try {
 298                 return new JmodFile(path);
 299             } catch (IOException ioe) {
 300                 throw new UncheckedIOException(ioe);
 301             }
 302         }
 303 
 304         JModModuleReader(Path path, URI uri) {
 305             this.jf = newJmodFile(path);
 306             this.uri = uri;
 307         }
 308 
 309         private JmodFile.Entry getEntry(String name) {
 310             Objects.requireNonNull(name);
 311             return jf.getEntry(JmodFile.Section.CLASSES, name);
 312         }
 313 
 314         @Override
 315         Optional<URI> implFind(String name) {
 316             JmodFile.Entry je = getEntry(name);
 317             if (je != null) {
 318                 if (je.isDirectory() && !name.endsWith("/"))
 319                     name += "/";
 320                 String encodedPath = ParseUtil.encodePath(name, false);
 321                 String uris = "jmod:" + uri + "!/" + encodedPath;
 322                 return Optional.of(URI.create(uris));
 323             } else {
 324                 return Optional.empty();
 325             }
 326         }
 327 
 328         @Override
 329         Optional<InputStream> implOpen(String name) throws IOException {
 330             JmodFile.Entry je = getEntry(name);
 331             if (je != null) {
 332                 return Optional.of(jf.getInputStream(je));
 333             } else {
 334                 return Optional.empty();
 335             }
 336         }
 337 
 338         @Override
 339         Stream<String> implList() throws IOException {
 340             // take snapshot to avoid async close
 341             List<String> names = jf.stream()
 342                     .filter(e -> e.section() == JmodFile.Section.CLASSES)
 343                     .map(JmodFile.Entry::name)
 344                     .collect(Collectors.toList());
 345             return names.stream();
 346         }
 347 
 348         @Override
 349         void implClose() throws IOException {
 350             jf.close();
 351         }
 352     }
 353 
 354 
 355     /**
 356      * A ModuleReader for an exploded module.
 357      */
 358     static class ExplodedModuleReader implements ModuleReader {
 359         private final Path dir;
 360         private volatile boolean closed;
 361 
 362         ExplodedModuleReader(Path dir) {
 363             this.dir = dir;
 364 
 365             // when running with a security manager then check that the caller
 366             // has access to the directory.
 367             SecurityManager sm = System.getSecurityManager();
 368             if (sm != null) {
 369                 boolean unused = Files.isDirectory(dir);
 370             }
 371         }
 372 
 373         /**
 374          * Throws IOException if the module reader is closed;
 375          */
 376         private void ensureOpen() throws IOException {
 377             if (closed) throw new IOException("ModuleReader is closed");
 378         }
 379 
 380         @Override
 381         public Optional<URI> find(String name) throws IOException {
 382             ensureOpen();
 383             Path path = Resources.toFilePath(dir, name);
 384             if (path != null) {
 385                 try {
 386                     return Optional.of(path.toUri());
 387                 } catch (IOError e) {
 388                     throw (IOException) e.getCause();
 389                 }
 390             } else {
 391                 return Optional.empty();
 392             }
 393         }
 394 
 395         @Override
 396         public Optional<InputStream> open(String name) throws IOException {
 397             ensureOpen();
 398             Path path = Resources.toFilePath(dir, name);
 399             if (path != null) {
 400                 return Optional.of(Files.newInputStream(path));
 401             } else {
 402                 return Optional.empty();
 403             }
 404         }
 405 
 406         @Override
 407         public Optional<ByteBuffer> read(String name) throws IOException {
 408             ensureOpen();
 409             Path path = Resources.toFilePath(dir, name);
 410             if (path != null) {
 411                 return Optional.of(ByteBuffer.wrap(Files.readAllBytes(path)));
 412             } else {
 413                 return Optional.empty();
 414             }
 415         }
 416 
 417         @Override
 418         public Stream<String> list() throws IOException {
 419             ensureOpen();
 420             return Files.walk(dir, Integer.MAX_VALUE)
 421                         .map(f -> Resources.toResourceName(dir, f))
 422                         .filter(s -> s.length() > 0);
 423         }
 424 
 425         @Override
 426         public void close() {
 427             closed = true;
 428         }
 429     }
 430 
 431 }