JdepsConfiguration.java revision 3792:d516975e8110
1/*
2 * Copyright (c) 2012, 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 com.sun.tools.jdeps;
27
28import static com.sun.tools.jdeps.Module.trace;
29import static java.util.stream.Collectors.*;
30
31import com.sun.tools.classfile.Dependency;
32
33import java.io.BufferedInputStream;
34import java.io.File;
35import java.io.FileNotFoundException;
36import java.io.IOException;
37import java.io.InputStream;
38import java.io.UncheckedIOException;
39import java.lang.module.Configuration;
40import java.lang.module.ModuleDescriptor;
41import java.lang.module.ModuleDescriptor.Exports;
42import java.lang.module.ModuleDescriptor.Opens;
43import java.lang.module.ModuleFinder;
44import java.lang.module.ModuleReader;
45import java.lang.module.ModuleReference;
46import java.lang.module.ResolvedModule;
47import java.net.URI;
48import java.nio.file.DirectoryStream;
49import java.nio.file.FileSystem;
50import java.nio.file.FileSystems;
51import java.nio.file.Files;
52import java.nio.file.Path;
53import java.nio.file.Paths;
54import java.util.ArrayList;
55import java.util.Collections;
56import java.util.HashMap;
57import java.util.HashSet;
58import java.util.LinkedHashMap;
59import java.util.LinkedHashSet;
60import java.util.List;
61import java.util.Map;
62import java.util.Objects;
63import java.util.Optional;
64import java.util.Set;
65import java.util.function.Function;
66import java.util.function.Supplier;
67import java.util.stream.Stream;
68
69public class JdepsConfiguration implements AutoCloseable {
70    // the token for "all modules on the module path"
71    public static final String ALL_MODULE_PATH = "ALL-MODULE-PATH";
72    public static final String ALL_DEFAULT = "ALL-DEFAULT";
73    public static final String MODULE_INFO = "module-info.class";
74
75    private final SystemModuleFinder system;
76    private final ModuleFinder finder;
77
78    private final Map<String, Module> nameToModule = new LinkedHashMap<>();
79    private final Map<String, Module> packageToModule = new HashMap<>();
80    private final Map<String, List<Archive>> packageToUnnamedModule = new HashMap<>();
81
82    private final List<Archive> classpathArchives = new ArrayList<>();
83    private final List<Archive> initialArchives = new ArrayList<>();
84    private final Set<Module> rootModules = new HashSet<>();
85    private final Configuration configuration;
86    private final Runtime.Version version;
87
88    private JdepsConfiguration(SystemModuleFinder systemModulePath,
89                               ModuleFinder finder,
90                               Set<String> roots,
91                               List<Path> classpaths,
92                               List<Archive> initialArchives,
93                               boolean allDefaultModules,
94                               boolean allSystemModules,
95                               Runtime.Version version)
96        throws IOException
97    {
98        trace("root: %s%n", roots);
99
100        this.system = systemModulePath;
101        this.finder = finder;
102        this.version = version;
103
104        // build root set for resolution
105        Set<String> mods = new HashSet<>(roots);
106
107        // add default modules to the root set
108        // unnamed module
109        if (!initialArchives.isEmpty() || !classpaths.isEmpty() ||
110                roots.isEmpty() || allDefaultModules) {
111            mods.addAll(systemModulePath.defaultSystemRoots());
112        }
113        if (allSystemModules) {
114            systemModulePath.findAll().stream()
115                .map(mref -> mref.descriptor().name())
116                .forEach(mods::add);
117        }
118
119        this.configuration = Configuration.empty()
120                .resolveRequires(finder, ModuleFinder.of(), mods);
121
122        this.configuration.modules().stream()
123                .map(ResolvedModule::reference)
124                .forEach(this::addModuleReference);
125
126        // packages in unnamed module
127        initialArchives.forEach(archive -> {
128            addPackagesInUnnamedModule(archive);
129            this.initialArchives.add(archive);
130        });
131
132        // classpath archives
133        for (Path p : classpaths) {
134            if (Files.exists(p)) {
135                Archive archive = Archive.getInstance(p, version);
136                addPackagesInUnnamedModule(archive);
137                classpathArchives.add(archive);
138            }
139        }
140
141        // all roots specified in --add-modules or -m are included
142        // as the initial set for analysis.
143        roots.stream()
144             .map(nameToModule::get)
145             .forEach(this.rootModules::add);
146
147        initProfiles();
148
149        trace("resolved modules: %s%n", nameToModule.keySet().stream()
150                .sorted().collect(joining("\n", "\n", "")));
151    }
152
153    private void initProfiles() {
154        // other system modules are not observed and not added in nameToModule map
155        Map<String, Module> systemModules =
156            system.moduleNames()
157                .collect(toMap(Function.identity(), (mn) -> {
158                    Module m = nameToModule.get(mn);
159                    if (m == null) {
160                        ModuleReference mref = finder.find(mn).get();
161                        m = toModule(mref);
162                    }
163                    return m;
164                }));
165        Profile.init(systemModules);
166    }
167
168    private void addModuleReference(ModuleReference mref) {
169        Module module = toModule(mref);
170        nameToModule.put(mref.descriptor().name(), module);
171        mref.descriptor().packages()
172            .forEach(pn -> packageToModule.putIfAbsent(pn, module));
173    }
174
175    private void addPackagesInUnnamedModule(Archive archive) {
176        archive.reader().entries().stream()
177               .filter(e -> e.endsWith(".class") && !e.equals(MODULE_INFO))
178               .map(this::toPackageName)
179               .distinct()
180               .forEach(pn -> packageToUnnamedModule
181                   .computeIfAbsent(pn, _n -> new ArrayList<>()).add(archive));
182    }
183
184    private String toPackageName(String name) {
185        int i = name.lastIndexOf('/');
186        return i > 0 ? name.replace('/', '.').substring(0, i) : "";
187    }
188
189    public Optional<Module> findModule(String name) {
190        Objects.requireNonNull(name);
191        Module m = nameToModule.get(name);
192        return m!= null ? Optional.of(m) : Optional.empty();
193
194    }
195
196    public Optional<ModuleDescriptor> findModuleDescriptor(String name) {
197        Objects.requireNonNull(name);
198        Module m = nameToModule.get(name);
199        return m!= null ? Optional.of(m.descriptor()) : Optional.empty();
200    }
201
202    boolean isSystem(Module m) {
203        return system.find(m.name()).isPresent();
204    }
205
206    boolean isValidToken(String name) {
207        return ALL_MODULE_PATH.equals(name) || ALL_DEFAULT.equals(name);
208    }
209
210    /**
211     * Returns the modules that the given module can read
212     */
213    public Stream<Module> reads(Module module) {
214        return configuration.findModule(module.name()).get()
215            .reads().stream()
216            .map(ResolvedModule::name)
217            .map(nameToModule::get);
218    }
219
220    /**
221     * Returns the list of packages that split between resolved module and
222     * unnamed module
223     */
224    public Map<String, Set<String>> splitPackages() {
225        Set<String> splitPkgs = packageToModule.keySet().stream()
226                                       .filter(packageToUnnamedModule::containsKey)
227                                       .collect(toSet());
228        if (splitPkgs.isEmpty())
229            return Collections.emptyMap();
230
231        return splitPkgs.stream().collect(toMap(Function.identity(), (pn) -> {
232            Set<String> sources = new LinkedHashSet<>();
233            sources.add(packageToModule.get(pn).getModule().location().toString());
234            packageToUnnamedModule.get(pn).stream()
235                .map(Archive::getPathName)
236                .forEach(sources::add);
237            return sources;
238        }));
239    }
240
241    /**
242     * Returns an optional archive containing the given Location
243     */
244    public Optional<Archive> findClass(Dependency.Location location) {
245        String name = location.getName();
246        int i = name.lastIndexOf('/');
247        String pn = i > 0 ? name.substring(0, i).replace('/', '.') : "";
248        Archive archive = packageToModule.get(pn);
249        if (archive != null) {
250            return archive.contains(name + ".class")
251                        ? Optional.of(archive)
252                        : Optional.empty();
253        }
254
255        if (packageToUnnamedModule.containsKey(pn)) {
256            return packageToUnnamedModule.get(pn).stream()
257                    .filter(a -> a.contains(name + ".class"))
258                    .findFirst();
259        }
260        return Optional.empty();
261    }
262
263    /**
264     * Returns the list of Modules that can be found in the specified
265     * module paths.
266     */
267    public Map<String, Module> getModules() {
268        return nameToModule;
269    }
270
271    public Stream<Module> resolve(Set<String> roots) {
272        if (roots.isEmpty()) {
273            return nameToModule.values().stream();
274        } else {
275            return Configuration.empty()
276                    .resolveRequires(finder, ModuleFinder.of(), roots)
277                    .modules().stream()
278                    .map(ResolvedModule::name)
279                    .map(nameToModule::get);
280        }
281    }
282
283    public List<Archive> classPathArchives() {
284        return classpathArchives;
285    }
286
287    public List<Archive> initialArchives() {
288        return initialArchives;
289    }
290
291    public Set<Module> rootModules() {
292        return rootModules;
293    }
294
295    public Module toModule(ModuleReference mref) {
296        try {
297            String mn = mref.descriptor().name();
298            URI location = mref.location().orElseThrow(FileNotFoundException::new);
299            ModuleDescriptor md = mref.descriptor();
300            Module.Builder builder = new Module.Builder(md, system.find(mn).isPresent());
301
302            final ClassFileReader reader;
303            if (location.getScheme().equals("jrt")) {
304                reader = system.getClassReader(mn);
305            } else {
306                reader = ClassFileReader.newInstance(Paths.get(location), version);
307            }
308
309            builder.classes(reader);
310            builder.location(location);
311
312            return builder.build();
313        } catch (IOException e) {
314            throw new UncheckedIOException(e);
315        }
316    }
317
318    public Runtime.Version getVersion() {
319        return version;
320    }
321
322    /*
323     * Close all archives e.g. JarFile
324     */
325    @Override
326    public void close() throws IOException {
327        for (Archive archive : initialArchives)
328            archive.close();
329        for (Archive archive : classpathArchives)
330            archive.close();
331        for (Module module : nameToModule.values())
332            module.close();
333    }
334
335    static class SystemModuleFinder implements ModuleFinder {
336        private static final String JAVA_HOME = System.getProperty("java.home");
337        private static final String JAVA_SE = "java.se";
338
339        private final FileSystem fileSystem;
340        private final Path root;
341        private final Map<String, ModuleReference> systemModules;
342
343        SystemModuleFinder() {
344            if (Files.isRegularFile(Paths.get(JAVA_HOME, "lib", "modules"))) {
345                // jrt file system
346                this.fileSystem = FileSystems.getFileSystem(URI.create("jrt:/"));
347                this.root = fileSystem.getPath("/modules");
348                this.systemModules = walk(root);
349            } else {
350                // exploded image
351                this.fileSystem = FileSystems.getDefault();
352                root = Paths.get(JAVA_HOME, "modules");
353                this.systemModules = ModuleFinder.ofSystem().findAll().stream()
354                    .collect(toMap(mref -> mref.descriptor().name(), Function.identity()));
355            }
356        }
357
358        SystemModuleFinder(String javaHome) throws IOException {
359            if (javaHome == null) {
360                // --system none
361                this.fileSystem = null;
362                this.root = null;
363                this.systemModules = Collections.emptyMap();
364            } else {
365                if (Files.isRegularFile(Paths.get(javaHome, "lib", "modules")))
366                    throw new IllegalArgumentException("Invalid java.home: " + javaHome);
367
368                // alternate java.home
369                Map<String, String> env = new HashMap<>();
370                env.put("java.home", javaHome);
371                // a remote run-time image
372                this.fileSystem = FileSystems.newFileSystem(URI.create("jrt:/"), env);
373                this.root = fileSystem.getPath("/modules");
374                this.systemModules = walk(root);
375            }
376        }
377
378        private Map<String, ModuleReference> walk(Path root) {
379            try (Stream<Path> stream = Files.walk(root, 1)) {
380                return stream.filter(path -> !path.equals(root))
381                             .map(this::toModuleReference)
382                             .collect(toMap(mref -> mref.descriptor().name(),
383                                            Function.identity()));
384            } catch (IOException e) {
385                throw new UncheckedIOException(e);
386            }
387        }
388
389        private ModuleReference toModuleReference(Path path) {
390            Path minfo = path.resolve(MODULE_INFO);
391            try (InputStream in = Files.newInputStream(minfo);
392                 BufferedInputStream bin = new BufferedInputStream(in)) {
393
394                ModuleDescriptor descriptor = dropHashes(ModuleDescriptor.read(bin));
395                String mn = descriptor.name();
396                URI uri = URI.create("jrt:/" + path.getFileName().toString());
397                Supplier<ModuleReader> readerSupplier = new Supplier<>() {
398                    @Override
399                    public ModuleReader get() {
400                        return new ModuleReader() {
401                            @Override
402                            public Optional<URI> find(String name) throws IOException {
403                                return name.equals(mn)
404                                    ? Optional.of(uri) : Optional.empty();
405                            }
406
407                            @Override
408                            public Stream<String> list() {
409                                return Stream.empty();
410                            }
411
412                            @Override
413                            public void close() {
414                            }
415                        };
416                    }
417                };
418
419                return new ModuleReference(descriptor, uri, readerSupplier);
420            } catch (IOException e) {
421                throw new UncheckedIOException(e);
422            }
423        }
424
425        private ModuleDescriptor dropHashes(ModuleDescriptor md) {
426            ModuleDescriptor.Builder builder = ModuleDescriptor.module(md.name());
427            md.requires().forEach(builder::requires);
428            md.exports().forEach(builder::exports);
429            md.opens().forEach(builder::opens);
430            md.provides().stream().forEach(builder::provides);
431            md.uses().stream().forEach(builder::uses);
432
433            Set<String> concealed = new HashSet<>(md.packages());
434            md.exports().stream().map(Exports::source).forEach(concealed::remove);
435            md.opens().stream().map(Opens::source).forEach(concealed::remove);
436            concealed.forEach(builder::contains);
437
438            return builder.build();
439        }
440
441        @Override
442        public Set<ModuleReference> findAll() {
443            return systemModules.values().stream().collect(toSet());
444        }
445
446        @Override
447        public Optional<ModuleReference> find(String mn) {
448            return systemModules.containsKey(mn)
449                    ? Optional.of(systemModules.get(mn)) : Optional.empty();
450        }
451
452        public Stream<String> moduleNames() {
453            return systemModules.values().stream()
454                .map(mref -> mref.descriptor().name());
455        }
456
457        public ClassFileReader getClassReader(String modulename) throws IOException {
458            Path mp = root.resolve(modulename);
459            if (Files.exists(mp) && Files.isDirectory(mp)) {
460                return ClassFileReader.newInstance(fileSystem, mp);
461            } else {
462                throw new FileNotFoundException(mp.toString());
463            }
464        }
465
466        public Set<String> defaultSystemRoots() {
467            Set<String> roots = new HashSet<>();
468            boolean hasJava = false;
469            if (systemModules.containsKey(JAVA_SE)) {
470                // java.se is a system module
471                hasJava = true;
472                roots.add(JAVA_SE);
473            }
474
475            for (ModuleReference mref : systemModules.values()) {
476                String mn = mref.descriptor().name();
477                if (hasJava && mn.startsWith("java."))
478                    continue;
479
480                // add as root if observable and exports at least one package
481                ModuleDescriptor descriptor = mref.descriptor();
482                for (ModuleDescriptor.Exports e : descriptor.exports()) {
483                    if (!e.isQualified()) {
484                        roots.add(mn);
485                        break;
486                    }
487                }
488            }
489            return roots;
490        }
491    }
492
493    public static class Builder {
494
495        final SystemModuleFinder systemModulePath;
496        final Set<String> rootModules = new HashSet<>();
497        final List<Archive> initialArchives = new ArrayList<>();
498        final List<Path> paths = new ArrayList<>();
499        final List<Path> classPaths = new ArrayList<>();
500
501        ModuleFinder upgradeModulePath;
502        ModuleFinder appModulePath;
503        boolean addAllApplicationModules;
504        boolean addAllDefaultModules;
505        boolean addAllSystemModules;
506        Runtime.Version version;
507
508        public Builder() {
509            this.systemModulePath = new SystemModuleFinder();
510        }
511
512        public Builder(String javaHome) throws IOException {
513            this.systemModulePath = SystemModuleFinder.JAVA_HOME.equals(javaHome)
514                ? new SystemModuleFinder()
515                : new SystemModuleFinder(javaHome);
516        }
517
518        public Builder upgradeModulePath(String upgradeModulePath) {
519            this.upgradeModulePath = createModulePathFinder(upgradeModulePath);
520            return this;
521        }
522
523        public Builder appModulePath(String modulePath) {
524            this.appModulePath = createModulePathFinder(modulePath);
525            return this;
526        }
527
528        public Builder addmods(Set<String> addmods) {
529            for (String mn : addmods) {
530                switch (mn) {
531                    case ALL_MODULE_PATH:
532                        this.addAllApplicationModules = true;
533                        break;
534                    case ALL_DEFAULT:
535                        this.addAllDefaultModules = true;
536                        break;
537                    default:
538                        this.rootModules.add(mn);
539                }
540            }
541            return this;
542        }
543
544        /*
545         * This method is for --check option to find all target modules specified
546         * in qualified exports.
547         *
548         * Include all system modules and modules found on modulepath
549         */
550        public Builder allModules() {
551            this.addAllSystemModules = true;
552            this.addAllApplicationModules = true;
553            return this;
554        }
555
556        public Builder multiRelease(Runtime.Version version) {
557            this.version = version;
558            return this;
559        }
560
561        public Builder addRoot(Path path) {
562            Archive archive = Archive.getInstance(path, version);
563            if (archive.contains(MODULE_INFO)) {
564                paths.add(path);
565            } else {
566                initialArchives.add(archive);
567            }
568            return this;
569        }
570
571        public Builder addClassPath(String classPath) {
572            this.classPaths.addAll(getClassPaths(classPath));
573            return this;
574        }
575
576        public JdepsConfiguration build() throws  IOException {
577            ModuleFinder finder = systemModulePath;
578            if (upgradeModulePath != null) {
579                finder = ModuleFinder.compose(upgradeModulePath, systemModulePath);
580            }
581            if (appModulePath != null) {
582                finder = ModuleFinder.compose(finder, appModulePath);
583            }
584            if (!paths.isEmpty()) {
585                ModuleFinder otherModulePath = ModuleFinder.of(paths.toArray(new Path[0]));
586
587                finder = ModuleFinder.compose(finder, otherModulePath);
588                // add modules specified on command-line (convenience) as root set
589                otherModulePath.findAll().stream()
590                        .map(mref -> mref.descriptor().name())
591                        .forEach(rootModules::add);
592            }
593            if (addAllApplicationModules && appModulePath != null) {
594                appModulePath.findAll().stream()
595                    .map(mref -> mref.descriptor().name())
596                    .forEach(rootModules::add);
597            }
598
599            return new JdepsConfiguration(systemModulePath,
600                                          finder,
601                                          rootModules,
602                                          classPaths,
603                                          initialArchives,
604                                          addAllDefaultModules,
605                                          addAllSystemModules,
606                                          version);
607        }
608
609        private static ModuleFinder createModulePathFinder(String mpaths) {
610            if (mpaths == null) {
611                return null;
612            } else {
613                String[] dirs = mpaths.split(File.pathSeparator);
614                Path[] paths = new Path[dirs.length];
615                int i = 0;
616                for (String dir : dirs) {
617                    paths[i++] = Paths.get(dir);
618                }
619                return ModuleFinder.of(paths);
620            }
621        }
622
623        /*
624         * Returns the list of Archive specified in cpaths and not included
625         * initialArchives
626         */
627        private List<Path> getClassPaths(String cpaths) {
628            if (cpaths.isEmpty()) {
629                return Collections.emptyList();
630            }
631            List<Path> paths = new ArrayList<>();
632            for (String p : cpaths.split(File.pathSeparator)) {
633                if (p.length() > 0) {
634                    // wildcard to parse all JAR files e.g. -classpath dir/*
635                    int i = p.lastIndexOf(".*");
636                    if (i > 0) {
637                        Path dir = Paths.get(p.substring(0, i));
638                        try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) {
639                            for (Path entry : stream) {
640                                paths.add(entry);
641                            }
642                        } catch (IOException e) {
643                            throw new UncheckedIOException(e);
644                        }
645                    } else {
646                        paths.add(Paths.get(p));
647                    }
648                }
649            }
650            return paths;
651        }
652    }
653
654}
655