Main.java revision 3890:05b91c7f6f9e
1187160Sthompsa/* 2187160Sthompsa * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. 3187160Sthompsa * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4187160Sthompsa * 5187160Sthompsa * This code is free software; you can redistribute it and/or modify it 6187160Sthompsa * under the terms of the GNU General Public License version 2 only, as 7187160Sthompsa * published by the Free Software Foundation. Oracle designates this 8187160Sthompsa * particular file as subject to the "Classpath" exception as provided 9187160Sthompsa * by Oracle in the LICENSE file that accompanied this code. 10187160Sthompsa * 11187160Sthompsa * This code is distributed in the hope that it will be useful, but WITHOUT 12187160Sthompsa * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13187160Sthompsa * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14187160Sthompsa * version 2 for more details (a copy is included in the LICENSE file that 15187160Sthompsa * accompanied this code). 16187160Sthompsa * 17187160Sthompsa * You should have received a copy of the GNU General Public License version 18187160Sthompsa * 2 along with this work; if not, write to the Free Software Foundation, 19187160Sthompsa * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20187160Sthompsa * 21187160Sthompsa * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22187160Sthompsa * or visit www.oracle.com if you need additional information or have any 23187160Sthompsa * questions. 24187160Sthompsa */ 25187160Sthompsa 26187160Sthompsapackage com.sun.tools.jdeprscan; 27187160Sthompsa 28190754Sthompsaimport java.io.File; 29190754Sthompsaimport java.io.IOException; 30187160Sthompsaimport java.io.PrintStream; 31187160Sthompsaimport java.net.URI; 32187160Sthompsaimport java.nio.charset.StandardCharsets; 33187160Sthompsaimport java.nio.file.Files; 34187160Sthompsaimport java.nio.file.FileSystems; 35187160Sthompsaimport java.nio.file.Path; 36187160Sthompsaimport java.nio.file.Paths; 37192446Sthompsaimport java.util.ArrayDeque; 38192446Sthompsaimport java.util.ArrayList; 39192446Sthompsaimport java.util.Arrays; 40192446Sthompsaimport java.util.Collection; 41187160Sthompsaimport java.util.HashSet; 42187160Sthompsaimport java.util.List; 43187160Sthompsaimport java.util.Map; 44187160Sthompsaimport java.util.NoSuchElementException; 45187160Sthompsaimport java.util.Set; 46187160Sthompsaimport java.util.Queue; 47187160Sthompsaimport java.util.stream.Stream; 48187160Sthompsaimport java.util.jar.JarEntry; 49187160Sthompsaimport java.util.jar.JarFile; 50187160Sthompsa 51187160Sthompsaimport javax.tools.Diagnostic; 52187160Sthompsaimport javax.tools.DiagnosticListener; 53187160Sthompsaimport javax.tools.JavaCompiler; 54187160Sthompsaimport javax.tools.JavaFileObject; 55187160Sthompsaimport javax.tools.StandardJavaFileManager; 56187160Sthompsaimport javax.tools.StandardLocation; 57187160Sthompsaimport javax.tools.ToolProvider; 58187160Sthompsa 59187160Sthompsaimport com.sun.tools.javac.file.JavacFileManager; 60187160Sthompsa 61187160Sthompsaimport com.sun.tools.jdeprscan.scan.Scan; 62187160Sthompsa 63187160Sthompsaimport static java.util.stream.Collectors.*; 64187160Sthompsa 65187160Sthompsaimport javax.lang.model.element.PackageElement; 66187160Sthompsaimport javax.lang.model.element.TypeElement; 67187160Sthompsa 68187160Sthompsa/** 69187160Sthompsa * Deprecation Scanner tool. Loads API deprecation information from the 70187160Sthompsa * JDK image, or optionally, from a jar file or class hierarchy. Then scans 71187160Sthompsa * a class library for usages of those APIs. 72187160Sthompsa * 73187160Sthompsa * TODO: 74187160Sthompsa * - audit error handling throughout, but mainly in scan package 75187160Sthompsa * - handling of covariant overrides 76187160Sthompsa * - handling of override of method found in multiple superinterfaces 77187160Sthompsa * - convert type/method/field output to Java source like syntax, e.g. 78187160Sthompsa * instead of java/lang/Runtime.runFinalizersOnExit(Z)V 79187160Sthompsa * print void java.lang.Runtime.runFinalizersOnExit(boolean) 80187160Sthompsa * - more example output in man page 81187160Sthompsa * - more rigorous GNU style option parsing; use joptsimple? 82187160Sthompsa * 83187160Sthompsa * FUTURES: 84187160Sthompsa * - add module support: --add-modules, --module-path, module arg 85187160Sthompsa * - load deprecation declarations from a designated class library instead 86187160Sthompsa * of the JDK 87187160Sthompsa * - load deprecation declarations from a module 88187160Sthompsa * - scan a module (but a modular jar can be treated just a like an ordinary jar) 89187160Sthompsa * - multi-version jar 90187160Sthompsa */ 91187160Sthompsapublic class Main implements DiagnosticListener<JavaFileObject> { 92187160Sthompsa final PrintStream out; 93187160Sthompsa final PrintStream err; 94187160Sthompsa final List<File> bootClassPath = new ArrayList<>(); 95187160Sthompsa final List<File> classPath = new ArrayList<>(); 96187160Sthompsa final List<File> systemModules = new ArrayList<>(); 97187160Sthompsa final List<String> options = new ArrayList<>(); 98187160Sthompsa final List<String> comments = new ArrayList<>(); 99187160Sthompsa 100187160Sthompsa // Valid releases need to match what the compiler supports. 101187160Sthompsa // Keep these updated manually until there's a compiler API 102187160Sthompsa // that allows querying of supported releases. 103187160Sthompsa final Set<String> releasesWithoutForRemoval = Set.of("6", "7", "8"); 104187160Sthompsa final Set<String> releasesWithForRemoval = Set.of("9", "10"); 105187160Sthompsa 106187160Sthompsa final Set<String> validReleases; 107187160Sthompsa { 108187160Sthompsa Set<String> temp = new HashSet<>(releasesWithoutForRemoval); 109187160Sthompsa temp.addAll(releasesWithForRemoval); 110187160Sthompsa validReleases = Set.of(temp.toArray(new String[0])); 111187160Sthompsa } 112187160Sthompsa 113187160Sthompsa boolean verbose = false; 114187160Sthompsa boolean forRemoval = false; 115187160Sthompsa 116187160Sthompsa final JavaCompiler compiler; 117187160Sthompsa final StandardJavaFileManager fm; 118187160Sthompsa 119187160Sthompsa List<DeprData> deprList; // non-null after successful load phase 120187160Sthompsa 121187160Sthompsa /** 122187160Sthompsa * Processes a collection of class names. Names should fully qualified 123187160Sthompsa * names in the form "pkg.pkg.pkg.classname". 124187160Sthompsa * 125187160Sthompsa * @param classNames collection of fully qualified classnames to process 126187160Sthompsa * @return true for success, false for failure 127187160Sthompsa * @throws IOException if an I/O error occurs 128187160Sthompsa */ 129187160Sthompsa boolean doClassNames(Collection<String> classNames) throws IOException { 130187160Sthompsa if (verbose) { 131187160Sthompsa out.println("List of classes to process:"); 132187160Sthompsa classNames.forEach(out::println); 133187160Sthompsa out.println("End of class list."); 134187160Sthompsa } 135187160Sthompsa 136187160Sthompsa // TODO: not sure this is necessary... 137187160Sthompsa if (fm instanceof JavacFileManager) { 138187160Sthompsa ((JavacFileManager)fm).setSymbolFileEnabled(false); 139187160Sthompsa } 140187160Sthompsa 141187160Sthompsa fm.setLocation(StandardLocation.CLASS_PATH, classPath); 142187160Sthompsa if (!bootClassPath.isEmpty()) { 143192446Sthompsa fm.setLocation(StandardLocation.PLATFORM_CLASS_PATH, bootClassPath); 144192446Sthompsa } 145192446Sthompsa 146192446Sthompsa if (!systemModules.isEmpty()) { 147192446Sthompsa fm.setLocation(StandardLocation.SYSTEM_MODULES, systemModules); 148192446Sthompsa } 149192446Sthompsa 150192446Sthompsa LoadProc proc = new LoadProc(); 151192446Sthompsa JavaCompiler.CompilationTask task = 152192446Sthompsa compiler.getTask(null, fm, this, options, classNames, null); 153187160Sthompsa task.setProcessors(List.of(proc)); 154187160Sthompsa boolean r = task.call(); 155192446Sthompsa if (r) { 156187160Sthompsa if (forRemoval) { 157187160Sthompsa deprList = proc.getDeprecations().stream() 158187160Sthompsa .filter(DeprData::isForRemoval) 159187160Sthompsa .collect(toList()); 160187160Sthompsa } else { 161187160Sthompsa deprList = proc.getDeprecations(); 162187160Sthompsa } 163192446Sthompsa } 164187160Sthompsa return r; 165187160Sthompsa } 166187160Sthompsa 167187160Sthompsa /** 168187160Sthompsa * Processes a stream of filenames (strings). The strings are in the 169187160Sthompsa * form pkg/pkg/pkg/classname.class relative to the root of a package 170189677Sthompsa * hierarchy. 171189677Sthompsa * 172189677Sthompsa * @param filenames a Stream of filenames to process 173187160Sthompsa * @return true for success, false for failure 174187160Sthompsa * @throws IOException if an I/O error occurs 175187160Sthompsa */ 176187160Sthompsa boolean doFileNames(Stream<String> filenames) throws IOException { 177187160Sthompsa return doClassNames( 178187160Sthompsa filenames.filter(name -> name.endsWith(".class")) 179187160Sthompsa .filter(name -> !name.endsWith("package-info.class")) 180187160Sthompsa .filter(name -> !name.endsWith("module-info.class")) 181187160Sthompsa .map(s -> s.replaceAll("\\.class$", "")) 182187160Sthompsa .map(s -> s.replace(File.separatorChar, '.')) 183187160Sthompsa .collect(toList())); 184187160Sthompsa } 185187160Sthompsa 186187160Sthompsa /** 187187160Sthompsa * Replaces all but the first occurrence of '/' with '.'. Assumes 188187160Sthompsa * that the name is in the format module/pkg/pkg/classname.class. 189187160Sthompsa * That is, the name should contain at least one '/' character 190187160Sthompsa * separating the module name from the package-class name. 191187160Sthompsa * 192187160Sthompsa * @param filename the input filename 193187160Sthompsa * @return the modular classname 194192984Sthompsa */ 195187160Sthompsa String convertModularFileName(String filename) { 196187160Sthompsa int slash = filename.indexOf('/'); 197187160Sthompsa return filename.substring(0, slash) 198187160Sthompsa + "/" 199192984Sthompsa + filename.substring(slash+1).replace('/', '.'); 200187160Sthompsa } 201187160Sthompsa 202187160Sthompsa /** 203187160Sthompsa * Processes a stream of filenames (strings) including a module prefix. 204187160Sthompsa * The strings are in the form module/pkg/pkg/pkg/classname.class relative 205187160Sthompsa * to the root of a directory containing modules. The strings are processed 206187160Sthompsa * into module-qualified class names of the form 207187160Sthompsa * "module/pkg.pkg.pkg.classname". 208187160Sthompsa * 209187160Sthompsa * @param filenames a Stream of filenames to process 210187160Sthompsa * @return true for success, false for failure 211187160Sthompsa * @throws IOException if an I/O error occurs 212187160Sthompsa */ 213192984Sthompsa boolean doModularFileNames(Stream<String> filenames) throws IOException { 214187160Sthompsa return doClassNames( 215187160Sthompsa filenames.filter(name -> name.endsWith(".class")) 216187160Sthompsa .filter(name -> !name.endsWith("package-info.class")) 217187160Sthompsa .filter(name -> !name.endsWith("module-info.class")) 218187160Sthompsa .map(s -> s.replaceAll("\\.class$", "")) 219187160Sthompsa .map(this::convertModularFileName) 220187160Sthompsa .collect(toList())); 221187160Sthompsa } 222187160Sthompsa 223187160Sthompsa /** 224187160Sthompsa * Processes named class files in the given directory. The directory 225192552Sthompsa * should be the root of a package hierarchy. If classNames is 226187160Sthompsa * empty, walks the directory hierarchy to find all classes. 227187160Sthompsa * 228187160Sthompsa * @param dirname the name of the directory to process 229192984Sthompsa * @param classNames the names of classes to process 230192984Sthompsa * @return true for success, false for failure 231192984Sthompsa * @throws IOException if an I/O error occurs 232187160Sthompsa */ 233187160Sthompsa boolean processDirectory(String dirname, Collection<String> classNames) throws IOException { 234187160Sthompsa if (!Files.isDirectory(Paths.get(dirname))) { 235187160Sthompsa err.printf("%s: not a directory%n", dirname); 236192984Sthompsa return false; 237187160Sthompsa } 238187160Sthompsa 239187160Sthompsa classPath.add(0, new File(dirname)); 240187160Sthompsa 241187160Sthompsa if (classNames.isEmpty()) { 242187160Sthompsa Path base = Paths.get(dirname); 243187160Sthompsa int baseCount = base.getNameCount(); 244187160Sthompsa try (Stream<Path> paths = Files.walk(base)) { 245187160Sthompsa Stream<String> files = 246187160Sthompsa paths.filter(p -> p.getNameCount() > baseCount) 247187160Sthompsa .map(p -> p.subpath(baseCount, p.getNameCount())) 248187160Sthompsa .map(Path::toString); 249187160Sthompsa return doFileNames(files); 250187160Sthompsa } 251187160Sthompsa } else { 252187160Sthompsa return doClassNames(classNames); 253187160Sthompsa } 254192984Sthompsa } 255187160Sthompsa 256187160Sthompsa /** 257187160Sthompsa * Processes all class files in the given jar file. 258187160Sthompsa * 259187160Sthompsa * @param jarname the name of the jar file to process 260187160Sthompsa * @return true for success, false for failure 261192984Sthompsa * @throws IOException if an I/O error occurs 262187160Sthompsa */ 263187160Sthompsa boolean doJarFile(String jarname) throws IOException { 264187160Sthompsa try (JarFile jf = new JarFile(jarname)) { 265187160Sthompsa Stream<String> files = 266187160Sthompsa jf.stream() 267190735Sthompsa .map(JarEntry::getName); 268187160Sthompsa return doFileNames(files); 269187160Sthompsa } 270187160Sthompsa } 271187160Sthompsa 272187160Sthompsa /** 273187160Sthompsa * Processes named class files from the given jar file, 274187160Sthompsa * or all classes if classNames is empty. 275187160Sthompsa * 276187160Sthompsa * @param jarname the name of the jar file to process 277187160Sthompsa * @param classNames the names of classes to process 278187160Sthompsa * @return true for success, false for failure 279193045Sthompsa * @throws IOException if an I/O error occurs 280187160Sthompsa */ 281187160Sthompsa boolean processJarFile(String jarname, Collection<String> classNames) throws IOException { 282187160Sthompsa classPath.add(0, new File(jarname)); 283187160Sthompsa 284 if (classNames.isEmpty()) { 285 return doJarFile(jarname); 286 } else { 287 return doClassNames(classNames); 288 } 289 } 290 291 /** 292 * Processes named class files from rt.jar of a JDK version 7 or 8. 293 * If classNames is empty, processes all classes. 294 * 295 * @param jdkHome the path to the "home" of the JDK to process 296 * @param classNames the names of classes to process 297 * @return true for success, false for failure 298 * @throws IOException if an I/O error occurs 299 */ 300 boolean processOldJdk(String jdkHome, Collection<String> classNames) throws IOException { 301 String RTJAR = jdkHome + "/jre/lib/rt.jar"; 302 String CSJAR = jdkHome + "/jre/lib/charsets.jar"; 303 304 bootClassPath.add(0, new File(RTJAR)); 305 bootClassPath.add(1, new File(CSJAR)); 306 options.add("-source"); 307 options.add("8"); 308 309 if (classNames.isEmpty()) { 310 return doJarFile(RTJAR); 311 } else { 312 return doClassNames(classNames); 313 } 314 } 315 316 /** 317 * Processes listed classes given a JDK 9 home. 318 */ 319 boolean processJdk9(String jdkHome, Collection<String> classes) throws IOException { 320 systemModules.add(new File(jdkHome)); 321 return doClassNames(classes); 322 } 323 324 /** 325 * Processes the class files from the currently running JDK, 326 * using the jrt: filesystem. 327 * 328 * @return true for success, false for failure 329 * @throws IOException if an I/O error occurs 330 */ 331 boolean processSelf(Collection<String> classes) throws IOException { 332 options.add("--add-modules"); 333 options.add("java.se.ee,jdk.xml.bind"); // TODO why jdk.xml.bind? 334 335 if (classes.isEmpty()) { 336 Path modules = FileSystems.getFileSystem(URI.create("jrt:/")) 337 .getPath("/modules"); 338 339 // names are /modules/<modulename>/pkg/.../Classname.class 340 try (Stream<Path> paths = Files.walk(modules)) { 341 Stream<String> files = 342 paths.filter(p -> p.getNameCount() > 2) 343 .map(p -> p.subpath(1, p.getNameCount())) 344 .map(Path::toString); 345 return doModularFileNames(files); 346 } 347 } else { 348 return doClassNames(classes); 349 } 350 } 351 352 /** 353 * Process classes from a particular JDK release, using only information 354 * in this JDK. 355 * 356 * @param release "6", "7", "8", "9", or "10" 357 * @param classes collection of classes to process, may be empty 358 * @return success value 359 */ 360 boolean processRelease(String release, Collection<String> classes) throws IOException { 361 options.addAll(List.of("--release", release)); 362 363 if (release.equals("9") || release.equals("10")) { 364 List<String> rootMods = List.of("java.se", "java.se.ee"); 365 TraverseProc proc = new TraverseProc(rootMods); 366 JavaCompiler.CompilationTask task = 367 compiler.getTask(null, fm, this, 368 // options 369 List.of("--add-modules", String.join(",", rootMods)), 370 // classes 371 List.of("java.lang.Object"), 372 null); 373 task.setProcessors(List.of(proc)); 374 if (!task.call()) { 375 return false; 376 } 377 Map<PackageElement, List<TypeElement>> types = proc.getPublicTypes(); 378 options.add("--add-modules"); 379 options.add(String.join(",", rootMods)); 380 return doClassNames( 381 types.values().stream() 382 .flatMap(List::stream) 383 .map(TypeElement::toString) 384 .collect(toList())); 385 } else { 386 // TODO: kind of a hack... 387 // Create a throwaway compilation task with options "--release N" 388 // which has the side effect of setting the file manager's 389 // PLATFORM_CLASS_PATH to the right value. 390 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 391 StandardJavaFileManager fm = 392 compiler.getStandardFileManager(this, null, StandardCharsets.UTF_8); 393 JavaCompiler.CompilationTask task = 394 compiler.getTask(null, fm, this, List.of("--release", release), null, null); 395 List<Path> paths = new ArrayList<>(); 396 for (Path p : fm.getLocationAsPaths(StandardLocation.PLATFORM_CLASS_PATH)) { 397 try (Stream<Path> str = Files.walk(p)) { 398 str.forEachOrdered(paths::add); 399 } 400 } 401 402 options.add("-Xlint:-options"); 403 404 return doClassNames( 405 paths.stream() 406 .filter(path -> path.toString().endsWith(".sig")) 407 .map(path -> path.subpath(1, path.getNameCount())) 408 .map(Path::toString) 409 .map(s -> s.replaceAll("\\.sig$", "")) 410 .map(s -> s.replace('/', '.')) 411 .collect(toList())); 412 } 413 } 414 415 /** 416 * An enum denoting the mode in which the tool is running. 417 * Different modes correspond to the different process* methods. 418 * The exception is UNKNOWN, which indicates that a mode wasn't 419 * specified on the command line, which is an error. 420 */ 421 static enum LoadMode { 422 CLASSES, DIR, JAR, OLD_JDK, JDK9, SELF, RELEASE, LOAD_CSV 423 } 424 425 static enum ScanMode { 426 ARGS, LIST, PRINT_CSV 427 } 428 429 /** 430 * A checked exception that's thrown if a command-line syntax error 431 * is detected. 432 */ 433 static class UsageException extends Exception { 434 private static final long serialVersionUID = 3611828659572908743L; 435 } 436 437 /** 438 * Convenience method to throw UsageException if a condition is false. 439 * 440 * @param cond the condition that's required to be true 441 * @throws UsageException 442 */ 443 void require(boolean cond) throws UsageException { 444 if (!cond) { 445 throw new UsageException(); 446 } 447 } 448 449 /** 450 * Constructs an instance of the finder tool. 451 * 452 * @param out the stream to which the tool's output is sent 453 * @param err the stream to which error messages are sent 454 */ 455 Main(PrintStream out, PrintStream err) { 456 this.out = out; 457 this.err = err; 458 compiler = ToolProvider.getSystemJavaCompiler(); 459 fm = compiler.getStandardFileManager(this, null, StandardCharsets.UTF_8); 460 } 461 462 /** 463 * Prints the diagnostic to the err stream. 464 * 465 * Specified by the DiagnosticListener interface. 466 * 467 * @param diagnostic the tool diagnostic to print 468 */ 469 @Override 470 public void report(Diagnostic<? extends JavaFileObject> diagnostic) { 471 err.println(diagnostic); 472 } 473 474 /** 475 * Parses arguments and performs the requested processing. 476 * 477 * @param argArray command-line arguments 478 * @return true on success, false on error 479 */ 480 boolean run(String... argArray) { 481 Queue<String> args = new ArrayDeque<>(Arrays.asList(argArray)); 482 LoadMode loadMode = LoadMode.RELEASE; 483 ScanMode scanMode = ScanMode.ARGS; 484 String dir = null; 485 String jar = null; 486 String jdkHome = null; 487 String release = "10"; 488 List<String> loadClasses = new ArrayList<>(); 489 String csvFile = null; 490 491 try { 492 while (!args.isEmpty()) { 493 String a = args.element(); 494 if (a.startsWith("-")) { 495 args.remove(); 496 switch (a) { 497 case "--class-path": 498 classPath.clear(); 499 Arrays.stream(args.remove().split(File.pathSeparator)) 500 .map(File::new) 501 .forEachOrdered(classPath::add); 502 break; 503 case "--for-removal": 504 forRemoval = true; 505 break; 506 case "--full-version": 507 out.println(System.getProperty("java.vm.version")); 508 return false; 509 case "--help": 510 case "-h": 511 out.println(Messages.get("main.usage")); 512 out.println(); 513 out.println(Messages.get("main.help")); 514 return false; 515 case "-l": 516 case "--list": 517 require(scanMode == ScanMode.ARGS); 518 scanMode = ScanMode.LIST; 519 break; 520 case "--release": 521 loadMode = LoadMode.RELEASE; 522 release = args.remove(); 523 if (!validReleases.contains(release)) { 524 throw new UsageException(); 525 } 526 break; 527 case "-v": 528 case "--verbose": 529 verbose = true; 530 break; 531 case "--version": 532 out.println(System.getProperty("java.version")); 533 return false; 534 case "--Xcompiler-arg": 535 options.add(args.remove()); 536 break; 537 case "--Xcsv-comment": 538 comments.add(args.remove()); 539 break; 540 case "--Xhelp": 541 out.println(Messages.get("main.xhelp")); 542 return false; 543 case "--Xload-class": 544 loadMode = LoadMode.CLASSES; 545 loadClasses.add(args.remove()); 546 break; 547 case "--Xload-csv": 548 loadMode = LoadMode.LOAD_CSV; 549 csvFile = args.remove(); 550 break; 551 case "--Xload-dir": 552 loadMode = LoadMode.DIR; 553 dir = args.remove(); 554 break; 555 case "--Xload-jar": 556 loadMode = LoadMode.JAR; 557 jar = args.remove(); 558 break; 559 case "--Xload-jdk9": 560 loadMode = LoadMode.JDK9; 561 jdkHome = args.remove(); 562 break; 563 case "--Xload-old-jdk": 564 loadMode = LoadMode.OLD_JDK; 565 jdkHome = args.remove(); 566 break; 567 case "--Xload-self": 568 loadMode = LoadMode.SELF; 569 break; 570 case "--Xprint-csv": 571 require(scanMode == ScanMode.ARGS); 572 scanMode = ScanMode.PRINT_CSV; 573 break; 574 default: 575 throw new UsageException(); 576 } 577 } else { 578 break; 579 } 580 } 581 582 if ((scanMode == ScanMode.ARGS) == args.isEmpty()) { 583 throw new UsageException(); 584 } 585 586 if ( forRemoval && loadMode == LoadMode.RELEASE && 587 releasesWithoutForRemoval.contains(release)) { 588 throw new UsageException(); 589 } 590 591 boolean success = false; 592 593 switch (loadMode) { 594 case CLASSES: 595 success = doClassNames(loadClasses); 596 break; 597 case DIR: 598 success = processDirectory(dir, loadClasses); 599 break; 600 case JAR: 601 success = processJarFile(jar, loadClasses); 602 break; 603 case JDK9: 604 require(!args.isEmpty()); 605 success = processJdk9(jdkHome, loadClasses); 606 break; 607 case LOAD_CSV: 608 deprList = DeprDB.loadFromFile(csvFile); 609 success = true; 610 break; 611 case OLD_JDK: 612 success = processOldJdk(jdkHome, loadClasses); 613 break; 614 case RELEASE: 615 success = processRelease(release, loadClasses); 616 break; 617 case SELF: 618 success = processSelf(loadClasses); 619 break; 620 default: 621 throw new UsageException(); 622 } 623 624 if (!success) { 625 return false; 626 } 627 } catch (NoSuchElementException | UsageException ex) { 628 err.println(Messages.get("main.usage")); 629 return false; 630 } catch (IOException ioe) { 631 if (verbose) { 632 ioe.printStackTrace(err); 633 } else { 634 err.println(ioe); 635 } 636 return false; 637 } 638 639 // now the scanning phase 640 641 boolean scanStatus = true; 642 643 switch (scanMode) { 644 case LIST: 645 for (DeprData dd : deprList) { 646 if (!forRemoval || dd.isForRemoval()) { 647 out.println(Pretty.print(dd)); 648 } 649 } 650 break; 651 case PRINT_CSV: 652 out.println("#jdepr1"); 653 comments.forEach(s -> out.println("# " + s)); 654 for (DeprData dd : deprList) { 655 CSV.write(out, dd.kind, dd.typeName, dd.nameSig, dd.since, dd.forRemoval); 656 } 657 break; 658 case ARGS: 659 DeprDB db = DeprDB.loadFromList(deprList); 660 List<String> cp = classPath.stream() 661 .map(File::toString) 662 .collect(toList()); 663 Scan scan = new Scan(out, err, cp, db, verbose); 664 665 for (String a : args) { 666 boolean s; 667 if (a.endsWith(".jar")) { 668 s = scan.scanJar(a); 669 } else if (a.endsWith(".class")) { 670 s = scan.processClassFile(a); 671 } else if (Files.isDirectory(Paths.get(a))) { 672 s = scan.scanDir(a); 673 } else { 674 s = scan.processClassName(a.replace('.', '/')); 675 } 676 scanStatus = scanStatus && s; 677 } 678 break; 679 } 680 681 return scanStatus; 682 } 683 684 /** 685 * Programmatic main entry point: initializes the tool instance to 686 * use stdout and stderr; runs the tool, passing command-line args; 687 * returns an exit status. 688 * 689 * @return true on success, false otherwise 690 */ 691 public static boolean call(PrintStream out, PrintStream err, String... args) { 692 return new Main(out, err).run(args); 693 } 694 695 /** 696 * Calls the main entry point and exits the JVM with an exit 697 * status determined by the return status. 698 */ 699 public static void main(String[] args) { 700 System.exit(call(System.out, System.err, args) ? 0 : 1); 701 } 702} 703