blob: 06449fa5480459b4f7b3e39af74963e9dfa353b0 [file] [log] [blame]
/*
* Copyright (c) 2015, 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 java.io.IOException;
import java.io.PrintWriter;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import static com.sun.tools.jdeps.Analyzer.Type.*;
public abstract class JdepsWriter {
final Analyzer.Type type;
final boolean showProfile;
final boolean showModule;
JdepsWriter(Analyzer.Type type, boolean showProfile, boolean showModule) {
this.type = type;
this.showProfile = showProfile;
this.showModule = showModule;
}
abstract void generateOutput(Collection<Archive> archives, Analyzer analyzer) throws IOException;
public static class DotFileWriter extends JdepsWriter {
final boolean showLabel;
final Path outputDir;
DotFileWriter(Path dir, Analyzer.Type type,
boolean showProfile, boolean showModule, boolean showLabel) {
super(type, showProfile, showModule);
this.showLabel = showLabel;
this.outputDir = dir;
}
@Override
void generateOutput(Collection<Archive> archives, Analyzer analyzer)
throws IOException
{
// output individual .dot file for each archive
if (type != SUMMARY) {
archives.stream()
.filter(analyzer::hasDependences)
.forEach(archive -> {
Path dotfile = outputDir.resolve(archive.getName() + ".dot");
try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile));
DotFileFormatter formatter = new DotFileFormatter(pw, archive)) {
analyzer.visitDependences(archive, formatter);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
// generate summary dot file
generateSummaryDotFile(archives, analyzer);
}
private void generateSummaryDotFile(Collection<Archive> archives, Analyzer analyzer)
throws IOException
{
// If verbose mode (-v or -verbose option),
// the summary.dot file shows package-level dependencies.
Analyzer.Type summaryType =
(type == PACKAGE || type == SUMMARY) ? SUMMARY : PACKAGE;
Path summary = outputDir.resolve("summary.dot");
try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary));
SummaryDotFile dotfile = new SummaryDotFile(sw, summaryType)) {
for (Archive archive : archives) {
if (type == PACKAGE || type == SUMMARY) {
if (showLabel) {
// build labels listing package-level dependencies
analyzer.visitDependences(archive, dotfile.labelBuilder(), PACKAGE);
}
}
analyzer.visitDependences(archive, dotfile, summaryType);
}
}
}
class DotFileFormatter implements Analyzer.Visitor, AutoCloseable {
private final PrintWriter writer;
private final String name;
DotFileFormatter(PrintWriter writer, Archive archive) {
this.writer = writer;
this.name = archive.getName();
writer.format("digraph \"%s\" {%n", name);
writer.format(" // Path: %s%n", archive.getPathName());
}
@Override
public void close() {
writer.println("}");
}
@Override
public void visitDependence(String origin, Archive originArchive,
String target, Archive targetArchive) {
String tag = toTag(originArchive, target, targetArchive);
writer.format(" %-50s -> \"%s\";%n",
String.format("\"%s\"", origin),
tag.isEmpty() ? target
: String.format("%s (%s)", target, tag));
}
}
class SummaryDotFile implements Analyzer.Visitor, AutoCloseable {
private final PrintWriter writer;
private final Analyzer.Type type;
private final Map<Archive, Map<Archive,StringBuilder>> edges = new HashMap<>();
SummaryDotFile(PrintWriter writer, Analyzer.Type type) {
this.writer = writer;
this.type = type;
writer.format("digraph \"summary\" {%n");
}
@Override
public void close() {
writer.println("}");
}
@Override
public void visitDependence(String origin, Archive originArchive,
String target, Archive targetArchive) {
String targetName = type == PACKAGE ? target : targetArchive.getName();
if (targetArchive.getModule().isJDK()) {
Module m = (Module)targetArchive;
String n = showProfileOrModule(m);
if (!n.isEmpty()) {
targetName += " (" + n + ")";
}
} else if (type == PACKAGE) {
targetName += " (" + targetArchive.getName() + ")";
}
String label = getLabel(originArchive, targetArchive);
writer.format(" %-50s -> \"%s\"%s;%n",
String.format("\"%s\"", origin), targetName, label);
}
String getLabel(Archive origin, Archive target) {
if (edges.isEmpty())
return "";
StringBuilder label = edges.get(origin).get(target);
return label == null ? "" : String.format(" [label=\"%s\",fontsize=9]", label.toString());
}
Analyzer.Visitor labelBuilder() {
// show the package-level dependencies as labels in the dot graph
return new Analyzer.Visitor() {
@Override
public void visitDependence(String origin, Archive originArchive,
String target, Archive targetArchive)
{
edges.putIfAbsent(originArchive, new HashMap<>());
edges.get(originArchive).putIfAbsent(targetArchive, new StringBuilder());
StringBuilder sb = edges.get(originArchive).get(targetArchive);
String tag = toTag(originArchive, target, targetArchive);
addLabel(sb, origin, target, tag);
}
void addLabel(StringBuilder label, String origin, String target, String tag) {
label.append(origin).append(" -> ").append(target);
if (!tag.isEmpty()) {
label.append(" (" + tag + ")");
}
label.append("\\n");
}
};
}
}
}
static class SimpleWriter extends JdepsWriter {
final PrintWriter writer;
SimpleWriter(PrintWriter writer, Analyzer.Type type,
boolean showProfile, boolean showModule) {
super(type, showProfile, showModule);
this.writer = writer;
}
@Override
void generateOutput(Collection<Archive> archives, Analyzer analyzer) {
RawOutputFormatter depFormatter = new RawOutputFormatter(writer);
RawSummaryFormatter summaryFormatter = new RawSummaryFormatter(writer);
for (Archive archive : archives) {
// print summary
if (showModule && archive.getModule().isNamed()) {
summaryFormatter.showModuleRequires(archive.getModule());
} else {
analyzer.visitDependences(archive, summaryFormatter, SUMMARY);
}
if (analyzer.hasDependences(archive) && type != SUMMARY) {
// print the class-level or package-level dependences
analyzer.visitDependences(archive, depFormatter);
}
}
}
class RawOutputFormatter implements Analyzer.Visitor {
private final PrintWriter writer;
private String pkg = "";
RawOutputFormatter(PrintWriter writer) {
this.writer = writer;
}
@Override
public void visitDependence(String origin, Archive originArchive,
String target, Archive targetArchive) {
String tag = toTag(originArchive, target, targetArchive);
if (showModule || type == VERBOSE) {
writer.format(" %-50s -> %-50s %s%n", origin, target, tag);
} else {
if (!origin.equals(pkg)) {
pkg = origin;
writer.format(" %s (%s)%n", origin, originArchive.getName());
}
writer.format(" -> %-50s %s%n", target, tag);
}
}
}
class RawSummaryFormatter implements Analyzer.Visitor {
private final PrintWriter writer;
RawSummaryFormatter(PrintWriter writer) {
this.writer = writer;
}
@Override
public void visitDependence(String origin, Archive originArchive,
String target, Archive targetArchive) {
String targetName = targetArchive.getPathName();
if (targetArchive.getModule().isNamed()) {
targetName = targetArchive.getModule().name();
}
writer.format("%s -> %s", originArchive.getName(), targetName);
if (showProfile && targetArchive.getModule().isJDK()) {
writer.format(" (%s)", target);
}
writer.format("%n");
}
public void showModuleRequires(Module module) {
if (!module.isNamed())
return;
writer.format("module %s", module.name());
if (module.isAutomatic())
writer.format(" (automatic)");
writer.println();
module.requires().keySet()
.stream()
.sorted()
.forEach(req -> writer.format(" requires %s%s%n",
module.requires.get(req) ? "public " : "",
req));
}
}
}
/**
* If the given archive is JDK archive, this method returns the profile name
* only if -profile option is specified; it accesses a private JDK API and
* the returned value will have "JDK internal API" prefix
*
* For non-JDK archives, this method returns the file name of the archive.
*/
String toTag(Archive source, String name, Archive target) {
if (source == target || !target.getModule().isNamed()) {
return target.getName();
}
Module module = target.getModule();
String pn = name;
if ((type == CLASS || type == VERBOSE)) {
int i = name.lastIndexOf('.');
pn = i > 0 ? name.substring(0, i) : "";
}
// exported API
boolean jdkunsupported = Module.isJDKUnsupported(module, pn);
if (module.isExported(pn) && !jdkunsupported) {
return showProfileOrModule(module);
}
// JDK internal API
if (!source.getModule().isJDK() && module.isJDK()){
return "JDK internal API (" + module.name() + ")";
}
// qualified exports or inaccessible
boolean isExported = module.isExported(pn, source.getModule().name());
return module.name() + (isExported ? " (qualified)" : " (internal)");
}
String showProfileOrModule(Module m) {
String tag = "";
if (showProfile) {
Profile p = Profile.getProfile(m);
if (p != null) {
tag = p.profileName();
}
} else if (showModule) {
tag = m.name();
}
return tag;
}
Profile getProfile(String name) {
String pn = name;
if (type == CLASS || type == VERBOSE) {
int i = name.lastIndexOf('.');
pn = i > 0 ? name.substring(0, i) : "";
}
return Profile.getProfile(pn);
}
}