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

Wed, 30 Oct 2013 08:35:52 -0700

author
mchung
date
Wed, 30 Oct 2013 08:35:52 -0700
changeset 2172
aa91bc6e8480
parent 2139
defadd528513
child 2214
4a2ed1900428
permissions
-rw-r--r--

8027481: jdeps to handle classes with the same package name and correct profile for javax.crypto.*
Reviewed-by: alanb, dfuchs

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

mercurial