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.ByteArrayInputStream;
  29 import java.io.IOException;
  30 import java.io.InputStream;
  31 import java.io.UncheckedIOException;
  32 import java.lang.module.ModuleDescriptor;
  33 import java.lang.module.ModuleFinder;
  34 import java.lang.module.ModuleReader;
  35 import java.lang.module.ModuleReference;
  36 import java.net.URI;
  37 import java.net.URLConnection;
  38 import java.nio.ByteBuffer;
  39 import java.util.ArrayDeque;
  40 import java.util.Collections;
  41 import java.util.Deque;
  42 import java.util.HashMap;
  43 import java.util.Iterator;
  44 import java.util.Map;
  45 import java.util.Map.Entry;
  46 import java.util.Objects;
  47 import java.util.Optional;
  48 import java.util.Set;
  49 import java.util.Spliterator;
  50 import java.util.function.Consumer;
  51 import java.util.function.Supplier;
  52 import java.util.stream.Stream;
  53 import java.util.stream.StreamSupport;
  54 
  55 import jdk.internal.jimage.ImageLocation;
  56 import jdk.internal.jimage.ImageReader;
  57 import jdk.internal.jimage.ImageReaderFactory;
  58 import jdk.internal.misc.JavaNetUriAccess;
  59 import jdk.internal.misc.SharedSecrets;
  60 import jdk.internal.module.ModuleHashes.HashSupplier;
  61 import jdk.internal.perf.PerfCounter;
  62 
  63 /**
  64  * A {@code ModuleFinder} that finds modules that are linked into the
  65  * run-time image.
  66  *
  67  * The modules linked into the run-time image are assumed to have the
  68  * Packages attribute.
  69  */
  70 
  71 public class SystemModuleFinder implements ModuleFinder {
  72 
  73     private static final JavaNetUriAccess JNUA = SharedSecrets.getJavaNetUriAccess();
  74 
  75     private static final PerfCounter initTime
  76         = PerfCounter.newPerfCounter("jdk.module.finder.jimage.initTime");
  77     private static final PerfCounter moduleCount
  78         = PerfCounter.newPerfCounter("jdk.module.finder.jimage.modules");
  79     private static final PerfCounter packageCount
  80         = PerfCounter.newPerfCounter("jdk.module.finder.jimage.packages");
  81     private static final PerfCounter exportsCount
  82         = PerfCounter.newPerfCounter("jdk.module.finder.jimage.exports");
  83     // ImageReader used to access all modules in the image
  84     private static final ImageReader imageReader;
  85 
  86     // singleton finder to find modules in the run-time images
  87     private static final SystemModuleFinder INSTANCE;
  88 
  89     public static SystemModuleFinder getInstance() {
  90         return INSTANCE;
  91     }
  92 
  93     /**
  94      * For now, the module references are created eagerly on the assumption
  95      * that service binding will require all modules to be located.
  96      */
  97     static {
  98         long t0 = System.nanoTime();
  99         imageReader = ImageReaderFactory.getImageReader();
 100 
 101         INSTANCE = new SystemModuleFinder();
 102 
 103         initTime.addElapsedTimeFrom(t0);
 104     }
 105 
 106     private static boolean isFastPathSupported() {
 107        return SystemModules.MODULE_NAMES.length > 0;
 108     }
 109 
 110     private static String[] moduleNames() {
 111         if (isFastPathSupported())
 112             // module names recorded at link time
 113             return SystemModules.MODULE_NAMES;
 114 
 115         // this happens when java.base is patched with java.base
 116         // from an exploded image
 117         return imageReader.getModuleNames();
 118     }
 119 
 120     // the set of modules in the run-time image
 121     private final Set<ModuleReference> modules;
 122 
 123     // maps module name to module reference
 124     private final Map<String, ModuleReference> nameToModule;
 125 
 126     // module name to hashes
 127     private final Map<String, byte[]> hashes = new HashMap<>();
 128 
 129     private SystemModuleFinder() {
 130         String[] names = moduleNames();
 131         int n = names.length;
 132         moduleCount.add(n);
 133 
 134         // fastpath is enabled by default.
 135         // It can be disabled for troubleshooting purpose.
 136         boolean disabled =
 137             System.getProperty("jdk.system.module.finder.disabledFastPath") != null;
 138 
 139         ModuleDescriptor[] descriptors;
 140         ModuleHashes[] recordedHashes;
 141         ModuleResolution[] moduleResolutions;
 142 
 143         // fast loading of ModuleDescriptor of system modules
 144         if (isFastPathSupported() && !disabled) {
 145             descriptors = SystemModules.descriptors();
 146             recordedHashes = SystemModules.hashes();
 147             moduleResolutions = SystemModules.moduleResolutions();
 148         } else {
 149             // if fast loading of ModuleDescriptors is disabled
 150             // fallback to read module-info.class
 151             descriptors = new ModuleDescriptor[n];
 152             recordedHashes = new ModuleHashes[n];
 153             moduleResolutions = new ModuleResolution[n];
 154             for (int i = 0; i < names.length; i++) {
 155                 String mn = names[i];
 156                 ImageLocation loc = imageReader.findLocation(mn, "module-info.class");
 157                 ModuleInfo.Attributes attrs =
 158                     ModuleInfo.read(imageReader.getResourceBuffer(loc), null);
 159                 descriptors[i] = attrs.descriptor();
 160                 recordedHashes[i] = attrs.recordedHashes();
 161                 moduleResolutions[i] = attrs.moduleResolution();
 162             }
 163         }
 164 
 165         // record the hashes to build HashSupplier
 166         for (ModuleHashes mh : recordedHashes) {
 167             if (mh != null) {
 168                 hashes.putAll(mh.hashes());
 169             }
 170         }
 171 
 172         ModuleReference[] mods = new ModuleReference[n];
 173 
 174         @SuppressWarnings(value = {"rawtypes", "unchecked"})
 175         Entry<String, ModuleReference>[] map
 176             = (Entry<String, ModuleReference>[])new Entry[n];
 177 
 178         for (int i = 0; i < n; i++) {
 179             ModuleDescriptor md = descriptors[i];
 180 
 181             // create the ModuleReference
 182             ModuleReference mref = toModuleReference(md,
 183                                                      recordedHashes[i],
 184                                                      hashSupplier(names[i]),
 185                                                      moduleResolutions[i]);
 186             mods[i] = mref;
 187             map[i] = Map.entry(names[i], mref);
 188 
 189             // counters
 190             packageCount.add(md.packages().size());
 191             exportsCount.add(md.exports().size());
 192         }
 193 
 194         modules = Set.of(mods);
 195         nameToModule = Map.ofEntries(map);
 196     }
 197 
 198     @Override
 199     public Optional<ModuleReference> find(String name) {
 200         Objects.requireNonNull(name);
 201         return Optional.ofNullable(nameToModule.get(name));
 202     }
 203 
 204     @Override
 205     public Set<ModuleReference> findAll() {
 206         return modules;
 207     }
 208 
 209     private ModuleReference toModuleReference(ModuleDescriptor md,
 210                                               ModuleHashes recordedHashes,
 211                                               HashSupplier hasher,
 212                                               ModuleResolution mres) {
 213         String mn = md.name();
 214         URI uri = JNUA.create("jrt", "/".concat(mn));
 215 
 216         Supplier<ModuleReader> readerSupplier = new Supplier<>() {
 217             @Override
 218             public ModuleReader get() {
 219                 return new ImageModuleReader(mn, uri);
 220             }
 221         };
 222 
 223         ModuleReference mref =
 224             new ModuleReferenceImpl(md, uri, readerSupplier, null,
 225                                     recordedHashes, hasher, mres);
 226 
 227         // may need a reference to a patched module if --patch-module specified
 228         mref = ModuleBootstrap.patcher().patchIfNeeded(mref);
 229 
 230         return mref;
 231     }
 232 
 233     private HashSupplier hashSupplier(String name) {
 234         if (!hashes.containsKey(name))
 235             return null;
 236 
 237         return new HashSupplier() {
 238             @Override
 239             public byte[] generate(String algorithm) {
 240                 return hashes.get(name);
 241             }
 242         };
 243     }
 244 
 245     /**
 246      * A ModuleReader for reading resources from a module linked into the
 247      * run-time image.
 248      */
 249     static class ImageModuleReader implements ModuleReader {
 250         private final String module;
 251         private volatile boolean closed;
 252 
 253         /**
 254          * If there is a security manager set then check permission to
 255          * connect to the run-time image.
 256          */
 257         private static void checkPermissionToConnect(URI uri) {
 258             SecurityManager sm = System.getSecurityManager();
 259             if (sm != null) {
 260                 try {
 261                     URLConnection uc = uri.toURL().openConnection();
 262                     sm.checkPermission(uc.getPermission());
 263                 } catch (IOException ioe) {
 264                     throw new UncheckedIOException(ioe);
 265                 }
 266             }
 267         }
 268 
 269         ImageModuleReader(String module, URI uri) {
 270             checkPermissionToConnect(uri);
 271             this.module = module;
 272         }
 273 
 274         /**
 275          * Returns the ImageLocation for the given resource, {@code null}
 276          * if not found.
 277          */
 278         private ImageLocation findImageLocation(String name) throws IOException {
 279             Objects.requireNonNull(name);
 280             if (closed)
 281                 throw new IOException("ModuleReader is closed");
 282             if (imageReader != null) {
 283                 return imageReader.findLocation(module, name);
 284             } else {
 285                 // not an images build
 286                 return null;
 287             }
 288         }
 289 
 290         @Override
 291         public Optional<URI> find(String name) throws IOException {
 292             ImageLocation location = findImageLocation(name);
 293             if (location != null) {
 294                 URI u = URI.create("jrt:/" + module + "/" + name);
 295                 return Optional.of(u);
 296             } else {
 297                 return Optional.empty();
 298             }
 299         }
 300 
 301         @Override
 302         public Optional<InputStream> open(String name) throws IOException {
 303             return read(name).map(this::toInputStream);
 304         }
 305 
 306         private InputStream toInputStream(ByteBuffer bb) { // ## -> ByteBuffer?
 307             try {
 308                 int rem = bb.remaining();
 309                 byte[] bytes = new byte[rem];
 310                 bb.get(bytes);
 311                 return new ByteArrayInputStream(bytes);
 312             } finally {
 313                 release(bb);
 314             }
 315         }
 316 
 317         @Override
 318         public Optional<ByteBuffer> read(String name) throws IOException {
 319             ImageLocation location = findImageLocation(name);
 320             if (location != null) {
 321                 return Optional.of(imageReader.getResourceBuffer(location));
 322             } else {
 323                 return Optional.empty();
 324             }
 325         }
 326 
 327         @Override
 328         public void release(ByteBuffer bb) {
 329             Objects.requireNonNull(bb);
 330             ImageReader.releaseByteBuffer(bb);
 331         }
 332 
 333         @Override
 334         public Stream<String> list() throws IOException {
 335             if (closed)
 336                 throw new IOException("ModuleReader is closed");
 337 
 338             Spliterator<String> s = new ModuleContentSpliterator(module);
 339             return StreamSupport.stream(s, false);
 340         }
 341 
 342         @Override
 343         public void close() {
 344             // nothing else to do
 345             closed = true;
 346         }
 347     }
 348 
 349     /**
 350      * A Spliterator for traversing the resources of a module linked into the
 351      * run-time image.
 352      */
 353     static class ModuleContentSpliterator implements Spliterator<String> {
 354         final String moduleRoot;
 355         final Deque<ImageReader.Node> stack;
 356         Iterator<ImageReader.Node> iterator;
 357 
 358         ModuleContentSpliterator(String module) throws IOException {
 359             moduleRoot = "/modules/" + module;
 360             stack = new ArrayDeque<>();
 361 
 362             // push the root node to the stack to get started
 363             ImageReader.Node dir = imageReader.findNode(moduleRoot);
 364             if (dir == null || !dir.isDirectory())
 365                 throw new IOException(moduleRoot + " not a directory");
 366             stack.push(dir);
 367             iterator = Collections.emptyIterator();
 368         }
 369 
 370         /**
 371          * Returns the name of the next non-directory node or {@code null} if
 372          * there are no remaining nodes to visit.
 373          */
 374         private String next() throws IOException {
 375             for (;;) {
 376                 while (iterator.hasNext()) {
 377                     ImageReader.Node node = iterator.next();
 378                     String name = node.getName();
 379                     if (node.isDirectory()) {
 380                         // build node
 381                         ImageReader.Node dir = imageReader.findNode(name);
 382                         assert dir.isDirectory();
 383                         stack.push(dir);
 384                     } else {
 385                         // strip /modules/$MODULE/ prefix
 386                         return name.substring(moduleRoot.length() + 1);
 387                     }
 388                 }
 389 
 390                 if (stack.isEmpty()) {
 391                     return null;
 392                 } else {
 393                     ImageReader.Node dir = stack.poll();
 394                     assert dir.isDirectory();
 395                     iterator = dir.getChildren().iterator();
 396                 }
 397             }
 398         }
 399 
 400         @Override
 401         public boolean tryAdvance(Consumer<? super String> action) {
 402             String next;
 403             try {
 404                 next = next();
 405             } catch (IOException ioe) {
 406                 throw new UncheckedIOException(ioe);
 407             }
 408             if (next != null) {
 409                 action.accept(next);
 410                 return true;
 411             } else {
 412                 return false;
 413             }
 414         }
 415 
 416         @Override
 417         public Spliterator<String> trySplit() {
 418             return null;
 419         }
 420 
 421         @Override
 422         public int characteristics() {
 423             return Spliterator.DISTINCT + Spliterator.NONNULL + Spliterator.IMMUTABLE;
 424         }
 425 
 426         @Override
 427         public long estimateSize() {
 428             return Long.MAX_VALUE;
 429         }
 430     }
 431 }