Path: blob/master/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/DepsAnalyzer.java
41161 views
/*1* Copyright (c) 2016, 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 com.sun.tools.classfile.Dependency.Location;28import java.io.IOException;29import java.util.ArrayList;30import java.util.Collection;31import java.util.Deque;32import java.util.LinkedHashSet;33import java.util.LinkedList;34import java.util.List;35import java.util.Optional;36import java.util.Set;37import java.util.concurrent.ConcurrentLinkedDeque;38import java.util.stream.Collectors;39import java.util.stream.Stream;4041import static com.sun.tools.jdeps.Analyzer.Type.CLASS;42import static com.sun.tools.jdeps.Analyzer.Type.VERBOSE;43import static com.sun.tools.jdeps.Module.trace;44import static java.util.stream.Collectors.*;4546/**47* Dependency Analyzer.48*49* Type of filters:50* source filter: -include <pattern>51* target filter: -package, -regex, --require52*53* The initial archive set for analysis includes54* 1. archives specified in the command line arguments55* 2. observable modules matching the source filter56* 3. classpath archives matching the source filter or target filter57* 4. --add-modules and -m root modules58*/59public class DepsAnalyzer {60final JdepsConfiguration configuration;61final JdepsFilter filter;62final JdepsWriter writer;63final Analyzer.Type verbose;64final boolean apiOnly;6566final DependencyFinder finder;67final Analyzer analyzer;68final List<Archive> rootArchives = new ArrayList<>();6970// parsed archives71final Set<Archive> archives = new LinkedHashSet<>();7273public DepsAnalyzer(JdepsConfiguration config,74JdepsFilter filter,75JdepsWriter writer,76Analyzer.Type verbose,77boolean apiOnly) {78this.configuration = config;79this.filter = filter;80this.writer = writer;81this.verbose = verbose;82this.apiOnly = apiOnly;8384this.finder = new DependencyFinder(config, filter);85this.analyzer = new Analyzer(configuration, verbose, filter);8687// determine initial archives to be analyzed88this.rootArchives.addAll(configuration.initialArchives());8990// if -include pattern is specified, add the matching archives on91// classpath to the root archives92if (filter.hasIncludePattern() || filter.hasTargetFilter()) {93configuration.getModules().values().stream()94.filter(source -> include(source) && filter.matches(source))95.forEach(this.rootArchives::add);96}9798// class path archives99configuration.classPathArchives().stream()100.filter(filter::matches)101.forEach(this.rootArchives::add);102103// Include the root modules for analysis104this.rootArchives.addAll(configuration.rootModules());105106trace("analyze root archives: %s%n", this.rootArchives);107}108109/*110* Perform runtime dependency analysis111*/112public boolean run() throws IOException {113return run(false, 1);114}115116/**117* Perform compile-time view or run-time view dependency analysis.118*119* @param compileTimeView120* @param maxDepth depth of recursive analysis. depth == 0 if -R is set121*/122public boolean run(boolean compileTimeView, int maxDepth) throws IOException {123try {124// parse each packaged module or classpath archive125if (apiOnly) {126finder.parseExportedAPIs(rootArchives.stream());127} else {128finder.parse(rootArchives.stream());129}130archives.addAll(rootArchives);131132int depth = maxDepth > 0 ? maxDepth : Integer.MAX_VALUE;133134// transitive analysis135if (depth > 1) {136if (compileTimeView)137transitiveArchiveDeps(depth-1);138else139transitiveDeps(depth-1);140}141142Set<Archive> archives = archives();143144// analyze the dependencies collected145analyzer.run(archives, finder.locationToArchive());146147if (writer != null) {148writer.generateOutput(archives, analyzer);149}150} finally {151finder.shutdown();152}153return true;154}155156/**157* Returns the archives for reporting that has matching dependences.158*159* If --require is set, they should be excluded.160*/161Set<Archive> archives() {162if (filter.requiresFilter().isEmpty()) {163return archives.stream()164.filter(this::include)165.filter(Archive::hasDependences)166.collect(Collectors.toSet());167} else {168// use the archives that have dependences and not specified in --require169return archives.stream()170.filter(this::include)171.filter(source -> !filter.requiresFilter().contains(source.getName()))172.filter(source ->173source.getDependencies()174.map(finder::locationToArchive)175.anyMatch(a -> a != source))176.collect(Collectors.toSet());177}178}179180/**181* Returns the dependences, either class name or package name182* as specified in the given verbose level.183*/184Set<String> dependences() {185return analyzer.archives().stream()186.map(analyzer::dependences)187.flatMap(Set::stream)188.collect(Collectors.toSet());189}190191/**192* Returns the archives that contains the given locations and193* not parsed and analyzed.194*/195private Set<Archive> unresolvedArchives(Stream<Location> locations) {196return locations.filter(l -> !finder.isParsed(l))197.distinct()198.map(configuration::findClass)199.flatMap(Optional::stream)200.collect(toSet());201}202203/*204* Recursively analyzes entire module/archives.205*/206private void transitiveArchiveDeps(int depth) throws IOException {207Stream<Location> deps = archives.stream()208.flatMap(Archive::getDependencies);209210// start with the unresolved archives211Set<Archive> unresolved = unresolvedArchives(deps);212do {213// parse all unresolved archives214Set<Location> targets = apiOnly215? finder.parseExportedAPIs(unresolved.stream())216: finder.parse(unresolved.stream());217archives.addAll(unresolved);218219// Add dependencies to the next batch for analysis220unresolved = unresolvedArchives(targets.stream());221} while (!unresolved.isEmpty() && depth-- > 0);222}223224/*225* Recursively analyze the class dependences226*/227private void transitiveDeps(int depth) throws IOException {228Stream<Location> deps = archives.stream()229.flatMap(Archive::getDependencies);230231Deque<Location> unresolved = deps.collect(Collectors.toCollection(LinkedList::new));232ConcurrentLinkedDeque<Location> deque = new ConcurrentLinkedDeque<>();233do {234Location target;235while ((target = unresolved.poll()) != null) {236if (finder.isParsed(target))237continue;238239Archive archive = configuration.findClass(target).orElse(null);240if (archive != null) {241archives.add(archive);242243String name = target.getName();244Set<Location> targets = apiOnly245? finder.parseExportedAPIs(archive, name)246: finder.parse(archive, name);247248// build unresolved dependencies249targets.stream()250.filter(t -> !finder.isParsed(t))251.forEach(deque::add);252}253}254unresolved = deque;255deque = new ConcurrentLinkedDeque<>();256} while (!unresolved.isEmpty() && depth-- > 0);257}258259/*260* Tests if the given archive is requested for analysis.261* It includes the root modules specified in --module, --add-modules262* or modules specified on the command line263*264* This filters system module by default unless they are explicitly265* requested.266*/267public boolean include(Archive source) {268Module module = source.getModule();269// skip system module by default270return !module.isSystem()271|| configuration.rootModules().contains(source);272}273274// ----- for testing purpose -----275276public static enum Info {277REQUIRES,278REQUIRES_TRANSITIVE,279EXPORTED_API,280MODULE_PRIVATE,281QUALIFIED_EXPORTED_API,282INTERNAL_API,283JDK_INTERNAL_API,284JDK_REMOVED_INTERNAL_API285}286287public static class Node {288public final String name;289public final String source;290public final Info info;291Node(String name, Info info) {292this(name, name, info);293}294Node(String name, String source, Info info) {295this.name = name;296this.source = source;297this.info = info;298}299300@Override301public String toString() {302StringBuilder sb = new StringBuilder();303if (info != Info.REQUIRES && info != Info.REQUIRES_TRANSITIVE)304sb.append(source).append("/");305306sb.append(name);307if (info == Info.QUALIFIED_EXPORTED_API)308sb.append(" (qualified)");309else if (info == Info.JDK_INTERNAL_API)310sb.append(" (JDK internal)");311else if (info == Info.INTERNAL_API)312sb.append(" (internal)");313return sb.toString();314}315316@Override317public boolean equals(Object o) {318if (!(o instanceof Node))319return false;320321Node other = (Node)o;322return this.name.equals(other.name) &&323this.source.equals(other.source) &&324this.info.equals(other.info);325}326327@Override328public int hashCode() {329int result = name.hashCode();330result = 31 * result + source.hashCode();331result = 31 * result + info.hashCode();332return result;333}334}335336/**337* Returns a graph of module dependences.338*339* Each Node represents a module and each edge is a dependence.340* No analysis on "requires transitive".341*/342public Graph<Node> moduleGraph() {343Graph.Builder<Node> builder = new Graph.Builder<>();344345archives().stream()346.forEach(m -> {347Node u = new Node(m.getName(), Info.REQUIRES);348builder.addNode(u);349analyzer.requires(m)350.map(req -> new Node(req.getName(), Info.REQUIRES))351.forEach(v -> builder.addEdge(u, v));352});353return builder.build();354}355356/**357* Returns a graph of dependences.358*359* Each Node represents a class or package per the specified verbose level.360* Each edge indicates361*/362public Graph<Node> dependenceGraph() {363Graph.Builder<Node> builder = new Graph.Builder<>();364365archives().stream()366.map(analyzer.results::get)367.filter(deps -> !deps.dependencies().isEmpty())368.flatMap(deps -> deps.dependencies().stream())369.forEach(d -> addEdge(builder, d));370return builder.build();371}372373private void addEdge(Graph.Builder<Node> builder, Analyzer.Dep dep) {374Archive source = dep.originArchive();375Archive target = dep.targetArchive();376String pn = dep.target();377if (verbose == CLASS || verbose == VERBOSE) {378int i = dep.target().lastIndexOf('.');379pn = i > 0 ? dep.target().substring(0, i) : "";380}381final Info info;382Module targetModule = target.getModule();383if (source == target) {384info = Info.MODULE_PRIVATE;385} else if (!targetModule.isNamed()) {386info = Info.EXPORTED_API;387} else if (targetModule.isExported(pn) && !targetModule.isJDKUnsupported()) {388info = Info.EXPORTED_API;389} else {390Module module = target.getModule();391if (module == Analyzer.REMOVED_JDK_INTERNALS) {392info = Info.JDK_REMOVED_INTERNAL_API;393} else if (!source.getModule().isJDK() && module.isJDK())394info = Info.JDK_INTERNAL_API;395// qualified exports or inaccessible396else if (module.isExported(pn, source.getModule().name()))397info = Info.QUALIFIED_EXPORTED_API;398else399info = Info.INTERNAL_API;400}401402Node u = new Node(dep.origin(), source.getName(), info);403Node v = new Node(dep.target(), target.getName(), info);404builder.addEdge(u, v);405}406407}408409410