JdepsWriter.java revision 3294:9adfb22ff08f
1/* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25package com.sun.tools.jdeps; 26 27import java.io.IOException; 28import java.io.PrintWriter; 29import java.io.UncheckedIOException; 30import java.nio.file.Files; 31import java.nio.file.Path; 32import java.util.Collection; 33import java.util.HashMap; 34import java.util.Map; 35 36import static com.sun.tools.jdeps.Analyzer.Type.*; 37 38public abstract class JdepsWriter { 39 final Analyzer.Type type; 40 final boolean showProfile; 41 final boolean showModule; 42 43 JdepsWriter(Analyzer.Type type, boolean showProfile, boolean showModule) { 44 this.type = type; 45 this.showProfile = showProfile; 46 this.showModule = showModule; 47 } 48 49 abstract void generateOutput(Collection<Archive> archives, Analyzer analyzer) throws IOException; 50 51 public static class DotFileWriter extends JdepsWriter { 52 final boolean showLabel; 53 final Path outputDir; 54 DotFileWriter(Path dir, Analyzer.Type type, 55 boolean showProfile, boolean showModule, boolean showLabel) { 56 super(type, showProfile, showModule); 57 this.showLabel = showLabel; 58 this.outputDir = dir; 59 } 60 61 @Override 62 void generateOutput(Collection<Archive> archives, Analyzer analyzer) 63 throws IOException 64 { 65 // output individual .dot file for each archive 66 if (type != SUMMARY) { 67 archives.stream() 68 .filter(analyzer::hasDependences) 69 .forEach(archive -> { 70 Path dotfile = outputDir.resolve(archive.getName() + ".dot"); 71 try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile)); 72 DotFileFormatter formatter = new DotFileFormatter(pw, archive)) { 73 analyzer.visitDependences(archive, formatter); 74 } catch (IOException e) { 75 throw new UncheckedIOException(e); 76 } 77 }); 78 } 79 // generate summary dot file 80 generateSummaryDotFile(archives, analyzer); 81 } 82 83 private void generateSummaryDotFile(Collection<Archive> archives, Analyzer analyzer) 84 throws IOException 85 { 86 // If verbose mode (-v or -verbose option), 87 // the summary.dot file shows package-level dependencies. 88 Analyzer.Type summaryType = 89 (type == PACKAGE || type == SUMMARY) ? SUMMARY : PACKAGE; 90 Path summary = outputDir.resolve("summary.dot"); 91 try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary)); 92 SummaryDotFile dotfile = new SummaryDotFile(sw, summaryType)) { 93 for (Archive archive : archives) { 94 if (type == PACKAGE || type == SUMMARY) { 95 if (showLabel) { 96 // build labels listing package-level dependencies 97 analyzer.visitDependences(archive, dotfile.labelBuilder(), PACKAGE); 98 } 99 } 100 analyzer.visitDependences(archive, dotfile, summaryType); 101 } 102 } 103 } 104 105 class DotFileFormatter implements Analyzer.Visitor, AutoCloseable { 106 private final PrintWriter writer; 107 private final String name; 108 DotFileFormatter(PrintWriter writer, Archive archive) { 109 this.writer = writer; 110 this.name = archive.getName(); 111 writer.format("digraph \"%s\" {%n", name); 112 writer.format(" // Path: %s%n", archive.getPathName()); 113 } 114 115 @Override 116 public void close() { 117 writer.println("}"); 118 } 119 120 @Override 121 public void visitDependence(String origin, Archive originArchive, 122 String target, Archive targetArchive) { 123 String tag = toTag(originArchive, target, targetArchive); 124 writer.format(" %-50s -> \"%s\";%n", 125 String.format("\"%s\"", origin), 126 tag.isEmpty() ? target 127 : String.format("%s (%s)", target, tag)); 128 } 129 } 130 131 class SummaryDotFile implements Analyzer.Visitor, AutoCloseable { 132 private final PrintWriter writer; 133 private final Analyzer.Type type; 134 private final Map<Archive, Map<Archive,StringBuilder>> edges = new HashMap<>(); 135 SummaryDotFile(PrintWriter writer, Analyzer.Type type) { 136 this.writer = writer; 137 this.type = type; 138 writer.format("digraph \"summary\" {%n"); 139 } 140 141 @Override 142 public void close() { 143 writer.println("}"); 144 } 145 146 @Override 147 public void visitDependence(String origin, Archive originArchive, 148 String target, Archive targetArchive) { 149 150 String targetName = type == PACKAGE ? target : targetArchive.getName(); 151 if (targetArchive.getModule().isJDK()) { 152 Module m = (Module)targetArchive; 153 String n = showProfileOrModule(m); 154 if (!n.isEmpty()) { 155 targetName += " (" + n + ")"; 156 } 157 } else if (type == PACKAGE) { 158 targetName += " (" + targetArchive.getName() + ")"; 159 } 160 String label = getLabel(originArchive, targetArchive); 161 writer.format(" %-50s -> \"%s\"%s;%n", 162 String.format("\"%s\"", origin), targetName, label); 163 } 164 165 String getLabel(Archive origin, Archive target) { 166 if (edges.isEmpty()) 167 return ""; 168 169 StringBuilder label = edges.get(origin).get(target); 170 return label == null ? "" : String.format(" [label=\"%s\",fontsize=9]", label.toString()); 171 } 172 173 Analyzer.Visitor labelBuilder() { 174 // show the package-level dependencies as labels in the dot graph 175 return new Analyzer.Visitor() { 176 @Override 177 public void visitDependence(String origin, Archive originArchive, 178 String target, Archive targetArchive) 179 { 180 edges.putIfAbsent(originArchive, new HashMap<>()); 181 edges.get(originArchive).putIfAbsent(targetArchive, new StringBuilder()); 182 StringBuilder sb = edges.get(originArchive).get(targetArchive); 183 String tag = toTag(originArchive, target, targetArchive); 184 addLabel(sb, origin, target, tag); 185 } 186 187 void addLabel(StringBuilder label, String origin, String target, String tag) { 188 label.append(origin).append(" -> ").append(target); 189 if (!tag.isEmpty()) { 190 label.append(" (" + tag + ")"); 191 } 192 label.append("\\n"); 193 } 194 }; 195 } 196 } 197 } 198 199 static class SimpleWriter extends JdepsWriter { 200 final PrintWriter writer; 201 SimpleWriter(PrintWriter writer, Analyzer.Type type, 202 boolean showProfile, boolean showModule) { 203 super(type, showProfile, showModule); 204 this.writer = writer; 205 } 206 207 @Override 208 void generateOutput(Collection<Archive> archives, Analyzer analyzer) { 209 RawOutputFormatter depFormatter = new RawOutputFormatter(writer); 210 RawSummaryFormatter summaryFormatter = new RawSummaryFormatter(writer); 211 for (Archive archive : archives) { 212 // print summary 213 if (showModule && archive.getModule().isNamed()) { 214 summaryFormatter.showModuleRequires(archive.getModule()); 215 } else { 216 analyzer.visitDependences(archive, summaryFormatter, SUMMARY); 217 } 218 219 if (analyzer.hasDependences(archive) && type != SUMMARY) { 220 // print the class-level or package-level dependences 221 analyzer.visitDependences(archive, depFormatter); 222 } 223 } 224 } 225 226 class RawOutputFormatter implements Analyzer.Visitor { 227 private final PrintWriter writer; 228 private String pkg = ""; 229 230 RawOutputFormatter(PrintWriter writer) { 231 this.writer = writer; 232 } 233 234 @Override 235 public void visitDependence(String origin, Archive originArchive, 236 String target, Archive targetArchive) { 237 String tag = toTag(originArchive, target, targetArchive); 238 if (showModule || type == VERBOSE) { 239 writer.format(" %-50s -> %-50s %s%n", origin, target, tag); 240 } else { 241 if (!origin.equals(pkg)) { 242 pkg = origin; 243 writer.format(" %s (%s)%n", origin, originArchive.getName()); 244 } 245 writer.format(" -> %-50s %s%n", target, tag); 246 } 247 } 248 } 249 250 class RawSummaryFormatter implements Analyzer.Visitor { 251 private final PrintWriter writer; 252 253 RawSummaryFormatter(PrintWriter writer) { 254 this.writer = writer; 255 } 256 257 @Override 258 public void visitDependence(String origin, Archive originArchive, 259 String target, Archive targetArchive) { 260 261 String targetName = targetArchive.getPathName(); 262 if (targetArchive.getModule().isNamed()) { 263 targetName = targetArchive.getModule().name(); 264 } 265 writer.format("%s -> %s", originArchive.getName(), targetName); 266 if (showProfile && targetArchive.getModule().isJDK()) { 267 writer.format(" (%s)", target); 268 } 269 writer.format("%n"); 270 } 271 272 public void showModuleRequires(Module module) { 273 if (!module.isNamed()) 274 return; 275 276 writer.format("module %s", module.name()); 277 if (module.isAutomatic()) 278 writer.format(" (automatic)"); 279 writer.println(); 280 module.requires().keySet() 281 .stream() 282 .sorted() 283 .forEach(req -> writer.format(" requires %s%s%n", 284 module.requires.get(req) ? "public " : "", 285 req)); 286 } 287 } 288 } 289 290 /** 291 * If the given archive is JDK archive, this method returns the profile name 292 * only if -profile option is specified; it accesses a private JDK API and 293 * the returned value will have "JDK internal API" prefix 294 * 295 * For non-JDK archives, this method returns the file name of the archive. 296 */ 297 String toTag(Archive source, String name, Archive target) { 298 if (source == target || !target.getModule().isNamed()) { 299 return target.getName(); 300 } 301 302 Module module = target.getModule(); 303 String pn = name; 304 if ((type == CLASS || type == VERBOSE)) { 305 int i = name.lastIndexOf('.'); 306 pn = i > 0 ? name.substring(0, i) : ""; 307 } 308 309 // exported API 310 boolean jdkunsupported = Module.isJDKUnsupported(module, pn); 311 if (module.isExported(pn) && !jdkunsupported) { 312 return showProfileOrModule(module); 313 } 314 315 // JDK internal API 316 if (!source.getModule().isJDK() && module.isJDK()){ 317 return "JDK internal API (" + module.name() + ")"; 318 } 319 320 // qualified exports or inaccessible 321 boolean isExported = module.isExported(pn, source.getModule().name()); 322 return module.name() + (isExported ? " (qualified)" : " (internal)"); 323 } 324 325 String showProfileOrModule(Module m) { 326 String tag = ""; 327 if (showProfile) { 328 Profile p = Profile.getProfile(m); 329 if (p != null) { 330 tag = p.profileName(); 331 } 332 } else if (showModule) { 333 tag = m.name(); 334 } 335 return tag; 336 } 337 338 Profile getProfile(String name) { 339 String pn = name; 340 if (type == CLASS || type == VERBOSE) { 341 int i = name.lastIndexOf('.'); 342 pn = i > 0 ? name.substring(0, i) : ""; 343 } 344 return Profile.getProfile(pn); 345 } 346 347} 348