Path: blob/master/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/JdepsWriter.java
41161 views
/*1* Copyright (c) 2015, 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.Analyzer.Type.*;2728import java.io.IOException;29import java.io.PrintWriter;30import java.io.UncheckedIOException;31import java.lang.module.ModuleDescriptor.Requires;32import java.nio.file.Files;33import java.nio.file.Path;34import java.nio.file.Paths;35import java.util.Collection;36import java.util.Comparator;37import java.util.HashMap;38import java.util.Map;39import java.util.Optional;4041public abstract class JdepsWriter {42public static JdepsWriter newDotWriter(Path outputdir, Analyzer.Type type) {43return new DotFileWriter(outputdir, type, false, true, false);44}4546public static JdepsWriter newSimpleWriter(PrintWriter writer, Analyzer.Type type) {47return new SimpleWriter(writer, type, false, true);48}4950final Analyzer.Type type;51final boolean showProfile;52final boolean showModule;5354JdepsWriter(Analyzer.Type type, boolean showProfile, boolean showModule) {55this.type = type;56this.showProfile = showProfile;57this.showModule = showModule;58}5960abstract void generateOutput(Collection<Archive> archives, Analyzer analyzer) throws IOException;6162static class DotFileWriter extends JdepsWriter {63final boolean showLabel;64final Path outputDir;65DotFileWriter(Path dir, Analyzer.Type type,66boolean showProfile, boolean showModule, boolean showLabel) {67super(type, showProfile, showModule);68this.showLabel = showLabel;69this.outputDir = dir;70}7172@Override73void generateOutput(Collection<Archive> archives, Analyzer analyzer)74throws IOException75{76Files.createDirectories(outputDir);7778// output individual .dot file for each archive79if (type != SUMMARY && type != MODULE) {80archives.stream()81.filter(analyzer::hasDependences)82.forEach(archive -> {83// use the filename if path is present; otherwise84// use the module name e.g. from jrt file system85Path path = archive.path().orElse(Paths.get(archive.getName()));86Path dotfile = outputDir.resolve(path.getFileName().toString() + ".dot");87try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile));88DotFileFormatter formatter = new DotFileFormatter(pw, archive)) {89analyzer.visitDependences(archive, formatter);90} catch (IOException e) {91throw new UncheckedIOException(e);92}93});94}95// generate summary dot file96generateSummaryDotFile(archives, analyzer);97}9899100private void generateSummaryDotFile(Collection<Archive> archives, Analyzer analyzer)101throws IOException102{103// If verbose mode (-v or -verbose option),104// the summary.dot file shows package-level dependencies.105boolean isSummary = type == PACKAGE || type == SUMMARY || type == MODULE;106Analyzer.Type summaryType = isSummary ? SUMMARY : PACKAGE;107Path summary = outputDir.resolve("summary.dot");108try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary));109SummaryDotFile dotfile = new SummaryDotFile(sw, summaryType)) {110for (Archive archive : archives) {111if (isSummary) {112if (showLabel) {113// build labels listing package-level dependencies114analyzer.visitDependences(archive, dotfile.labelBuilder(), PACKAGE);115}116}117analyzer.visitDependences(archive, dotfile, summaryType);118}119}120}121122class DotFileFormatter implements Analyzer.Visitor, AutoCloseable {123private final PrintWriter writer;124private final String name;125DotFileFormatter(PrintWriter writer, Archive archive) {126this.writer = writer;127this.name = archive.getName();128writer.format("digraph \"%s\" {%n", name);129writer.format(" // Path: %s%n", archive.getPathName());130}131132@Override133public void close() {134writer.println("}");135}136137@Override138public void visitDependence(String origin, Archive originArchive,139String target, Archive targetArchive) {140String tag = toTag(originArchive, target, targetArchive);141writer.format(" %-50s -> \"%s\";%n",142String.format("\"%s\"", origin),143tag.isEmpty() ? target144: String.format("%s (%s)", target, tag));145}146}147148class SummaryDotFile implements Analyzer.Visitor, AutoCloseable {149private final PrintWriter writer;150private final Analyzer.Type type;151private final Map<Archive, Map<Archive,StringBuilder>> edges = new HashMap<>();152SummaryDotFile(PrintWriter writer, Analyzer.Type type) {153this.writer = writer;154this.type = type;155writer.format("digraph \"summary\" {%n");156}157158@Override159public void close() {160writer.println("}");161}162163@Override164public void visitDependence(String origin, Archive originArchive,165String target, Archive targetArchive) {166167String targetName = type == PACKAGE ? target : targetArchive.getName();168if (targetArchive.getModule().isJDK()) {169Module m = (Module)targetArchive;170String n = showProfileOrModule(m);171if (!n.isEmpty()) {172targetName += " (" + n + ")";173}174} else if (type == PACKAGE) {175targetName += " (" + targetArchive.getName() + ")";176}177String label = getLabel(originArchive, targetArchive);178writer.format(" %-50s -> \"%s\"%s;%n",179String.format("\"%s\"", origin), targetName, label);180}181182String getLabel(Archive origin, Archive target) {183if (edges.isEmpty())184return "";185186StringBuilder label = edges.get(origin).get(target);187return label == null ? "" : String.format(" [label=\"%s\",fontsize=9]", label.toString());188}189190Analyzer.Visitor labelBuilder() {191// show the package-level dependencies as labels in the dot graph192return new Analyzer.Visitor() {193@Override194public void visitDependence(String origin, Archive originArchive,195String target, Archive targetArchive)196{197edges.putIfAbsent(originArchive, new HashMap<>());198edges.get(originArchive).putIfAbsent(targetArchive, new StringBuilder());199StringBuilder sb = edges.get(originArchive).get(targetArchive);200String tag = toTag(originArchive, target, targetArchive);201addLabel(sb, origin, target, tag);202}203204void addLabel(StringBuilder label, String origin, String target, String tag) {205label.append(origin).append(" -> ").append(target);206if (!tag.isEmpty()) {207label.append(" (" + tag + ")");208}209label.append("\\n");210}211};212}213}214}215216static class SimpleWriter extends JdepsWriter {217final PrintWriter writer;218SimpleWriter(PrintWriter writer, Analyzer.Type type,219boolean showProfile, boolean showModule) {220super(type, showProfile, showModule);221this.writer = writer;222}223224@Override225void generateOutput(Collection<Archive> archives, Analyzer analyzer) {226RawOutputFormatter depFormatter = new RawOutputFormatter(writer);227RawSummaryFormatter summaryFormatter = new RawSummaryFormatter(writer);228archives.stream()229.filter(analyzer::hasDependences)230.sorted(Comparator.comparing(Archive::getName))231.forEach(archive -> {232if (showModule && archive.getModule().isNamed() && type != SUMMARY) {233// print module-info except -summary234summaryFormatter.printModuleDescriptor(archive.getModule());235}236// print summary237analyzer.visitDependences(archive, summaryFormatter, SUMMARY);238239if (analyzer.hasDependences(archive) && type != SUMMARY) {240// print the class-level or package-level dependences241analyzer.visitDependences(archive, depFormatter);242}243});244}245246class RawOutputFormatter implements Analyzer.Visitor {247private final PrintWriter writer;248private String pkg = "";249250RawOutputFormatter(PrintWriter writer) {251this.writer = writer;252}253254@Override255public void visitDependence(String origin, Archive originArchive,256String target, Archive targetArchive) {257String tag = toTag(originArchive, target, targetArchive);258if (showModule || type == VERBOSE) {259writer.format(" %-50s -> %-50s %s%n", origin, target, tag);260} else {261if (!origin.equals(pkg)) {262pkg = origin;263writer.format(" %s (%s)%n", origin, originArchive.getName());264}265writer.format(" -> %-50s %s%n", target, tag);266}267}268}269270class RawSummaryFormatter implements Analyzer.Visitor {271private final PrintWriter writer;272273RawSummaryFormatter(PrintWriter writer) {274this.writer = writer;275}276277@Override278public void visitDependence(String origin, Archive originArchive,279String target, Archive targetArchive) {280281String targetName = targetArchive.getPathName();282if (targetArchive.getModule().isNamed()) {283targetName = targetArchive.getModule().name();284}285writer.format("%s -> %s", originArchive.getName(), targetName);286if (showProfile && targetArchive.getModule().isJDK()) {287writer.format(" (%s)", target);288}289writer.format("%n");290}291292public void printModuleDescriptor(Module module) {293if (!module.isNamed())294return;295296writer.format("%s%s%n", module.name(), module.isAutomatic() ? " automatic" : "");297writer.format(" [%s]%n", module.location());298module.descriptor().requires()299.stream()300.sorted(Comparator.comparing(Requires::name))301.forEach(req -> writer.format(" requires %s%n", req));302}303}304}305306/**307* If the given archive is JDK archive, this method returns the profile name308* only if -profile option is specified; it accesses a private JDK API and309* the returned value will have "JDK internal API" prefix310*311* For non-JDK archives, this method returns the file name of the archive.312*/313String toTag(Archive source, String name, Archive target) {314if (source == target || !target.getModule().isNamed() || Analyzer.notFound(target)) {315return target.getName();316}317318Module module = target.getModule();319String pn = name;320if ((type == CLASS || type == VERBOSE)) {321int i = name.lastIndexOf('.');322pn = i > 0 ? name.substring(0, i) : "";323}324325// exported API326if (module.isExported(pn) && !module.isJDKUnsupported()) {327return showProfileOrModule(module);328}329330// JDK internal API331if (!source.getModule().isJDK() && module.isJDK()){332return "JDK internal API (" + module.name() + ")";333}334335// qualified exports or inaccessible336boolean isExported = module.isExported(pn, source.getModule().name());337return module.name() + (isExported ? " (qualified)" : " (internal)");338}339340String showProfileOrModule(Module m) {341String tag = "";342if (showProfile) {343Profile p = Profile.getProfile(m);344if (p != null) {345tag = p.profileName();346}347} else if (showModule) {348tag = m.name();349}350return tag;351}352353Profile getProfile(String name) {354String pn = name;355if (type == CLASS || type == VERBOSE) {356int i = name.lastIndexOf('.');357pn = i > 0 ? name.substring(0, i) : "";358}359return Profile.getProfile(pn);360}361362}363364365