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