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

Thu, 17 Oct 2013 13:19:48 -0700

author
mchung
date
Thu, 17 Oct 2013 13:19:48 -0700
changeset 2139
defadd528513
parent 1648
a03c4a86ea2b
child 2172
aa91bc6e8480
permissions
-rw-r--r--

8015912: jdeps support to output in dot file format
8026255: Switch jdeps to follow traditional Java option style
Reviewed-by: alanb

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@2139 193 new HiddenOption(true, "-depth") {
mchung@1472 194 void process(JdepsTask task, String opt, String arg) throws BadArgs {
mchung@1472 195 try {
mchung@1472 196 task.options.depth = Integer.parseInt(arg);
mchung@1472 197 } catch (NumberFormatException e) {
jjg@1648 198 throw new BadArgs("err.invalid.arg.for.option", opt);
mchung@1472 199 }
mchung@1472 200 }
mchung@1472 201 },
mchung@1472 202 };
mchung@1472 203
mchung@1472 204 private static final String PROGNAME = "jdeps";
mchung@1472 205 private final Options options = new Options();
mchung@1472 206 private final List<String> classes = new ArrayList<String>();
mchung@1472 207
mchung@1472 208 private PrintWriter log;
mchung@1472 209 void setLog(PrintWriter out) {
mchung@1472 210 log = out;
mchung@1472 211 }
mchung@1472 212
mchung@1472 213 /**
mchung@1472 214 * Result codes.
mchung@1472 215 */
mchung@1472 216 static final int EXIT_OK = 0, // Completed with no errors.
mchung@1472 217 EXIT_ERROR = 1, // Completed but reported errors.
mchung@1472 218 EXIT_CMDERR = 2, // Bad command-line arguments
mchung@1472 219 EXIT_SYSERR = 3, // System error or resource exhaustion.
mchung@1472 220 EXIT_ABNORMAL = 4;// terminated abnormally
mchung@1472 221
mchung@1472 222 int run(String[] args) {
mchung@1472 223 if (log == null) {
mchung@1472 224 log = new PrintWriter(System.out);
mchung@1472 225 }
mchung@1472 226 try {
mchung@1472 227 handleOptions(args);
mchung@1472 228 if (options.help) {
mchung@1472 229 showHelp();
mchung@1472 230 }
mchung@1472 231 if (options.version || options.fullVersion) {
mchung@1472 232 showVersion(options.fullVersion);
mchung@1472 233 }
mchung@2139 234 if (classes.isEmpty() && options.includePattern == null) {
mchung@1472 235 if (options.help || options.version || options.fullVersion) {
mchung@1472 236 return EXIT_OK;
mchung@1472 237 } else {
mchung@1472 238 showHelp();
mchung@1472 239 return EXIT_CMDERR;
mchung@1472 240 }
mchung@1472 241 }
mchung@1472 242 if (options.regex != null && options.packageNames.size() > 0) {
mchung@1472 243 showHelp();
mchung@1472 244 return EXIT_CMDERR;
mchung@1472 245 }
mchung@1577 246 if (options.showSummary && options.verbose != Analyzer.Type.SUMMARY) {
mchung@1472 247 showHelp();
mchung@1472 248 return EXIT_CMDERR;
mchung@1472 249 }
mchung@1472 250 boolean ok = run();
mchung@1472 251 return ok ? EXIT_OK : EXIT_ERROR;
mchung@1472 252 } catch (BadArgs e) {
mchung@1472 253 reportError(e.key, e.args);
mchung@1472 254 if (e.showUsage) {
mchung@1472 255 log.println(getMessage("main.usage.summary", PROGNAME));
mchung@1472 256 }
mchung@1472 257 return EXIT_CMDERR;
mchung@1472 258 } catch (IOException e) {
mchung@1472 259 return EXIT_ABNORMAL;
mchung@1472 260 } finally {
mchung@1472 261 log.flush();
mchung@1472 262 }
mchung@1472 263 }
mchung@1472 264
mchung@2139 265 private final List<Archive> sourceLocations = new ArrayList<>();
mchung@1472 266 private boolean run() throws IOException {
mchung@1472 267 findDependencies();
mchung@1577 268 Analyzer analyzer = new Analyzer(options.verbose);
mchung@1577 269 analyzer.run(sourceLocations);
mchung@2139 270 if (options.dotOutputDir != null) {
mchung@2139 271 Path dir = Paths.get(options.dotOutputDir);
mchung@2139 272 Files.createDirectories(dir);
mchung@2139 273 generateDotFiles(dir, analyzer);
mchung@1577 274 } else {
mchung@2139 275 printRawOutput(log, analyzer);
mchung@1472 276 }
mchung@1472 277 return true;
mchung@1472 278 }
mchung@1472 279
mchung@2139 280 private void generateDotFiles(Path dir, Analyzer analyzer) throws IOException {
mchung@2139 281 Path summary = dir.resolve("summary.dot");
mchung@2139 282 try (PrintWriter sw = new PrintWriter(Files.newOutputStream(summary));
mchung@2139 283 DotFileFormatter formatter = new DotFileFormatter(sw, "summary")) {
mchung@2139 284 for (Archive archive : sourceLocations) {
mchung@2139 285 analyzer.visitArchiveDependences(archive, formatter);
mchung@2139 286 }
mchung@2139 287 }
mchung@2139 288 if (options.verbose != Analyzer.Type.SUMMARY) {
mchung@2139 289 for (Archive archive : sourceLocations) {
mchung@2139 290 if (analyzer.hasDependences(archive)) {
mchung@2139 291 Path dotfile = dir.resolve(archive.getFileName() + ".dot");
mchung@2139 292 try (PrintWriter pw = new PrintWriter(Files.newOutputStream(dotfile));
mchung@2139 293 DotFileFormatter formatter = new DotFileFormatter(pw, archive)) {
mchung@2139 294 analyzer.visitDependences(archive, formatter);
mchung@2139 295 }
mchung@2139 296 }
mchung@2139 297 }
mchung@2139 298 }
mchung@2139 299 }
mchung@2139 300
mchung@2139 301 private void printRawOutput(PrintWriter writer, Analyzer analyzer) {
mchung@2139 302 for (Archive archive : sourceLocations) {
mchung@2139 303 RawOutputFormatter formatter = new RawOutputFormatter(writer);
mchung@2139 304 analyzer.visitArchiveDependences(archive, formatter);
mchung@2139 305 if (options.verbose != Analyzer.Type.SUMMARY) {
mchung@2139 306 analyzer.visitDependences(archive, formatter);
mchung@2139 307 }
mchung@2139 308 }
mchung@2139 309 }
mchung@1472 310 private boolean isValidClassName(String name) {
mchung@1472 311 if (!Character.isJavaIdentifierStart(name.charAt(0))) {
mchung@1472 312 return false;
mchung@1472 313 }
mchung@1472 314 for (int i=1; i < name.length(); i++) {
mchung@1472 315 char c = name.charAt(i);
mchung@1472 316 if (c != '.' && !Character.isJavaIdentifierPart(c)) {
mchung@1472 317 return false;
mchung@1472 318 }
mchung@1472 319 }
mchung@1472 320 return true;
mchung@1472 321 }
mchung@1472 322
mchung@2139 323 private Dependency.Filter getDependencyFilter() {
mchung@2139 324 if (options.regex != null) {
mchung@2139 325 return Dependencies.getRegexFilter(Pattern.compile(options.regex));
mchung@1472 326 } else if (options.packageNames.size() > 0) {
mchung@2139 327 return Dependencies.getPackageFilter(options.packageNames, false);
mchung@1472 328 } else {
mchung@2139 329 return new Dependency.Filter() {
mchung@2139 330 @Override
mchung@1472 331 public boolean accepts(Dependency dependency) {
mchung@1472 332 return !dependency.getOrigin().equals(dependency.getTarget());
mchung@1472 333 }
mchung@1472 334 };
mchung@1472 335 }
mchung@2139 336 }
mchung@1472 337
mchung@2139 338 private boolean matches(String classname, AccessFlags flags) {
mchung@2139 339 if (options.apiOnly && !flags.is(AccessFlags.ACC_PUBLIC)) {
mchung@2139 340 return false;
mchung@2139 341 } else if (options.includePattern != null) {
mchung@2139 342 return options.includePattern.matcher(classname.replace('/', '.')).matches();
mchung@2139 343 } else {
mchung@2139 344 return true;
mchung@2139 345 }
mchung@2139 346 }
mchung@2139 347
mchung@2139 348 private void findDependencies() throws IOException {
mchung@2139 349 Dependency.Finder finder =
mchung@2139 350 options.apiOnly ? Dependencies.getAPIFinder(AccessFlags.ACC_PROTECTED)
mchung@2139 351 : Dependencies.getClassDependencyFinder();
mchung@2139 352 Dependency.Filter filter = getDependencyFilter();
mchung@2139 353
mchung@2139 354 List<Archive> archives = new ArrayList<>();
mchung@2139 355 Deque<String> roots = new LinkedList<>();
mchung@1472 356 for (String s : classes) {
mchung@2139 357 Path p = Paths.get(s);
mchung@2139 358 if (Files.exists(p)) {
mchung@2139 359 archives.add(new Archive(p, ClassFileReader.newInstance(p)));
mchung@1472 360 } else {
mchung@1472 361 if (isValidClassName(s)) {
mchung@1472 362 roots.add(s);
mchung@1472 363 } else {
mchung@1472 364 warning("warn.invalid.arg", s);
mchung@1472 365 }
mchung@1472 366 }
mchung@1472 367 }
mchung@1472 368
mchung@2139 369 List<Archive> classpaths = new ArrayList<>(); // for class file lookup
mchung@2139 370 if (options.includePattern != null) {
mchung@1472 371 archives.addAll(getClassPathArchives(options.classpath));
mchung@1472 372 } else {
mchung@1472 373 classpaths.addAll(getClassPathArchives(options.classpath));
mchung@1472 374 }
mchung@1472 375 classpaths.addAll(PlatformClassPath.getArchives());
mchung@1472 376
mchung@1472 377 // add all archives to the source locations for reporting
mchung@1472 378 sourceLocations.addAll(archives);
mchung@1472 379 sourceLocations.addAll(classpaths);
mchung@1472 380
mchung@1472 381 // Work queue of names of classfiles to be searched.
mchung@1472 382 // Entries will be unique, and for classes that do not yet have
mchung@1472 383 // dependencies in the results map.
mchung@2139 384 Deque<String> deque = new LinkedList<>();
mchung@2139 385 Set<String> doneClasses = new HashSet<>();
mchung@1472 386
mchung@1472 387 // get the immediate dependencies of the input files
mchung@1472 388 for (Archive a : archives) {
mchung@1472 389 for (ClassFile cf : a.reader().getClassFiles()) {
mchung@1472 390 String classFileName;
mchung@1472 391 try {
mchung@1472 392 classFileName = cf.getName();
mchung@1472 393 } catch (ConstantPoolException e) {
mchung@1472 394 throw new ClassFileError(e);
mchung@1472 395 }
mchung@1577 396
mchung@2139 397 if (matches(classFileName, cf.access_flags)) {
mchung@2139 398 if (!doneClasses.contains(classFileName)) {
mchung@2139 399 doneClasses.add(classFileName);
mchung@2139 400 }
mchung@2139 401 for (Dependency d : finder.findDependencies(cf)) {
mchung@2139 402 if (filter.accepts(d)) {
mchung@2139 403 String cn = d.getTarget().getName();
mchung@2139 404 if (!doneClasses.contains(cn) && !deque.contains(cn)) {
mchung@2139 405 deque.add(cn);
mchung@2139 406 }
mchung@2139 407 a.addClass(d.getOrigin(), d.getTarget());
mchung@1472 408 }
mchung@1472 409 }
mchung@1472 410 }
mchung@1472 411 }
mchung@1472 412 }
mchung@1472 413
mchung@1472 414 // add Archive for looking up classes from the classpath
mchung@1472 415 // for transitive dependency analysis
mchung@1472 416 Deque<String> unresolved = roots;
mchung@1472 417 int depth = options.depth > 0 ? options.depth : Integer.MAX_VALUE;
mchung@1472 418 do {
mchung@1472 419 String name;
mchung@1472 420 while ((name = unresolved.poll()) != null) {
mchung@1472 421 if (doneClasses.contains(name)) {
mchung@1472 422 continue;
mchung@1472 423 }
mchung@1472 424 ClassFile cf = null;
mchung@1472 425 for (Archive a : classpaths) {
mchung@1472 426 cf = a.reader().getClassFile(name);
mchung@1472 427 if (cf != null) {
mchung@1472 428 String classFileName;
mchung@1472 429 try {
mchung@1472 430 classFileName = cf.getName();
mchung@1472 431 } catch (ConstantPoolException e) {
mchung@1472 432 throw new ClassFileError(e);
mchung@1472 433 }
mchung@1472 434 if (!doneClasses.contains(classFileName)) {
mchung@1472 435 // if name is a fully-qualified class name specified
mchung@1472 436 // from command-line, this class might already be parsed
mchung@1472 437 doneClasses.add(classFileName);
mchung@1577 438 for (Dependency d : finder.findDependencies(cf)) {
mchung@1577 439 if (depth == 0) {
mchung@1577 440 // ignore the dependency
mchung@1577 441 a.addClass(d.getOrigin());
mchung@1577 442 break;
mchung@1577 443 } else if (filter.accepts(d)) {
mchung@1577 444 a.addClass(d.getOrigin(), d.getTarget());
mchung@1577 445 String cn = d.getTarget().getName();
mchung@1577 446 if (!doneClasses.contains(cn) && !deque.contains(cn)) {
mchung@1577 447 deque.add(cn);
mchung@1472 448 }
mchung@1472 449 }
mchung@1472 450 }
mchung@1472 451 }
mchung@1472 452 break;
mchung@1472 453 }
mchung@1472 454 }
mchung@1472 455 if (cf == null) {
mchung@1577 456 doneClasses.add(name);
mchung@1472 457 }
mchung@1472 458 }
mchung@1472 459 unresolved = deque;
mchung@2139 460 deque = new LinkedList<>();
mchung@1472 461 } while (!unresolved.isEmpty() && depth-- > 0);
mchung@1472 462 }
mchung@1472 463
mchung@1472 464 public void handleOptions(String[] args) throws BadArgs {
mchung@1472 465 // process options
mchung@1472 466 for (int i=0; i < args.length; i++) {
mchung@1472 467 if (args[i].charAt(0) == '-') {
mchung@1472 468 String name = args[i];
mchung@1472 469 Option option = getOption(name);
mchung@1472 470 String param = null;
mchung@1472 471 if (option.hasArg) {
mchung@2139 472 if (name.startsWith("-") && name.indexOf('=') > 0) {
mchung@1472 473 param = name.substring(name.indexOf('=') + 1, name.length());
mchung@1472 474 } else if (i + 1 < args.length) {
mchung@1472 475 param = args[++i];
mchung@1472 476 }
mchung@1472 477 if (param == null || param.isEmpty() || param.charAt(0) == '-') {
mchung@1472 478 throw new BadArgs("err.missing.arg", name).showUsage(true);
mchung@1472 479 }
mchung@1472 480 }
mchung@1472 481 option.process(this, name, param);
mchung@1472 482 if (option.ignoreRest()) {
mchung@1472 483 i = args.length;
mchung@1472 484 }
mchung@1472 485 } else {
mchung@1472 486 // process rest of the input arguments
mchung@1472 487 for (; i < args.length; i++) {
mchung@1472 488 String name = args[i];
mchung@1472 489 if (name.charAt(0) == '-') {
mchung@1472 490 throw new BadArgs("err.option.after.class", name).showUsage(true);
mchung@1472 491 }
mchung@2139 492 classes.add(name);
mchung@1472 493 }
mchung@1472 494 }
mchung@1472 495 }
mchung@1472 496 }
mchung@1472 497
mchung@1472 498 private Option getOption(String name) throws BadArgs {
mchung@1472 499 for (Option o : recognizedOptions) {
mchung@1472 500 if (o.matches(name)) {
mchung@1472 501 return o;
mchung@1472 502 }
mchung@1472 503 }
mchung@1472 504 throw new BadArgs("err.unknown.option", name).showUsage(true);
mchung@1472 505 }
mchung@1472 506
mchung@1472 507 private void reportError(String key, Object... args) {
mchung@1472 508 log.println(getMessage("error.prefix") + " " + getMessage(key, args));
mchung@1472 509 }
mchung@1472 510
mchung@1472 511 private void warning(String key, Object... args) {
mchung@1472 512 log.println(getMessage("warn.prefix") + " " + getMessage(key, args));
mchung@1472 513 }
mchung@1472 514
mchung@1472 515 private void showHelp() {
mchung@1472 516 log.println(getMessage("main.usage", PROGNAME));
mchung@1472 517 for (Option o : recognizedOptions) {
mchung@1472 518 String name = o.aliases[0].substring(1); // there must always be at least one name
mchung@1472 519 name = name.charAt(0) == '-' ? name.substring(1) : name;
mchung@1472 520 if (o.isHidden() || name.equals("h")) {
mchung@1472 521 continue;
mchung@1472 522 }
mchung@1472 523 log.println(getMessage("main.opt." + name));
mchung@1472 524 }
mchung@1472 525 }
mchung@1472 526
mchung@1472 527 private void showVersion(boolean full) {
mchung@1472 528 log.println(version(full ? "full" : "release"));
mchung@1472 529 }
mchung@1472 530
mchung@1472 531 private String version(String key) {
mchung@1472 532 // key=version: mm.nn.oo[-milestone]
mchung@1472 533 // key=full: mm.mm.oo[-milestone]-build
mchung@1472 534 if (ResourceBundleHelper.versionRB == null) {
mchung@1472 535 return System.getProperty("java.version");
mchung@1472 536 }
mchung@1472 537 try {
mchung@1472 538 return ResourceBundleHelper.versionRB.getString(key);
mchung@1472 539 } catch (MissingResourceException e) {
mchung@1472 540 return getMessage("version.unknown", System.getProperty("java.version"));
mchung@1472 541 }
mchung@1472 542 }
mchung@1472 543
mchung@1577 544 static String getMessage(String key, Object... args) {
mchung@1472 545 try {
mchung@1472 546 return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);
mchung@1472 547 } catch (MissingResourceException e) {
mchung@1472 548 throw new InternalError("Missing message: " + key);
mchung@1472 549 }
mchung@1472 550 }
mchung@1472 551
mchung@1472 552 private static class Options {
mchung@1472 553 boolean help;
mchung@1472 554 boolean version;
mchung@1472 555 boolean fullVersion;
mchung@1472 556 boolean showProfile;
mchung@1472 557 boolean showSummary;
mchung@1472 558 boolean wildcard;
mchung@2139 559 boolean apiOnly;
mchung@2139 560 String dotOutputDir;
mchung@1472 561 String classpath = "";
mchung@1472 562 int depth = 1;
mchung@1577 563 Analyzer.Type verbose = Analyzer.Type.PACKAGE;
mchung@2139 564 Set<String> packageNames = new HashSet<>();
mchung@2139 565 String regex; // apply to the dependences
mchung@2139 566 Pattern includePattern; // apply to classes
mchung@1472 567 }
mchung@1472 568 private static class ResourceBundleHelper {
mchung@1472 569 static final ResourceBundle versionRB;
mchung@1472 570 static final ResourceBundle bundle;
mchung@1472 571
mchung@1472 572 static {
mchung@1472 573 Locale locale = Locale.getDefault();
mchung@1472 574 try {
mchung@1472 575 bundle = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.jdeps", locale);
mchung@1472 576 } catch (MissingResourceException e) {
mchung@1472 577 throw new InternalError("Cannot find jdeps resource bundle for locale " + locale);
mchung@1472 578 }
mchung@1472 579 try {
mchung@1472 580 versionRB = ResourceBundle.getBundle("com.sun.tools.jdeps.resources.version");
mchung@1472 581 } catch (MissingResourceException e) {
mchung@1472 582 throw new InternalError("version.resource.missing");
mchung@1472 583 }
mchung@1472 584 }
mchung@1472 585 }
mchung@1472 586
mchung@1472 587 private List<Archive> getArchives(List<String> filenames) throws IOException {
mchung@1472 588 List<Archive> result = new ArrayList<Archive>();
mchung@1472 589 for (String s : filenames) {
mchung@2139 590 Path p = Paths.get(s);
mchung@2139 591 if (Files.exists(p)) {
mchung@2139 592 result.add(new Archive(p, ClassFileReader.newInstance(p)));
mchung@1472 593 } else {
mchung@1472 594 warning("warn.file.not.exist", s);
mchung@1472 595 }
mchung@1472 596 }
mchung@1472 597 return result;
mchung@1472 598 }
mchung@1472 599
mchung@1472 600 private List<Archive> getClassPathArchives(String paths) throws IOException {
mchung@2139 601 List<Archive> result = new ArrayList<>();
mchung@1472 602 if (paths.isEmpty()) {
mchung@1472 603 return result;
mchung@1472 604 }
mchung@1472 605 for (String p : paths.split(File.pathSeparator)) {
mchung@1472 606 if (p.length() > 0) {
mchung@2139 607 List<Path> files = new ArrayList<>();
mchung@2139 608 // wildcard to parse all JAR files e.g. -classpath dir/*
mchung@2139 609 int i = p.lastIndexOf(".*");
mchung@2139 610 if (i > 0) {
mchung@2139 611 Path dir = Paths.get(p.substring(0, i));
mchung@2139 612 try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.jar")) {
mchung@2139 613 for (Path entry : stream) {
mchung@2139 614 files.add(entry);
mchung@2139 615 }
mchung@2139 616 }
mchung@2139 617 } else {
mchung@2139 618 files.add(Paths.get(p));
mchung@2139 619 }
mchung@2139 620 for (Path f : files) {
mchung@2139 621 if (Files.exists(f)) {
mchung@2139 622 result.add(new Archive(f, ClassFileReader.newInstance(f)));
mchung@2139 623 }
mchung@1472 624 }
mchung@1472 625 }
mchung@1472 626 }
mchung@1472 627 return result;
mchung@1472 628 }
mchung@2139 629
mchung@2139 630
mchung@2139 631 /**
mchung@2139 632 * Returns the file name of the archive for non-JRE class or
mchung@2139 633 * internal JRE classes. It returns empty string for SE API.
mchung@2139 634 */
mchung@2139 635 private static String getArchiveName(Archive source, String profile) {
mchung@2139 636 String name = source.getFileName();
mchung@2139 637 if (source instanceof JDKArchive)
mchung@2139 638 return profile.isEmpty() ? "JDK internal API (" + name + ")" : "";
mchung@2139 639 return name;
mchung@2139 640 }
mchung@2139 641
mchung@2139 642 class RawOutputFormatter implements Analyzer.Visitor {
mchung@2139 643 private final PrintWriter writer;
mchung@2139 644 RawOutputFormatter(PrintWriter writer) {
mchung@2139 645 this.writer = writer;
mchung@2139 646 }
mchung@2139 647
mchung@2139 648 private String pkg = "";
mchung@2139 649 @Override
mchung@2139 650 public void visitDependence(String origin, Archive source,
mchung@2139 651 String target, Archive archive, String profile) {
mchung@2139 652 if (!origin.equals(pkg)) {
mchung@2139 653 pkg = origin;
mchung@2139 654 writer.format(" %s (%s)%n", origin, source.getFileName());
mchung@2139 655 }
mchung@2139 656 String name = (options.showProfile && !profile.isEmpty())
mchung@2139 657 ? profile
mchung@2139 658 : getArchiveName(archive, profile);
mchung@2139 659 writer.format(" -> %-50s %s%n", target, name);
mchung@2139 660 }
mchung@2139 661
mchung@2139 662 @Override
mchung@2139 663 public void visitArchiveDependence(Archive origin, Archive target, String profile) {
mchung@2139 664 writer.format("%s -> %s", origin, target);
mchung@2139 665 if (options.showProfile && !profile.isEmpty()) {
mchung@2139 666 writer.format(" (%s)%n", profile);
mchung@2139 667 } else {
mchung@2139 668 writer.format("%n");
mchung@2139 669 }
mchung@2139 670 }
mchung@2139 671 }
mchung@2139 672
mchung@2139 673 class DotFileFormatter implements Analyzer.Visitor, AutoCloseable {
mchung@2139 674 private final PrintWriter writer;
mchung@2139 675 private final String name;
mchung@2139 676 DotFileFormatter(PrintWriter writer, String name) {
mchung@2139 677 this.writer = writer;
mchung@2139 678 this.name = name;
mchung@2139 679 writer.format("digraph \"%s\" {%n", name);
mchung@2139 680 }
mchung@2139 681 DotFileFormatter(PrintWriter writer, Archive archive) {
mchung@2139 682 this.writer = writer;
mchung@2139 683 this.name = archive.getFileName();
mchung@2139 684 writer.format("digraph \"%s\" {%n", name);
mchung@2139 685 writer.format(" // Path: %s%n", archive.toString());
mchung@2139 686 }
mchung@2139 687
mchung@2139 688 @Override
mchung@2139 689 public void close() {
mchung@2139 690 writer.println("}");
mchung@2139 691 }
mchung@2139 692
mchung@2139 693 private final Set<String> edges = new HashSet<>();
mchung@2139 694 private String node = "";
mchung@2139 695 @Override
mchung@2139 696 public void visitDependence(String origin, Archive source,
mchung@2139 697 String target, Archive archive, String profile) {
mchung@2139 698 if (!node.equals(origin)) {
mchung@2139 699 edges.clear();
mchung@2139 700 node = origin;
mchung@2139 701 }
mchung@2139 702 // if -P option is specified, package name -> profile will
mchung@2139 703 // be shown and filter out multiple same edges.
mchung@2139 704 if (!edges.contains(target)) {
mchung@2139 705 StringBuilder sb = new StringBuilder();
mchung@2139 706 String name = options.showProfile && !profile.isEmpty()
mchung@2139 707 ? profile
mchung@2139 708 : getArchiveName(archive, profile);
mchung@2139 709 writer.format(" %-50s -> %s;%n",
mchung@2139 710 String.format("\"%s\"", origin),
mchung@2139 711 name.isEmpty() ? String.format("\"%s\"", target)
mchung@2139 712 : String.format("\"%s (%s)\"", target, name));
mchung@2139 713 edges.add(target);
mchung@2139 714 }
mchung@2139 715 }
mchung@2139 716
mchung@2139 717 @Override
mchung@2139 718 public void visitArchiveDependence(Archive origin, Archive target, String profile) {
mchung@2139 719 String name = options.showProfile && !profile.isEmpty()
mchung@2139 720 ? profile : "";
mchung@2139 721 writer.format(" %-30s -> \"%s\";%n",
mchung@2139 722 String.format("\"%s\"", origin.getFileName()),
mchung@2139 723 name.isEmpty()
mchung@2139 724 ? target.getFileName()
mchung@2139 725 : String.format("%s (%s)", target.getFileName(), name));
mchung@2139 726 }
mchung@2139 727 }
mchung@1472 728 }

mercurial