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
26package jdk.internal.module;
27
28import java.io.ByteArrayInputStream;
29import java.io.IOException;
30import java.io.InputStream;
31import java.io.UncheckedIOException;
32import java.lang.module.ModuleDescriptor;
33import java.lang.module.ModuleFinder;
34import java.lang.module.ModuleReader;
35import java.lang.module.ModuleReference;
36import java.net.URI;
37import java.net.URLConnection;
38import java.nio.ByteBuffer;
39import java.util.ArrayDeque;
40import java.util.Collections;
41import java.util.Deque;
42import java.util.HashMap;
43import java.util.Iterator;
44import java.util.Map;
45import java.util.Map.Entry;
46import java.util.Objects;
47import java.util.Optional;
48import java.util.Set;
49import java.util.Spliterator;
50import java.util.function.Consumer;
51import java.util.function.Supplier;
52import java.util.stream.Stream;
53import java.util.stream.StreamSupport;
54
55import jdk.internal.jimage.ImageLocation;
56import jdk.internal.jimage.ImageReader;
57import jdk.internal.jimage.ImageReaderFactory;
58import jdk.internal.misc.JavaNetUriAccess;
59import jdk.internal.misc.SharedSecrets;
60import jdk.internal.module.ModuleHashes.HashSupplier;
61import 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
71public 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}
470