Path: blob/master/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsConfiguration.java
41161 views
/*1* Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.sun.tools.jdeps;2627import static com.sun.tools.jdeps.Module.trace;28import static java.util.stream.Collectors.*;2930import com.sun.tools.classfile.Dependency;3132import java.io.BufferedInputStream;33import java.io.File;34import java.io.FileNotFoundException;35import java.io.IOException;36import java.io.InputStream;37import java.io.UncheckedIOException;38import java.lang.module.Configuration;39import java.lang.module.ModuleDescriptor;40import java.lang.module.ModuleFinder;41import java.lang.module.ModuleReader;42import java.lang.module.ModuleReference;43import java.lang.module.ResolvedModule;44import java.net.URI;45import java.nio.file.DirectoryStream;46import java.nio.file.FileSystem;47import java.nio.file.FileSystems;48import java.nio.file.Files;49import java.nio.file.Path;50import java.nio.file.Paths;51import java.util.ArrayList;52import java.util.Collections;53import java.util.HashMap;54import java.util.HashSet;55import java.util.LinkedHashMap;56import java.util.LinkedHashSet;57import java.util.List;58import java.util.Map;59import java.util.Objects;60import java.util.Optional;61import java.util.Set;62import java.util.function.Function;63import java.util.function.Supplier;64import java.util.stream.Collectors;65import java.util.stream.Stream;6667public class JdepsConfiguration implements AutoCloseable {68// the token for "all modules on the module path"69public static final String ALL_MODULE_PATH = "ALL-MODULE-PATH";70public static final String ALL_DEFAULT = "ALL-DEFAULT";71public static final String ALL_SYSTEM = "ALL-SYSTEM";7273public static final String MODULE_INFO = "module-info.class";7475private final SystemModuleFinder system;76private final ModuleFinder finder;7778private final Map<String, Module> nameToModule = new LinkedHashMap<>();79private final Map<String, Module> packageToModule = new HashMap<>();80private final Map<String, List<Archive>> packageToUnnamedModule = new HashMap<>();8182private final List<Archive> classpathArchives = new ArrayList<>();83private final List<Archive> initialArchives = new ArrayList<>();84private final Set<Module> rootModules = new HashSet<>();85private final Runtime.Version version;8687private JdepsConfiguration(Configuration config,88SystemModuleFinder systemModulePath,89ModuleFinder finder,90Set<String> roots,91List<Path> classpaths,92List<Archive> initialArchives,93Runtime.Version version)94throws IOException95{96trace("root: %s%n", roots);97trace("initial archives: %s%n", initialArchives);98trace("class path: %s%n", classpaths);99this.system = systemModulePath;100this.finder = finder;101this.version = version;102103config.modules().stream()104.map(ResolvedModule::reference)105.forEach(this::addModuleReference);106107// packages in unnamed module108initialArchives.forEach(archive -> {109addPackagesInUnnamedModule(archive);110this.initialArchives.add(archive);111});112113// classpath archives114for (Path p : classpaths) {115if (Files.exists(p)) {116Archive archive = Archive.getInstance(p, version);117addPackagesInUnnamedModule(archive);118classpathArchives.add(archive);119}120}121122// all roots specified in --add-modules or -m are included123// as the initial set for analysis.124roots.stream()125.map(nameToModule::get)126.forEach(this.rootModules::add);127128initProfiles();129130trace("resolved modules: %s%n", nameToModule.keySet().stream()131.sorted().collect(joining("\n", "\n", "")));132}133134private void initProfiles() {135// other system modules are not observed and not added in nameToModule map136Map<String, Module> systemModules =137system.moduleNames()138.collect(toMap(Function.identity(), (mn) -> {139Module m = nameToModule.get(mn);140if (m == null) {141ModuleReference mref = finder.find(mn).get();142m = toModule(mref);143}144return m;145}));146Profile.init(systemModules);147}148149private void addModuleReference(ModuleReference mref) {150Module module = toModule(mref);151nameToModule.put(mref.descriptor().name(), module);152mref.descriptor().packages()153.forEach(pn -> packageToModule.putIfAbsent(pn, module));154}155156private void addPackagesInUnnamedModule(Archive archive) {157archive.reader().entries().stream()158.filter(e -> e.endsWith(".class") && !e.equals(MODULE_INFO))159.map(this::toPackageName)160.distinct()161.forEach(pn -> packageToUnnamedModule162.computeIfAbsent(pn, _n -> new ArrayList<>()).add(archive));163}164165private String toPackageName(String name) {166int i = name.lastIndexOf('/');167return i > 0 ? name.replace('/', '.').substring(0, i) : "";168}169170public Optional<Module> findModule(String name) {171Objects.requireNonNull(name);172Module m = nameToModule.get(name);173return m!= null ? Optional.of(m) : Optional.empty();174175}176177public Optional<ModuleDescriptor> findModuleDescriptor(String name) {178Objects.requireNonNull(name);179Module m = nameToModule.get(name);180return m!= null ? Optional.of(m.descriptor()) : Optional.empty();181}182183public static boolean isToken(String name) {184return ALL_MODULE_PATH.equals(name) ||185ALL_DEFAULT.equals(name) ||186ALL_SYSTEM.equals(name);187}188189/**190* Returns the list of packages that split between resolved module and191* unnamed module192*/193public Map<String, Set<String>> splitPackages() {194Set<String> splitPkgs = packageToModule.keySet().stream()195.filter(packageToUnnamedModule::containsKey)196.collect(toSet());197if (splitPkgs.isEmpty())198return Collections.emptyMap();199200return splitPkgs.stream().collect(toMap(Function.identity(), (pn) -> {201Set<String> sources = new LinkedHashSet<>();202sources.add(packageToModule.get(pn).getModule().location().toString());203packageToUnnamedModule.get(pn).stream()204.map(Archive::getPathName)205.forEach(sources::add);206return sources;207}));208}209210/**211* Returns an optional archive containing the given Location212*/213public Optional<Archive> findClass(Dependency.Location location) {214String name = location.getName();215int i = name.lastIndexOf('/');216String pn = i > 0 ? name.substring(0, i).replace('/', '.') : "";217Archive archive = packageToModule.get(pn);218if (archive != null) {219return archive.contains(name + ".class")220? Optional.of(archive)221: Optional.empty();222}223224if (packageToUnnamedModule.containsKey(pn)) {225return packageToUnnamedModule.get(pn).stream()226.filter(a -> a.contains(name + ".class"))227.findFirst();228}229return Optional.empty();230}231232/**233* Returns the list of Modules that can be found in the specified234* module paths.235*/236public Map<String, Module> getModules() {237return nameToModule;238}239240/**241* Returns Configuration with the given roots242*/243public Configuration resolve(Set<String> roots) {244if (roots.isEmpty())245throw new IllegalArgumentException("empty roots");246247return Configuration.empty()248.resolve(finder, ModuleFinder.of(), roots);249}250251public List<Archive> classPathArchives() {252return classpathArchives;253}254255public List<Archive> initialArchives() {256return initialArchives;257}258259public Set<Module> rootModules() {260return rootModules;261}262263public Module toModule(ModuleReference mref) {264try {265String mn = mref.descriptor().name();266URI location = mref.location().orElseThrow(FileNotFoundException::new);267ModuleDescriptor md = mref.descriptor();268// is this module from the system module path?269URI loc = system.find(mn).flatMap(ModuleReference::location).orElse(null);270boolean isSystem = location.equals(loc);271272final ClassFileReader reader;273if (location.getScheme().equals("jrt")) {274reader = system.getClassReader(mn);275} else {276reader = ClassFileReader.newInstance(Paths.get(location), version);277}278Module.Builder builder = new Module.Builder(md, isSystem);279builder.classes(reader);280builder.location(location);281282return builder.build();283} catch (IOException e) {284throw new UncheckedIOException(e);285}286}287288public Runtime.Version getVersion() {289return version;290}291292/*293* Close all archives e.g. JarFile294*/295@Override296public void close() throws IOException {297for (Archive archive : initialArchives)298archive.close();299for (Archive archive : classpathArchives)300archive.close();301for (Module module : nameToModule.values())302module.close();303}304305static class SystemModuleFinder implements ModuleFinder {306private static final String JAVA_HOME = System.getProperty("java.home");307308private final FileSystem fileSystem;309private final Path root;310private final Map<String, ModuleReference> systemModules;311312SystemModuleFinder() {313if (Files.isRegularFile(Paths.get(JAVA_HOME, "lib", "modules"))) {314// jrt file system315this.fileSystem = FileSystems.getFileSystem(URI.create("jrt:/"));316this.root = fileSystem.getPath("/modules");317this.systemModules = walk(root);318} else {319// exploded image320this.fileSystem = FileSystems.getDefault();321root = Paths.get(JAVA_HOME, "modules");322this.systemModules = ModuleFinder.ofSystem().findAll().stream()323.collect(toMap(mref -> mref.descriptor().name(), Function.identity()));324}325}326327SystemModuleFinder(String javaHome) throws IOException {328if (javaHome == null) {329// --system none330this.fileSystem = null;331this.root = null;332this.systemModules = Collections.emptyMap();333} else {334if (!Files.isRegularFile(Paths.get(javaHome, "lib", "modules")))335throw new IllegalArgumentException("Invalid java.home: " + javaHome);336337// alternate java.home338Map<String, String> env = new HashMap<>();339env.put("java.home", javaHome);340// a remote run-time image341this.fileSystem = FileSystems.newFileSystem(URI.create("jrt:/"), env);342this.root = fileSystem.getPath("/modules");343this.systemModules = walk(root);344}345}346347private Map<String, ModuleReference> walk(Path root) {348try (Stream<Path> stream = Files.walk(root, 1)) {349return stream.filter(path -> !path.equals(root))350.map(this::toModuleReference)351.collect(toMap(mref -> mref.descriptor().name(),352Function.identity()));353} catch (IOException e) {354throw new UncheckedIOException(e);355}356}357358private ModuleReference toModuleReference(Path path) {359Path minfo = path.resolve(MODULE_INFO);360try (InputStream in = Files.newInputStream(minfo);361BufferedInputStream bin = new BufferedInputStream(in)) {362363ModuleDescriptor descriptor = dropHashes(ModuleDescriptor.read(bin));364String mn = descriptor.name();365URI uri = URI.create("jrt:/" + path.getFileName().toString());366Supplier<ModuleReader> readerSupplier = () -> new ModuleReader() {367@Override368public Optional<URI> find(String name) throws IOException {369return name.equals(mn)370? Optional.of(uri) : Optional.empty();371}372373@Override374public Stream<String> list() {375return Stream.empty();376}377378@Override379public void close() {380}381};382383return new ModuleReference(descriptor, uri) {384@Override385public ModuleReader open() {386return readerSupplier.get();387}388};389} catch (IOException e) {390throw new UncheckedIOException(e);391}392}393394private ModuleDescriptor dropHashes(ModuleDescriptor md) {395ModuleDescriptor.Builder builder = ModuleDescriptor.newModule(md.name());396md.requires().forEach(builder::requires);397md.exports().forEach(builder::exports);398md.opens().forEach(builder::opens);399md.provides().stream().forEach(builder::provides);400md.uses().stream().forEach(builder::uses);401builder.packages(md.packages());402return builder.build();403}404405@Override406public Set<ModuleReference> findAll() {407return systemModules.values().stream().collect(toSet());408}409410@Override411public Optional<ModuleReference> find(String mn) {412return systemModules.containsKey(mn)413? Optional.of(systemModules.get(mn)) : Optional.empty();414}415416public Stream<String> moduleNames() {417return systemModules.values().stream()418.map(mref -> mref.descriptor().name());419}420421public ClassFileReader getClassReader(String modulename) throws IOException {422Path mp = root.resolve(modulename);423if (Files.exists(mp) && Files.isDirectory(mp)) {424return ClassFileReader.newInstance(fileSystem, mp);425} else {426throw new FileNotFoundException(mp.toString());427}428}429430public Set<String> defaultSystemRoots() {431return systemModules.values().stream()432.map(ModuleReference::descriptor)433.filter(descriptor -> descriptor.exports()434.stream()435.filter(e -> !e.isQualified())436.findAny()437.isPresent())438.map(ModuleDescriptor::name)439.collect(Collectors.toSet());440}441}442443public static class Builder {444445final SystemModuleFinder systemModulePath;446final Set<String> rootModules = new HashSet<>();447final List<Archive> initialArchives = new ArrayList<>();448final List<Path> paths = new ArrayList<>();449final List<Path> classPaths = new ArrayList<>();450final Set<String> tokens = new HashSet<>();451452ModuleFinder upgradeModulePath;453ModuleFinder appModulePath;454Runtime.Version version;455456public Builder() {457this.systemModulePath = new SystemModuleFinder();458}459460public Builder(String javaHome) throws IOException {461this.systemModulePath = SystemModuleFinder.JAVA_HOME.equals(javaHome)462? new SystemModuleFinder()463: new SystemModuleFinder(javaHome);464}465466public Builder upgradeModulePath(String upgradeModulePath) {467this.upgradeModulePath = createModulePathFinder(upgradeModulePath);468return this;469}470471public Builder appModulePath(String modulePath) {472this.appModulePath = createModulePathFinder(modulePath);473return this;474}475476public Builder addmods(Set<String> addmods) {477for (String mn : addmods) {478if (isToken(mn)) {479tokens.add(mn);480} else {481rootModules.add(mn);482}483}484return this;485}486487public Builder multiRelease(Runtime.Version version) {488this.version = version;489return this;490}491492public Builder addRoot(Path path) {493Archive archive = Archive.getInstance(path, version);494if (archive.contains(MODULE_INFO)) {495paths.add(path);496} else {497initialArchives.add(archive);498}499return this;500}501502public Builder addClassPath(String classPath) {503this.classPaths.addAll(getClassPaths(classPath));504return this;505}506507public JdepsConfiguration build() throws IOException {508ModuleFinder finder = systemModulePath;509if (upgradeModulePath != null) {510finder = ModuleFinder.compose(upgradeModulePath, systemModulePath);511}512if (appModulePath != null) {513finder = ModuleFinder.compose(finder, appModulePath);514}515if (!paths.isEmpty()) {516ModuleFinder otherModulePath = ModuleFinder.of(paths.toArray(new Path[0]));517518finder = ModuleFinder.compose(finder, otherModulePath);519// add modules specified on command-line (convenience) as root set520otherModulePath.findAll().stream()521.map(mref -> mref.descriptor().name())522.forEach(rootModules::add);523}524525// no archive is specified for analysis526// add all system modules as root if --add-modules ALL-SYSTEM is specified527if (tokens.contains(ALL_SYSTEM) && rootModules.isEmpty() &&528initialArchives.isEmpty() && classPaths.isEmpty()) {529systemModulePath.findAll()530.stream()531.map(mref -> mref.descriptor().name())532.forEach(rootModules::add);533}534535// add all modules on app module path as roots if ALL-MODULE-PATH is specified536if ((tokens.contains(ALL_MODULE_PATH)) && appModulePath != null) {537appModulePath.findAll().stream()538.map(mref -> mref.descriptor().name())539.forEach(rootModules::add);540}541542543// build root set for module resolution544Set<String> mods = new HashSet<>(rootModules);545// if archives are specified for analysis, then consider as unnamed module546boolean unnamed = !initialArchives.isEmpty() || !classPaths.isEmpty();547if (tokens.contains(ALL_DEFAULT)) {548mods.addAll(systemModulePath.defaultSystemRoots());549} else if (tokens.contains(ALL_SYSTEM) || unnamed) {550// resolve all system modules as unnamed module may reference any class551systemModulePath.findAll().stream()552.map(mref -> mref.descriptor().name())553.forEach(mods::add);554}555if (unnamed && appModulePath != null) {556// resolve all modules on module path as unnamed module may reference any class557appModulePath.findAll().stream()558.map(mref -> mref.descriptor().name())559.forEach(mods::add);560}561562// resolve the module graph563Configuration config = Configuration.empty().resolve(finder, ModuleFinder.of(), mods);564return new JdepsConfiguration(config,565systemModulePath,566finder,567rootModules,568classPaths,569initialArchives,570version);571}572573private static ModuleFinder createModulePathFinder(String mpaths) {574if (mpaths == null) {575return null;576} else {577String[] dirs = mpaths.split(File.pathSeparator);578Path[] paths = new Path[dirs.length];579int i = 0;580for (String dir : dirs) {581paths[i++] = Paths.get(dir);582}583return ModuleFinder.of(paths);584}585}586587/*588* Returns the list of Archive specified in cpaths and not included589* initialArchives590*/591private List<Path> getClassPaths(String cpaths) {592if (cpaths.isEmpty()) {593return Collections.emptyList();594}595List<Path> paths = new ArrayList<>();596for (String p : cpaths.split(File.pathSeparator)) {597if (p.length() > 0) {598// wildcard to parse all JAR files e.g. -classpath dir/*599int i = p.lastIndexOf(".*");600if (i > 0) {601Path dir = Paths.get(p.substring(0, i));602try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) {603for (Path entry : stream) {604paths.add(entry);605}606} catch (IOException e) {607throw new UncheckedIOException(e);608}609} else {610paths.add(Paths.get(p));611}612}613}614return paths;615}616}617618}619620621