Path: blob/master/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/ModuleAnalyzer.java
41161 views
/*1* Copyright (c) 2016, 2020, 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*/24package com.sun.tools.jdeps;2526import static com.sun.tools.jdeps.JdepsFilter.DEFAULT_FILTER;27import static com.sun.tools.jdeps.Module.*;28import static java.lang.module.ModuleDescriptor.Requires.Modifier.*;29import static java.util.stream.Collectors.*;3031import com.sun.tools.classfile.Dependency;3233import java.io.IOException;34import java.io.PrintWriter;35import java.lang.module.ModuleDescriptor;36import java.util.Comparator;37import java.util.HashMap;38import java.util.HashSet;39import java.util.Map;40import java.util.Optional;41import java.util.Set;42import java.util.function.Function;43import java.util.stream.Collectors;44import java.util.stream.Stream;4546/**47* Analyze module dependences and compare with module descriptor.48* Also identify any qualified exports not used by the target module.49*/50public class ModuleAnalyzer {51private static final String JAVA_BASE = "java.base";5253private final JdepsConfiguration configuration;54private final PrintWriter log;55private final DependencyFinder dependencyFinder;56private final Map<Module, ModuleDeps> modules;5758public ModuleAnalyzer(JdepsConfiguration config,59PrintWriter log,60Set<String> names) {61this.configuration = config;62this.log = log;6364this.dependencyFinder = new DependencyFinder(config, DEFAULT_FILTER);65if (names.isEmpty()) {66this.modules = configuration.rootModules().stream()67.collect(toMap(Function.identity(), ModuleDeps::new));68} else {69this.modules = names.stream()70.map(configuration::findModule)71.flatMap(Optional::stream)72.collect(toMap(Function.identity(), ModuleDeps::new));73}74}7576public boolean run(boolean ignoreMissingDeps) throws IOException {77try {78for (ModuleDeps md: modules.values()) {79// compute "requires transitive" dependences80md.computeRequiresTransitive(ignoreMissingDeps);81// compute "requires" dependences82md.computeRequires(ignoreMissingDeps);83// print module descriptor84md.printModuleDescriptor();8586// apply transitive reduction and reports recommended requires.87boolean ok = md.analyzeDeps();88if (!ok) return false;8990if (ignoreMissingDeps && md.hasMissingDependencies()) {91log.format("Warning: --ignore-missing-deps specified. Missing dependencies from %s are ignored%n",92md.root.name());93}94}95} finally {96dependencyFinder.shutdown();97}98return true;99}100101102class ModuleDeps {103final Module root;104Set<Module> requiresTransitive;105Set<Module> requires;106Map<String, Set<String>> unusedQualifiedExports;107108ModuleDeps(Module root) {109this.root = root;110}111112/**113* Compute 'requires transitive' dependences by analyzing API dependencies114*/115private void computeRequiresTransitive(boolean ignoreMissingDeps) {116// record requires transitive117this.requiresTransitive = computeRequires(true, ignoreMissingDeps)118.filter(m -> !m.name().equals(JAVA_BASE))119.collect(toSet());120121trace("requires transitive: %s%n", requiresTransitive);122}123124private void computeRequires(boolean ignoreMissingDeps) {125this.requires = computeRequires(false, ignoreMissingDeps).collect(toSet());126trace("requires: %s%n", requires);127}128129private Stream<Module> computeRequires(boolean apionly, boolean ignoreMissingDeps) {130// analyze all classes131if (apionly) {132dependencyFinder.parseExportedAPIs(Stream.of(root));133} else {134dependencyFinder.parse(Stream.of(root));135}136137// find the modules of all the dependencies found138return dependencyFinder.getDependences(root)139.filter(a -> !(ignoreMissingDeps && Analyzer.notFound(a)))140.map(Archive::getModule);141}142143boolean hasMissingDependencies() {144return dependencyFinder.getDependences(root).anyMatch(Analyzer::notFound);145}146147ModuleDescriptor descriptor() {148return descriptor(requiresTransitive, requires);149}150151private ModuleDescriptor descriptor(Set<Module> requiresTransitive,152Set<Module> requires) {153154ModuleDescriptor.Builder builder = ModuleDescriptor.newModule(root.name());155156if (!root.name().equals(JAVA_BASE))157builder.requires(Set.of(MANDATED), JAVA_BASE);158159requiresTransitive.stream()160.filter(m -> !m.name().equals(JAVA_BASE))161.map(Module::name)162.forEach(mn -> builder.requires(Set.of(TRANSITIVE), mn));163164requires.stream()165.filter(m -> !requiresTransitive.contains(m))166.filter(m -> !m.name().equals(JAVA_BASE))167.map(Module::name)168.forEach(mn -> builder.requires(mn));169170return builder.build();171}172173private Graph<Module> buildReducedGraph() {174ModuleGraphBuilder rpBuilder = new ModuleGraphBuilder(configuration);175rpBuilder.addModule(root);176requiresTransitive.stream()177.forEach(m -> rpBuilder.addEdge(root, m));178179// requires transitive graph180Graph<Module> rbg = rpBuilder.build().reduce();181182ModuleGraphBuilder gb = new ModuleGraphBuilder(configuration);183gb.addModule(root);184requires.stream()185.forEach(m -> gb.addEdge(root, m));186187// transitive reduction188Graph<Module> newGraph = gb.buildGraph().reduce(rbg);189if (DEBUG) {190System.err.println("after transitive reduction: ");191newGraph.printGraph(log);192}193return newGraph;194}195196/**197* Apply the transitive reduction on the module graph198* and returns the corresponding ModuleDescriptor199*/200ModuleDescriptor reduced() {201Graph<Module> g = buildReducedGraph();202return descriptor(requiresTransitive, g.adjacentNodes(root));203}204205private void showMissingDeps() {206// build the analyzer if there are missing dependences207Analyzer analyzer = new Analyzer(configuration, Analyzer.Type.CLASS, DEFAULT_FILTER);208analyzer.run(Set.of(root), dependencyFinder.locationToArchive());209log.println("Error: Missing dependencies: classes not found from the module path.");210Analyzer.Visitor visitor = new Analyzer.Visitor() {211@Override212public void visitDependence(String origin, Archive originArchive, String target, Archive targetArchive) {213log.format(" %-50s -> %-50s %s%n", origin, target, targetArchive.getName());214}215};216analyzer.visitDependences(root, visitor, Analyzer.Type.VERBOSE, Analyzer::notFound);217log.println();218}219220/**221* Apply transitive reduction on the resulting graph and reports222* recommended requires.223*/224private boolean analyzeDeps() {225if (requires.stream().anyMatch(m -> m == UNNAMED_MODULE)) {226showMissingDeps();227return false;228}229230ModuleDescriptor analyzedDescriptor = descriptor();231if (!matches(root.descriptor(), analyzedDescriptor)) {232log.format(" [Suggested module descriptor for %s]%n", root.name());233analyzedDescriptor.requires()234.stream()235.sorted(Comparator.comparing(ModuleDescriptor.Requires::name))236.forEach(req -> log.format(" requires %s;%n", req));237}238239ModuleDescriptor reduced = reduced();240if (!matches(root.descriptor(), reduced)) {241log.format(" [Transitive reduced graph for %s]%n", root.name());242reduced.requires()243.stream()244.sorted(Comparator.comparing(ModuleDescriptor.Requires::name))245.forEach(req -> log.format(" requires %s;%n", req));246}247248checkQualifiedExports();249log.println();250return true;251}252253private void checkQualifiedExports() {254// detect any qualified exports not used by the target module255unusedQualifiedExports = unusedQualifiedExports();256if (!unusedQualifiedExports.isEmpty())257log.format(" [Unused qualified exports in %s]%n", root.name());258259unusedQualifiedExports.keySet().stream()260.sorted()261.forEach(pn -> log.format(" exports %s to %s%n", pn,262unusedQualifiedExports.get(pn).stream()263.sorted()264.collect(joining(","))));265}266267void printModuleDescriptor() {268printModuleDescriptor(log, root);269}270271private void printModuleDescriptor(PrintWriter out, Module module) {272ModuleDescriptor descriptor = module.descriptor();273out.format("%s (%s)%n", descriptor.name(), module.location());274275if (descriptor.name().equals(JAVA_BASE))276return;277278out.println(" [Module descriptor]");279descriptor.requires()280.stream()281.sorted(Comparator.comparing(ModuleDescriptor.Requires::name))282.forEach(req -> out.format(" requires %s;%n", req));283}284285286/**287* Detects any qualified exports not used by the target module.288*/289private Map<String, Set<String>> unusedQualifiedExports() {290Map<String, Set<String>> unused = new HashMap<>();291292// build the qualified exports map293Map<String, Set<String>> qualifiedExports =294root.exports().entrySet().stream()295.filter(e -> !e.getValue().isEmpty())296.map(Map.Entry::getKey)297.collect(toMap(Function.identity(), _k -> new HashSet<>()));298299Set<Module> mods = new HashSet<>();300root.exports().values()301.stream()302.flatMap(Set::stream)303.forEach(target -> configuration.findModule(target)304.ifPresentOrElse(mods::add,305() -> log.format("Warning: %s not found%n", target))306);307308// parse all target modules309dependencyFinder.parse(mods.stream());310311// adds to the qualified exports map if a module references it312mods.stream().forEach(m ->313m.getDependencies()314.map(Dependency.Location::getPackageName)315.filter(qualifiedExports::containsKey)316.forEach(pn -> qualifiedExports.get(pn).add(m.name())));317318// compare with the exports from ModuleDescriptor319Set<String> staleQualifiedExports =320qualifiedExports.keySet().stream()321.filter(pn -> !qualifiedExports.get(pn).equals(root.exports().get(pn)))322.collect(toSet());323324if (!staleQualifiedExports.isEmpty()) {325for (String pn : staleQualifiedExports) {326Set<String> targets = new HashSet<>(root.exports().get(pn));327targets.removeAll(qualifiedExports.get(pn));328unused.put(pn, targets);329}330}331return unused;332}333}334335private boolean matches(ModuleDescriptor md, ModuleDescriptor other) {336// build requires transitive from ModuleDescriptor337Set<ModuleDescriptor.Requires> reqTransitive = md.requires().stream()338.filter(req -> req.modifiers().contains(TRANSITIVE))339.collect(toSet());340Set<ModuleDescriptor.Requires> otherReqTransitive = other.requires().stream()341.filter(req -> req.modifiers().contains(TRANSITIVE))342.collect(toSet());343344if (!reqTransitive.equals(otherReqTransitive)) {345trace("mismatch requires transitive: %s%n", reqTransitive);346return false;347}348349Set<ModuleDescriptor.Requires> unused = md.requires().stream()350.filter(req -> !other.requires().contains(req))351.collect(Collectors.toSet());352353if (!unused.isEmpty()) {354trace("mismatch requires: %s%n", unused);355return false;356}357return true;358}359360// ---- for testing purpose361public ModuleDescriptor[] descriptors(String name) {362ModuleDeps moduleDeps = modules.keySet().stream()363.filter(m -> m.name().equals(name))364.map(modules::get)365.findFirst().get();366367ModuleDescriptor[] descriptors = new ModuleDescriptor[3];368descriptors[0] = moduleDeps.root.descriptor();369descriptors[1] = moduleDeps.descriptor();370descriptors[2] = moduleDeps.reduced();371return descriptors;372}373374public Map<String, Set<String>> unusedQualifiedExports(String name) {375ModuleDeps moduleDeps = modules.keySet().stream()376.filter(m -> m.name().equals(name))377.map(modules::get)378.findFirst().get();379return moduleDeps.unusedQualifiedExports;380}381}382383384