src/share/classes/com/sun/tools/jdeps/JdepsTask.java

changeset 0
959103a6100f
child 2525
2eb010b6cb22
equal deleted inserted replaced
-1:000000000000 0:959103a6100f
1 /*
2 * Copyright (c) 2012, 2013, 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 */
25 package com.sun.tools.jdeps;
26
27 import com.sun.tools.classfile.AccessFlags;
28 import com.sun.tools.classfile.ClassFile;
29 import com.sun.tools.classfile.ConstantPoolException;
30 import com.sun.tools.classfile.Dependencies;
31 import com.sun.tools.classfile.Dependencies.ClassFileError;
32 import com.sun.tools.classfile.Dependency;
33 import com.sun.tools.jdeps.PlatformClassPath.JDKArchive;
34 import java.io.*;
35 import java.nio.file.DirectoryStream;
36 import java.nio.file.Files;
37 import java.nio.file.Path;
38 import java.nio.file.Paths;
39 import java.text.MessageFormat;
40 import java.util.*;
41 import java.util.regex.Pattern;
42
43 /**
44 * Implementation for the jdeps tool for static class dependency analysis.
45 */
46 class JdepsTask {
47 static class BadArgs extends Exception {
48 static final long serialVersionUID = 8765093759964640721L;
49 BadArgs(String key, Object... args) {
50 super(JdepsTask.getMessage(key, args));
51 this.key = key;
52 this.args = args;
53 }
54
55 BadArgs showUsage(boolean b) {
56 showUsage = b;
57 return this;
58 }
59 final String key;
60 final Object[] args;
61 boolean showUsage;
62 }
63
64 static abstract class Option {
65 Option(boolean hasArg, String... aliases) {
66 this.hasArg = hasArg;
67 this.aliases = aliases;
68 }
69
70 boolean isHidden() {
71 return false;
72 }
73
74 boolean matches(String opt) {
75 for (String a : aliases) {
76 if (a.equals(opt))
77 return true;
78 if (hasArg && opt.startsWith(a + "="))
79 return true;
80 }
81 return false;
82 }
83
84 boolean ignoreRest() {
85 return false;
86 }
87
88 abstract void process(JdepsTask task, String opt, String arg) throws BadArgs;
89 final boolean hasArg;
90 final String[] aliases;
91 }
92
93 static abstract class HiddenOption extends Option {
94 HiddenOption(boolean hasArg, String... aliases) {
95 super(hasArg, aliases);
96 }
97
98 boolean isHidden() {
99 return true;
100 }
101 }
102
103 static Option[] recognizedOptions = {
104 new Option(false, "-h", "-?", "-help") {
105 void process(JdepsTask task, String opt, String arg) {
106 task.options.help = true;
107 }
108 },
109 new Option(true, "-dotoutput") {
110 void process(JdepsTask task, String opt, String arg) throws BadArgs {
111 Path p = Paths.get(arg);
112 if (Files.exists(p) && (!Files.isDirectory(p) || !Files.isWritable(p))) {
113 throw new BadArgs("err.dot.output.path", arg);
114 }
115 task.options.dotOutputDir = arg;
116 }
117 },
118 new Option(false, "-s", "-summary") {
119 void process(JdepsTask task, String opt, String arg) {
120 task.options.showSummary = true;
121 task.options.verbose = Analyzer.Type.SUMMARY;
122 }
123 },
124 new Option(false, "-v", "-verbose",
125 "-verbose:package",
126 "-verbose:class")
127 {
128 void process(JdepsTask task, String opt, String arg) throws BadArgs {
129 switch (opt) {
130 case "-v":
131 case "-verbose":
132 task.options.verbose = Analyzer.Type.VERBOSE;
133 break;
134 case "-verbose:package":
135 task.options.verbose = Analyzer.Type.PACKAGE;
136 break;
137 case "-verbose:class":
138 task.options.verbose = Analyzer.Type.CLASS;
139 break;
140 default:
141 throw new BadArgs("err.invalid.arg.for.option", opt);
142 }
143 }
144 },
145 new Option(true, "-cp", "-classpath") {
146 void process(JdepsTask task, String opt, String arg) {
147 task.options.classpath = arg;
148 }
149 },
150 new Option(true, "-p", "-package") {
151 void process(JdepsTask task, String opt, String arg) {
152 task.options.packageNames.add(arg);
153 }
154 },
155 new Option(true, "-e", "-regex") {
156 void process(JdepsTask task, String opt, String arg) {
157 task.options.regex = arg;
158 }
159 },
160 new Option(true, "-include") {
161 void process(JdepsTask task, String opt, String arg) throws BadArgs {
162 task.options.includePattern = Pattern.compile(arg);
163 }
164 },
165 new Option(false, "-P", "-profile") {
166 void process(JdepsTask task, String opt, String arg) throws BadArgs {
167 task.options.showProfile = true;
168 if (Profile.getProfileCount() == 0) {
169 throw new BadArgs("err.option.unsupported", opt, getMessage("err.profiles.msg"));
170 }
171 }
172 },
173 new Option(false, "-apionly") {
174 void process(JdepsTask task, String opt, String arg) {
175 task.options.apiOnly = true;
176 }
177 },
178 new Option(false, "-R", "-recursive") {
179 void process(JdepsTask task, String opt, String arg) {
180 task.options.depth = 0;
181 }
182 },
183 new Option(false, "-jdkinternals") {
184 void process(JdepsTask task, String opt, String arg) {
185 task.options.findJDKInternals = true;
186 task.options.verbose = Analyzer.Type.CLASS;
187 if (task.options.includePattern == null) {
188 task.options.includePattern = Pattern.compile(".*");
189 }
190 }
191 },
192 new Option(false, "-version") {
193 void process(JdepsTask task, String opt, String arg) {
194 task.options.version = true;
195 }
196 },
197 new HiddenOption(false, "-fullversion") {
198 void process(JdepsTask task, String opt, String arg) {
199 task.options.fullVersion = true;
200 }
201 },
202 new HiddenOption(false, "-showlabel") {
203 void process(JdepsTask task, String opt, String arg) {
204 task.options.showLabel = true;
205 }
206 },
207 new HiddenOption(true, "-depth") {
208 void process(JdepsTask task, String opt, String arg) throws BadArgs {
209 try {
210 task.options.depth = Integer.parseInt(arg);
211 } catch (NumberFormatException e) {
212 throw new BadArgs("err.invalid.arg.for.option", opt);
213 }
214 }
215 },
216 };
217
218 private static final String PROGNAME = "jdeps";
219 private final Options options = new Options();
220 private final List<String> classes = new ArrayList<String>();
221
222 private PrintWriter log;
223 void setLog(PrintWriter out) {
224 log = out;
225 }
226
227 /**
228 * Result codes.
229 */
230 static final int EXIT_OK = 0, // Completed with no errors.
231 EXIT_ERROR = 1, // Completed but reported errors.
232 EXIT_CMDERR = 2, // Bad command-line arguments
233 EXIT_SYSERR = 3, // System error or resource exhaustion.
234 EXIT_ABNORMAL = 4;// terminated abnormally
235
236 int run(String[] args) {
237 if (log == null) {
238 log = new PrintWriter(System.out);
239 }
240 try {
241 handleOptions(args);
242 if (options.help) {
243 showHelp();
244 }
245 if (options.version || options.fullVersion) {
246 showVersion(options.fullVersion);
247 }
248 if (classes.isEmpty() && options.includePattern == null) {
249 if (options.help || options.version || options.fullVersion) {
250 return EXIT_OK;
251 } else {
252 showHelp();
253 return EXIT_CMDERR;
254 }
255 }
256 if (options.regex != null && options.packageNames.size() > 0) {
257 showHelp();
258 return EXIT_CMDERR;
259 }
260 if (options.findJDKInternals &&
261 (options.regex != null || options.packageNames.size() > 0 || options.showSummary)) {
262 showHelp();
263 return EXIT_CMDERR;
264 }
265 if (options.showSummary && options.verbose != Analyzer.Type.SUMMARY) {
266 showHelp();
267 return EXIT_CMDERR;
268 }
269 boolean ok = run();
270 return ok ? EXIT_OK : EXIT_ERROR;
271 } catch (BadArgs e) {
272 reportError(e.key, e.args);
273 if (e.showUsage) {
274 log.println(getMessage("main.usage.summary", PROGNAME));
275 }
276 return EXIT_CMDERR;
277 } catch (IOException e) {
278 return EXIT_ABNORMAL;
279 } finally {
280 log.flush();
281 }
282 }
283
284 private final List<Archive> sourceLocations = new ArrayList<>();
285 private boolean run() throws IOException {
286 findDependencies();
287 Analyzer analyzer = new Analyzer(options.verbose);
288 analyzer.run(sourceLocations);
289 if (options.dotOutputDir != null) {
290 Path dir = Paths.get(options.dotOutputDir);
291 Files.createDirectories(dir);
292 generateDotFiles(dir, analyzer);
293 } else {
294 printRawOutput(log, analyzer);
295 }
296 return true;
297 }
298
299 private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException {
300 Path summary = dir.resolve("summary.dot");
301 boolean verbose = options.verbose == Analyzer.Type.VERBOSE;
302 DotGraph<?> graph = verbose ? new DotSummaryForPackage()
303 : new DotSummaryForArchive();
304 for (Archive archive : sourceLocations) {
305 analyzer.visitArchiveDependences(archive, graph);
306 if (verbose || options.showLabel) {
307 // traverse detailed dependences to generate package-level
308 // summary or build labels for edges
309 analyzer.visitDependences(archive, graph);
310 }
311 }
312 try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary))) {
313 graph.writeTo(sw);
314 }
315 // output individual .dot file for each archive
316 if (options.verbose != Analyzer.Type.SUMMARY) {
317 for (Archive archive : sourceLocations) {
318 if (analyzer.hasDependences(archive)) {
319 Path dotfile = dir.resolve(archive.getFileName() + ".dot");
320 try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile));
321 DotFileFormatter formatter = new DotFileFormatter(pw, archive)) {
322 analyzer.visitDependences(archive, formatter);
323 }
324 }
325 }
326 }
327 }
328
329 private void printRawOutput(PrintWriter writer, Analyzer analyzer) {
330 for (Archive archive : sourceLocations) {
331 RawOutputFormatter formatter = new RawOutputFormatter(writer);
332 analyzer.visitArchiveDependences(archive, formatter);
333 if (options.verbose != Analyzer.Type.SUMMARY) {
334 analyzer.visitDependences(archive, formatter);
335 }
336 }
337 }
338 private boolean isValidClassName(String name) {
339 if (!Character.isJavaIdentifierStart(name.charAt(0))) {
340 return false;
341 }
342 for (int i=1; i < name.length(); i++) {
343 char c = name.charAt(i);
344 if (c != '.' && !Character.isJavaIdentifierPart(c)) {
345 return false;
346 }
347 }
348 return true;
349 }
350
351 private Dependency.Filter getDependencyFilter() {
352 if (options.regex != null) {
353 return Dependencies.getRegexFilter(Pattern.compile(options.regex));
354 } else if (options.packageNames.size() > 0) {
355 return Dependencies.getPackageFilter(options.packageNames, false);
356 } else {
357 return new Dependency.Filter() {
358 @Override
359 public boolean accepts(Dependency dependency) {
360 return !dependency.getOrigin().equals(dependency.getTarget());
361 }
362 };
363 }
364 }
365
366 private boolean matches(String classname, AccessFlags flags) {
367 if (options.apiOnly && !flags.is(AccessFlags.ACC_PUBLIC)) {
368 return false;
369 } else if (options.includePattern != null) {
370 return options.includePattern.matcher(classname.replace('/', '.')).matches();
371 } else {
372 return true;
373 }
374 }
375
376 private void findDependencies() throws IOException {
377 Dependency.Finder finder =
378 options.apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED)
379 : Dependencies.getClassDependencyFinder();
380 Dependency.Filter filter = getDependencyFilter();
381
382 List<Archive> archives = new ArrayList<>();
383 Deque<String> roots = new LinkedList<>();
384 for (String s : classes) {
385 Path p = Paths.get(s);
386 if (Files.exists(p)) {
387 archives.add(new Archive(p, ClassFileReader.newInstance(p)));
388 } else {
389 if (isValidClassName(s)) {
390 roots.add(s);
391 } else {
392 warning("warn.invalid.arg", s);
393 }
394 }
395 }
396 sourceLocations.addAll(archives);
397
398 List<Archive> classpaths = new ArrayList<>(); // for class file lookup
399 classpaths.addAll(getClassPathArchives(options.classpath));
400 if (options.includePattern != null) {
401 archives.addAll(classpaths);
402 }
403 classpaths.addAll(PlatformClassPath.getArchives());
404
405 // add all classpath archives to the source locations for reporting
406 sourceLocations.addAll(classpaths);
407
408 // Work queue of names of classfiles to be searched.
409 // Entries will be unique, and for classes that do not yet have
410 // dependencies in the results map.
411 Deque<String> deque = new LinkedList<>();
412 Set<String> doneClasses = new HashSet<>();
413
414 // get the immediate dependencies of the input files
415 for (Archive a : archives) {
416 for (ClassFile cf : a.reader().getClassFiles()) {
417 String classFileName;
418 try {
419 classFileName = cf.getName();
420 } catch (ConstantPoolException e) {
421 throw new ClassFileError(e);
422 }
423
424 if (matches(classFileName, cf.access_flags)) {
425 if (!doneClasses.contains(classFileName)) {
426 doneClasses.add(classFileName);
427 }
428 for (Dependency d : finder.findDependencies(cf)) {
429 if (filter.accepts(d)) {
430 String cn = d.getTarget().getName();
431 if (!doneClasses.contains(cn) && !deque.contains(cn)) {
432 deque.add(cn);
433 }
434 a.addClass(d.getOrigin(), d.getTarget());
435 }
436 }
437 }
438 }
439 }
440
441 // add Archive for looking up classes from the classpath
442 // for transitive dependency analysis
443 Deque<String> unresolved = roots;
444 int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE;
445 do {
446 String name;
447 while ((name = unresolved.poll()) != null) {
448 if (doneClasses.contains(name)) {
449 continue;
450 }
451 ClassFile cf = null;
452 for (Archive a : classpaths) {
453 cf = a.reader().getClassFile(name);
454 if (cf != null) {
455 String classFileName;
456 try {
457 classFileName = cf.getName();
458 } catch (ConstantPoolException e) {
459 throw new ClassFileError(e);
460 }
461 if (!doneClasses.contains(classFileName)) {
462 // if name is a fully-qualified class name specified
463 // from command-line, this class might already be parsed
464 doneClasses.add(classFileName);
465 for (Dependency d : finder.findDependencies(cf)) {
466 if (depth == 0) {
467 // ignore the dependency
468 a.addClass(d.getOrigin());
469 break;
470 } else if (filter.accepts(d)) {
471 a.addClass(d.getOrigin(), d.getTarget());
472 String cn = d.getTarget().getName();
473 if (!doneClasses.contains(cn) && !deque.contains(cn)) {
474 deque.add(cn);
475 }
476 }
477 }
478 }
479 break;
480 }
481 }
482 if (cf == null) {
483 doneClasses.add(name);
484 }
485 }
486 unresolved = deque;
487 deque = new LinkedList<>();
488 } while (!unresolved.isEmpty() && depth-- > 0);
489 }
490
491 public void handleOptions(String[] args) throws BadArgs {
492 // process options
493 for (int i=0; i < args.length; i++) {
494 if (args[i].charAt(0) == '-') {
495 String name = args[i];
496 Option option = getOption(name);
497 String param = null;
498 if (option.hasArg) {
499 if (name.startsWith("-") && name.indexOf('=') > 0) {
500 param = name.substring(name.indexOf('=') + 1, name.length());
501 } else if (i + 1 < args.length) {
502 param = args[++i];
503 }
504 if (param == null || param.isEmpty() || param.charAt(0) == '-') {
505 throw new BadArgs("err.missing.arg", name).showUsage(true);
506 }
507 }
508 option.process(this, name, param);
509 if (option.ignoreRest()) {
510 i = args.length;
511 }
512 } else {
513 // process rest of the input arguments
514 for (; i < args.length; i++) {
515 String name = args[i];
516 if (name.charAt(0) == '-') {
517 throw new BadArgs("err.option.after.class", name).showUsage(true);
518 }
519 classes.add(name);
520 }
521 }
522 }
523 }
524
525 private Option getOption(String name) throws BadArgs {
526 for (Option o : recognizedOptions) {
527 if (o.matches(name)) {
528 return o;
529 }
530 }
531 throw new BadArgs("err.unknown.option", name).showUsage(true);
532 }
533
534 private void reportError(String key, Object... args) {
535 log.println(getMessage("error.prefix") + " " + getMessage(key, args));
536 }
537
538 private void warning(String key, Object... args) {
539 log.println(getMessage("warn.prefix") + " " + getMessage(key, args));
540 }
541
542 private void showHelp() {
543 log.println(getMessage("main.usage", PROGNAME));
544 for (Option o : recognizedOptions) {
545 String name = o.aliases[0].substring(1); // there must always be at least one name
546 name = name.charAt(0) == '-' ? name.substring(1) : name;
547 if (o.isHidden() || name.equals("h")) {
548 continue;
549 }
550 log.println(getMessage("main.opt." + name));
551 }
552 }
553
554 private void showVersion(boolean full) {
555 log.println(version(full ? "full" : "release"));
556 }
557
558 private String version(String key) {
559 // key=version: mm.nn.oo[-milestone]
560 // key=full: mm.mm.oo[-milestone]-build
561 if (ResourceBundleHelper.versionRB == null) {
562 return System.getProperty("java.version");
563 }
564 try {
565 return ResourceBundleHelper.versionRB.getString(key);
566 } catch (MissingResourceException e) {
567 return getMessage("version.unknown", System.getProperty("java.version"));
568 }
569 }
570
571 static String getMessage(String key, Object... args) {
572 try {
573 return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);
574 } catch (MissingResourceException e) {
575 throw new InternalError("Missing message: " + key);
576 }
577 }
578
579 private static class Options {
580 boolean help;
581 boolean version;
582 boolean fullVersion;
583 boolean showProfile;
584 boolean showSummary;
585 boolean wildcard;
586 boolean apiOnly;
587 boolean showLabel;
588 boolean findJDKInternals;
589 String dotOutputDir;
590 String classpath = "";
591 int depth = 1;
592 Analyzer.Type verbose = Analyzer.Type.PACKAGE;
593 Set<String> packageNames = new HashSet<>();
594 String regex; // apply to the dependences
595 Pattern includePattern; // apply to classes
596 }
597 private static class ResourceBundleHelper {
598 static final ResourceBundle versionRB;
599 static final ResourceBundle bundle;
600
601 static {
602 Locale locale = Locale.getDefault();
603 try {
604 bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale);
605 } catch (MissingResourceException e) {
606 throw new InternalError("Cannot find jdeps resource bundle for locale " + locale);
607 }
608 try {
609 versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version");
610 } catch (MissingResourceException e) {
611 throw new InternalError("version.resource.missing");
612 }
613 }
614 }
615
616 private List<Archive> getArchives(List<String> filenames) throws IOException {
617 List<Archive> result = new ArrayList<Archive>();
618 for (String s : filenames) {
619 Path p = Paths.get(s);
620 if (Files.exists(p)) {
621 result.add(new Archive(p, ClassFileReader.newInstance(p)));
622 } else {
623 warning("warn.file.not.exist", s);
624 }
625 }
626 return result;
627 }
628
629 private List<Archive> getClassPathArchives(String paths) throws IOException {
630 List<Archive> result = new ArrayList<>();
631 if (paths.isEmpty()) {
632 return result;
633 }
634 for (String p : paths.split(File.pathSeparator)) {
635 if (p.length() > 0) {
636 List<Path> files = new ArrayList<>();
637 // wildcard to parse all JAR files e.g. -classpath dir/*
638 int i = p.lastIndexOf(".*");
639 if (i > 0) {
640 Path dir = Paths.get(p.substring(0, i));
641 try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) {
642 for (Path entry : stream) {
643 files.add(entry);
644 }
645 }
646 } else {
647 files.add(Paths.get(p));
648 }
649 for (Path f : files) {
650 if (Files.exists(f)) {
651 result.add(new Archive(f, ClassFileReader.newInstance(f)));
652 }
653 }
654 }
655 }
656 return result;
657 }
658
659 /**
660 * If the given archive is JDK archive and non-null Profile,
661 * this method returns the profile name only if -profile option is specified;
662 * a null profile indicates it accesses a private JDK API and this method
663 * will return "JDK internal API".
664 *
665 * For non-JDK archives, this method returns the file name of the archive.
666 */
667 private String getProfileArchiveInfo(Archive source, Profile profile) {
668 if (options.showProfile && profile != null)
669 return profile.toString();
670
671 if (source instanceof JDKArchive) {
672 return profile == null ? "JDK internal API (" + source.getFileName() + ")" : "";
673 }
674 return source.getFileName();
675 }
676
677 /**
678 * Returns the profile name or "JDK internal API" for JDK archive;
679 * otherwise empty string.
680 */
681 private String profileName(Archive archive, Profile profile) {
682 if (archive instanceof JDKArchive) {
683 return Objects.toString(profile, "JDK internal API");
684 } else {
685 return "";
686 }
687 }
688
689 class RawOutputFormatter implements Analyzer.Visitor {
690 private final PrintWriter writer;
691 RawOutputFormatter(PrintWriter writer) {
692 this.writer = writer;
693 }
694
695 private String pkg = "";
696 @Override
697 public void visitDependence(String origin, Archive source,
698 String target, Archive archive, Profile profile) {
699 if (options.findJDKInternals &&
700 !(archive instanceof JDKArchive && profile == null)) {
701 // filter dependences other than JDK internal APIs
702 return;
703 }
704 if (options.verbose == Analyzer.Type.VERBOSE) {
705 writer.format(" %-50s -> %-50s %s%n",
706 origin, target, getProfileArchiveInfo(archive, profile));
707 } else {
708 if (!origin.equals(pkg)) {
709 pkg = origin;
710 writer.format(" %s (%s)%n", origin, source.getFileName());
711 }
712 writer.format(" -> %-50s %s%n",
713 target, getProfileArchiveInfo(archive, profile));
714 }
715 }
716
717 @Override
718 public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
719 writer.format("%s -> %s", origin.getPathName(), target.getPathName());
720 if (options.showProfile && profile != null) {
721 writer.format(" (%s)%n", profile);
722 } else {
723 writer.format("%n");
724 }
725 }
726 }
727
728 class DotFileFormatter extends DotGraph<String> implements AutoCloseable {
729 private final PrintWriter writer;
730 private final String name;
731 DotFileFormatter(PrintWriter writer, Archive archive) {
732 this.writer = writer;
733 this.name = archive.getFileName();
734 writer.format("digraph \"%s\" {%n", name);
735 writer.format(" // Path: %s%n", archive.getPathName());
736 }
737
738 @Override
739 public void close() {
740 writer.println("}");
741 }
742
743 @Override
744 public void visitDependence(String origin, Archive source,
745 String target, Archive archive, Profile profile) {
746 if (options.findJDKInternals &&
747 !(archive instanceof JDKArchive && profile == null)) {
748 // filter dependences other than JDK internal APIs
749 return;
750 }
751 // if -P option is specified, package name -> profile will
752 // be shown and filter out multiple same edges.
753 String name = getProfileArchiveInfo(archive, profile);
754 writeEdge(writer, new Edge(origin, target, getProfileArchiveInfo(archive, profile)));
755 }
756 @Override
757 public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
758 throw new UnsupportedOperationException();
759 }
760 }
761
762 class DotSummaryForArchive extends DotGraph<Archive> {
763 @Override
764 public void visitDependence(String origin, Archive source,
765 String target, Archive archive, Profile profile) {
766 Edge e = findEdge(source, archive);
767 assert e != null;
768 // add the dependency to the label if enabled and not compact1
769 if (profile == Profile.COMPACT1) {
770 return;
771 }
772 e.addLabel(origin, target, profileName(archive, profile));
773 }
774 @Override
775 public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
776 // add an edge with the archive's name with no tag
777 // so that there is only one node for each JDK archive
778 // while there may be edges to different profiles
779 Edge e = addEdge(origin, target, "");
780 if (target instanceof JDKArchive) {
781 // add a label to print the profile
782 if (profile == null) {
783 e.addLabel("JDK internal API");
784 } else if (options.showProfile && !options.showLabel) {
785 e.addLabel(profile.toString());
786 }
787 }
788 }
789 }
790
791 // DotSummaryForPackage generates the summary.dot file for verbose mode
792 // (-v or -verbose option) that includes all class dependencies.
793 // The summary.dot file shows package-level dependencies.
794 class DotSummaryForPackage extends DotGraph<String> {
795 private String packageOf(String cn) {
796 int i = cn.lastIndexOf('.');
797 return i > 0 ? cn.substring(0, i) : "<unnamed>";
798 }
799 @Override
800 public void visitDependence(String origin, Archive source,
801 String target, Archive archive, Profile profile) {
802 // add a package dependency edge
803 String from = packageOf(origin);
804 String to = packageOf(target);
805 Edge e = addEdge(from, to, getProfileArchiveInfo(archive, profile));
806
807 // add the dependency to the label if enabled and not compact1
808 if (!options.showLabel || profile == Profile.COMPACT1) {
809 return;
810 }
811
812 // trim the package name of origin to shorten the label
813 int i = origin.lastIndexOf('.');
814 String n1 = i < 0 ? origin : origin.substring(i+1);
815 e.addLabel(n1, target, profileName(archive, profile));
816 }
817 @Override
818 public void visitArchiveDependence(Archive origin, Archive target, Profile profile) {
819 // nop
820 }
821 }
822 abstract class DotGraph<T> implements Analyzer.Visitor {
823 private final Set<Edge> edges = new LinkedHashSet<>();
824 private Edge curEdge;
825 public void writeTo(PrintWriter writer) {
826 writer.format("digraph \"summary\" {%n");
827 for (Edge e: edges) {
828 writeEdge(writer, e);
829 }
830 writer.println("}");
831 }
832
833 void writeEdge(PrintWriter writer, Edge e) {
834 writer.format(" %-50s -> \"%s\"%s;%n",
835 String.format("\"%s\"", e.from.toString()),
836 e.tag.isEmpty() ? e.to
837 : String.format("%s (%s)", e.to, e.tag),
838 getLabel(e));
839 }
840
841 Edge addEdge(T origin, T target, String tag) {
842 Edge e = new Edge(origin, target, tag);
843 if (e.equals(curEdge)) {
844 return curEdge;
845 }
846
847 if (edges.contains(e)) {
848 for (Edge e1 : edges) {
849 if (e.equals(e1)) {
850 curEdge = e1;
851 }
852 }
853 } else {
854 edges.add(e);
855 curEdge = e;
856 }
857 return curEdge;
858 }
859
860 Edge findEdge(T origin, T target) {
861 for (Edge e : edges) {
862 if (e.from.equals(origin) && e.to.equals(target)) {
863 return e;
864 }
865 }
866 return null;
867 }
868
869 String getLabel(Edge e) {
870 String label = e.label.toString();
871 return label.isEmpty() ? "" : String.format("[label=\"%s\",fontsize=9]", label);
872 }
873
874 class Edge {
875 final T from;
876 final T to;
877 final String tag; // optional tag
878 final StringBuilder label = new StringBuilder();
879 Edge(T from, T to, String tag) {
880 this.from = from;
881 this.to = to;
882 this.tag = tag;
883 }
884 void addLabel(String s) {
885 label.append(s).append("\\n");
886 }
887 void addLabel(String origin, String target, String profile) {
888 label.append(origin).append(" -> ").append(target);
889 if (!profile.isEmpty()) {
890 label.append(" (" + profile + ")");
891 }
892 label.append("\\n");
893 }
894 @Override @SuppressWarnings("unchecked")
895 public boolean equals(Object o) {
896 if (o instanceof DotGraph<?>.Edge) {
897 DotGraph<?>.Edge e = (DotGraph<?>.Edge)o;
898 return this.from.equals(e.from) &&
899 this.to.equals(e.to) &&
900 this.tag.equals(e.tag);
901 }
902 return false;
903 }
904 @Override
905 public int hashCode() {
906 int hash = 7;
907 hash = 67 * hash + Objects.hashCode(this.from) +
908 Objects.hashCode(this.to) + Objects.hashCode(this.tag);
909 return hash;
910 }
911 }
912 }
913 }

mercurial