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

changeset 1472
0c244701188e
child 1577
88286a36bb34
equal deleted inserted replaced
1468:690c41cdab55 1472:0c244701188e
1 /*
2 * Copyright (c) 2012, 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.ClassFile;
28 import com.sun.tools.classfile.ConstantPoolException;
29 import com.sun.tools.classfile.Dependencies;
30 import com.sun.tools.classfile.Dependencies.ClassFileError;
31 import com.sun.tools.classfile.Dependency;
32 import com.sun.tools.classfile.Dependency.Location;
33 import java.io.*;
34 import java.text.MessageFormat;
35 import java.util.*;
36 import java.util.regex.Pattern;
37
38 /**
39 * Implementation for the jdeps tool for static class dependency analysis.
40 */
41 class JdepsTask {
42 class BadArgs extends Exception {
43 static final long serialVersionUID = 8765093759964640721L;
44 BadArgs(String key, Object... args) {
45 super(JdepsTask.this.getMessage(key, args));
46 this.key = key;
47 this.args = args;
48 }
49
50 BadArgs showUsage(boolean b) {
51 showUsage = b;
52 return this;
53 }
54 final String key;
55 final Object[] args;
56 boolean showUsage;
57 }
58
59 static abstract class Option {
60 Option(boolean hasArg, String... aliases) {
61 this.hasArg = hasArg;
62 this.aliases = aliases;
63 }
64
65 boolean isHidden() {
66 return false;
67 }
68
69 boolean matches(String opt) {
70 for (String a : aliases) {
71 if (a.equals(opt)) {
72 return true;
73 } else if (opt.startsWith("--") && hasArg && opt.startsWith(a + "=")) {
74 return true;
75 }
76 }
77 return false;
78 }
79
80 boolean ignoreRest() {
81 return false;
82 }
83
84 abstract void process(JdepsTask task, String opt, String arg) throws BadArgs;
85 final boolean hasArg;
86 final String[] aliases;
87 }
88
89 static abstract class HiddenOption extends Option {
90 HiddenOption(boolean hasArg, String... aliases) {
91 super(hasArg, aliases);
92 }
93
94 boolean isHidden() {
95 return true;
96 }
97 }
98
99 static Option[] recognizedOptions = {
100 new Option(false, "-h", "-?", "--help") {
101 void process(JdepsTask task, String opt, String arg) {
102 task.options.help = true;
103 }
104 },
105 new Option(false, "-s", "--summary") {
106 void process(JdepsTask task, String opt, String arg) {
107 task.options.showSummary = true;
108 task.options.verbose = Options.Verbose.SUMMARY;
109 }
110 },
111 new Option(false, "-v", "--verbose") {
112 void process(JdepsTask task, String opt, String arg) {
113 task.options.verbose = Options.Verbose.VERBOSE;
114 }
115 },
116 new Option(true, "-V", "--verbose-level") {
117 void process(JdepsTask task, String opt, String arg) throws BadArgs {
118 switch (arg) {
119 case "package":
120 task.options.verbose = Options.Verbose.PACKAGE;
121 break;
122 case "class":
123 task.options.verbose = Options.Verbose.CLASS;
124 break;
125 default:
126 throw task.new BadArgs("err.invalid.arg.for.option", opt);
127 }
128 }
129 },
130 new Option(true, "-c", "--classpath") {
131 void process(JdepsTask task, String opt, String arg) {
132 task.options.classpath = arg;
133 }
134 },
135 new Option(true, "-p", "--package") {
136 void process(JdepsTask task, String opt, String arg) {
137 task.options.packageNames.add(arg);
138 }
139 },
140 new Option(true, "-e", "--regex") {
141 void process(JdepsTask task, String opt, String arg) {
142 task.options.regex = arg;
143 }
144 },
145 new Option(false, "-P", "--profile") {
146 void process(JdepsTask task, String opt, String arg) {
147 task.options.showProfile = true;
148 }
149 },
150 new Option(false, "-R", "--recursive") {
151 void process(JdepsTask task, String opt, String arg) {
152 task.options.depth = 0;
153 }
154 },
155 new HiddenOption(true, "-d", "--depth") {
156 void process(JdepsTask task, String opt, String arg) throws BadArgs {
157 try {
158 task.options.depth = Integer.parseInt(arg);
159 } catch (NumberFormatException e) {
160 throw task.new BadArgs("err.invalid.arg.for.option", opt);
161 }
162 }
163 },
164 new Option(false, "--version") {
165 void process(JdepsTask task, String opt, String arg) {
166 task.options.version = true;
167 }
168 },
169 new HiddenOption(false, "--fullversion") {
170 void process(JdepsTask task, String opt, String arg) {
171 task.options.fullVersion = true;
172 }
173 },
174
175 };
176
177 private static final String PROGNAME = "jdeps";
178 private final Options options = new Options();
179 private final List<String> classes = new ArrayList<String>();
180
181 private PrintWriter log;
182 void setLog(PrintWriter out) {
183 log = out;
184 }
185
186 /**
187 * Result codes.
188 */
189 static final int EXIT_OK = 0, // Completed with no errors.
190 EXIT_ERROR = 1, // Completed but reported errors.
191 EXIT_CMDERR = 2, // Bad command-line arguments
192 EXIT_SYSERR = 3, // System error or resource exhaustion.
193 EXIT_ABNORMAL = 4;// terminated abnormally
194
195 int run(String[] args) {
196 if (log == null) {
197 log = new PrintWriter(System.out);
198 }
199 try {
200 handleOptions(args);
201 if (options.help) {
202 showHelp();
203 }
204 if (options.version || options.fullVersion) {
205 showVersion(options.fullVersion);
206 }
207 if (classes.isEmpty() && !options.wildcard) {
208 if (options.help || options.version || options.fullVersion) {
209 return EXIT_OK;
210 } else {
211 showHelp();
212 return EXIT_CMDERR;
213 }
214 }
215 if (options.regex != null && options.packageNames.size() > 0) {
216 showHelp();
217 return EXIT_CMDERR;
218 }
219 if (options.showSummary && options.verbose != Options.Verbose.SUMMARY) {
220 showHelp();
221 return EXIT_CMDERR;
222 }
223 boolean ok = run();
224 return ok ? EXIT_OK : EXIT_ERROR;
225 } catch (BadArgs e) {
226 reportError(e.key, e.args);
227 if (e.showUsage) {
228 log.println(getMessage("main.usage.summary", PROGNAME));
229 }
230 return EXIT_CMDERR;
231 } catch (IOException e) {
232 return EXIT_ABNORMAL;
233 } finally {
234 log.flush();
235 }
236 }
237
238 private final List<Archive> sourceLocations = new ArrayList<Archive>();
239 private final Archive NOT_FOUND = new Archive(getMessage("artifact.not.found"));
240 private boolean run() throws IOException {
241 findDependencies();
242 switch (options.verbose) {
243 case VERBOSE:
244 case CLASS:
245 printClassDeps(log);
246 break;
247 case PACKAGE:
248 printPackageDeps(log);
249 break;
250 case SUMMARY:
251 for (Archive origin : sourceLocations) {
252 for (Archive target : origin.getRequiredArchives()) {
253 log.format("%-30s -> %s%n", origin, target);
254 }
255 }
256 break;
257 default:
258 throw new InternalError("Should not reach here");
259 }
260 return true;
261 }
262
263 private boolean isValidClassName(String name) {
264 if (!Character.isJavaIdentifierStart(name.charAt(0))) {
265 return false;
266 }
267 for (int i=1; i < name.length(); i++) {
268 char c = name.charAt(i);
269 if (c != '.' && !Character.isJavaIdentifierPart(c)) {
270 return false;
271 }
272 }
273 return true;
274 }
275
276 private void findDependencies() throws IOException {
277 Dependency.Finder finder = Dependencies.getClassDependencyFinder();
278 Dependency.Filter filter;
279 if (options.regex != null) {
280 filter = Dependencies.getRegexFilter(Pattern.compile(options.regex));
281 } else if (options.packageNames.size() > 0) {
282 filter = Dependencies.getPackageFilter(options.packageNames, false);
283 } else {
284 filter = new Dependency.Filter() {
285 public boolean accepts(Dependency dependency) {
286 return !dependency.getOrigin().equals(dependency.getTarget());
287 }
288 };
289 }
290
291 List<Archive> archives = new ArrayList<Archive>();
292 Deque<String> roots = new LinkedList<String>();
293 for (String s : classes) {
294 File f = new File(s);
295 if (f.exists()) {
296 archives.add(new Archive(f, ClassFileReader.newInstance(f)));
297 } else {
298 if (isValidClassName(s)) {
299 roots.add(s);
300 } else {
301 warning("warn.invalid.arg", s);
302 }
303 }
304 }
305
306 List<Archive> classpaths = new ArrayList<Archive>(); // for class file lookup
307 if (options.wildcard) {
308 // include all archives from classpath to the initial list
309 archives.addAll(getClassPathArchives(options.classpath));
310 } else {
311 classpaths.addAll(getClassPathArchives(options.classpath));
312 }
313 classpaths.addAll(PlatformClassPath.getArchives());
314
315 // add all archives to the source locations for reporting
316 sourceLocations.addAll(archives);
317 sourceLocations.addAll(classpaths);
318
319 // Work queue of names of classfiles to be searched.
320 // Entries will be unique, and for classes that do not yet have
321 // dependencies in the results map.
322 Deque<String> deque = new LinkedList<String>();
323 Set<String> doneClasses = new HashSet<String>();
324
325 // get the immediate dependencies of the input files
326 for (Archive a : archives) {
327 for (ClassFile cf : a.reader().getClassFiles()) {
328 String classFileName;
329 try {
330 classFileName = cf.getName();
331 } catch (ConstantPoolException e) {
332 throw new ClassFileError(e);
333 }
334 a.addClass(classFileName);
335 if (!doneClasses.contains(classFileName)) {
336 doneClasses.add(classFileName);
337 }
338 for (Dependency d : finder.findDependencies(cf)) {
339 if (filter.accepts(d)) {
340 String cn = d.getTarget().getName();
341 if (!doneClasses.contains(cn) && !deque.contains(cn)) {
342 deque.add(cn);
343 }
344 a.addDependency(d);
345 }
346 }
347 }
348 }
349
350 // add Archive for looking up classes from the classpath
351 // for transitive dependency analysis
352 Deque<String> unresolved = roots;
353 int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE;
354 do {
355 String name;
356 while ((name = unresolved.poll()) != null) {
357 if (doneClasses.contains(name)) {
358 continue;
359 }
360 ClassFile cf = null;
361 for (Archive a : classpaths) {
362 cf = a.reader().getClassFile(name);
363 if (cf != null) {
364 String classFileName;
365 try {
366 classFileName = cf.getName();
367 } catch (ConstantPoolException e) {
368 throw new ClassFileError(e);
369 }
370 a.addClass(classFileName);
371 if (!doneClasses.contains(classFileName)) {
372 // if name is a fully-qualified class name specified
373 // from command-line, this class might already be parsed
374 doneClasses.add(classFileName);
375 if (depth > 0) {
376 for (Dependency d : finder.findDependencies(cf)) {
377 if (filter.accepts(d)) {
378 String cn = d.getTarget().getName();
379 if (!doneClasses.contains(cn) && !deque.contains(cn)) {
380 deque.add(cn);
381 }
382 a.addDependency(d);
383 }
384 }
385 }
386 }
387 break;
388 }
389 }
390 if (cf == null) {
391 NOT_FOUND.addClass(name);
392 }
393 }
394 unresolved = deque;
395 deque = new LinkedList<String>();
396 } while (!unresolved.isEmpty() && depth-- > 0);
397 }
398
399 private void printPackageDeps(PrintWriter out) {
400 for (Archive source : sourceLocations) {
401 SortedMap<Location, SortedSet<Location>> deps = source.getDependencies();
402 if (deps.isEmpty())
403 continue;
404
405 for (Archive target : source.getRequiredArchives()) {
406 out.format("%s -> %s%n", source, target);
407 }
408
409 Map<String, Archive> pkgs = new TreeMap<String, Archive>();
410 SortedMap<String, Archive> targets = new TreeMap<String, Archive>();
411 String pkg = "";
412 for (Map.Entry<Location, SortedSet<Location>> e : deps.entrySet()) {
413 String cn = e.getKey().getClassName();
414 String p = packageOf(e.getKey());
415 Archive origin = Archive.find(e.getKey());
416 assert origin != null;
417 if (!pkgs.containsKey(p)) {
418 pkgs.put(p, origin);
419 } else if (pkgs.get(p) != origin) {
420 warning("warn.split.package", p, origin, pkgs.get(p));
421 }
422
423 if (!p.equals(pkg)) {
424 printTargets(out, targets);
425 pkg = p;
426 targets.clear();
427 out.format(" %s (%s)%n", p, origin.getFileName());
428 }
429
430 for (Location t : e.getValue()) {
431 p = packageOf(t);
432 Archive target = Archive.find(t);
433 if (!targets.containsKey(p)) {
434 targets.put(p, target);
435 }
436 }
437 }
438 printTargets(out, targets);
439 out.println();
440 }
441 }
442
443 private void printTargets(PrintWriter out, Map<String, Archive> targets) {
444 for (Map.Entry<String, Archive> t : targets.entrySet()) {
445 String pn = t.getKey();
446 out.format(" -> %-40s %s%n", pn, getPackageInfo(pn, t.getValue()));
447 }
448 }
449
450 private String getPackageInfo(String pn, Archive source) {
451 if (PlatformClassPath.contains(source)) {
452 String name = PlatformClassPath.getProfileName(pn);
453 if (name.isEmpty()) {
454 return "JDK internal API (" + source.getFileName() + ")";
455 }
456 return options.showProfile ? name : "";
457 }
458 return source.getFileName();
459 }
460
461 private static String packageOf(Location loc) {
462 String pkg = loc.getPackageName();
463 return pkg.isEmpty() ? "<unnamed>" : pkg;
464 }
465
466 private void printClassDeps(PrintWriter out) {
467 for (Archive source : sourceLocations) {
468 SortedMap<Location, SortedSet<Location>> deps = source.getDependencies();
469 if (deps.isEmpty())
470 continue;
471
472 for (Archive target : source.getRequiredArchives()) {
473 out.format("%s -> %s%n", source, target);
474 }
475 out.format("%s%n", source);
476 for (Map.Entry<Location, SortedSet<Location>> e : deps.entrySet()) {
477 String cn = e.getKey().getClassName();
478 Archive origin = Archive.find(e.getKey());
479 out.format(" %s (%s)%n", cn, origin.getFileName());
480 for (Location t : e.getValue()) {
481 cn = t.getClassName();
482 Archive target = Archive.find(t);
483 out.format(" -> %-60s %s%n", cn, getPackageInfo(t.getPackageName(), target));
484 }
485 }
486 out.println();
487 }
488 }
489 public void handleOptions(String[] args) throws BadArgs {
490 // process options
491 for (int i=0; i < args.length; i++) {
492 if (args[i].charAt(0) == '-') {
493 String name = args[i];
494 Option option = getOption(name);
495 String param = null;
496 if (option.hasArg) {
497 if (name.startsWith("--") && name.indexOf('=') > 0) {
498 param = name.substring(name.indexOf('=') + 1, name.length());
499 } else if (i + 1 < args.length) {
500 param = args[++i];
501 }
502 if (param == null || param.isEmpty() || param.charAt(0) == '-') {
503 throw new BadArgs("err.missing.arg", name).showUsage(true);
504 }
505 }
506 option.process(this, name, param);
507 if (option.ignoreRest()) {
508 i = args.length;
509 }
510 } else {
511 // process rest of the input arguments
512 for (; i < args.length; i++) {
513 String name = args[i];
514 if (name.charAt(0) == '-') {
515 throw new BadArgs("err.option.after.class", name).showUsage(true);
516 }
517 if (name.equals("*") || name.equals("\"*\"")) {
518 options.wildcard = true;
519 } else {
520 classes.add(name);
521 }
522 }
523 }
524 }
525 }
526
527 private Option getOption(String name) throws BadArgs {
528 for (Option o : recognizedOptions) {
529 if (o.matches(name)) {
530 return o;
531 }
532 }
533 throw new BadArgs("err.unknown.option", name).showUsage(true);
534 }
535
536 private void reportError(String key, Object... args) {
537 log.println(getMessage("error.prefix") + " " + getMessage(key, args));
538 }
539
540 private void warning(String key, Object... args) {
541 log.println(getMessage("warn.prefix") + " " + getMessage(key, args));
542 }
543
544 private void showHelp() {
545 log.println(getMessage("main.usage", PROGNAME));
546 for (Option o : recognizedOptions) {
547 String name = o.aliases[0].substring(1); // there must always be at least one name
548 name = name.charAt(0) == '-' ? name.substring(1) : name;
549 if (o.isHidden() || name.equals("h")) {
550 continue;
551 }
552 log.println(getMessage("main.opt." + name));
553 }
554 }
555
556 private void showVersion(boolean full) {
557 log.println(version(full ? "full" : "release"));
558 }
559
560 private String version(String key) {
561 // key=version: mm.nn.oo[-milestone]
562 // key=full: mm.mm.oo[-milestone]-build
563 if (ResourceBundleHelper.versionRB == null) {
564 return System.getProperty("java.version");
565 }
566 try {
567 return ResourceBundleHelper.versionRB.getString(key);
568 } catch (MissingResourceException e) {
569 return getMessage("version.unknown", System.getProperty("java.version"));
570 }
571 }
572
573 public String getMessage(String key, Object... args) {
574 try {
575 return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);
576 } catch (MissingResourceException e) {
577 throw new InternalError("Missing message: " + key);
578 }
579 }
580
581 private static class Options {
582 enum Verbose {
583 CLASS,
584 PACKAGE,
585 SUMMARY,
586 VERBOSE
587 };
588
589 boolean help;
590 boolean version;
591 boolean fullVersion;
592 boolean showFlags;
593 boolean showProfile;
594 boolean showSummary;
595 boolean wildcard;
596 String regex;
597 String classpath = "";
598 int depth = 1;
599 Verbose verbose = Verbose.PACKAGE;
600 Set<String> packageNames = new HashSet<String>();
601 }
602
603 private static class ResourceBundleHelper {
604 static final ResourceBundle versionRB;
605 static final ResourceBundle bundle;
606
607 static {
608 Locale locale = Locale.getDefault();
609 try {
610 bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale);
611 } catch (MissingResourceException e) {
612 throw new InternalError("Cannot find jdeps resource bundle for locale " + locale);
613 }
614 try {
615 versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version");
616 } catch (MissingResourceException e) {
617 throw new InternalError("version.resource.missing");
618 }
619 }
620 }
621
622 private List<Archive> getArchives(List<String> filenames) throws IOException {
623 List<Archive> result = new ArrayList<Archive>();
624 for (String s : filenames) {
625 File f = new File(s);
626 if (f.exists()) {
627 result.add(new Archive(f, ClassFileReader.newInstance(f)));
628 } else {
629 warning("warn.file.not.exist", s);
630 }
631 }
632 return result;
633 }
634
635 private List<Archive> getClassPathArchives(String paths) throws IOException {
636 List<Archive> result = new ArrayList<Archive>();
637 if (paths.isEmpty()) {
638 return result;
639 }
640 for (String p : paths.split(File.pathSeparator)) {
641 if (p.length() > 0) {
642 File f = new File(p);
643 if (f.exists()) {
644 result.add(new Archive(f, ClassFileReader.newInstance(f)));
645 }
646 }
647 }
648 return result;
649 }
650 }

mercurial