1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/share/classes/com/sun/tools/jdeps/JdepsTask.java Wed Apr 27 01:34:52 2016 +0800 1.3 @@ -0,0 +1,913 @@ 1.4 +/* 1.5 + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. 1.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 1.7 + * 1.8 + * This code is free software; you can redistribute it and/or modify it 1.9 + * under the terms of the GNU General Public License version 2 only, as 1.10 + * published by the Free Software Foundation. Oracle designates this 1.11 + * particular file as subject to the "Classpath" exception as provided 1.12 + * by Oracle in the LICENSE file that accompanied this code. 1.13 + * 1.14 + * This code is distributed in the hope that it will be useful, but WITHOUT 1.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1.16 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1.17 + * version 2 for more details (a copy is included in the LICENSE file that 1.18 + * accompanied this code). 1.19 + * 1.20 + * You should have received a copy of the GNU General Public License version 1.21 + * 2 along with this work; if not, write to the Free Software Foundation, 1.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 1.23 + * 1.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 1.25 + * or visit www.oracle.com if you need additional information or have any 1.26 + * questions. 1.27 + */ 1.28 +package com.sun.tools.jdeps; 1.29 + 1.30 +import com.sun.tools.classfile.AccessFlags; 1.31 +import com.sun.tools.classfile.ClassFile; 1.32 +import com.sun.tools.classfile.ConstantPoolException; 1.33 +import com.sun.tools.classfile.Dependencies; 1.34 +import com.sun.tools.classfile.Dependencies.ClassFileError; 1.35 +import com.sun.tools.classfile.Dependency; 1.36 +import com.sun.tools.jdeps.PlatformClassPath.JDKArchive; 1.37 +import java.io.*; 1.38 +import java.nio.file.DirectoryStream; 1.39 +import java.nio.file.Files; 1.40 +import java.nio.file.Path; 1.41 +import java.nio.file.Paths; 1.42 +import java.text.MessageFormat; 1.43 +import java.util.*; 1.44 +import java.util.regex.Pattern; 1.45 + 1.46 +/** 1.47 + * Implementation for the jdeps tool for static class dependency analysis. 1.48 + */ 1.49 +class JdepsTask { 1.50 + static class BadArgs extends Exception { 1.51 + static final long serialVersionUID = 8765093759964640721L; 1.52 + BadArgs(String key, Object... args) { 1.53 + super(JdepsTask.getMessage(key, args)); 1.54 + this.key = key; 1.55 + this.args = args; 1.56 + } 1.57 + 1.58 + BadArgs showUsage(boolean b) { 1.59 + showUsage = b; 1.60 + return this; 1.61 + } 1.62 + final String key; 1.63 + final Object[] args; 1.64 + boolean showUsage; 1.65 + } 1.66 + 1.67 + static abstract class Option { 1.68 + Option(boolean hasArg, String... aliases) { 1.69 + this.hasArg = hasArg; 1.70 + this.aliases = aliases; 1.71 + } 1.72 + 1.73 + boolean isHidden() { 1.74 + return false; 1.75 + } 1.76 + 1.77 + boolean matches(String opt) { 1.78 + for (String a : aliases) { 1.79 + if (a.equals(opt)) 1.80 + return true; 1.81 + if (hasArg && opt.startsWith(a + "=")) 1.82 + return true; 1.83 + } 1.84 + return false; 1.85 + } 1.86 + 1.87 + boolean ignoreRest() { 1.88 + return false; 1.89 + } 1.90 + 1.91 + abstract void process(JdepsTask task, String opt, String arg) throws BadArgs; 1.92 + final boolean hasArg; 1.93 + final String[] aliases; 1.94 + } 1.95 + 1.96 + static abstract class HiddenOption extends Option { 1.97 + HiddenOption(boolean hasArg, String... aliases) { 1.98 + super(hasArg, aliases); 1.99 + } 1.100 + 1.101 + boolean isHidden() { 1.102 + return true; 1.103 + } 1.104 + } 1.105 + 1.106 + static Option[] recognizedOptions = { 1.107 + new Option(false, "-h", "-?", "-help") { 1.108 + void process(JdepsTask task, String opt, String arg) { 1.109 + task.options.help = true; 1.110 + } 1.111 + }, 1.112 + new Option(true, "-dotoutput") { 1.113 + void process(JdepsTask task, String opt, String arg) throws BadArgs { 1.114 + Path p = Paths.get(arg); 1.115 + if (Files.exists(p) && (!Files.isDirectory(p) || !Files.isWritable(p))) { 1.116 + throw new BadArgs("err.dot.output.path", arg); 1.117 + } 1.118 + task.options.dotOutputDir = arg; 1.119 + } 1.120 + }, 1.121 + new Option(false, "-s", "-summary") { 1.122 + void process(JdepsTask task, String opt, String arg) { 1.123 + task.options.showSummary = true; 1.124 + task.options.verbose = Analyzer.Type.SUMMARY; 1.125 + } 1.126 + }, 1.127 + new Option(false, "-v", "-verbose", 1.128 + "-verbose:package", 1.129 + "-verbose:class") 1.130 + { 1.131 + void process(JdepsTask task, String opt, String arg) throws BadArgs { 1.132 + switch (opt) { 1.133 + case "-v": 1.134 + case "-verbose": 1.135 + task.options.verbose = Analyzer.Type.VERBOSE; 1.136 + break; 1.137 + case "-verbose:package": 1.138 + task.options.verbose = Analyzer.Type.PACKAGE; 1.139 + break; 1.140 + case "-verbose:class": 1.141 + task.options.verbose = Analyzer.Type.CLASS; 1.142 + break; 1.143 + default: 1.144 + throw new BadArgs("err.invalid.arg.for.option", opt); 1.145 + } 1.146 + } 1.147 + }, 1.148 + new Option(true, "-cp", "-classpath") { 1.149 + void process(JdepsTask task, String opt, String arg) { 1.150 + task.options.classpath = arg; 1.151 + } 1.152 + }, 1.153 + new Option(true, "-p", "-package") { 1.154 + void process(JdepsTask task, String opt, String arg) { 1.155 + task.options.packageNames.add(arg); 1.156 + } 1.157 + }, 1.158 + new Option(true, "-e", "-regex") { 1.159 + void process(JdepsTask task, String opt, String arg) { 1.160 + task.options.regex = arg; 1.161 + } 1.162 + }, 1.163 + new Option(true, "-include") { 1.164 + void process(JdepsTask task, String opt, String arg) throws BadArgs { 1.165 + task.options.includePattern = Pattern.compile(arg); 1.166 + } 1.167 + }, 1.168 + new Option(false, "-P", "-profile") { 1.169 + void process(JdepsTask task, String opt, String arg) throws BadArgs { 1.170 + task.options.showProfile = true; 1.171 + if (Profile.getProfileCount() == 0) { 1.172 + throw new BadArgs("err.option.unsupported", opt, getMessage("err.profiles.msg")); 1.173 + } 1.174 + } 1.175 + }, 1.176 + new Option(false, "-apionly") { 1.177 + void process(JdepsTask task, String opt, String arg) { 1.178 + task.options.apiOnly = true; 1.179 + } 1.180 + }, 1.181 + new Option(false, "-R", "-recursive") { 1.182 + void process(JdepsTask task, String opt, String arg) { 1.183 + task.options.depth = 0; 1.184 + } 1.185 + }, 1.186 + new Option(false, "-jdkinternals") { 1.187 + void process(JdepsTask task, String opt, String arg) { 1.188 + task.options.findJDKInternals = true; 1.189 + task.options.verbose = Analyzer.Type.CLASS; 1.190 + if (task.options.includePattern == null) { 1.191 + task.options.includePattern = Pattern.compile(".*"); 1.192 + } 1.193 + } 1.194 + }, 1.195 + new Option(false, "-version") { 1.196 + void process(JdepsTask task, String opt, String arg) { 1.197 + task.options.version = true; 1.198 + } 1.199 + }, 1.200 + new HiddenOption(false, "-fullversion") { 1.201 + void process(JdepsTask task, String opt, String arg) { 1.202 + task.options.fullVersion = true; 1.203 + } 1.204 + }, 1.205 + new HiddenOption(false, "-showlabel") { 1.206 + void process(JdepsTask task, String opt, String arg) { 1.207 + task.options.showLabel = true; 1.208 + } 1.209 + }, 1.210 + new HiddenOption(true, "-depth") { 1.211 + void process(JdepsTask task, String opt, String arg) throws BadArgs { 1.212 + try { 1.213 + task.options.depth = Integer.parseInt(arg); 1.214 + } catch (NumberFormatException e) { 1.215 + throw new BadArgs("err.invalid.arg.for.option", opt); 1.216 + } 1.217 + } 1.218 + }, 1.219 + }; 1.220 + 1.221 + private static final String PROGNAME = "jdeps"; 1.222 + private final Options options = new Options(); 1.223 + private final List<String> classes = new ArrayList<String>(); 1.224 + 1.225 + private PrintWriter log; 1.226 + void setLog(PrintWriter out) { 1.227 + log = out; 1.228 + } 1.229 + 1.230 + /** 1.231 + * Result codes. 1.232 + */ 1.233 + static final int EXIT_OK = 0, // Completed with no errors. 1.234 + EXIT_ERROR = 1, // Completed but reported errors. 1.235 + EXIT_CMDERR = 2, // Bad command-line arguments 1.236 + EXIT_SYSERR = 3, // System error or resource exhaustion. 1.237 + EXIT_ABNORMAL = 4;// terminated abnormally 1.238 + 1.239 + int run(String[] args) { 1.240 + if (log == null) { 1.241 + log = new PrintWriter(System.out); 1.242 + } 1.243 + try { 1.244 + handleOptions(args); 1.245 + if (options.help) { 1.246 + showHelp(); 1.247 + } 1.248 + if (options.version || options.fullVersion) { 1.249 + showVersion(options.fullVersion); 1.250 + } 1.251 + if (classes.isEmpty() && options.includePattern == null) { 1.252 + if (options.help || options.version || options.fullVersion) { 1.253 + return EXIT_OK; 1.254 + } else { 1.255 + showHelp(); 1.256 + return EXIT_CMDERR; 1.257 + } 1.258 + } 1.259 + if (options.regex != null && options.packageNames.size() > 0) { 1.260 + showHelp(); 1.261 + return EXIT_CMDERR; 1.262 + } 1.263 + if (options.findJDKInternals && 1.264 + (options.regex != null || options.packageNames.size() > 0 || options.showSummary)) { 1.265 + showHelp(); 1.266 + return EXIT_CMDERR; 1.267 + } 1.268 + if (options.showSummary && options.verbose != Analyzer.Type.SUMMARY) { 1.269 + showHelp(); 1.270 + return EXIT_CMDERR; 1.271 + } 1.272 + boolean ok = run(); 1.273 + return ok ? EXIT_OK : EXIT_ERROR; 1.274 + } catch (BadArgs e) { 1.275 + reportError(e.key, e.args); 1.276 + if (e.showUsage) { 1.277 + log.println(getMessage("main.usage.summary", PROGNAME)); 1.278 + } 1.279 + return EXIT_CMDERR; 1.280 + } catch (IOException e) { 1.281 + return EXIT_ABNORMAL; 1.282 + } finally { 1.283 + log.flush(); 1.284 + } 1.285 + } 1.286 + 1.287 + private final List<Archive> sourceLocations = new ArrayList<>(); 1.288 + private boolean run() throws IOException { 1.289 + findDependencies(); 1.290 + Analyzer analyzer = new Analyzer(options.verbose); 1.291 + analyzer.run(sourceLocations); 1.292 + if (options.dotOutputDir != null) { 1.293 + Path dir = Paths.get(options.dotOutputDir); 1.294 + Files.createDirectories(dir); 1.295 + generateDotFiles(dir, analyzer); 1.296 + } else { 1.297 + printRawOutput(log, analyzer); 1.298 + } 1.299 + return true; 1.300 + } 1.301 + 1.302 + private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException { 1.303 + Path summary = dir.resolve("summary.dot"); 1.304 + boolean verbose = options.verbose == Analyzer.Type.VERBOSE; 1.305 + DotGraph<?> graph = verbose ? new DotSummaryForPackage() 1.306 + : new DotSummaryForArchive(); 1.307 + for (Archive archive : sourceLocations) { 1.308 + analyzer.visitArchiveDependences(archive, graph); 1.309 + if (verbose || options.showLabel) { 1.310 + // traverse detailed dependences to generate package-level 1.311 + // summary or build labels for edges 1.312 + analyzer.visitDependences(archive, graph); 1.313 + } 1.314 + } 1.315 + try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary))) { 1.316 + graph.writeTo(sw); 1.317 + } 1.318 + // output individual .dot file for each archive 1.319 + if (options.verbose != Analyzer.Type.SUMMARY) { 1.320 + for (Archive archive : sourceLocations) { 1.321 + if (analyzer.hasDependences(archive)) { 1.322 + Path dotfile = dir.resolve(archive.getFileName() + ".dot"); 1.323 + try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile)); 1.324 + DotFileFormatter formatter = new DotFileFormatter(pw, archive)) { 1.325 + analyzer.visitDependences(archive, formatter); 1.326 + } 1.327 + } 1.328 + } 1.329 + } 1.330 + } 1.331 + 1.332 + private void printRawOutput(PrintWriter writer, Analyzer analyzer) { 1.333 + for (Archive archive : sourceLocations) { 1.334 + RawOutputFormatter formatter = new RawOutputFormatter(writer); 1.335 + analyzer.visitArchiveDependences(archive, formatter); 1.336 + if (options.verbose != Analyzer.Type.SUMMARY) { 1.337 + analyzer.visitDependences(archive, formatter); 1.338 + } 1.339 + } 1.340 + } 1.341 + private boolean isValidClassName(String name) { 1.342 + if (!Character.isJavaIdentifierStart(name.charAt(0))) { 1.343 + return false; 1.344 + } 1.345 + for (int i=1; i < name.length(); i++) { 1.346 + char c = name.charAt(i); 1.347 + if (c != '.' && !Character.isJavaIdentifierPart(c)) { 1.348 + return false; 1.349 + } 1.350 + } 1.351 + return true; 1.352 + } 1.353 + 1.354 + private Dependency.Filter getDependencyFilter() { 1.355 + if (options.regex != null) { 1.356 + return Dependencies.getRegexFilter(Pattern.compile(options.regex)); 1.357 + } else if (options.packageNames.size() > 0) { 1.358 + return Dependencies.getPackageFilter(options.packageNames, false); 1.359 + } else { 1.360 + return new Dependency.Filter() { 1.361 + @Override 1.362 + public boolean accepts(Dependency dependency) { 1.363 + return !dependency.getOrigin().equals(dependency.getTarget()); 1.364 + } 1.365 + }; 1.366 + } 1.367 + } 1.368 + 1.369 + private boolean matches(String classname, AccessFlags flags) { 1.370 + if (options.apiOnly && !flags.is(AccessFlags.ACC_PUBLIC)) { 1.371 + return false; 1.372 + } else if (options.includePattern != null) { 1.373 + return options.includePattern.matcher(classname.replace('/', '.')).matches(); 1.374 + } else { 1.375 + return true; 1.376 + } 1.377 + } 1.378 + 1.379 + private void findDependencies() throws IOException { 1.380 + Dependency.Finder finder = 1.381 + options.apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED) 1.382 + : Dependencies.getClassDependencyFinder(); 1.383 + Dependency.Filter filter = getDependencyFilter(); 1.384 + 1.385 + List<Archive> archives = new ArrayList<>(); 1.386 + Deque<String> roots = new LinkedList<>(); 1.387 + for (String s : classes) { 1.388 + Path p = Paths.get(s); 1.389 + if (Files.exists(p)) { 1.390 + archives.add(new Archive(p, ClassFileReader.newInstance(p))); 1.391 + } else { 1.392 + if (isValidClassName(s)) { 1.393 + roots.add(s); 1.394 + } else { 1.395 + warning("warn.invalid.arg", s); 1.396 + } 1.397 + } 1.398 + } 1.399 + sourceLocations.addAll(archives); 1.400 + 1.401 + List<Archive> classpaths = new ArrayList<>(); // for class file lookup 1.402 + classpaths.addAll(getClassPathArchives(options.classpath)); 1.403 + if (options.includePattern != null) { 1.404 + archives.addAll(classpaths); 1.405 + } 1.406 + classpaths.addAll(PlatformClassPath.getArchives()); 1.407 + 1.408 + // add all classpath archives to the source locations for reporting 1.409 + sourceLocations.addAll(classpaths); 1.410 + 1.411 + // Work queue of names of classfiles to be searched. 1.412 + // Entries will be unique, and for classes that do not yet have 1.413 + // dependencies in the results map. 1.414 + Deque<String> deque = new LinkedList<>(); 1.415 + Set<String> doneClasses = new HashSet<>(); 1.416 + 1.417 + // get the immediate dependencies of the input files 1.418 + for (Archive a : archives) { 1.419 + for (ClassFile cf : a.reader().getClassFiles()) { 1.420 + String classFileName; 1.421 + try { 1.422 + classFileName = cf.getName(); 1.423 + } catch (ConstantPoolException e) { 1.424 + throw new ClassFileError(e); 1.425 + } 1.426 + 1.427 + if (matches(classFileName, cf.access_flags)) { 1.428 + if (!doneClasses.contains(classFileName)) { 1.429 + doneClasses.add(classFileName); 1.430 + } 1.431 + for (Dependency d : finder.findDependencies(cf)) { 1.432 + if (filter.accepts(d)) { 1.433 + String cn = d.getTarget().getName(); 1.434 + if (!doneClasses.contains(cn) && !deque.contains(cn)) { 1.435 + deque.add(cn); 1.436 + } 1.437 + a.addClass(d.getOrigin(), d.getTarget()); 1.438 + } 1.439 + } 1.440 + } 1.441 + } 1.442 + } 1.443 + 1.444 + // add Archive for looking up classes from the classpath 1.445 + // for transitive dependency analysis 1.446 + Deque<String> unresolved = roots; 1.447 + int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE; 1.448 + do { 1.449 + String name; 1.450 + while ((name = unresolved.poll()) != null) { 1.451 + if (doneClasses.contains(name)) { 1.452 + continue; 1.453 + } 1.454 + ClassFile cf = null; 1.455 + for (Archive a : classpaths) { 1.456 + cf = a.reader().getClassFile(name); 1.457 + if (cf != null) { 1.458 + String classFileName; 1.459 + try { 1.460 + classFileName = cf.getName(); 1.461 + } catch (ConstantPoolException e) { 1.462 + throw new ClassFileError(e); 1.463 + } 1.464 + if (!doneClasses.contains(classFileName)) { 1.465 + // if name is a fully-qualified class name specified 1.466 + // from command-line, this class might already be parsed 1.467 + doneClasses.add(classFileName); 1.468 + for (Dependency d : finder.findDependencies(cf)) { 1.469 + if (depth == 0) { 1.470 + // ignore the dependency 1.471 + a.addClass(d.getOrigin()); 1.472 + break; 1.473 + } else if (filter.accepts(d)) { 1.474 + a.addClass(d.getOrigin(), d.getTarget()); 1.475 + String cn = d.getTarget().getName(); 1.476 + if (!doneClasses.contains(cn) && !deque.contains(cn)) { 1.477 + deque.add(cn); 1.478 + } 1.479 + } 1.480 + } 1.481 + } 1.482 + break; 1.483 + } 1.484 + } 1.485 + if (cf == null) { 1.486 + doneClasses.add(name); 1.487 + } 1.488 + } 1.489 + unresolved = deque; 1.490 + deque = new LinkedList<>(); 1.491 + } while (!unresolved.isEmpty() && depth-- > 0); 1.492 + } 1.493 + 1.494 + public void handleOptions(String[] args) throws BadArgs { 1.495 + // process options 1.496 + for (int i=0; i < args.length; i++) { 1.497 + if (args[i].charAt(0) == '-') { 1.498 + String name = args[i]; 1.499 + Option option = getOption(name); 1.500 + String param = null; 1.501 + if (option.hasArg) { 1.502 + if (name.startsWith("-") && name.indexOf('=') > 0) { 1.503 + param = name.substring(name.indexOf('=') + 1, name.length()); 1.504 + } else if (i + 1 < args.length) { 1.505 + param = args[++i]; 1.506 + } 1.507 + if (param == null || param.isEmpty() || param.charAt(0) == '-') { 1.508 + throw new BadArgs("err.missing.arg", name).showUsage(true); 1.509 + } 1.510 + } 1.511 + option.process(this, name, param); 1.512 + if (option.ignoreRest()) { 1.513 + i = args.length; 1.514 + } 1.515 + } else { 1.516 + // process rest of the input arguments 1.517 + for (; i < args.length; i++) { 1.518 + String name = args[i]; 1.519 + if (name.charAt(0) == '-') { 1.520 + throw new BadArgs("err.option.after.class", name).showUsage(true); 1.521 + } 1.522 + classes.add(name); 1.523 + } 1.524 + } 1.525 + } 1.526 + } 1.527 + 1.528 + private Option getOption(String name) throws BadArgs { 1.529 + for (Option o : recognizedOptions) { 1.530 + if (o.matches(name)) { 1.531 + return o; 1.532 + } 1.533 + } 1.534 + throw new BadArgs("err.unknown.option", name).showUsage(true); 1.535 + } 1.536 + 1.537 + private void reportError(String key, Object... args) { 1.538 + log.println(getMessage("error.prefix") + " " + getMessage(key, args)); 1.539 + } 1.540 + 1.541 + private void warning(String key, Object... args) { 1.542 + log.println(getMessage("warn.prefix") + " " + getMessage(key, args)); 1.543 + } 1.544 + 1.545 + private void showHelp() { 1.546 + log.println(getMessage("main.usage", PROGNAME)); 1.547 + for (Option o : recognizedOptions) { 1.548 + String name = o.aliases[0].substring(1); // there must always be at least one name 1.549 + name = name.charAt(0) == '-' ? name.substring(1) : name; 1.550 + if (o.isHidden() || name.equals("h")) { 1.551 + continue; 1.552 + } 1.553 + log.println(getMessage("main.opt." + name)); 1.554 + } 1.555 + } 1.556 + 1.557 + private void showVersion(boolean full) { 1.558 + log.println(version(full ? "full" : "release")); 1.559 + } 1.560 + 1.561 + private String version(String key) { 1.562 + // key=version: mm.nn.oo[-milestone] 1.563 + // key=full: mm.mm.oo[-milestone]-build 1.564 + if (ResourceBundleHelper.versionRB == null) { 1.565 + return System.getProperty("java.version"); 1.566 + } 1.567 + try { 1.568 + return ResourceBundleHelper.versionRB.getString(key); 1.569 + } catch (MissingResourceException e) { 1.570 + return getMessage("version.unknown", System.getProperty("java.version")); 1.571 + } 1.572 + } 1.573 + 1.574 + static String getMessage(String key, Object... args) { 1.575 + try { 1.576 + return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args); 1.577 + } catch (MissingResourceException e) { 1.578 + throw new InternalError("Missing message: " + key); 1.579 + } 1.580 + } 1.581 + 1.582 + private static class Options { 1.583 + boolean help; 1.584 + boolean version; 1.585 + boolean fullVersion; 1.586 + boolean showProfile; 1.587 + boolean showSummary; 1.588 + boolean wildcard; 1.589 + boolean apiOnly; 1.590 + boolean showLabel; 1.591 + boolean findJDKInternals; 1.592 + String dotOutputDir; 1.593 + String classpath = ""; 1.594 + int depth = 1; 1.595 + Analyzer.Type verbose = Analyzer.Type.PACKAGE; 1.596 + Set<String> packageNames = new HashSet<>(); 1.597 + String regex; // apply to the dependences 1.598 + Pattern includePattern; // apply to classes 1.599 + } 1.600 + private static class ResourceBundleHelper { 1.601 + static final ResourceBundle versionRB; 1.602 + static final ResourceBundle bundle; 1.603 + 1.604 + static { 1.605 + Locale locale = Locale.getDefault(); 1.606 + try { 1.607 + bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale); 1.608 + } catch (MissingResourceException e) { 1.609 + throw new InternalError("Cannot find jdeps resource bundle for locale " + locale); 1.610 + } 1.611 + try { 1.612 + versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version"); 1.613 + } catch (MissingResourceException e) { 1.614 + throw new InternalError("version.resource.missing"); 1.615 + } 1.616 + } 1.617 + } 1.618 + 1.619 + private List<Archive> getArchives(List<String> filenames) throws IOException { 1.620 + List<Archive> result = new ArrayList<Archive>(); 1.621 + for (String s : filenames) { 1.622 + Path p = Paths.get(s); 1.623 + if (Files.exists(p)) { 1.624 + result.add(new Archive(p, ClassFileReader.newInstance(p))); 1.625 + } else { 1.626 + warning("warn.file.not.exist", s); 1.627 + } 1.628 + } 1.629 + return result; 1.630 + } 1.631 + 1.632 + private List<Archive> getClassPathArchives(String paths) throws IOException { 1.633 + List<Archive> result = new ArrayList<>(); 1.634 + if (paths.isEmpty()) { 1.635 + return result; 1.636 + } 1.637 + for (String p : paths.split(File.pathSeparator)) { 1.638 + if (p.length() > 0) { 1.639 + List<Path> files = new ArrayList<>(); 1.640 + // wildcard to parse all JAR files e.g. -classpath dir/* 1.641 + int i = p.lastIndexOf(".*"); 1.642 + if (i > 0) { 1.643 + Path dir = Paths.get(p.substring(0, i)); 1.644 + try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) { 1.645 + for (Path entry : stream) { 1.646 + files.add(entry); 1.647 + } 1.648 + } 1.649 + } else { 1.650 + files.add(Paths.get(p)); 1.651 + } 1.652 + for (Path f : files) { 1.653 + if (Files.exists(f)) { 1.654 + result.add(new Archive(f, ClassFileReader.newInstance(f))); 1.655 + } 1.656 + } 1.657 + } 1.658 + } 1.659 + return result; 1.660 + } 1.661 + 1.662 + /** 1.663 + * If the given archive is JDK archive and non-null Profile, 1.664 + * this method returns the profile name only if -profile option is specified; 1.665 + * a null profile indicates it accesses a private JDK API and this method 1.666 + * will return "JDK internal API". 1.667 + * 1.668 + * For non-JDK archives, this method returns the file name of the archive. 1.669 + */ 1.670 + private String getProfileArchiveInfo(Archive source, Profile profile) { 1.671 + if (options.showProfile && profile != null) 1.672 + return profile.toString(); 1.673 + 1.674 + if (source instanceof JDKArchive) { 1.675 + return profile == null ? "JDK internal API (" + source.getFileName() + ")" : ""; 1.676 + } 1.677 + return source.getFileName(); 1.678 + } 1.679 + 1.680 + /** 1.681 + * Returns the profile name or "JDK internal API" for JDK archive; 1.682 + * otherwise empty string. 1.683 + */ 1.684 + private String profileName(Archive archive, Profile profile) { 1.685 + if (archive instanceof JDKArchive) { 1.686 + return Objects.toString(profile, "JDK internal API"); 1.687 + } else { 1.688 + return ""; 1.689 + } 1.690 + } 1.691 + 1.692 + class RawOutputFormatter implements Analyzer.Visitor { 1.693 + private final PrintWriter writer; 1.694 + RawOutputFormatter(PrintWriter writer) { 1.695 + this.writer = writer; 1.696 + } 1.697 + 1.698 + private String pkg = ""; 1.699 + @Override 1.700 + public void visitDependence(String origin, Archive source, 1.701 + String target, Archive archive, Profile profile) { 1.702 + if (options.findJDKInternals && 1.703 + !(archive instanceof JDKArchive && profile == null)) { 1.704 + // filter dependences other than JDK internal APIs 1.705 + return; 1.706 + } 1.707 + if (options.verbose == Analyzer.Type.VERBOSE) { 1.708 + writer.format(" %-50s -> %-50s %s%n", 1.709 + origin, target, getProfileArchiveInfo(archive, profile)); 1.710 + } else { 1.711 + if (!origin.equals(pkg)) { 1.712 + pkg = origin; 1.713 + writer.format(" %s (%s)%n", origin, source.getFileName()); 1.714 + } 1.715 + writer.format(" -> %-50s %s%n", 1.716 + target, getProfileArchiveInfo(archive, profile)); 1.717 + } 1.718 + } 1.719 + 1.720 + @Override 1.721 + public void visitArchiveDependence(Archive origin, Archive target, Profile profile) { 1.722 + writer.format("%s -> %s", origin.getPathName(), target.getPathName()); 1.723 + if (options.showProfile && profile != null) { 1.724 + writer.format(" (%s)%n", profile); 1.725 + } else { 1.726 + writer.format("%n"); 1.727 + } 1.728 + } 1.729 + } 1.730 + 1.731 + class DotFileFormatter extends DotGraph<String> implements AutoCloseable { 1.732 + private final PrintWriter writer; 1.733 + private final String name; 1.734 + DotFileFormatter(PrintWriter writer, Archive archive) { 1.735 + this.writer = writer; 1.736 + this.name = archive.getFileName(); 1.737 + writer.format("digraph \"%s\" {%n", name); 1.738 + writer.format(" // Path: %s%n", archive.getPathName()); 1.739 + } 1.740 + 1.741 + @Override 1.742 + public void close() { 1.743 + writer.println("}"); 1.744 + } 1.745 + 1.746 + @Override 1.747 + public void visitDependence(String origin, Archive source, 1.748 + String target, Archive archive, Profile profile) { 1.749 + if (options.findJDKInternals && 1.750 + !(archive instanceof JDKArchive && profile == null)) { 1.751 + // filter dependences other than JDK internal APIs 1.752 + return; 1.753 + } 1.754 + // if -P option is specified, package name -> profile will 1.755 + // be shown and filter out multiple same edges. 1.756 + String name = getProfileArchiveInfo(archive, profile); 1.757 + writeEdge(writer, new Edge(origin, target, getProfileArchiveInfo(archive, profile))); 1.758 + } 1.759 + @Override 1.760 + public void visitArchiveDependence(Archive origin, Archive target, Profile profile) { 1.761 + throw new UnsupportedOperationException(); 1.762 + } 1.763 + } 1.764 + 1.765 + class DotSummaryForArchive extends DotGraph<Archive> { 1.766 + @Override 1.767 + public void visitDependence(String origin, Archive source, 1.768 + String target, Archive archive, Profile profile) { 1.769 + Edge e = findEdge(source, archive); 1.770 + assert e != null; 1.771 + // add the dependency to the label if enabled and not compact1 1.772 + if (profile == Profile.COMPACT1) { 1.773 + return; 1.774 + } 1.775 + e.addLabel(origin, target, profileName(archive, profile)); 1.776 + } 1.777 + @Override 1.778 + public void visitArchiveDependence(Archive origin, Archive target, Profile profile) { 1.779 + // add an edge with the archive's name with no tag 1.780 + // so that there is only one node for each JDK archive 1.781 + // while there may be edges to different profiles 1.782 + Edge e = addEdge(origin, target, ""); 1.783 + if (target instanceof JDKArchive) { 1.784 + // add a label to print the profile 1.785 + if (profile == null) { 1.786 + e.addLabel("JDK internal API"); 1.787 + } else if (options.showProfile && !options.showLabel) { 1.788 + e.addLabel(profile.toString()); 1.789 + } 1.790 + } 1.791 + } 1.792 + } 1.793 + 1.794 + // DotSummaryForPackage generates the summary.dot file for verbose mode 1.795 + // (-v or -verbose option) that includes all class dependencies. 1.796 + // The summary.dot file shows package-level dependencies. 1.797 + class DotSummaryForPackage extends DotGraph<String> { 1.798 + private String packageOf(String cn) { 1.799 + int i = cn.lastIndexOf('.'); 1.800 + return i > 0 ? cn.substring(0, i) : "<unnamed>"; 1.801 + } 1.802 + @Override 1.803 + public void visitDependence(String origin, Archive source, 1.804 + String target, Archive archive, Profile profile) { 1.805 + // add a package dependency edge 1.806 + String from = packageOf(origin); 1.807 + String to = packageOf(target); 1.808 + Edge e = addEdge(from, to, getProfileArchiveInfo(archive, profile)); 1.809 + 1.810 + // add the dependency to the label if enabled and not compact1 1.811 + if (!options.showLabel || profile == Profile.COMPACT1) { 1.812 + return; 1.813 + } 1.814 + 1.815 + // trim the package name of origin to shorten the label 1.816 + int i = origin.lastIndexOf('.'); 1.817 + String n1 = i < 0 ? origin : origin.substring(i+1); 1.818 + e.addLabel(n1, target, profileName(archive, profile)); 1.819 + } 1.820 + @Override 1.821 + public void visitArchiveDependence(Archive origin, Archive target, Profile profile) { 1.822 + // nop 1.823 + } 1.824 + } 1.825 + abstract class DotGraph<T> implements Analyzer.Visitor { 1.826 + private final Set<Edge> edges = new LinkedHashSet<>(); 1.827 + private Edge curEdge; 1.828 + public void writeTo(PrintWriter writer) { 1.829 + writer.format("digraph \"summary\" {%n"); 1.830 + for (Edge e: edges) { 1.831 + writeEdge(writer, e); 1.832 + } 1.833 + writer.println("}"); 1.834 + } 1.835 + 1.836 + void writeEdge(PrintWriter writer, Edge e) { 1.837 + writer.format(" %-50s -> \"%s\"%s;%n", 1.838 + String.format("\"%s\"", e.from.toString()), 1.839 + e.tag.isEmpty() ? e.to 1.840 + : String.format("%s (%s)", e.to, e.tag), 1.841 + getLabel(e)); 1.842 + } 1.843 + 1.844 + Edge addEdge(T origin, T target, String tag) { 1.845 + Edge e = new Edge(origin, target, tag); 1.846 + if (e.equals(curEdge)) { 1.847 + return curEdge; 1.848 + } 1.849 + 1.850 + if (edges.contains(e)) { 1.851 + for (Edge e1 : edges) { 1.852 + if (e.equals(e1)) { 1.853 + curEdge = e1; 1.854 + } 1.855 + } 1.856 + } else { 1.857 + edges.add(e); 1.858 + curEdge = e; 1.859 + } 1.860 + return curEdge; 1.861 + } 1.862 + 1.863 + Edge findEdge(T origin, T target) { 1.864 + for (Edge e : edges) { 1.865 + if (e.from.equals(origin) && e.to.equals(target)) { 1.866 + return e; 1.867 + } 1.868 + } 1.869 + return null; 1.870 + } 1.871 + 1.872 + String getLabel(Edge e) { 1.873 + String label = e.label.toString(); 1.874 + return label.isEmpty() ? "" : String.format("[label=\"%s\",fontsize=9]", label); 1.875 + } 1.876 + 1.877 + class Edge { 1.878 + final T from; 1.879 + final T to; 1.880 + final String tag; // optional tag 1.881 + final StringBuilder label = new StringBuilder(); 1.882 + Edge(T from, T to, String tag) { 1.883 + this.from = from; 1.884 + this.to = to; 1.885 + this.tag = tag; 1.886 + } 1.887 + void addLabel(String s) { 1.888 + label.append(s).append("\\n"); 1.889 + } 1.890 + void addLabel(String origin, String target, String profile) { 1.891 + label.append(origin).append(" -> ").append(target); 1.892 + if (!profile.isEmpty()) { 1.893 + label.append(" (" + profile + ")"); 1.894 + } 1.895 + label.append("\\n"); 1.896 + } 1.897 + @Override @SuppressWarnings("unchecked") 1.898 + public boolean equals(Object o) { 1.899 + if (o instanceof DotGraph<?>.Edge) { 1.900 + DotGraph<?>.Edge e = (DotGraph<?>.Edge)o; 1.901 + return this.from.equals(e.from) && 1.902 + this.to.equals(e.to) && 1.903 + this.tag.equals(e.tag); 1.904 + } 1.905 + return false; 1.906 + } 1.907 + @Override 1.908 + public int hashCode() { 1.909 + int hash = 7; 1.910 + hash = 67 * hash + Objects.hashCode(this.from) + 1.911 + Objects.hashCode(this.to) + Objects.hashCode(this.tag); 1.912 + return hash; 1.913 + } 1.914 + } 1.915 + } 1.916 +}