package com.sun.tools.jdeps;
import static com.sun.tools.jdeps.Analyzer.Type.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UncheckedIOException;
import java.lang.module.ModuleDescriptor.Requires;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public abstract class JdepsWriter {
public static JdepsWriter newDotWriter(Path outputdir, Analyzer.Type type) {
return new DotFileWriter(outputdir, type, false, true, false);
}
public static JdepsWriter newSimpleWriter(PrintWriter writer, Analyzer.Type type) {
return new SimpleWriter(writer, type, false, true);
}
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;
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
{
Files.createDirectories(outputDir);
if (type != SUMMARY && type != MODULE) {
archives.stream()
.filter(analyzer::hasDependences)
.forEach(archive -> {
Path path = archive.path().orElse(Paths.get(archive.getName()));
Path dotfile = outputDir.resolve(path.getFileName().toString() + ".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);
}
});
}
generateSummaryDotFile(archives, analyzer);
}
private void generateSummaryDotFile(Collection<Archive> archives, Analyzer analyzer)
throws IOException
{
boolean isSummary = type == PACKAGE || type == SUMMARY || type == MODULE;
Analyzer.Type summaryType = isSummary ? 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 (isSummary) {
if (showLabel) {
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() {
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);
archives.stream()
.filter(analyzer::hasDependences)
.sorted(Comparator.comparing(Archive::getName))
.forEach(archive -> {
if (showModule && archive.getModule().isNamed() && type != SUMMARY) {
summaryFormatter.printModuleDescriptor(archive.getModule());
}
analyzer.visitDependences(archive, summaryFormatter, SUMMARY);
if (analyzer.hasDependences(archive) && type != SUMMARY) {
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 printModuleDescriptor(Module module) {
if (!module.isNamed())
return;
writer.format("%s%s%n", module.name(), module.isAutomatic() ? " automatic" : "");
writer.format(" [%s]%n", module.location());
module.descriptor().requires()
.stream()
.sorted(Comparator.comparing(Requires::name))
.forEach(req -> writer.format(" requires %s%n", req));
}
}
}
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) : "";
}
if (module.isExported(pn) && !module.isJDKUnsupported()) {
return showProfileOrModule(module);
}
if (!source.getModule().isJDK() && module.isJDK()){
return "JDK internal API (" + module.name() + ")";
}
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);
}
}