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