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

Thu, 12 Oct 2017 19:50:01 +0800

author
aoqi
date
Thu, 12 Oct 2017 19:50:01 +0800
changeset 2702
9ca8d8713094
parent 2539
a51b7fd0543b
parent 2525
2eb010b6cb22
child 2893
ca5783d9a597
permissions
-rw-r--r--

merge

aoqi@0 1 /*
mchung@2538 2 * Copyright (c) 2012, 2014, 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;
mchung@2538 33 import com.sun.tools.classfile.Dependency.Location;
aoqi@0 34 import com.sun.tools.jdeps.PlatformClassPath.JDKArchive;
mchung@2538 35 import static com.sun.tools.jdeps.Analyzer.Type.*;
aoqi@0 36 import java.io.*;
aoqi@0 37 import java.nio.file.DirectoryStream;
aoqi@0 38 import java.nio.file.Files;
aoqi@0 39 import java.nio.file.Path;
aoqi@0 40 import java.nio.file.Paths;
aoqi@0 41 import java.text.MessageFormat;
aoqi@0 42 import java.util.*;
aoqi@0 43 import java.util.regex.Pattern;
aoqi@0 44
aoqi@0 45 /**
aoqi@0 46 * Implementation for the jdeps tool for static class dependency analysis.
aoqi@0 47 */
aoqi@0 48 class JdepsTask {
aoqi@0 49 static class BadArgs extends Exception {
aoqi@0 50 static final long serialVersionUID = 8765093759964640721L;
aoqi@0 51 BadArgs(String key, Object... args) {
aoqi@0 52 super(JdepsTask.getMessage(key, args));
aoqi@0 53 this.key = key;
aoqi@0 54 this.args = args;
aoqi@0 55 }
aoqi@0 56
aoqi@0 57 BadArgs showUsage(boolean b) {
aoqi@0 58 showUsage = b;
aoqi@0 59 return this;
aoqi@0 60 }
aoqi@0 61 final String key;
aoqi@0 62 final Object[] args;
aoqi@0 63 boolean showUsage;
aoqi@0 64 }
aoqi@0 65
aoqi@0 66 static abstract class Option {
aoqi@0 67 Option(boolean hasArg, String... aliases) {
aoqi@0 68 this.hasArg = hasArg;
aoqi@0 69 this.aliases = aliases;
aoqi@0 70 }
aoqi@0 71
aoqi@0 72 boolean isHidden() {
aoqi@0 73 return false;
aoqi@0 74 }
aoqi@0 75
aoqi@0 76 boolean matches(String opt) {
aoqi@0 77 for (String a : aliases) {
aoqi@0 78 if (a.equals(opt))
aoqi@0 79 return true;
aoqi@0 80 if (hasArg && opt.startsWith(a + "="))
aoqi@0 81 return true;
aoqi@0 82 }
aoqi@0 83 return false;
aoqi@0 84 }
aoqi@0 85
aoqi@0 86 boolean ignoreRest() {
aoqi@0 87 return false;
aoqi@0 88 }
aoqi@0 89
aoqi@0 90 abstract void process(JdepsTask task, String opt, String arg) throws BadArgs;
aoqi@0 91 final boolean hasArg;
aoqi@0 92 final String[] aliases;
aoqi@0 93 }
aoqi@0 94
aoqi@0 95 static abstract class HiddenOption extends Option {
aoqi@0 96 HiddenOption(boolean hasArg, String... aliases) {
aoqi@0 97 super(hasArg, aliases);
aoqi@0 98 }
aoqi@0 99
aoqi@0 100 boolean isHidden() {
aoqi@0 101 return true;
aoqi@0 102 }
aoqi@0 103 }
aoqi@0 104
aoqi@0 105 static Option[] recognizedOptions = {
aoqi@0 106 new Option(false, "-h", "-?", "-help") {
aoqi@0 107 void process(JdepsTask task, String opt, String arg) {
aoqi@0 108 task.options.help = true;
aoqi@0 109 }
aoqi@0 110 },
aoqi@0 111 new Option(true, "-dotoutput") {
aoqi@0 112 void process(JdepsTask task, String opt, String arg) throws BadArgs {
aoqi@0 113 Path p = Paths.get(arg);
aoqi@0 114 if (Files.exists(p) && (!Files.isDirectory(p) || !Files.isWritable(p))) {
mchung@2538 115 throw new BadArgs("err.invalid.path", arg);
aoqi@0 116 }
aoqi@0 117 task.options.dotOutputDir = arg;
aoqi@0 118 }
aoqi@0 119 },
aoqi@0 120 new Option(false, "-s", "-summary") {
aoqi@0 121 void process(JdepsTask task, String opt, String arg) {
aoqi@0 122 task.options.showSummary = true;
mchung@2538 123 task.options.verbose = SUMMARY;
aoqi@0 124 }
aoqi@0 125 },
aoqi@0 126 new Option(false, "-v", "-verbose",
aoqi@0 127 "-verbose:package",
mchung@2538 128 "-verbose:class") {
aoqi@0 129 void process(JdepsTask task, String opt, String arg) throws BadArgs {
aoqi@0 130 switch (opt) {
aoqi@0 131 case "-v":
aoqi@0 132 case "-verbose":
mchung@2538 133 task.options.verbose = VERBOSE;
mchung@2538 134 task.options.filterSameArchive = false;
mchung@2538 135 task.options.filterSamePackage = false;
aoqi@0 136 break;
aoqi@0 137 case "-verbose:package":
mchung@2538 138 task.options.verbose = PACKAGE;
mchung@2538 139 break;
aoqi@0 140 case "-verbose:class":
mchung@2538 141 task.options.verbose = CLASS;
mchung@2538 142 break;
aoqi@0 143 default:
aoqi@0 144 throw new BadArgs("err.invalid.arg.for.option", opt);
aoqi@0 145 }
aoqi@0 146 }
aoqi@0 147 },
aoqi@0 148 new Option(true, "-cp", "-classpath") {
aoqi@0 149 void process(JdepsTask task, String opt, String arg) {
aoqi@0 150 task.options.classpath = arg;
aoqi@0 151 }
aoqi@0 152 },
aoqi@0 153 new Option(true, "-p", "-package") {
aoqi@0 154 void process(JdepsTask task, String opt, String arg) {
aoqi@0 155 task.options.packageNames.add(arg);
aoqi@0 156 }
aoqi@0 157 },
aoqi@0 158 new Option(true, "-e", "-regex") {
aoqi@0 159 void process(JdepsTask task, String opt, String arg) {
aoqi@0 160 task.options.regex = arg;
aoqi@0 161 }
aoqi@0 162 },
mchung@2538 163
mchung@2538 164 new Option(true, "-f", "-filter") {
mchung@2538 165 void process(JdepsTask task, String opt, String arg) {
mchung@2538 166 task.options.filterRegex = arg;
mchung@2538 167 }
mchung@2538 168 },
mchung@2538 169 new Option(false, "-filter:package",
mchung@2538 170 "-filter:archive",
mchung@2538 171 "-filter:none") {
mchung@2538 172 void process(JdepsTask task, String opt, String arg) {
mchung@2538 173 switch (opt) {
mchung@2538 174 case "-filter:package":
mchung@2538 175 task.options.filterSamePackage = true;
mchung@2538 176 task.options.filterSameArchive = false;
mchung@2538 177 break;
mchung@2538 178 case "-filter:archive":
mchung@2538 179 task.options.filterSameArchive = true;
mchung@2538 180 task.options.filterSamePackage = false;
mchung@2538 181 break;
mchung@2538 182 case "-filter:none":
mchung@2538 183 task.options.filterSameArchive = false;
mchung@2538 184 task.options.filterSamePackage = false;
mchung@2538 185 break;
mchung@2538 186 }
mchung@2538 187 }
mchung@2538 188 },
aoqi@0 189 new Option(true, "-include") {
aoqi@0 190 void process(JdepsTask task, String opt, String arg) throws BadArgs {
aoqi@0 191 task.options.includePattern = Pattern.compile(arg);
aoqi@0 192 }
aoqi@0 193 },
aoqi@0 194 new Option(false, "-P", "-profile") {
aoqi@0 195 void process(JdepsTask task, String opt, String arg) throws BadArgs {
aoqi@0 196 task.options.showProfile = true;
aoqi@0 197 if (Profile.getProfileCount() == 0) {
aoqi@0 198 throw new BadArgs("err.option.unsupported", opt, getMessage("err.profiles.msg"));
aoqi@0 199 }
aoqi@0 200 }
aoqi@0 201 },
aoqi@0 202 new Option(false, "-apionly") {
aoqi@0 203 void process(JdepsTask task, String opt, String arg) {
aoqi@0 204 task.options.apiOnly = true;
aoqi@0 205 }
aoqi@0 206 },
aoqi@0 207 new Option(false, "-R", "-recursive") {
aoqi@0 208 void process(JdepsTask task, String opt, String arg) {
aoqi@0 209 task.options.depth = 0;
mchung@2538 210 // turn off filtering
mchung@2538 211 task.options.filterSameArchive = false;
mchung@2538 212 task.options.filterSamePackage = false;
aoqi@0 213 }
aoqi@0 214 },
aoqi@0 215 new Option(false, "-jdkinternals") {
aoqi@0 216 void process(JdepsTask task, String opt, String arg) {
aoqi@0 217 task.options.findJDKInternals = true;
mchung@2538 218 task.options.verbose = CLASS;
aoqi@0 219 if (task.options.includePattern == null) {
aoqi@0 220 task.options.includePattern = Pattern.compile(".*");
aoqi@0 221 }
aoqi@0 222 }
aoqi@0 223 },
aoqi@0 224 new Option(false, "-version") {
aoqi@0 225 void process(JdepsTask task, String opt, String arg) {
aoqi@0 226 task.options.version = true;
aoqi@0 227 }
aoqi@0 228 },
aoqi@0 229 new HiddenOption(false, "-fullversion") {
aoqi@0 230 void process(JdepsTask task, String opt, String arg) {
aoqi@0 231 task.options.fullVersion = true;
aoqi@0 232 }
aoqi@0 233 },
aoqi@0 234 new HiddenOption(false, "-showlabel") {
aoqi@0 235 void process(JdepsTask task, String opt, String arg) {
aoqi@0 236 task.options.showLabel = true;
aoqi@0 237 }
aoqi@0 238 },
mchung@2539 239 new HiddenOption(false, "-q", "-quiet") {
mchung@2539 240 void process(JdepsTask task, String opt, String arg) {
mchung@2539 241 task.options.nowarning = true;
mchung@2539 242 }
mchung@2539 243 },
aoqi@0 244 new HiddenOption(true, "-depth") {
aoqi@0 245 void process(JdepsTask task, String opt, String arg) throws BadArgs {
aoqi@0 246 try {
aoqi@0 247 task.options.depth = Integer.parseInt(arg);
aoqi@0 248 } catch (NumberFormatException e) {
aoqi@0 249 throw new BadArgs("err.invalid.arg.for.option", opt);
aoqi@0 250 }
aoqi@0 251 }
aoqi@0 252 },
aoqi@0 253 };
aoqi@0 254
aoqi@0 255 private static final String PROGNAME = "jdeps";
aoqi@0 256 private final Options options = new Options();
mchung@2539 257 private final List<String> classes = new ArrayList<>();
aoqi@0 258
aoqi@0 259 private PrintWriter log;
aoqi@0 260 void setLog(PrintWriter out) {
aoqi@0 261 log = out;
aoqi@0 262 }
aoqi@0 263
aoqi@0 264 /**
aoqi@0 265 * Result codes.
aoqi@0 266 */
aoqi@0 267 static final int EXIT_OK = 0, // Completed with no errors.
aoqi@0 268 EXIT_ERROR = 1, // Completed but reported errors.
aoqi@0 269 EXIT_CMDERR = 2, // Bad command-line arguments
aoqi@0 270 EXIT_SYSERR = 3, // System error or resource exhaustion.
aoqi@0 271 EXIT_ABNORMAL = 4;// terminated abnormally
aoqi@0 272
aoqi@0 273 int run(String[] args) {
aoqi@0 274 if (log == null) {
aoqi@0 275 log = new PrintWriter(System.out);
aoqi@0 276 }
aoqi@0 277 try {
aoqi@0 278 handleOptions(args);
aoqi@0 279 if (options.help) {
aoqi@0 280 showHelp();
aoqi@0 281 }
aoqi@0 282 if (options.version || options.fullVersion) {
aoqi@0 283 showVersion(options.fullVersion);
aoqi@0 284 }
aoqi@0 285 if (classes.isEmpty() && options.includePattern == null) {
aoqi@0 286 if (options.help || options.version || options.fullVersion) {
aoqi@0 287 return EXIT_OK;
aoqi@0 288 } else {
aoqi@0 289 showHelp();
aoqi@0 290 return EXIT_CMDERR;
aoqi@0 291 }
aoqi@0 292 }
aoqi@0 293 if (options.regex != null && options.packageNames.size() > 0) {
aoqi@0 294 showHelp();
aoqi@0 295 return EXIT_CMDERR;
aoqi@0 296 }
aoqi@0 297 if (options.findJDKInternals &&
aoqi@0 298 (options.regex != null || options.packageNames.size() > 0 || options.showSummary)) {
aoqi@0 299 showHelp();
aoqi@0 300 return EXIT_CMDERR;
aoqi@0 301 }
mchung@2538 302 if (options.showSummary && options.verbose != SUMMARY) {
aoqi@0 303 showHelp();
aoqi@0 304 return EXIT_CMDERR;
aoqi@0 305 }
aoqi@0 306 boolean ok = run();
aoqi@0 307 return ok ? EXIT_OK : EXIT_ERROR;
aoqi@0 308 } catch (BadArgs e) {
aoqi@0 309 reportError(e.key, e.args);
aoqi@0 310 if (e.showUsage) {
aoqi@0 311 log.println(getMessage("main.usage.summary", PROGNAME));
aoqi@0 312 }
aoqi@0 313 return EXIT_CMDERR;
aoqi@0 314 } catch (IOException e) {
aoqi@0 315 return EXIT_ABNORMAL;
aoqi@0 316 } finally {
aoqi@0 317 log.flush();
aoqi@0 318 }
aoqi@0 319 }
aoqi@0 320
aoqi@0 321 private final List<Archive> sourceLocations = new ArrayList<>();
aoqi@0 322 private boolean run() throws IOException {
mchung@2538 323 // parse classfiles and find all dependencies
aoqi@0 324 findDependencies();
mchung@2538 325
mchung@2538 326 Analyzer analyzer = new Analyzer(options.verbose, new Analyzer.Filter() {
mchung@2538 327 @Override
mchung@2539 328 public boolean accepts(Location origin, Archive originArchive,
mchung@2539 329 Location target, Archive targetArchive)
mchung@2539 330 {
mchung@2538 331 if (options.findJDKInternals) {
mchung@2538 332 // accepts target that is JDK class but not exported
mchung@2538 333 return isJDKArchive(targetArchive) &&
mchung@2538 334 !((JDKArchive) targetArchive).isExported(target.getClassName());
mchung@2538 335 } else if (options.filterSameArchive) {
mchung@2538 336 // accepts origin and target that from different archive
mchung@2538 337 return originArchive != targetArchive;
mchung@2538 338 }
mchung@2538 339 return true;
mchung@2538 340 }
mchung@2538 341 });
mchung@2538 342
mchung@2538 343 // analyze the dependencies
aoqi@0 344 analyzer.run(sourceLocations);
mchung@2538 345
mchung@2538 346 // output result
aoqi@0 347 if (options.dotOutputDir != null) {
aoqi@0 348 Path dir = Paths.get(options.dotOutputDir);
aoqi@0 349 Files.createDirectories(dir);
aoqi@0 350 generateDotFiles(dir, analyzer);
aoqi@0 351 } else {
aoqi@0 352 printRawOutput(log, analyzer);
aoqi@0 353 }
mchung@2539 354
mchung@2539 355 if (options.findJDKInternals && !options.nowarning) {
mchung@2539 356 showReplacements(analyzer);
mchung@2539 357 }
aoqi@0 358 return true;
aoqi@0 359 }
aoqi@0 360
mchung@2538 361 private void generateSummaryDotFile(Path dir, Analyzer analyzer) throws IOException {
mchung@2538 362 // If verbose mode (-v or -verbose option),
mchung@2538 363 // the summary.dot file shows package-level dependencies.
mchung@2538 364 Analyzer.Type summaryType =
mchung@2538 365 (options.verbose == PACKAGE || options.verbose == SUMMARY) ? SUMMARY : PACKAGE;
aoqi@0 366 Path summary = dir.resolve("summary.dot");
mchung@2538 367 try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary));
mchung@2538 368 SummaryDotFile dotfile = new SummaryDotFile(sw, summaryType)) {
mchung@2538 369 for (Archive archive : sourceLocations) {
mchung@2538 370 if (!archive.isEmpty()) {
mchung@2538 371 if (options.verbose == PACKAGE || options.verbose == SUMMARY) {
mchung@2538 372 if (options.showLabel) {
mchung@2538 373 // build labels listing package-level dependencies
mchung@2538 374 analyzer.visitDependences(archive, dotfile.labelBuilder(), PACKAGE);
mchung@2538 375 }
mchung@2538 376 }
mchung@2538 377 analyzer.visitDependences(archive, dotfile, summaryType);
mchung@2538 378 }
aoqi@0 379 }
aoqi@0 380 }
mchung@2538 381 }
mchung@2538 382
mchung@2538 383 private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException {
aoqi@0 384 // output individual .dot file for each archive
mchung@2538 385 if (options.verbose != SUMMARY) {
aoqi@0 386 for (Archive archive : sourceLocations) {
aoqi@0 387 if (analyzer.hasDependences(archive)) {
mchung@2538 388 Path dotfile = dir.resolve(archive.getName() + ".dot");
aoqi@0 389 try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile));
aoqi@0 390 DotFileFormatter formatter = new DotFileFormatter(pw, archive)) {
aoqi@0 391 analyzer.visitDependences(archive, formatter);
aoqi@0 392 }
aoqi@0 393 }
aoqi@0 394 }
aoqi@0 395 }
mchung@2538 396 // generate summary dot file
mchung@2538 397 generateSummaryDotFile(dir, analyzer);
aoqi@0 398 }
aoqi@0 399
aoqi@0 400 private void printRawOutput(PrintWriter writer, Analyzer analyzer) {
mchung@2538 401 RawOutputFormatter depFormatter = new RawOutputFormatter(writer);
mchung@2538 402 RawSummaryFormatter summaryFormatter = new RawSummaryFormatter(writer);
aoqi@0 403 for (Archive archive : sourceLocations) {
mchung@2538 404 if (!archive.isEmpty()) {
mchung@2538 405 analyzer.visitDependences(archive, summaryFormatter, SUMMARY);
mchung@2538 406 if (analyzer.hasDependences(archive) && options.verbose != SUMMARY) {
mchung@2538 407 analyzer.visitDependences(archive, depFormatter);
mchung@2538 408 }
aoqi@0 409 }
aoqi@0 410 }
aoqi@0 411 }
mchung@2538 412
aoqi@0 413 private boolean isValidClassName(String name) {
aoqi@0 414 if (!Character.isJavaIdentifierStart(name.charAt(0))) {
aoqi@0 415 return false;
aoqi@0 416 }
aoqi@0 417 for (int i=1; i < name.length(); i++) {
aoqi@0 418 char c = name.charAt(i);
aoqi@0 419 if (c != '.' && !Character.isJavaIdentifierPart(c)) {
aoqi@0 420 return false;
aoqi@0 421 }
aoqi@0 422 }
aoqi@0 423 return true;
aoqi@0 424 }
aoqi@0 425
mchung@2538 426 /*
mchung@2538 427 * Dep Filter configured based on the input jdeps option
mchung@2538 428 * 1. -p and -regex to match target dependencies
mchung@2538 429 * 2. -filter:package to filter out same-package dependencies
mchung@2538 430 *
mchung@2538 431 * This filter is applied when jdeps parses the class files
mchung@2538 432 * and filtered dependencies are not stored in the Analyzer.
mchung@2538 433 *
mchung@2538 434 * -filter:archive is applied later in the Analyzer as the
mchung@2538 435 * containing archive of a target class may not be known until
mchung@2538 436 * the entire archive
mchung@2538 437 */
mchung@2538 438 class DependencyFilter implements Dependency.Filter {
mchung@2538 439 final Dependency.Filter filter;
mchung@2538 440 final Pattern filterPattern;
mchung@2538 441 DependencyFilter() {
mchung@2538 442 if (options.regex != null) {
mchung@2538 443 this.filter = Dependencies.getRegexFilter(Pattern.compile(options.regex));
mchung@2538 444 } else if (options.packageNames.size() > 0) {
mchung@2538 445 this.filter = Dependencies.getPackageFilter(options.packageNames, false);
mchung@2538 446 } else {
mchung@2538 447 this.filter = null;
mchung@2538 448 }
mchung@2538 449
mchung@2538 450 this.filterPattern =
mchung@2538 451 options.filterRegex != null ? Pattern.compile(options.filterRegex) : null;
mchung@2538 452 }
mchung@2538 453 @Override
mchung@2538 454 public boolean accepts(Dependency d) {
mchung@2538 455 if (d.getOrigin().equals(d.getTarget())) {
mchung@2538 456 return false;
mchung@2538 457 }
mchung@2538 458 String pn = d.getTarget().getPackageName();
mchung@2538 459 if (options.filterSamePackage && d.getOrigin().getPackageName().equals(pn)) {
mchung@2538 460 return false;
mchung@2538 461 }
mchung@2538 462
mchung@2538 463 if (filterPattern != null && filterPattern.matcher(pn).matches()) {
mchung@2538 464 return false;
mchung@2538 465 }
mchung@2538 466 return filter != null ? filter.accepts(d) : true;
aoqi@0 467 }
aoqi@0 468 }
aoqi@0 469
mchung@2538 470 /**
mchung@2538 471 * Tests if the given class matches the pattern given in the -include option
mchung@2538 472 * or if it's a public class if -apionly option is specified
mchung@2538 473 */
aoqi@0 474 private boolean matches(String classname, AccessFlags flags) {
aoqi@0 475 if (options.apiOnly && !flags.is(AccessFlags.ACC_PUBLIC)) {
aoqi@0 476 return false;
aoqi@0 477 } else if (options.includePattern != null) {
aoqi@0 478 return options.includePattern.matcher(classname.replace('/', '.')).matches();
aoqi@0 479 } else {
aoqi@0 480 return true;
aoqi@0 481 }
aoqi@0 482 }
aoqi@0 483
aoqi@0 484 private void findDependencies() throws IOException {
aoqi@0 485 Dependency.Finder finder =
aoqi@0 486 options.apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED)
aoqi@0 487 : Dependencies.getClassDependencyFinder();
mchung@2538 488 Dependency.Filter filter = new DependencyFilter();
aoqi@0 489
aoqi@0 490 List<Archive> archives = new ArrayList<>();
aoqi@0 491 Deque<String> roots = new LinkedList<>();
aoqi@0 492 for (String s : classes) {
aoqi@0 493 Path p = Paths.get(s);
aoqi@0 494 if (Files.exists(p)) {
mchung@2538 495 archives.add(Archive.getInstance(p));
aoqi@0 496 } else {
aoqi@0 497 if (isValidClassName(s)) {
aoqi@0 498 roots.add(s);
aoqi@0 499 } else {
aoqi@0 500 warning("warn.invalid.arg", s);
aoqi@0 501 }
aoqi@0 502 }
aoqi@0 503 }
aoqi@0 504 sourceLocations.addAll(archives);
aoqi@0 505
aoqi@0 506 List<Archive> classpaths = new ArrayList<>(); // for class file lookup
aoqi@0 507 classpaths.addAll(getClassPathArchives(options.classpath));
aoqi@0 508 if (options.includePattern != null) {
aoqi@0 509 archives.addAll(classpaths);
aoqi@0 510 }
aoqi@0 511 classpaths.addAll(PlatformClassPath.getArchives());
aoqi@0 512
aoqi@0 513 // add all classpath archives to the source locations for reporting
aoqi@0 514 sourceLocations.addAll(classpaths);
aoqi@0 515
aoqi@0 516 // Work queue of names of classfiles to be searched.
aoqi@0 517 // Entries will be unique, and for classes that do not yet have
aoqi@0 518 // dependencies in the results map.
aoqi@0 519 Deque<String> deque = new LinkedList<>();
aoqi@0 520 Set<String> doneClasses = new HashSet<>();
aoqi@0 521
aoqi@0 522 // get the immediate dependencies of the input files
aoqi@0 523 for (Archive a : archives) {
aoqi@0 524 for (ClassFile cf : a.reader().getClassFiles()) {
aoqi@0 525 String classFileName;
aoqi@0 526 try {
aoqi@0 527 classFileName = cf.getName();
aoqi@0 528 } catch (ConstantPoolException e) {
aoqi@0 529 throw new ClassFileError(e);
aoqi@0 530 }
aoqi@0 531
mchung@2538 532 // tests if this class matches the -include or -apiOnly option if specified
mchung@2538 533 if (!matches(classFileName, cf.access_flags)) {
mchung@2538 534 continue;
mchung@2538 535 }
mchung@2538 536
mchung@2538 537 if (!doneClasses.contains(classFileName)) {
mchung@2538 538 doneClasses.add(classFileName);
mchung@2538 539 }
mchung@2538 540
mchung@2538 541 for (Dependency d : finder.findDependencies(cf)) {
mchung@2538 542 if (filter.accepts(d)) {
mchung@2538 543 String cn = d.getTarget().getName();
mchung@2538 544 if (!doneClasses.contains(cn) && !deque.contains(cn)) {
mchung@2538 545 deque.add(cn);
mchung@2538 546 }
mchung@2538 547 a.addClass(d.getOrigin(), d.getTarget());
aoqi@0 548 }
mchung@2538 549 }
mchung@2538 550 for (String name : a.reader().skippedEntries()) {
mchung@2538 551 warning("warn.skipped.entry", name, a.getPathName());
aoqi@0 552 }
aoqi@0 553 }
aoqi@0 554 }
aoqi@0 555
aoqi@0 556 // add Archive for looking up classes from the classpath
aoqi@0 557 // for transitive dependency analysis
aoqi@0 558 Deque<String> unresolved = roots;
aoqi@0 559 int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE;
aoqi@0 560 do {
aoqi@0 561 String name;
aoqi@0 562 while ((name = unresolved.poll()) != null) {
aoqi@0 563 if (doneClasses.contains(name)) {
aoqi@0 564 continue;
aoqi@0 565 }
aoqi@0 566 ClassFile cf = null;
aoqi@0 567 for (Archive a : classpaths) {
aoqi@0 568 cf = a.reader().getClassFile(name);
aoqi@0 569 if (cf != null) {
aoqi@0 570 String classFileName;
aoqi@0 571 try {
aoqi@0 572 classFileName = cf.getName();
aoqi@0 573 } catch (ConstantPoolException e) {
aoqi@0 574 throw new ClassFileError(e);
aoqi@0 575 }
aoqi@0 576 if (!doneClasses.contains(classFileName)) {
aoqi@0 577 // if name is a fully-qualified class name specified
aoqi@0 578 // from command-line, this class might already be parsed
aoqi@0 579 doneClasses.add(classFileName);
mchung@2538 580 // process @jdk.Exported for JDK classes
mchung@2538 581 if (isJDKArchive(a)) {
mchung@2538 582 ((JDKArchive)a).processJdkExported(cf);
mchung@2538 583 }
aoqi@0 584 for (Dependency d : finder.findDependencies(cf)) {
aoqi@0 585 if (depth == 0) {
aoqi@0 586 // ignore the dependency
aoqi@0 587 a.addClass(d.getOrigin());
aoqi@0 588 break;
aoqi@0 589 } else if (filter.accepts(d)) {
aoqi@0 590 a.addClass(d.getOrigin(), d.getTarget());
aoqi@0 591 String cn = d.getTarget().getName();
aoqi@0 592 if (!doneClasses.contains(cn) && !deque.contains(cn)) {
aoqi@0 593 deque.add(cn);
aoqi@0 594 }
aoqi@0 595 }
aoqi@0 596 }
aoqi@0 597 }
aoqi@0 598 break;
aoqi@0 599 }
aoqi@0 600 }
aoqi@0 601 if (cf == null) {
aoqi@0 602 doneClasses.add(name);
aoqi@0 603 }
aoqi@0 604 }
aoqi@0 605 unresolved = deque;
aoqi@0 606 deque = new LinkedList<>();
aoqi@0 607 } while (!unresolved.isEmpty() && depth-- > 0);
aoqi@0 608 }
aoqi@0 609
aoqi@0 610 public void handleOptions(String[] args) throws BadArgs {
aoqi@0 611 // process options
aoqi@0 612 for (int i=0; i < args.length; i++) {
aoqi@0 613 if (args[i].charAt(0) == '-') {
aoqi@0 614 String name = args[i];
aoqi@0 615 Option option = getOption(name);
aoqi@0 616 String param = null;
aoqi@0 617 if (option.hasArg) {
aoqi@0 618 if (name.startsWith("-") && name.indexOf('=') > 0) {
aoqi@0 619 param = name.substring(name.indexOf('=') + 1, name.length());
aoqi@0 620 } else if (i + 1 < args.length) {
aoqi@0 621 param = args[++i];
aoqi@0 622 }
aoqi@0 623 if (param == null || param.isEmpty() || param.charAt(0) == '-') {
aoqi@0 624 throw new BadArgs("err.missing.arg", name).showUsage(true);
aoqi@0 625 }
aoqi@0 626 }
aoqi@0 627 option.process(this, name, param);
aoqi@0 628 if (option.ignoreRest()) {
aoqi@0 629 i = args.length;
aoqi@0 630 }
aoqi@0 631 } else {
aoqi@0 632 // process rest of the input arguments
aoqi@0 633 for (; i < args.length; i++) {
aoqi@0 634 String name = args[i];
aoqi@0 635 if (name.charAt(0) == '-') {
aoqi@0 636 throw new BadArgs("err.option.after.class", name).showUsage(true);
aoqi@0 637 }
aoqi@0 638 classes.add(name);
aoqi@0 639 }
aoqi@0 640 }
aoqi@0 641 }
aoqi@0 642 }
aoqi@0 643
aoqi@0 644 private Option getOption(String name) throws BadArgs {
aoqi@0 645 for (Option o : recognizedOptions) {
aoqi@0 646 if (o.matches(name)) {
aoqi@0 647 return o;
aoqi@0 648 }
aoqi@0 649 }
aoqi@0 650 throw new BadArgs("err.unknown.option", name).showUsage(true);
aoqi@0 651 }
aoqi@0 652
aoqi@0 653 private void reportError(String key, Object... args) {
aoqi@0 654 log.println(getMessage("error.prefix") + " " + getMessage(key, args));
aoqi@0 655 }
aoqi@0 656
aoqi@0 657 private void warning(String key, Object... args) {
aoqi@0 658 log.println(getMessage("warn.prefix") + " " + getMessage(key, args));
aoqi@0 659 }
aoqi@0 660
aoqi@0 661 private void showHelp() {
aoqi@0 662 log.println(getMessage("main.usage", PROGNAME));
aoqi@0 663 for (Option o : recognizedOptions) {
aoqi@0 664 String name = o.aliases[0].substring(1); // there must always be at least one name
aoqi@0 665 name = name.charAt(0) == '-' ? name.substring(1) : name;
mchung@2538 666 if (o.isHidden() || name.equals("h") || name.startsWith("filter:")) {
aoqi@0 667 continue;
aoqi@0 668 }
aoqi@0 669 log.println(getMessage("main.opt." + name));
aoqi@0 670 }
aoqi@0 671 }
aoqi@0 672
aoqi@0 673 private void showVersion(boolean full) {
aoqi@0 674 log.println(version(full ? "full" : "release"));
aoqi@0 675 }
aoqi@0 676
aoqi@0 677 private String version(String key) {
aoqi@0 678 // key=version: mm.nn.oo[-milestone]
aoqi@0 679 // key=full: mm.mm.oo[-milestone]-build
aoqi@0 680 if (ResourceBundleHelper.versionRB == null) {
aoqi@0 681 return System.getProperty("java.version");
aoqi@0 682 }
aoqi@0 683 try {
aoqi@0 684 return ResourceBundleHelper.versionRB.getString(key);
aoqi@0 685 } catch (MissingResourceException e) {
aoqi@0 686 return getMessage("version.unknown", System.getProperty("java.version"));
aoqi@0 687 }
aoqi@0 688 }
aoqi@0 689
aoqi@0 690 static String getMessage(String key, Object... args) {
aoqi@0 691 try {
aoqi@0 692 return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);
aoqi@0 693 } catch (MissingResourceException e) {
aoqi@0 694 throw new InternalError("Missing message: " + key);
aoqi@0 695 }
aoqi@0 696 }
aoqi@0 697
aoqi@0 698 private static class Options {
aoqi@0 699 boolean help;
aoqi@0 700 boolean version;
aoqi@0 701 boolean fullVersion;
aoqi@0 702 boolean showProfile;
aoqi@0 703 boolean showSummary;
aoqi@0 704 boolean apiOnly;
aoqi@0 705 boolean showLabel;
aoqi@0 706 boolean findJDKInternals;
mchung@2539 707 boolean nowarning;
mchung@2538 708 // default is to show package-level dependencies
mchung@2538 709 // and filter references from same package
mchung@2538 710 Analyzer.Type verbose = PACKAGE;
mchung@2538 711 boolean filterSamePackage = true;
mchung@2538 712 boolean filterSameArchive = false;
mchung@2538 713 String filterRegex;
aoqi@0 714 String dotOutputDir;
aoqi@0 715 String classpath = "";
aoqi@0 716 int depth = 1;
aoqi@0 717 Set<String> packageNames = new HashSet<>();
aoqi@0 718 String regex; // apply to the dependences
aoqi@0 719 Pattern includePattern; // apply to classes
aoqi@0 720 }
aoqi@0 721 private static class ResourceBundleHelper {
aoqi@0 722 static final ResourceBundle versionRB;
aoqi@0 723 static final ResourceBundle bundle;
mchung@2539 724 static final ResourceBundle jdkinternals;
aoqi@0 725
aoqi@0 726 static {
aoqi@0 727 Locale locale = Locale.getDefault();
aoqi@0 728 try {
aoqi@0 729 bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale);
aoqi@0 730 } catch (MissingResourceException e) {
aoqi@0 731 throw new InternalError("Cannot find jdeps resource bundle for locale " + locale);
aoqi@0 732 }
aoqi@0 733 try {
aoqi@0 734 versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version");
aoqi@0 735 } catch (MissingResourceException e) {
aoqi@0 736 throw new InternalError("version.resource.missing");
aoqi@0 737 }
mchung@2539 738 try {
mchung@2539 739 jdkinternals = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdkinternals");
mchung@2539 740 } catch (MissingResourceException e) {
mchung@2539 741 throw new InternalError("Cannot find jdkinternals resource bundle");
aoqi@0 742 }
aoqi@0 743 }
aoqi@0 744 }
aoqi@0 745
aoqi@0 746 private List<Archive> getClassPathArchives(String paths) throws IOException {
aoqi@0 747 List<Archive> result = new ArrayList<>();
aoqi@0 748 if (paths.isEmpty()) {
aoqi@0 749 return result;
aoqi@0 750 }
aoqi@0 751 for (String p : paths.split(File.pathSeparator)) {
aoqi@0 752 if (p.length() > 0) {
aoqi@0 753 List<Path> files = new ArrayList<>();
aoqi@0 754 // wildcard to parse all JAR files e.g. -classpath dir/*
aoqi@0 755 int i = p.lastIndexOf(".*");
aoqi@0 756 if (i > 0) {
aoqi@0 757 Path dir = Paths.get(p.substring(0, i));
aoqi@0 758 try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) {
aoqi@0 759 for (Path entry : stream) {
aoqi@0 760 files.add(entry);
aoqi@0 761 }
aoqi@0 762 }
aoqi@0 763 } else {
aoqi@0 764 files.add(Paths.get(p));
aoqi@0 765 }
aoqi@0 766 for (Path f : files) {
aoqi@0 767 if (Files.exists(f)) {
mchung@2538 768 result.add(Archive.getInstance(f));
aoqi@0 769 }
aoqi@0 770 }
aoqi@0 771 }
aoqi@0 772 }
aoqi@0 773 return result;
aoqi@0 774 }
aoqi@0 775
aoqi@0 776 class RawOutputFormatter implements Analyzer.Visitor {
aoqi@0 777 private final PrintWriter writer;
mchung@2538 778 private String pkg = "";
aoqi@0 779 RawOutputFormatter(PrintWriter writer) {
aoqi@0 780 this.writer = writer;
aoqi@0 781 }
aoqi@0 782 @Override
mchung@2538 783 public void visitDependence(String origin, Archive originArchive,
mchung@2538 784 String target, Archive targetArchive) {
mchung@2538 785 String tag = toTag(target, targetArchive);
mchung@2538 786 if (options.verbose == VERBOSE) {
mchung@2538 787 writer.format(" %-50s -> %-50s %s%n", origin, target, tag);
aoqi@0 788 } else {
aoqi@0 789 if (!origin.equals(pkg)) {
aoqi@0 790 pkg = origin;
mchung@2538 791 writer.format(" %s (%s)%n", origin, originArchive.getName());
aoqi@0 792 }
mchung@2538 793 writer.format(" -> %-50s %s%n", target, tag);
aoqi@0 794 }
aoqi@0 795 }
aoqi@0 796 }
aoqi@0 797
mchung@2538 798 class RawSummaryFormatter implements Analyzer.Visitor {
mchung@2538 799 private final PrintWriter writer;
mchung@2538 800 RawSummaryFormatter(PrintWriter writer) {
mchung@2538 801 this.writer = writer;
mchung@2538 802 }
mchung@2538 803 @Override
mchung@2538 804 public void visitDependence(String origin, Archive originArchive,
mchung@2538 805 String target, Archive targetArchive) {
mchung@2538 806 writer.format("%s -> %s", originArchive.getName(), targetArchive.getPathName());
mchung@2538 807 if (options.showProfile && JDKArchive.isProfileArchive(targetArchive)) {
mchung@2538 808 writer.format(" (%s)", target);
mchung@2538 809 }
mchung@2538 810 writer.format("%n");
mchung@2538 811 }
mchung@2538 812 }
mchung@2538 813
mchung@2538 814 class DotFileFormatter implements Analyzer.Visitor, AutoCloseable {
aoqi@0 815 private final PrintWriter writer;
aoqi@0 816 private final String name;
aoqi@0 817 DotFileFormatter(PrintWriter writer, Archive archive) {
aoqi@0 818 this.writer = writer;
mchung@2538 819 this.name = archive.getName();
aoqi@0 820 writer.format("digraph \"%s\" {%n", name);
aoqi@0 821 writer.format(" // Path: %s%n", archive.getPathName());
aoqi@0 822 }
aoqi@0 823
aoqi@0 824 @Override
aoqi@0 825 public void close() {
aoqi@0 826 writer.println("}");
aoqi@0 827 }
aoqi@0 828
aoqi@0 829 @Override
mchung@2538 830 public void visitDependence(String origin, Archive originArchive,
mchung@2538 831 String target, Archive targetArchive) {
mchung@2538 832 String tag = toTag(target, targetArchive);
mchung@2538 833 writer.format(" %-50s -> \"%s\";%n",
mchung@2538 834 String.format("\"%s\"", origin),
mchung@2538 835 tag.isEmpty() ? target
mchung@2538 836 : String.format("%s (%s)", target, tag));
aoqi@0 837 }
aoqi@0 838 }
aoqi@0 839
mchung@2538 840 class SummaryDotFile implements Analyzer.Visitor, AutoCloseable {
mchung@2538 841 private final PrintWriter writer;
mchung@2538 842 private final Analyzer.Type type;
mchung@2538 843 private final Map<Archive, Map<Archive,StringBuilder>> edges = new HashMap<>();
mchung@2538 844 SummaryDotFile(PrintWriter writer, Analyzer.Type type) {
mchung@2538 845 this.writer = writer;
mchung@2538 846 this.type = type;
mchung@2538 847 writer.format("digraph \"summary\" {%n");
mchung@2538 848 }
mchung@2538 849
aoqi@0 850 @Override
mchung@2538 851 public void close() {
mchung@2538 852 writer.println("}");
mchung@2538 853 }
mchung@2538 854
mchung@2538 855 @Override
mchung@2538 856 public void visitDependence(String origin, Archive originArchive,
mchung@2538 857 String target, Archive targetArchive) {
mchung@2538 858 String targetName = type == PACKAGE ? target : targetArchive.getName();
mchung@2538 859 if (type == PACKAGE) {
mchung@2538 860 String tag = toTag(target, targetArchive, type);
mchung@2538 861 if (!tag.isEmpty())
mchung@2538 862 targetName += " (" + tag + ")";
mchung@2538 863 } else if (options.showProfile && JDKArchive.isProfileArchive(targetArchive)) {
mchung@2538 864 targetName += " (" + target + ")";
aoqi@0 865 }
mchung@2538 866 String label = getLabel(originArchive, targetArchive);
mchung@2538 867 writer.format(" %-50s -> \"%s\"%s;%n",
mchung@2538 868 String.format("\"%s\"", origin), targetName, label);
aoqi@0 869 }
mchung@2538 870
mchung@2538 871 String getLabel(Archive origin, Archive target) {
mchung@2538 872 if (edges.isEmpty())
mchung@2538 873 return "";
mchung@2538 874
mchung@2538 875 StringBuilder label = edges.get(origin).get(target);
mchung@2538 876 return label == null ? "" : String.format(" [label=\"%s\",fontsize=9]", label.toString());
mchung@2538 877 }
mchung@2538 878
mchung@2538 879 Analyzer.Visitor labelBuilder() {
mchung@2538 880 // show the package-level dependencies as labels in the dot graph
mchung@2538 881 return new Analyzer.Visitor() {
mchung@2538 882 @Override
mchung@2538 883 public void visitDependence(String origin, Archive originArchive,
mchung@2538 884 String target, Archive targetArchive)
mchung@2538 885 {
mchung@2538 886 Map<Archive,StringBuilder> labels = edges.get(originArchive);
mchung@2538 887 if (!edges.containsKey(originArchive)) {
mchung@2538 888 edges.put(originArchive, labels = new HashMap<>());
mchung@2538 889 }
mchung@2538 890 StringBuilder sb = labels.get(targetArchive);
mchung@2538 891 if (sb == null) {
mchung@2538 892 labels.put(targetArchive, sb = new StringBuilder());
mchung@2538 893 }
mchung@2538 894 String tag = toTag(target, targetArchive, PACKAGE);
mchung@2538 895 addLabel(sb, origin, target, tag);
mchung@2214 896 }
mchung@2538 897
mchung@2538 898 void addLabel(StringBuilder label, String origin, String target, String tag) {
mchung@2538 899 label.append(origin).append(" -> ").append(target);
mchung@2538 900 if (!tag.isEmpty()) {
mchung@2538 901 label.append(" (" + tag + ")");
mchung@2538 902 }
mchung@2538 903 label.append("\\n");
mchung@2538 904 }
mchung@2538 905 };
mchung@2214 906 }
mchung@2214 907 }
mchung@2214 908
mchung@2538 909 /**
mchung@2538 910 * Test if the given archive is part of the JDK
mchung@2538 911 */
mchung@2538 912 private boolean isJDKArchive(Archive archive) {
mchung@2538 913 return JDKArchive.class.isInstance(archive);
mchung@2538 914 }
mchung@2538 915
mchung@2538 916 /**
mchung@2538 917 * If the given archive is JDK archive, this method returns the profile name
mchung@2538 918 * only if -profile option is specified; it accesses a private JDK API and
mchung@2538 919 * the returned value will have "JDK internal API" prefix
mchung@2538 920 *
mchung@2538 921 * For non-JDK archives, this method returns the file name of the archive.
mchung@2538 922 */
mchung@2538 923 private String toTag(String name, Archive source, Analyzer.Type type) {
mchung@2538 924 if (!isJDKArchive(source)) {
mchung@2538 925 return source.getName();
mchung@2214 926 }
mchung@2214 927
mchung@2538 928 JDKArchive jdk = (JDKArchive)source;
mchung@2538 929 boolean isExported = false;
mchung@2538 930 if (type == CLASS || type == VERBOSE) {
mchung@2538 931 isExported = jdk.isExported(name);
mchung@2538 932 } else {
mchung@2538 933 isExported = jdk.isExportedPackage(name);
mchung@2214 934 }
mchung@2538 935 Profile p = getProfile(name, type);
mchung@2538 936 if (isExported) {
mchung@2538 937 // exported API
mchung@2538 938 return options.showProfile && p != null ? p.profileName() : "";
mchung@2538 939 } else {
mchung@2538 940 return "JDK internal API (" + source.getName() + ")";
mchung@2214 941 }
mchung@2214 942 }
mchung@2538 943
mchung@2538 944 private String toTag(String name, Archive source) {
mchung@2538 945 return toTag(name, source, options.verbose);
mchung@2538 946 }
mchung@2538 947
mchung@2538 948 private Profile getProfile(String name, Analyzer.Type type) {
mchung@2538 949 String pn = name;
mchung@2538 950 if (type == CLASS || type == VERBOSE) {
mchung@2538 951 int i = name.lastIndexOf('.');
mchung@2538 952 pn = i > 0 ? name.substring(0, i) : "";
mchung@2214 953 }
mchung@2538 954 return Profile.getProfile(pn);
mchung@2214 955 }
mchung@2539 956
mchung@2539 957 /**
mchung@2539 958 * Returns the recommended replacement API for the given classname;
mchung@2539 959 * or return null if replacement API is not known.
mchung@2539 960 */
mchung@2539 961 private String replacementFor(String cn) {
mchung@2539 962 String name = cn;
mchung@2539 963 String value = null;
mchung@2539 964 while (value == null && name != null) {
mchung@2539 965 try {
mchung@2539 966 value = ResourceBundleHelper.jdkinternals.getString(name);
mchung@2539 967 } catch (MissingResourceException e) {
mchung@2539 968 // go up one subpackage level
mchung@2539 969 int i = name.lastIndexOf('.');
mchung@2539 970 name = i > 0 ? name.substring(0, i) : null;
mchung@2539 971 }
mchung@2539 972 }
mchung@2539 973 return value;
mchung@2539 974 };
mchung@2539 975
mchung@2539 976 private void showReplacements(Analyzer analyzer) {
mchung@2539 977 Map<String,String> jdkinternals = new TreeMap<>();
mchung@2539 978 boolean useInternals = false;
mchung@2539 979 for (Archive source : sourceLocations) {
mchung@2539 980 useInternals = useInternals || analyzer.hasDependences(source);
mchung@2539 981 for (String cn : analyzer.dependences(source)) {
mchung@2539 982 String repl = replacementFor(cn);
mchung@2539 983 if (repl != null && !jdkinternals.containsKey(cn)) {
mchung@2539 984 jdkinternals.put(cn, repl);
aoqi@0 985 }
aoqi@0 986 }
aoqi@0 987 }
mchung@2539 988 if (useInternals) {
mchung@2539 989 log.println();
mchung@2539 990 warning("warn.replace.useJDKInternals", getMessage("jdeps.wiki.url"));
aoqi@0 991 }
mchung@2539 992 if (!jdkinternals.isEmpty()) {
mchung@2539 993 log.println();
mchung@2539 994 log.format("%-40s %s%n", "JDK Internal API", "Suggested Replacement");
mchung@2539 995 log.format("%-40s %s%n", "----------------", "---------------------");
mchung@2539 996 for (Map.Entry<String,String> e : jdkinternals.entrySet()) {
mchung@2539 997 log.format("%-40s %s%n", e.getKey(), e.getValue());
aoqi@0 998 }
aoqi@0 999 }
aoqi@0 1000
aoqi@0 1001 }
aoqi@0 1002 }

mercurial