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