Tue, 17 Sep 2013 14:17:13 -0700
8024538: -Xdoclint + -Xprefer:source + incremental compilation == FAIL
Reviewed-by: darcy
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;
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 java.io.*;
33 import java.text.MessageFormat;
34 import java.util.*;
35 import java.util.regex.Pattern;
37 /**
38 * Implementation for the jdeps tool for static class dependency analysis.
39 */
40 class JdepsTask {
41 static class BadArgs extends Exception {
42 static final long serialVersionUID = 8765093759964640721L;
43 BadArgs(String key, Object... args) {
44 super(JdepsTask.getMessage(key, args));
45 this.key = key;
46 this.args = args;
47 }
49 BadArgs showUsage(boolean b) {
50 showUsage = b;
51 return this;
52 }
53 final String key;
54 final Object[] args;
55 boolean showUsage;
56 }
58 static abstract class Option {
59 Option(boolean hasArg, String... aliases) {
60 this.hasArg = hasArg;
61 this.aliases = aliases;
62 }
64 boolean isHidden() {
65 return false;
66 }
68 boolean matches(String opt) {
69 for (String a : aliases) {
70 if (a.equals(opt)) {
71 return true;
72 } else if (opt.startsWith("--") && hasArg && opt.startsWith(a + "=")) {
73 return true;
74 }
75 }
76 return false;
77 }
79 boolean ignoreRest() {
80 return false;
81 }
83 abstract void process(JdepsTask task, String opt, String arg) throws BadArgs;
84 final boolean hasArg;
85 final String[] aliases;
86 }
88 static abstract class HiddenOption extends Option {
89 HiddenOption(boolean hasArg, String... aliases) {
90 super(hasArg, aliases);
91 }
93 boolean isHidden() {
94 return true;
95 }
96 }
98 static Option[] recognizedOptions = {
99 new Option(false, "-h", "-?", "--help") {
100 void process(JdepsTask task, String opt, String arg) {
101 task.options.help = true;
102 }
103 },
104 new Option(false, "-s", "--summary") {
105 void process(JdepsTask task, String opt, String arg) {
106 task.options.showSummary = true;
107 task.options.verbose = Analyzer.Type.SUMMARY;
108 }
109 },
110 new Option(false, "-v", "--verbose") {
111 void process(JdepsTask task, String opt, String arg) {
112 task.options.verbose = Analyzer.Type.VERBOSE;
113 }
114 },
115 new Option(true, "-V", "--verbose-level") {
116 void process(JdepsTask task, String opt, String arg) throws BadArgs {
117 if ("package".equals(arg)) {
118 task.options.verbose = Analyzer.Type.PACKAGE;
119 } else if ("class".equals(arg)) {
120 task.options.verbose = Analyzer.Type.CLASS;
121 } else {
122 throw new BadArgs("err.invalid.arg.for.option", opt);
123 }
124 }
125 },
126 new Option(true, "-c", "--classpath") {
127 void process(JdepsTask task, String opt, String arg) {
128 task.options.classpath = arg;
129 }
130 },
131 new Option(true, "-p", "--package") {
132 void process(JdepsTask task, String opt, String arg) {
133 task.options.packageNames.add(arg);
134 }
135 },
136 new Option(true, "-e", "--regex") {
137 void process(JdepsTask task, String opt, String arg) {
138 task.options.regex = arg;
139 }
140 },
141 new Option(false, "-P", "--profile") {
142 void process(JdepsTask task, String opt, String arg) throws BadArgs {
143 task.options.showProfile = true;
144 if (Profiles.getProfileCount() == 0) {
145 throw new BadArgs("err.option.unsupported", opt, getMessage("err.profiles.msg"));
146 }
147 }
148 },
149 new Option(false, "-R", "--recursive") {
150 void process(JdepsTask task, String opt, String arg) {
151 task.options.depth = 0;
152 }
153 },
154 new HiddenOption(true, "-d", "--depth") {
155 void process(JdepsTask task, String opt, String arg) throws BadArgs {
156 try {
157 task.options.depth = Integer.parseInt(arg);
158 } catch (NumberFormatException e) {
159 throw new BadArgs("err.invalid.arg.for.option", opt);
160 }
161 }
162 },
163 new Option(false, "--version") {
164 void process(JdepsTask task, String opt, String arg) {
165 task.options.version = true;
166 }
167 },
168 new HiddenOption(false, "--fullversion") {
169 void process(JdepsTask task, String opt, String arg) {
170 task.options.fullVersion = true;
171 }
172 },
173 };
175 private static final String PROGNAME = "jdeps";
176 private final Options options = new Options();
177 private final List<String> classes = new ArrayList<String>();
179 private PrintWriter log;
180 void setLog(PrintWriter out) {
181 log = out;
182 }
184 /**
185 * Result codes.
186 */
187 static final int EXIT_OK = 0, // Completed with no errors.
188 EXIT_ERROR = 1, // Completed but reported errors.
189 EXIT_CMDERR = 2, // Bad command-line arguments
190 EXIT_SYSERR = 3, // System error or resource exhaustion.
191 EXIT_ABNORMAL = 4;// terminated abnormally
193 int run(String[] args) {
194 if (log == null) {
195 log = new PrintWriter(System.out);
196 }
197 try {
198 handleOptions(args);
199 if (options.help) {
200 showHelp();
201 }
202 if (options.version || options.fullVersion) {
203 showVersion(options.fullVersion);
204 }
205 if (classes.isEmpty() && !options.wildcard) {
206 if (options.help || options.version || options.fullVersion) {
207 return EXIT_OK;
208 } else {
209 showHelp();
210 return EXIT_CMDERR;
211 }
212 }
213 if (options.regex != null && options.packageNames.size() > 0) {
214 showHelp();
215 return EXIT_CMDERR;
216 }
217 if (options.showSummary && options.verbose != Analyzer.Type.SUMMARY) {
218 showHelp();
219 return EXIT_CMDERR;
220 }
221 boolean ok = run();
222 return ok ? EXIT_OK : EXIT_ERROR;
223 } catch (BadArgs e) {
224 reportError(e.key, e.args);
225 if (e.showUsage) {
226 log.println(getMessage("main.usage.summary", PROGNAME));
227 }
228 return EXIT_CMDERR;
229 } catch (IOException e) {
230 return EXIT_ABNORMAL;
231 } finally {
232 log.flush();
233 }
234 }
236 private final List<Archive> sourceLocations = new ArrayList<Archive>();
237 private boolean run() throws IOException {
238 findDependencies();
239 Analyzer analyzer = new Analyzer(options.verbose);
240 analyzer.run(sourceLocations);
241 if (options.verbose == Analyzer.Type.SUMMARY) {
242 printSummary(log, analyzer);
243 } else {
244 printDependencies(log, analyzer);
245 }
246 return true;
247 }
249 private boolean isValidClassName(String name) {
250 if (!Character.isJavaIdentifierStart(name.charAt(0))) {
251 return false;
252 }
253 for (int i=1; i < name.length(); i++) {
254 char c = name.charAt(i);
255 if (c != '.' && !Character.isJavaIdentifierPart(c)) {
256 return false;
257 }
258 }
259 return true;
260 }
262 private void findDependencies() throws IOException {
263 Dependency.Finder finder = Dependencies.getClassDependencyFinder();
264 Dependency.Filter filter;
265 if (options.regex != null) {
266 filter = Dependencies.getRegexFilter(Pattern.compile(options.regex));
267 } else if (options.packageNames.size() > 0) {
268 filter = Dependencies.getPackageFilter(options.packageNames, false);
269 } else {
270 filter = new Dependency.Filter() {
271 public boolean accepts(Dependency dependency) {
272 return !dependency.getOrigin().equals(dependency.getTarget());
273 }
274 };
275 }
277 List<Archive> archives = new ArrayList<Archive>();
278 Deque<String> roots = new LinkedList<String>();
279 for (String s : classes) {
280 File f = new File(s);
281 if (f.exists()) {
282 archives.add(new Archive(f, ClassFileReader.newInstance(f)));
283 } else {
284 if (isValidClassName(s)) {
285 roots.add(s);
286 } else {
287 warning("warn.invalid.arg", s);
288 }
289 }
290 }
292 List<Archive> classpaths = new ArrayList<Archive>(); // for class file lookup
293 if (options.wildcard) {
294 // include all archives from classpath to the initial list
295 archives.addAll(getClassPathArchives(options.classpath));
296 } else {
297 classpaths.addAll(getClassPathArchives(options.classpath));
298 }
299 classpaths.addAll(PlatformClassPath.getArchives());
301 // add all archives to the source locations for reporting
302 sourceLocations.addAll(archives);
303 sourceLocations.addAll(classpaths);
305 // Work queue of names of classfiles to be searched.
306 // Entries will be unique, and for classes that do not yet have
307 // dependencies in the results map.
308 Deque<String> deque = new LinkedList<String>();
309 Set<String> doneClasses = new HashSet<String>();
311 // get the immediate dependencies of the input files
312 for (Archive a : archives) {
313 for (ClassFile cf : a.reader().getClassFiles()) {
314 String classFileName;
315 try {
316 classFileName = cf.getName();
317 } catch (ConstantPoolException e) {
318 throw new ClassFileError(e);
319 }
321 if (!doneClasses.contains(classFileName)) {
322 doneClasses.add(classFileName);
323 }
324 for (Dependency d : finder.findDependencies(cf)) {
325 if (filter.accepts(d)) {
326 String cn = d.getTarget().getName();
327 if (!doneClasses.contains(cn) && !deque.contains(cn)) {
328 deque.add(cn);
329 }
330 a.addClass(d.getOrigin(), d.getTarget());
331 }
332 }
333 }
334 }
336 // add Archive for looking up classes from the classpath
337 // for transitive dependency analysis
338 Deque<String> unresolved = roots;
339 int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE;
340 do {
341 String name;
342 while ((name = unresolved.poll()) != null) {
343 if (doneClasses.contains(name)) {
344 continue;
345 }
346 ClassFile cf = null;
347 for (Archive a : classpaths) {
348 cf = a.reader().getClassFile(name);
349 if (cf != null) {
350 String classFileName;
351 try {
352 classFileName = cf.getName();
353 } catch (ConstantPoolException e) {
354 throw new ClassFileError(e);
355 }
356 if (!doneClasses.contains(classFileName)) {
357 // if name is a fully-qualified class name specified
358 // from command-line, this class might already be parsed
359 doneClasses.add(classFileName);
360 for (Dependency d : finder.findDependencies(cf)) {
361 if (depth == 0) {
362 // ignore the dependency
363 a.addClass(d.getOrigin());
364 break;
365 } else if (filter.accepts(d)) {
366 a.addClass(d.getOrigin(), d.getTarget());
367 String cn = d.getTarget().getName();
368 if (!doneClasses.contains(cn) && !deque.contains(cn)) {
369 deque.add(cn);
370 }
371 }
372 }
373 }
374 break;
375 }
376 }
377 if (cf == null) {
378 doneClasses.add(name);
379 }
380 }
381 unresolved = deque;
382 deque = new LinkedList<String>();
383 } while (!unresolved.isEmpty() && depth-- > 0);
384 }
386 private void printSummary(final PrintWriter out, final Analyzer analyzer) {
387 Analyzer.Visitor visitor = new Analyzer.Visitor() {
388 public void visit(String origin, String target, String profile) {
389 if (options.showProfile) {
390 out.format("%-30s -> %s%n", origin, target);
391 }
392 }
393 public void visit(Archive origin, Archive target) {
394 if (!options.showProfile) {
395 out.format("%-30s -> %s%n", origin, target);
396 }
397 }
398 };
399 analyzer.visitSummary(visitor);
400 }
402 private void printDependencies(final PrintWriter out, final Analyzer analyzer) {
403 Analyzer.Visitor visitor = new Analyzer.Visitor() {
404 private String pkg = "";
405 public void visit(String origin, String target, String profile) {
406 if (!origin.equals(pkg)) {
407 pkg = origin;
408 out.format(" %s (%s)%n", origin, analyzer.getArchive(origin).getFileName());
409 }
410 out.format(" -> %-50s %s%n", target,
411 (options.showProfile && !profile.isEmpty())
412 ? profile
413 : analyzer.getArchiveName(target, profile));
414 }
415 public void visit(Archive origin, Archive target) {
416 out.format("%s -> %s%n", origin, target);
417 }
418 };
419 analyzer.visit(visitor);
420 }
422 public void handleOptions(String[] args) throws BadArgs {
423 // process options
424 for (int i=0; i < args.length; i++) {
425 if (args[i].charAt(0) == '-') {
426 String name = args[i];
427 Option option = getOption(name);
428 String param = null;
429 if (option.hasArg) {
430 if (name.startsWith("--") && name.indexOf('=') > 0) {
431 param = name.substring(name.indexOf('=') + 1, name.length());
432 } else if (i + 1 < args.length) {
433 param = args[++i];
434 }
435 if (param == null || param.isEmpty() || param.charAt(0) == '-') {
436 throw new BadArgs("err.missing.arg", name).showUsage(true);
437 }
438 }
439 option.process(this, name, param);
440 if (option.ignoreRest()) {
441 i = args.length;
442 }
443 } else {
444 // process rest of the input arguments
445 for (; i < args.length; i++) {
446 String name = args[i];
447 if (name.charAt(0) == '-') {
448 throw new BadArgs("err.option.after.class", name).showUsage(true);
449 }
450 if (name.equals("*") || name.equals("\"*\"")) {
451 options.wildcard = true;
452 } else {
453 classes.add(name);
454 }
455 }
456 }
457 }
458 }
460 private Option getOption(String name) throws BadArgs {
461 for (Option o : recognizedOptions) {
462 if (o.matches(name)) {
463 return o;
464 }
465 }
466 throw new BadArgs("err.unknown.option", name).showUsage(true);
467 }
469 private void reportError(String key, Object... args) {
470 log.println(getMessage("error.prefix") + " " + getMessage(key, args));
471 }
473 private void warning(String key, Object... args) {
474 log.println(getMessage("warn.prefix") + " " + getMessage(key, args));
475 }
477 private void showHelp() {
478 log.println(getMessage("main.usage", PROGNAME));
479 for (Option o : recognizedOptions) {
480 String name = o.aliases[0].substring(1); // there must always be at least one name
481 name = name.charAt(0) == '-' ? name.substring(1) : name;
482 if (o.isHidden() || name.equals("h")) {
483 continue;
484 }
485 log.println(getMessage("main.opt." + name));
486 }
487 }
489 private void showVersion(boolean full) {
490 log.println(version(full ? "full" : "release"));
491 }
493 private String version(String key) {
494 // key=version: mm.nn.oo[-milestone]
495 // key=full: mm.mm.oo[-milestone]-build
496 if (ResourceBundleHelper.versionRB == null) {
497 return System.getProperty("java.version");
498 }
499 try {
500 return ResourceBundleHelper.versionRB.getString(key);
501 } catch (MissingResourceException e) {
502 return getMessage("version.unknown", System.getProperty("java.version"));
503 }
504 }
506 static String getMessage(String key, Object... args) {
507 try {
508 return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);
509 } catch (MissingResourceException e) {
510 throw new InternalError("Missing message: " + key);
511 }
512 }
514 private static class Options {
515 boolean help;
516 boolean version;
517 boolean fullVersion;
518 boolean showProfile;
519 boolean showSummary;
520 boolean wildcard;
521 String regex;
522 String classpath = "";
523 int depth = 1;
524 Analyzer.Type verbose = Analyzer.Type.PACKAGE;
525 Set<String> packageNames = new HashSet<String>();
526 }
528 private static class ResourceBundleHelper {
529 static final ResourceBundle versionRB;
530 static final ResourceBundle bundle;
532 static {
533 Locale locale = Locale.getDefault();
534 try {
535 bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale);
536 } catch (MissingResourceException e) {
537 throw new InternalError("Cannot find jdeps resource bundle for locale " + locale);
538 }
539 try {
540 versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version");
541 } catch (MissingResourceException e) {
542 throw new InternalError("version.resource.missing");
543 }
544 }
545 }
547 private List<Archive> getArchives(List<String> filenames) throws IOException {
548 List<Archive> result = new ArrayList<Archive>();
549 for (String s : filenames) {
550 File f = new File(s);
551 if (f.exists()) {
552 result.add(new Archive(f, ClassFileReader.newInstance(f)));
553 } else {
554 warning("warn.file.not.exist", s);
555 }
556 }
557 return result;
558 }
560 private List<Archive> getClassPathArchives(String paths) throws IOException {
561 List<Archive> result = new ArrayList<Archive>();
562 if (paths.isEmpty()) {
563 return result;
564 }
565 for (String p : paths.split(File.pathSeparator)) {
566 if (p.length() > 0) {
567 File f = new File(p);
568 if (f.exists()) {
569 result.add(new Archive(f, ClassFileReader.newInstance(f)));
570 }
571 }
572 }
573 return result;
574 }
575 }