94 return true; |
99 return true; |
95 } |
100 } |
96 } |
101 } |
97 |
102 |
98 static Option[] recognizedOptions = { |
103 static Option[] recognizedOptions = { |
99 new Option(false, "-h", "-?", "--help") { |
104 new Option(false, "-h", "-?", "-help") { |
100 void process(JdepsTask task, String opt, String arg) { |
105 void process(JdepsTask task, String opt, String arg) { |
101 task.options.help = true; |
106 task.options.help = true; |
102 } |
107 } |
103 }, |
108 }, |
104 new Option(false, "-s", "--summary") { |
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") { |
105 void process(JdepsTask task, String opt, String arg) { |
119 void process(JdepsTask task, String opt, String arg) { |
106 task.options.showSummary = true; |
120 task.options.showSummary = true; |
107 task.options.verbose = Analyzer.Type.SUMMARY; |
121 task.options.verbose = Analyzer.Type.SUMMARY; |
108 } |
122 } |
109 }, |
123 }, |
110 new Option(false, "-v", "--verbose") { |
124 new Option(false, "-v", "-verbose", |
111 void process(JdepsTask task, String opt, String arg) { |
125 "-verbose:package", |
112 task.options.verbose = Analyzer.Type.VERBOSE; |
126 "-verbose:class") |
113 } |
127 { |
114 }, |
|
115 new Option(true, "-V", "--verbose-level") { |
|
116 void process(JdepsTask task, String opt, String arg) throws BadArgs { |
128 void process(JdepsTask task, String opt, String arg) throws BadArgs { |
117 if ("package".equals(arg)) { |
129 switch (opt) { |
118 task.options.verbose = Analyzer.Type.PACKAGE; |
130 case "-v": |
119 } else if ("class".equals(arg)) { |
131 case "-verbose": |
120 task.options.verbose = Analyzer.Type.CLASS; |
132 task.options.verbose = Analyzer.Type.VERBOSE; |
121 } else { |
133 break; |
122 throw new BadArgs("err.invalid.arg.for.option", opt); |
134 case "-verbose:package": |
123 } |
135 task.options.verbose = Analyzer.Type.PACKAGE; |
124 } |
136 break; |
125 }, |
137 case "-verbose:class": |
126 new Option(true, "-c", "--classpath") { |
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") { |
127 void process(JdepsTask task, String opt, String arg) { |
146 void process(JdepsTask task, String opt, String arg) { |
128 task.options.classpath = arg; |
147 task.options.classpath = arg; |
129 } |
148 } |
130 }, |
149 }, |
131 new Option(true, "-p", "--package") { |
150 new Option(true, "-p", "-package") { |
132 void process(JdepsTask task, String opt, String arg) { |
151 void process(JdepsTask task, String opt, String arg) { |
133 task.options.packageNames.add(arg); |
152 task.options.packageNames.add(arg); |
134 } |
153 } |
135 }, |
154 }, |
136 new Option(true, "-e", "--regex") { |
155 new Option(true, "-e", "-regex") { |
137 void process(JdepsTask task, String opt, String arg) { |
156 void process(JdepsTask task, String opt, String arg) { |
138 task.options.regex = arg; |
157 task.options.regex = arg; |
139 } |
158 } |
140 }, |
159 }, |
141 new Option(false, "-P", "--profile") { |
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") { |
142 void process(JdepsTask task, String opt, String arg) throws BadArgs { |
166 void process(JdepsTask task, String opt, String arg) throws BadArgs { |
143 task.options.showProfile = true; |
167 task.options.showProfile = true; |
144 if (Profiles.getProfileCount() == 0) { |
168 if (Profile.getProfileCount() == 0) { |
145 throw new BadArgs("err.option.unsupported", opt, getMessage("err.profiles.msg")); |
169 throw new BadArgs("err.option.unsupported", opt, getMessage("err.profiles.msg")); |
146 } |
170 } |
147 } |
171 } |
148 }, |
172 }, |
149 new Option(false, "-R", "--recursive") { |
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") { |
150 void process(JdepsTask task, String opt, String arg) { |
179 void process(JdepsTask task, String opt, String arg) { |
151 task.options.depth = 0; |
180 task.options.depth = 0; |
152 } |
181 } |
153 }, |
182 }, |
154 new HiddenOption(true, "-d", "--depth") { |
183 new Option(false, "-version") { |
|
184 void process(JdepsTask task, String opt, String arg) { |
|
185 task.options.version = true; |
|
186 } |
|
187 }, |
|
188 new HiddenOption(false, "-fullversion") { |
|
189 void process(JdepsTask task, String opt, String arg) { |
|
190 task.options.fullVersion = true; |
|
191 } |
|
192 }, |
|
193 new HiddenOption(true, "-depth") { |
155 void process(JdepsTask task, String opt, String arg) throws BadArgs { |
194 void process(JdepsTask task, String opt, String arg) throws BadArgs { |
156 try { |
195 try { |
157 task.options.depth = Integer.parseInt(arg); |
196 task.options.depth = Integer.parseInt(arg); |
158 } catch (NumberFormatException e) { |
197 } catch (NumberFormatException e) { |
159 throw new BadArgs("err.invalid.arg.for.option", opt); |
198 throw new BadArgs("err.invalid.arg.for.option", opt); |
160 } |
199 } |
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 } |
200 } |
172 }, |
201 }, |
173 }; |
202 }; |
174 |
203 |
175 private static final String PROGNAME = "jdeps"; |
204 private static final String PROGNAME = "jdeps"; |
231 } finally { |
260 } finally { |
232 log.flush(); |
261 log.flush(); |
233 } |
262 } |
234 } |
263 } |
235 |
264 |
236 private final List<Archive> sourceLocations = new ArrayList<Archive>(); |
265 private final List<Archive> sourceLocations = new ArrayList<>(); |
237 private boolean run() throws IOException { |
266 private boolean run() throws IOException { |
238 findDependencies(); |
267 findDependencies(); |
239 Analyzer analyzer = new Analyzer(options.verbose); |
268 Analyzer analyzer = new Analyzer(options.verbose); |
240 analyzer.run(sourceLocations); |
269 analyzer.run(sourceLocations); |
241 if (options.verbose == Analyzer.Type.SUMMARY) { |
270 if (options.dotOutputDir != null) { |
242 printSummary(log, analyzer); |
271 Path dir = Paths.get(options.dotOutputDir); |
|
272 Files.createDirectories(dir); |
|
273 generateDotFiles(dir, analyzer); |
243 } else { |
274 } else { |
244 printDependencies(log, analyzer); |
275 printRawOutput(log, analyzer); |
245 } |
276 } |
246 return true; |
277 return true; |
247 } |
278 } |
248 |
279 |
|
280 private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException { |
|
281 Path summary = dir.resolve("summary.dot"); |
|
282 try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary)); |
|
283 DotFileFormatter formatter = new DotFileFormatter(sw, "summary")) { |
|
284 for (Archive archive : sourceLocations) { |
|
285 analyzer.visitArchiveDependences(archive, formatter); |
|
286 } |
|
287 } |
|
288 if (options.verbose != Analyzer.Type.SUMMARY) { |
|
289 for (Archive archive : sourceLocations) { |
|
290 if (analyzer.hasDependences(archive)) { |
|
291 Path dotfile = dir.resolve(archive.getFileName() + ".dot"); |
|
292 try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile)); |
|
293 DotFileFormatter formatter = new DotFileFormatter(pw, archive)) { |
|
294 analyzer.visitDependences(archive, formatter); |
|
295 } |
|
296 } |
|
297 } |
|
298 } |
|
299 } |
|
300 |
|
301 private void printRawOutput(PrintWriter writer, Analyzer analyzer) { |
|
302 for (Archive archive : sourceLocations) { |
|
303 RawOutputFormatter formatter = new RawOutputFormatter(writer); |
|
304 analyzer.visitArchiveDependences(archive, formatter); |
|
305 if (options.verbose != Analyzer.Type.SUMMARY) { |
|
306 analyzer.visitDependences(archive, formatter); |
|
307 } |
|
308 } |
|
309 } |
249 private boolean isValidClassName(String name) { |
310 private boolean isValidClassName(String name) { |
250 if (!Character.isJavaIdentifierStart(name.charAt(0))) { |
311 if (!Character.isJavaIdentifierStart(name.charAt(0))) { |
251 return false; |
312 return false; |
252 } |
313 } |
253 for (int i=1; i < name.length(); i++) { |
314 for (int i=1; i < name.length(); i++) { |
257 } |
318 } |
258 } |
319 } |
259 return true; |
320 return true; |
260 } |
321 } |
261 |
322 |
262 private void findDependencies() throws IOException { |
323 private Dependency.Filter getDependencyFilter() { |
263 Dependency.Finder finder = Dependencies.getClassDependencyFinder(); |
324 if (options.regex != null) { |
264 Dependency.Filter filter; |
325 return Dependencies.getRegexFilter(Pattern.compile(options.regex)); |
265 if (options.regex != null) { |
|
266 filter = Dependencies.getRegexFilter(Pattern.compile(options.regex)); |
|
267 } else if (options.packageNames.size() > 0) { |
326 } else if (options.packageNames.size() > 0) { |
268 filter = Dependencies.getPackageFilter(options.packageNames, false); |
327 return Dependencies.getPackageFilter(options.packageNames, false); |
269 } else { |
328 } else { |
270 filter = new Dependency.Filter() { |
329 return new Dependency.Filter() { |
|
330 @Override |
271 public boolean accepts(Dependency dependency) { |
331 public boolean accepts(Dependency dependency) { |
272 return !dependency.getOrigin().equals(dependency.getTarget()); |
332 return !dependency.getOrigin().equals(dependency.getTarget()); |
273 } |
333 } |
274 }; |
334 }; |
275 } |
335 } |
276 |
336 } |
277 List<Archive> archives = new ArrayList<Archive>(); |
337 |
278 Deque<String> roots = new LinkedList<String>(); |
338 private boolean matches(String classname, AccessFlags flags) { |
|
339 if (options.apiOnly && !flags.is(AccessFlags.ACC_PUBLIC)) { |
|
340 return false; |
|
341 } else if (options.includePattern != null) { |
|
342 return options.includePattern.matcher(classname.replace('/', '.')).matches(); |
|
343 } else { |
|
344 return true; |
|
345 } |
|
346 } |
|
347 |
|
348 private void findDependencies() throws IOException { |
|
349 Dependency.Finder finder = |
|
350 options.apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED) |
|
351 : Dependencies.getClassDependencyFinder(); |
|
352 Dependency.Filter filter = getDependencyFilter(); |
|
353 |
|
354 List<Archive> archives = new ArrayList<>(); |
|
355 Deque<String> roots = new LinkedList<>(); |
279 for (String s : classes) { |
356 for (String s : classes) { |
280 File f = new File(s); |
357 Path p = Paths.get(s); |
281 if (f.exists()) { |
358 if (Files.exists(p)) { |
282 archives.add(new Archive(f, ClassFileReader.newInstance(f))); |
359 archives.add(new Archive(p, ClassFileReader.newInstance(p))); |
283 } else { |
360 } else { |
284 if (isValidClassName(s)) { |
361 if (isValidClassName(s)) { |
285 roots.add(s); |
362 roots.add(s); |
286 } else { |
363 } else { |
287 warning("warn.invalid.arg", s); |
364 warning("warn.invalid.arg", s); |
288 } |
365 } |
289 } |
366 } |
290 } |
367 } |
291 |
368 |
292 List<Archive> classpaths = new ArrayList<Archive>(); // for class file lookup |
369 List<Archive> classpaths = new ArrayList<>(); // for class file lookup |
293 if (options.wildcard) { |
370 if (options.includePattern != null) { |
294 // include all archives from classpath to the initial list |
|
295 archives.addAll(getClassPathArchives(options.classpath)); |
371 archives.addAll(getClassPathArchives(options.classpath)); |
296 } else { |
372 } else { |
297 classpaths.addAll(getClassPathArchives(options.classpath)); |
373 classpaths.addAll(getClassPathArchives(options.classpath)); |
298 } |
374 } |
299 classpaths.addAll(PlatformClassPath.getArchives()); |
375 classpaths.addAll(PlatformClassPath.getArchives()); |
377 if (cf == null) { |
455 if (cf == null) { |
378 doneClasses.add(name); |
456 doneClasses.add(name); |
379 } |
457 } |
380 } |
458 } |
381 unresolved = deque; |
459 unresolved = deque; |
382 deque = new LinkedList<String>(); |
460 deque = new LinkedList<>(); |
383 } while (!unresolved.isEmpty() && depth-- > 0); |
461 } while (!unresolved.isEmpty() && depth-- > 0); |
384 } |
|
385 |
|
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 } |
|
401 |
|
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 } |
462 } |
421 |
463 |
422 public void handleOptions(String[] args) throws BadArgs { |
464 public void handleOptions(String[] args) throws BadArgs { |
423 // process options |
465 // process options |
424 for (int i=0; i < args.length; i++) { |
466 for (int i=0; i < args.length; i++) { |
425 if (args[i].charAt(0) == '-') { |
467 if (args[i].charAt(0) == '-') { |
426 String name = args[i]; |
468 String name = args[i]; |
427 Option option = getOption(name); |
469 Option option = getOption(name); |
428 String param = null; |
470 String param = null; |
429 if (option.hasArg) { |
471 if (option.hasArg) { |
430 if (name.startsWith("--") && name.indexOf('=') > 0) { |
472 if (name.startsWith("-") && name.indexOf('=') > 0) { |
431 param = name.substring(name.indexOf('=') + 1, name.length()); |
473 param = name.substring(name.indexOf('=') + 1, name.length()); |
432 } else if (i + 1 < args.length) { |
474 } else if (i + 1 < args.length) { |
433 param = args[++i]; |
475 param = args[++i]; |
434 } |
476 } |
435 if (param == null || param.isEmpty() || param.charAt(0) == '-') { |
477 if (param == null || param.isEmpty() || param.charAt(0) == '-') { |
545 } |
585 } |
546 |
586 |
547 private List<Archive> getArchives(List<String> filenames) throws IOException { |
587 private List<Archive> getArchives(List<String> filenames) throws IOException { |
548 List<Archive> result = new ArrayList<Archive>(); |
588 List<Archive> result = new ArrayList<Archive>(); |
549 for (String s : filenames) { |
589 for (String s : filenames) { |
550 File f = new File(s); |
590 Path p = Paths.get(s); |
551 if (f.exists()) { |
591 if (Files.exists(p)) { |
552 result.add(new Archive(f, ClassFileReader.newInstance(f))); |
592 result.add(new Archive(p, ClassFileReader.newInstance(p))); |
553 } else { |
593 } else { |
554 warning("warn.file.not.exist", s); |
594 warning("warn.file.not.exist", s); |
555 } |
595 } |
556 } |
596 } |
557 return result; |
597 return result; |
558 } |
598 } |
559 |
599 |
560 private List<Archive> getClassPathArchives(String paths) throws IOException { |
600 private List<Archive> getClassPathArchives(String paths) throws IOException { |
561 List<Archive> result = new ArrayList<Archive>(); |
601 List<Archive> result = new ArrayList<>(); |
562 if (paths.isEmpty()) { |
602 if (paths.isEmpty()) { |
563 return result; |
603 return result; |
564 } |
604 } |
565 for (String p : paths.split(File.pathSeparator)) { |
605 for (String p : paths.split(File.pathSeparator)) { |
566 if (p.length() > 0) { |
606 if (p.length() > 0) { |
567 File f = new File(p); |
607 List<Path> files = new ArrayList<>(); |
568 if (f.exists()) { |
608 // wildcard to parse all JAR files e.g. -classpath dir/* |
569 result.add(new Archive(f, ClassFileReader.newInstance(f))); |
609 int i = p.lastIndexOf(".*"); |
|
610 if (i > 0) { |
|
611 Path dir = Paths.get(p.substring(0, i)); |
|
612 try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) { |
|
613 for (Path entry : stream) { |
|
614 files.add(entry); |
|
615 } |
|
616 } |
|
617 } else { |
|
618 files.add(Paths.get(p)); |
|
619 } |
|
620 for (Path f : files) { |
|
621 if (Files.exists(f)) { |
|
622 result.add(new Archive(f, ClassFileReader.newInstance(f))); |
|
623 } |
570 } |
624 } |
571 } |
625 } |
572 } |
626 } |
573 return result; |
627 return result; |
574 } |
628 } |
|
629 |
|
630 |
|
631 /** |
|
632 * Returns the file name of the archive for non-JRE class or |
|
633 * internal JRE classes. It returns empty string for SE API. |
|
634 */ |
|
635 private static String getArchiveName(Archive source, String profile) { |
|
636 String name = source.getFileName(); |
|
637 if (source instanceof JDKArchive) |
|
638 return profile.isEmpty() ? "JDK internal API (" + name + ")" : ""; |
|
639 return name; |
|
640 } |
|
641 |
|
642 class RawOutputFormatter implements Analyzer.Visitor { |
|
643 private final PrintWriter writer; |
|
644 RawOutputFormatter(PrintWriter writer) { |
|
645 this.writer = writer; |
|
646 } |
|
647 |
|
648 private String pkg = ""; |
|
649 @Override |
|
650 public void visitDependence(String origin, Archive source, |
|
651 String target, Archive archive, String profile) { |
|
652 if (!origin.equals(pkg)) { |
|
653 pkg = origin; |
|
654 writer.format(" %s (%s)%n", origin, source.getFileName()); |
|
655 } |
|
656 String name = (options.showProfile && !profile.isEmpty()) |
|
657 ? profile |
|
658 : getArchiveName(archive, profile); |
|
659 writer.format(" -> %-50s %s%n", target, name); |
|
660 } |
|
661 |
|
662 @Override |
|
663 public void visitArchiveDependence(Archive origin, Archive target, String profile) { |
|
664 writer.format("%s -> %s", origin, target); |
|
665 if (options.showProfile && !profile.isEmpty()) { |
|
666 writer.format(" (%s)%n", profile); |
|
667 } else { |
|
668 writer.format("%n"); |
|
669 } |
|
670 } |
|
671 } |
|
672 |
|
673 class DotFileFormatter implements Analyzer.Visitor, AutoCloseable { |
|
674 private final PrintWriter writer; |
|
675 private final String name; |
|
676 DotFileFormatter(PrintWriter writer, String name) { |
|
677 this.writer = writer; |
|
678 this.name = name; |
|
679 writer.format("digraph \"%s\" {%n", name); |
|
680 } |
|
681 DotFileFormatter(PrintWriter writer, Archive archive) { |
|
682 this.writer = writer; |
|
683 this.name = archive.getFileName(); |
|
684 writer.format("digraph \"%s\" {%n", name); |
|
685 writer.format(" // Path: %s%n", archive.toString()); |
|
686 } |
|
687 |
|
688 @Override |
|
689 public void close() { |
|
690 writer.println("}"); |
|
691 } |
|
692 |
|
693 private final Set<String> edges = new HashSet<>(); |
|
694 private String node = ""; |
|
695 @Override |
|
696 public void visitDependence(String origin, Archive source, |
|
697 String target, Archive archive, String profile) { |
|
698 if (!node.equals(origin)) { |
|
699 edges.clear(); |
|
700 node = origin; |
|
701 } |
|
702 // if -P option is specified, package name -> profile will |
|
703 // be shown and filter out multiple same edges. |
|
704 if (!edges.contains(target)) { |
|
705 StringBuilder sb = new StringBuilder(); |
|
706 String name = options.showProfile && !profile.isEmpty() |
|
707 ? profile |
|
708 : getArchiveName(archive, profile); |
|
709 writer.format(" %-50s -> %s;%n", |
|
710 String.format("\"%s\"", origin), |
|
711 name.isEmpty() ? String.format("\"%s\"", target) |
|
712 : String.format("\"%s (%s)\"", target, name)); |
|
713 edges.add(target); |
|
714 } |
|
715 } |
|
716 |
|
717 @Override |
|
718 public void visitArchiveDependence(Archive origin, Archive target, String profile) { |
|
719 String name = options.showProfile && !profile.isEmpty() |
|
720 ? profile : ""; |
|
721 writer.format(" %-30s -> \"%s\";%n", |
|
722 String.format("\"%s\"", origin.getFileName()), |
|
723 name.isEmpty() |
|
724 ? target.getFileName() |
|
725 : String.format("%s (%s)", target.getFileName(), name)); |
|
726 } |
|
727 } |
575 } |
728 } |