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

Wed, 27 Apr 2016 01:34:52 +0800

author
aoqi
date
Wed, 27 Apr 2016 01:34:52 +0800
changeset 0
959103a6100f
child 2525
2eb010b6cb22
permissions
-rw-r--r--

Initial load
http://hg.openjdk.java.net/jdk8u/jdk8u/langtools/
changeset: 2573:53ca196be1ae
tag: jdk8u25-b17

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

mercurial