com.sun.tools.jdeps.ModuleAnalyzer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of javac Show documentation
Show all versions of javac Show documentation
A repackaged copy of javac for Error Prone to depend on
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.jdeps;
import static com.sun.tools.jdeps.Graph.*;
import static com.sun.tools.jdeps.JdepsFilter.DEFAULT_FILTER;
import static com.sun.tools.jdeps.Module.*;
import static java.lang.module.ModuleDescriptor.Requires.Modifier.*;
import static java.util.stream.Collectors.*;
import com.sun.tools.classfile.Dependency;
import com.sun.tools.jdeps.JdepsTask.BadArgs;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.module.ModuleDescriptor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Analyze module dependences and compare with module descriptor.
* Also identify any qualified exports not used by the target module.
*/
public class ModuleAnalyzer {
private static final String JAVA_BASE = "java.base";
private final JdepsConfiguration configuration;
private final PrintWriter log;
private final DependencyFinder dependencyFinder;
private final Map modules;
public ModuleAnalyzer(JdepsConfiguration config,
PrintWriter log,
Set names) {
this.configuration = config;
this.log = log;
this.dependencyFinder = new DependencyFinder(config, DEFAULT_FILTER);
if (names.isEmpty()) {
this.modules = configuration.rootModules().stream()
.collect(toMap(Function.identity(), ModuleDeps::new));
} else {
this.modules = names.stream()
.map(configuration::findModule)
.flatMap(Optional::stream)
.collect(toMap(Function.identity(), ModuleDeps::new));
}
}
public boolean run() throws IOException {
try {
// compute "requires transitive" dependences
modules.values().forEach(ModuleDeps::computeRequiresTransitive);
modules.values().forEach(md -> {
// compute "requires" dependences
md.computeRequires();
// apply transitive reduction and reports recommended requires.
md.analyzeDeps();
});
} finally {
dependencyFinder.shutdown();
}
return true;
}
class ModuleDeps {
final Module root;
Set requiresTransitive;
Set requires;
Map> unusedQualifiedExports;
ModuleDeps(Module root) {
this.root = root;
}
/**
* Compute 'requires transitive' dependences by analyzing API dependencies
*/
private void computeRequiresTransitive() {
// record requires transitive
this.requiresTransitive = computeRequires(true)
.filter(m -> !m.name().equals(JAVA_BASE))
.collect(toSet());
trace("requires transitive: %s%n", requiresTransitive);
}
private void computeRequires() {
this.requires = computeRequires(false).collect(toSet());
trace("requires: %s%n", requires);
}
private Stream computeRequires(boolean apionly) {
// analyze all classes
if (apionly) {
dependencyFinder.parseExportedAPIs(Stream.of(root));
} else {
dependencyFinder.parse(Stream.of(root));
}
// find the modules of all the dependencies found
return dependencyFinder.getDependences(root)
.map(Archive::getModule);
}
ModuleDescriptor descriptor() {
return descriptor(requiresTransitive, requires);
}
private ModuleDescriptor descriptor(Set requiresTransitive,
Set requires) {
ModuleDescriptor.Builder builder = ModuleDescriptor.newModule(root.name());
if (!root.name().equals(JAVA_BASE))
builder.requires(Set.of(MANDATED), JAVA_BASE);
requiresTransitive.stream()
.filter(m -> !m.name().equals(JAVA_BASE))
.map(Module::name)
.forEach(mn -> builder.requires(Set.of(TRANSITIVE), mn));
requires.stream()
.filter(m -> !requiresTransitive.contains(m))
.filter(m -> !m.name().equals(JAVA_BASE))
.map(Module::name)
.forEach(mn -> builder.requires(mn));
return builder.build();
}
private Graph buildReducedGraph() {
ModuleGraphBuilder rpBuilder = new ModuleGraphBuilder(configuration);
rpBuilder.addModule(root);
requiresTransitive.stream()
.forEach(m -> rpBuilder.addEdge(root, m));
// requires transitive graph
Graph rbg = rpBuilder.build().reduce();
ModuleGraphBuilder gb = new ModuleGraphBuilder(configuration);
gb.addModule(root);
requires.stream()
.forEach(m -> gb.addEdge(root, m));
// transitive reduction
Graph newGraph = gb.buildGraph().reduce(rbg);
if (DEBUG) {
System.err.println("after transitive reduction: ");
newGraph.printGraph(log);
}
return newGraph;
}
/**
* Apply the transitive reduction on the module graph
* and returns the corresponding ModuleDescriptor
*/
ModuleDescriptor reduced() {
Graph g = buildReducedGraph();
return descriptor(requiresTransitive, g.adjacentNodes(root));
}
/**
* Apply transitive reduction on the resulting graph and reports
* recommended requires.
*/
private void analyzeDeps() {
printModuleDescriptor(log, root);
ModuleDescriptor analyzedDescriptor = descriptor();
if (!matches(root.descriptor(), analyzedDescriptor)) {
log.format(" [Suggested module descriptor for %s]%n", root.name());
analyzedDescriptor.requires()
.stream()
.sorted(Comparator.comparing(ModuleDescriptor.Requires::name))
.forEach(req -> log.format(" requires %s;%n", req));
}
ModuleDescriptor reduced = reduced();
if (!matches(root.descriptor(), reduced)) {
log.format(" [Transitive reduced graph for %s]%n", root.name());
reduced.requires()
.stream()
.sorted(Comparator.comparing(ModuleDescriptor.Requires::name))
.forEach(req -> log.format(" requires %s;%n", req));
}
checkQualifiedExports();
log.println();
}
private void checkQualifiedExports() {
// detect any qualified exports not used by the target module
unusedQualifiedExports = unusedQualifiedExports();
if (!unusedQualifiedExports.isEmpty())
log.format(" [Unused qualified exports in %s]%n", root.name());
unusedQualifiedExports.keySet().stream()
.sorted()
.forEach(pn -> log.format(" exports %s to %s%n", pn,
unusedQualifiedExports.get(pn).stream()
.sorted()
.collect(joining(","))));
}
private void printModuleDescriptor(PrintWriter out, Module module) {
ModuleDescriptor descriptor = module.descriptor();
out.format("%s (%s)%n", descriptor.name(), module.location());
if (descriptor.name().equals(JAVA_BASE))
return;
out.println(" [Module descriptor]");
descriptor.requires()
.stream()
.sorted(Comparator.comparing(ModuleDescriptor.Requires::name))
.forEach(req -> out.format(" requires %s;%n", req));
}
/**
* Detects any qualified exports not used by the target module.
*/
private Map> unusedQualifiedExports() {
Map> unused = new HashMap<>();
// build the qualified exports map
Map> qualifiedExports =
root.exports().entrySet().stream()
.filter(e -> !e.getValue().isEmpty())
.map(Map.Entry::getKey)
.collect(toMap(Function.identity(), _k -> new HashSet<>()));
Set mods = new HashSet<>();
root.exports().values()
.stream()
.flatMap(Set::stream)
.forEach(target -> configuration.findModule(target)
.ifPresentOrElse(mods::add,
() -> log.format("Warning: %s not found%n", target))
);
// parse all target modules
dependencyFinder.parse(mods.stream());
// adds to the qualified exports map if a module references it
mods.stream().forEach(m ->
m.getDependencies()
.map(Dependency.Location::getPackageName)
.filter(qualifiedExports::containsKey)
.forEach(pn -> qualifiedExports.get(pn).add(m.name())));
// compare with the exports from ModuleDescriptor
Set staleQualifiedExports =
qualifiedExports.keySet().stream()
.filter(pn -> !qualifiedExports.get(pn).equals(root.exports().get(pn)))
.collect(toSet());
if (!staleQualifiedExports.isEmpty()) {
for (String pn : staleQualifiedExports) {
Set targets = new HashSet<>(root.exports().get(pn));
targets.removeAll(qualifiedExports.get(pn));
unused.put(pn, targets);
}
}
return unused;
}
}
private boolean matches(ModuleDescriptor md, ModuleDescriptor other) {
// build requires transitive from ModuleDescriptor
Set reqTransitive = md.requires().stream()
.filter(req -> req.modifiers().contains(TRANSITIVE))
.collect(toSet());
Set otherReqTransitive = other.requires().stream()
.filter(req -> req.modifiers().contains(TRANSITIVE))
.collect(toSet());
if (!reqTransitive.equals(otherReqTransitive)) {
trace("mismatch requires transitive: %s%n", reqTransitive);
return false;
}
Set unused = md.requires().stream()
.filter(req -> !other.requires().contains(req))
.collect(Collectors.toSet());
if (!unused.isEmpty()) {
trace("mismatch requires: %s%n", unused);
return false;
}
return true;
}
// ---- for testing purpose
public ModuleDescriptor[] descriptors(String name) {
ModuleDeps moduleDeps = modules.keySet().stream()
.filter(m -> m.name().equals(name))
.map(modules::get)
.findFirst().get();
ModuleDescriptor[] descriptors = new ModuleDescriptor[3];
descriptors[0] = moduleDeps.root.descriptor();
descriptors[1] = moduleDeps.descriptor();
descriptors[2] = moduleDeps.reduced();
return descriptors;
}
public Map> unusedQualifiedExports(String name) {
ModuleDeps moduleDeps = modules.keySet().stream()
.filter(m -> m.name().equals(name))
.map(modules::get)
.findFirst().get();
return moduleDeps.unusedQualifiedExports;
}
}