1 /*
   2  * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.tools.jdeprscan;
  27 
  28 import java.io.File;
  29 import java.io.IOException;
  30 import java.io.PrintStream;
  31 import java.net.URI;
  32 import java.nio.charset.StandardCharsets;
  33 import java.nio.file.Files;
  34 import java.nio.file.FileSystems;
  35 import java.nio.file.Path;
  36 import java.nio.file.Paths;
  37 import java.util.ArrayDeque;
  38 import java.util.ArrayList;
  39 import java.util.Arrays;
  40 import java.util.Collection;
  41 import java.util.EnumSet;
  42 import java.util.HashSet;
  43 import java.util.List;
  44 import java.util.Map;
  45 import java.util.NoSuchElementException;
  46 import java.util.Set;
  47 import java.util.Queue;
  48 import java.util.stream.Stream;
  49 import java.util.stream.StreamSupport;
  50 import java.util.jar.JarEntry;
  51 import java.util.jar.JarFile;
  52 
  53 import javax.tools.Diagnostic;
  54 import javax.tools.DiagnosticListener;
  55 import javax.tools.JavaCompiler;
  56 import javax.tools.JavaFileManager;
  57 import javax.tools.JavaFileObject;
  58 import javax.tools.JavaFileObject.Kind;
  59 import javax.tools.StandardJavaFileManager;
  60 import javax.tools.StandardLocation;
  61 import javax.tools.ToolProvider;
  62 
  63 import com.sun.tools.javac.file.JavacFileManager;
  64 import com.sun.tools.javac.platform.JDKPlatformProvider;
  65 
  66 import com.sun.tools.jdeprscan.scan.Scan;
  67 
  68 import static java.util.stream.Collectors.*;
  69 
  70 import javax.lang.model.element.PackageElement;
  71 import javax.lang.model.element.TypeElement;
  72 
  73 /**
  74  * Deprecation Scanner tool. Loads API deprecation information from the
  75  * JDK image, or optionally, from a jar file or class hierarchy. Then scans
  76  * a class library for usages of those APIs.
  77  *
  78  * TODO:
  79  *  - audit error handling throughout, but mainly in scan package
  80  *  - handling of covariant overrides
  81  *  - handling of override of method found in multiple superinterfaces
  82  *  - convert type/method/field output to Java source like syntax, e.g.
  83  *      instead of java/lang/Character.isJavaLetter(C)Z
  84  *      print void java.lang.Character.isJavaLetter(char)boolean
  85  *  - more example output in man page
  86  *  - more rigorous GNU style option parsing; use joptsimple?
  87  *
  88  * FUTURES:
  89  *  - add module support: --add-modules, --module-path, module arg
  90  *  - load deprecation declarations from a designated class library instead
  91  *    of the JDK
  92  *  - load deprecation declarations from a module
  93  *  - scan a module (but a modular jar can be treated just a like an ordinary jar)
  94  *  - multi-version jar
  95  */
  96 public class Main implements DiagnosticListener<JavaFileObject> {
  97     final PrintStream out;
  98     final PrintStream err;
  99     final List<File> bootClassPath = new ArrayList<>();
 100     final List<File> classPath = new ArrayList<>();
 101     final List<File> systemModules = new ArrayList<>();
 102     final List<String> options = new ArrayList<>();
 103     final List<String> comments = new ArrayList<>();
 104 
 105     // Valid releases need to match what the compiler supports.
 106     // Keep these updated manually until there's a compiler API
 107     // that allows querying of supported releases.
 108     final Set<String> releasesWithoutForRemoval = Set.of("6", "7", "8");
 109     final Set<String> releasesWithForRemoval = Set.of("9", "10", "11", "12", "13");
 110 
 111     final Set<String> validReleases;
 112     {
 113         Set<String> temp = new HashSet<>(releasesWithoutForRemoval);
 114         temp.addAll(releasesWithForRemoval);
 115         validReleases = Set.of(temp.toArray(new String[0]));
 116     }
 117 
 118     boolean verbose = false;
 119     boolean forRemoval = false;
 120 
 121     final JavaCompiler compiler;
 122     final StandardJavaFileManager fm;
 123 
 124     List<DeprData> deprList; // non-null after successful load phase
 125 
 126     /**
 127      * Processes a collection of class names. Names should fully qualified
 128      * names in the form "pkg.pkg.pkg.classname".
 129      *
 130      * @param classNames collection of fully qualified classnames to process
 131      * @return true for success, false for failure
 132      * @throws IOException if an I/O error occurs
 133      */
 134     boolean doClassNames(Collection<String> classNames) throws IOException {
 135         if (verbose) {
 136             out.println("List of classes to process:");
 137             classNames.forEach(out::println);
 138             out.println("End of class list.");
 139         }
 140 
 141         // TODO: not sure this is necessary...
 142         if (fm instanceof JavacFileManager) {
 143             ((JavacFileManager)fm).setSymbolFileEnabled(false);
 144         }
 145 
 146         fm.setLocation(StandardLocation.CLASS_PATH, classPath);
 147         if (!bootClassPath.isEmpty()) {
 148             fm.setLocation(StandardLocation.PLATFORM_CLASS_PATH, bootClassPath);
 149         }
 150 
 151         if (!systemModules.isEmpty()) {
 152             fm.setLocation(StandardLocation.SYSTEM_MODULES, systemModules);
 153         }
 154 
 155         LoadProc proc = new LoadProc();
 156         JavaCompiler.CompilationTask task =
 157             compiler.getTask(null, fm, this, options, classNames, null);
 158         task.setProcessors(List.of(proc));
 159         boolean r = task.call();
 160         if (r) {
 161             if (forRemoval) {
 162                 deprList = proc.getDeprecations().stream()
 163                                .filter(DeprData::isForRemoval)
 164                                .collect(toList());
 165             } else {
 166                 deprList = proc.getDeprecations();
 167             }
 168         }
 169         return r;
 170     }
 171 
 172     /**
 173      * Processes a stream of filenames (strings). The strings are in the
 174      * form pkg/pkg/pkg/classname.class relative to the root of a package
 175      * hierarchy.
 176      *
 177      * @param filenames a Stream of filenames to process
 178      * @return true for success, false for failure
 179      * @throws IOException if an I/O error occurs
 180      */
 181     boolean doFileNames(Stream<String> filenames) throws IOException {
 182         return doClassNames(
 183             filenames.filter(name -> name.endsWith(".class"))
 184                      .filter(name -> !name.endsWith("package-info.class"))
 185                      .filter(name -> !name.endsWith("module-info.class"))
 186                      .map(s -> s.replaceAll("\\.class$", ""))
 187                      .map(s -> s.replace(File.separatorChar, '.'))
 188                      .collect(toList()));
 189     }
 190 
 191     /**
 192      * Replaces all but the first occurrence of '/' with '.'. Assumes
 193      * that the name is in the format module/pkg/pkg/classname.class.
 194      * That is, the name should contain at least one '/' character
 195      * separating the module name from the package-class name.
 196      *
 197      * @param filename the input filename
 198      * @return the modular classname
 199      */
 200     String convertModularFileName(String filename) {
 201         int slash = filename.indexOf('/');
 202         return filename.substring(0, slash)
 203                + "/"
 204                + filename.substring(slash+1).replace('/', '.');
 205     }
 206 
 207     /**
 208      * Processes a stream of filenames (strings) including a module prefix.
 209      * The strings are in the form module/pkg/pkg/pkg/classname.class relative
 210      * to the root of a directory containing modules. The strings are processed
 211      * into module-qualified class names of the form
 212      * "module/pkg.pkg.pkg.classname".
 213      *
 214      * @param filenames a Stream of filenames to process
 215      * @return true for success, false for failure
 216      * @throws IOException if an I/O error occurs
 217      */
 218     boolean doModularFileNames(Stream<String> filenames) throws IOException {
 219         return doClassNames(
 220             filenames.filter(name -> name.endsWith(".class"))
 221                      .filter(name -> !name.endsWith("package-info.class"))
 222                      .filter(name -> !name.endsWith("module-info.class"))
 223                      .map(s -> s.replaceAll("\\.class$", ""))
 224                      .map(this::convertModularFileName)
 225                      .collect(toList()));
 226     }
 227 
 228     /**
 229      * Processes named class files in the given directory. The directory
 230      * should be the root of a package hierarchy. If classNames is
 231      * empty, walks the directory hierarchy to find all classes.
 232      *
 233      * @param dirname the name of the directory to process
 234      * @param classNames the names of classes to process
 235      * @return true for success, false for failure
 236      * @throws IOException if an I/O error occurs
 237      */
 238     boolean processDirectory(String dirname, Collection<String> classNames) throws IOException {
 239         if (!Files.isDirectory(Paths.get(dirname))) {
 240             err.printf("%s: not a directory%n", dirname);
 241             return false;
 242         }
 243 
 244         classPath.add(0, new File(dirname));
 245 
 246         if (classNames.isEmpty()) {
 247             Path base = Paths.get(dirname);
 248             int baseCount = base.getNameCount();
 249             try (Stream<Path> paths = Files.walk(base)) {
 250                 Stream<String> files =
 251                     paths.filter(p -> p.getNameCount() > baseCount)
 252                          .map(p -> p.subpath(baseCount, p.getNameCount()))
 253                          .map(Path::toString);
 254                 return doFileNames(files);
 255             }
 256         } else {
 257             return doClassNames(classNames);
 258         }
 259     }
 260 
 261     /**
 262      * Processes all class files in the given jar file.
 263      *
 264      * @param jarname the name of the jar file to process
 265      * @return true for success, false for failure
 266      * @throws IOException if an I/O error occurs
 267      */
 268     boolean doJarFile(String jarname) throws IOException {
 269         try (JarFile jf = new JarFile(jarname)) {
 270             Stream<String> files =
 271                 jf.stream()
 272                   .map(JarEntry::getName);
 273             return doFileNames(files);
 274         }
 275     }
 276 
 277     /**
 278      * Processes named class files from the given jar file,
 279      * or all classes if classNames is empty.
 280      *
 281      * @param jarname the name of the jar file to process
 282      * @param classNames the names of classes to process
 283      * @return true for success, false for failure
 284      * @throws IOException if an I/O error occurs
 285      */
 286     boolean processJarFile(String jarname, Collection<String> classNames) throws IOException {
 287         classPath.add(0, new File(jarname));
 288 
 289         if (classNames.isEmpty()) {
 290             return doJarFile(jarname);
 291         } else {
 292             return doClassNames(classNames);
 293         }
 294     }
 295 
 296     /**
 297      * Processes named class files from rt.jar of a JDK version 7 or 8.
 298      * If classNames is empty, processes all classes.
 299      *
 300      * @param jdkHome the path to the "home" of the JDK to process
 301      * @param classNames the names of classes to process
 302      * @return true for success, false for failure
 303      * @throws IOException if an I/O error occurs
 304      */
 305     boolean processOldJdk(String jdkHome, Collection<String> classNames) throws IOException {
 306         String RTJAR = jdkHome + "/jre/lib/rt.jar";
 307         String CSJAR = jdkHome + "/jre/lib/charsets.jar";
 308 
 309         bootClassPath.add(0, new File(RTJAR));
 310         bootClassPath.add(1, new File(CSJAR));
 311         options.add("-source");
 312         options.add("8");
 313 
 314         if (classNames.isEmpty()) {
 315             return doJarFile(RTJAR);
 316         } else {
 317             return doClassNames(classNames);
 318         }
 319     }
 320 
 321     /**
 322      * Processes listed classes given a JDK 9 home.
 323      */
 324     boolean processJdk9(String jdkHome, Collection<String> classes) throws IOException {
 325         systemModules.add(new File(jdkHome));
 326         return doClassNames(classes);
 327     }
 328 
 329     /**
 330      * Processes the class files from the currently running JDK,
 331      * using the jrt: filesystem.
 332      *
 333      * @return true for success, false for failure
 334      * @throws IOException if an I/O error occurs
 335      */
 336     boolean processSelf(Collection<String> classes) throws IOException {
 337         options.add("--add-modules");
 338         options.add("java.se");
 339 
 340         if (classes.isEmpty()) {
 341             Path modules = FileSystems.getFileSystem(URI.create("jrt:/"))
 342                                       .getPath("/modules");
 343 
 344             // names are /modules/<modulename>/pkg/.../Classname.class
 345             try (Stream<Path> paths = Files.walk(modules)) {
 346                 Stream<String> files =
 347                     paths.filter(p -> p.getNameCount() > 2)
 348                          .map(p -> p.subpath(1, p.getNameCount()))
 349                          .map(Path::toString);
 350                 return doModularFileNames(files);
 351             }
 352         } else {
 353             return doClassNames(classes);
 354         }
 355     }
 356 
 357     /**
 358      * Process classes from a particular JDK release, using only information
 359      * in this JDK.
 360      *
 361      * @param release a supported release version, like "8" or "10".
 362      * @param classes collection of classes to process, may be empty
 363      * @return success value
 364      */
 365     boolean processRelease(String release, Collection<String> classes) throws IOException {
 366         boolean hasModules;
 367         boolean hasJavaSE_EE;
 368 
 369         try {
 370             int releaseNum = Integer.parseInt(release);
 371 
 372             hasModules = releaseNum >= 9;
 373             hasJavaSE_EE = hasModules && releaseNum <= 10;
 374         } catch (NumberFormatException ex) {
 375             hasModules = true;
 376             hasJavaSE_EE = false;
 377         }
 378 
 379         options.addAll(List.of("--release", release));
 380 
 381         if (hasModules) {
 382             List<String> rootMods = hasJavaSE_EE ? List.of("java.se", "java.se.ee")
 383                                                  : List.of("java.se");
 384             TraverseProc proc = new TraverseProc(rootMods);
 385             JavaCompiler.CompilationTask task =
 386                 compiler.getTask(null, fm, this,
 387                                  // options
 388                                  List.of("--add-modules", String.join(",", rootMods),
 389                                          "--release", release),
 390                                  // classes
 391                                  List.of("java.lang.Object"),
 392                                  null);
 393             task.setProcessors(List.of(proc));
 394             if (!task.call()) {
 395                 return false;
 396             }
 397             Map<PackageElement, List<TypeElement>> types = proc.getPublicTypes();
 398             options.add("--add-modules");
 399             options.add(String.join(",", rootMods));
 400             return doClassNames(
 401                 types.values().stream()
 402                      .flatMap(List::stream)
 403                      .map(TypeElement::toString)
 404                      .collect(toList()));
 405         } else {
 406             JDKPlatformProvider pp = new JDKPlatformProvider();
 407             if (StreamSupport.stream(pp.getSupportedPlatformNames().spliterator(),
 408                                  false)
 409                              .noneMatch(n -> n.equals(release))) {
 410                 return false;
 411             }
 412             JavaFileManager fm = pp.getPlatform(release, "").getFileManager();
 413             List<String> classNames = new ArrayList<>();
 414             for (JavaFileObject fo : fm.list(StandardLocation.PLATFORM_CLASS_PATH,
 415                                              "",
 416                                              EnumSet.of(Kind.CLASS),
 417                                              true)) {
 418                 classNames.add(fm.inferBinaryName(StandardLocation.PLATFORM_CLASS_PATH, fo));
 419             }
 420 
 421             options.add("-Xlint:-options");
 422 
 423             return doClassNames(classNames);
 424         }
 425     }
 426 
 427     /**
 428      * An enum denoting the mode in which the tool is running.
 429      * Different modes correspond to the different process* methods.
 430      * The exception is UNKNOWN, which indicates that a mode wasn't
 431      * specified on the command line, which is an error.
 432      */
 433     static enum LoadMode {
 434         CLASSES, DIR, JAR, OLD_JDK, JDK9, SELF, RELEASE, LOAD_CSV
 435     }
 436 
 437     static enum ScanMode {
 438         ARGS, LIST, PRINT_CSV
 439     }
 440 
 441     /**
 442      * A checked exception that's thrown if a command-line syntax error
 443      * is detected.
 444      */
 445     static class UsageException extends Exception {
 446         private static final long serialVersionUID = 3611828659572908743L;
 447     }
 448 
 449     /**
 450      * Convenience method to throw UsageException if a condition is false.
 451      *
 452      * @param cond the condition that's required to be true
 453      * @throws UsageException
 454      */
 455     void require(boolean cond) throws UsageException {
 456         if (!cond) {
 457             throw new UsageException();
 458         }
 459     }
 460 
 461     /**
 462      * Constructs an instance of the finder tool.
 463      *
 464      * @param out the stream to which the tool's output is sent
 465      * @param err the stream to which error messages are sent
 466      */
 467     Main(PrintStream out, PrintStream err) {
 468         this.out = out;
 469         this.err = err;
 470         compiler = ToolProvider.getSystemJavaCompiler();
 471         fm = compiler.getStandardFileManager(this, null, StandardCharsets.UTF_8);
 472     }
 473 
 474     /**
 475      * Prints the diagnostic to the err stream.
 476      *
 477      * Specified by the DiagnosticListener interface.
 478      *
 479      * @param diagnostic the tool diagnostic to print
 480      */
 481     @Override
 482     public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
 483         err.println(diagnostic);
 484     }
 485 
 486     /**
 487      * Parses arguments and performs the requested processing.
 488      *
 489      * @param argArray command-line arguments
 490      * @return true on success, false on error
 491      */
 492     boolean run(String... argArray) {
 493         Queue<String> args = new ArrayDeque<>(Arrays.asList(argArray));
 494         LoadMode loadMode = LoadMode.RELEASE;
 495         ScanMode scanMode = ScanMode.ARGS;
 496         String dir = null;
 497         String jar = null;
 498         String jdkHome = null;
 499         String release = Integer.toString(Runtime.version().feature());
 500         List<String> loadClasses = new ArrayList<>();
 501         String csvFile = null;
 502 
 503         try {
 504             while (!args.isEmpty()) {
 505                 String a = args.element();
 506                 if (a.startsWith("-")) {
 507                     args.remove();
 508                     switch (a) {
 509                         case "--class-path":
 510                             classPath.clear();
 511                             Arrays.stream(args.remove().split(File.pathSeparator))
 512                                   .map(File::new)
 513                                   .forEachOrdered(classPath::add);
 514                             break;
 515                         case "--for-removal":
 516                             forRemoval = true;
 517                             break;
 518                         case "--full-version":
 519                             out.println(System.getProperty("java.vm.version"));
 520                             return false;
 521                         case "--help":
 522                         case "-h":
 523                         case "-?":
 524                             printHelp(out);
 525                             out.println();
 526                             out.println(Messages.get("main.help"));
 527                             return true;
 528                         case "-l":
 529                         case "--list":
 530                             require(scanMode == ScanMode.ARGS);
 531                             scanMode = ScanMode.LIST;
 532                             break;
 533                         case "--release":
 534                             loadMode = LoadMode.RELEASE;
 535                             release = args.remove();
 536                             if (!validReleases.contains(release)) {
 537                                 throw new UsageException();
 538                             }
 539                             break;
 540                         case "-v":
 541                         case "--verbose":
 542                             verbose = true;
 543                             break;
 544                         case "--version":
 545                             out.println(System.getProperty("java.version"));
 546                             return false;
 547                         case "--Xcompiler-arg":
 548                             options.add(args.remove());
 549                             break;
 550                         case "--Xcsv-comment":
 551                             comments.add(args.remove());
 552                             break;
 553                         case "--Xhelp":
 554                             out.println(Messages.get("main.xhelp"));
 555                             return false;
 556                         case "--Xload-class":
 557                             loadMode = LoadMode.CLASSES;
 558                             loadClasses.add(args.remove());
 559                             break;
 560                         case "--Xload-csv":
 561                             loadMode = LoadMode.LOAD_CSV;
 562                             csvFile = args.remove();
 563                             break;
 564                         case "--Xload-dir":
 565                             loadMode = LoadMode.DIR;
 566                             dir = args.remove();
 567                             break;
 568                         case "--Xload-jar":
 569                             loadMode = LoadMode.JAR;
 570                             jar = args.remove();
 571                             break;
 572                         case "--Xload-jdk9":
 573                             loadMode = LoadMode.JDK9;
 574                             jdkHome = args.remove();
 575                             break;
 576                         case "--Xload-old-jdk":
 577                             loadMode = LoadMode.OLD_JDK;
 578                             jdkHome = args.remove();
 579                             break;
 580                         case "--Xload-self":
 581                             loadMode = LoadMode.SELF;
 582                             break;
 583                         case "--Xprint-csv":
 584                             require(scanMode == ScanMode.ARGS);
 585                             scanMode = ScanMode.PRINT_CSV;
 586                             break;
 587                         default:
 588                             throw new UsageException();
 589                     }
 590                 } else {
 591                     break;
 592                 }
 593             }
 594 
 595             if ((scanMode == ScanMode.ARGS) == args.isEmpty()) {
 596                 throw new UsageException();
 597             }
 598 
 599             if (    forRemoval && loadMode == LoadMode.RELEASE &&
 600                     releasesWithoutForRemoval.contains(release)) {
 601                 throw new UsageException();
 602             }
 603 
 604             boolean success = false;
 605 
 606             switch (loadMode) {
 607                 case CLASSES:
 608                     success = doClassNames(loadClasses);
 609                     break;
 610                 case DIR:
 611                     success = processDirectory(dir, loadClasses);
 612                     break;
 613                 case JAR:
 614                     success = processJarFile(jar, loadClasses);
 615                     break;
 616                 case JDK9:
 617                     require(!args.isEmpty());
 618                     success = processJdk9(jdkHome, loadClasses);
 619                     break;
 620                 case LOAD_CSV:
 621                     deprList = DeprDB.loadFromFile(csvFile);
 622                     success = true;
 623                     break;
 624                 case OLD_JDK:
 625                     success = processOldJdk(jdkHome, loadClasses);
 626                     break;
 627                 case RELEASE:
 628                     success = processRelease(release, loadClasses);
 629                     break;
 630                 case SELF:
 631                     success = processSelf(loadClasses);
 632                     break;
 633                 default:
 634                     throw new UsageException();
 635             }
 636 
 637             if (!success) {
 638                 return false;
 639             }
 640         } catch (NoSuchElementException | UsageException ex) {
 641             printHelp(err);
 642             return false;
 643         } catch (IOException ioe) {
 644             if (verbose) {
 645                 ioe.printStackTrace(err);
 646             } else {
 647                 err.println(ioe);
 648             }
 649             return false;
 650         }
 651 
 652         // now the scanning phase
 653 
 654         boolean scanStatus = true;
 655 
 656         switch (scanMode) {
 657             case LIST:
 658                 for (DeprData dd : deprList) {
 659                     if (!forRemoval || dd.isForRemoval()) {
 660                         out.println(Pretty.print(dd));
 661                     }
 662                 }
 663                 break;
 664             case PRINT_CSV:
 665                 out.println("#jdepr1");
 666                 comments.forEach(s -> out.println("# " + s));
 667                 for (DeprData dd : deprList) {
 668                     CSV.write(out, dd.kind, dd.typeName, dd.nameSig, dd.since, dd.forRemoval);
 669                 }
 670                 break;
 671             case ARGS:
 672                 DeprDB db = DeprDB.loadFromList(deprList);
 673                 List<String> cp = classPath.stream()
 674                                            .map(File::toString)
 675                                            .collect(toList());
 676                 Scan scan = new Scan(out, err, cp, db, verbose);
 677 
 678                 for (String a : args) {
 679                     boolean s;
 680                     if (a.endsWith(".jar")) {
 681                         s = scan.scanJar(a);
 682                     } else if (a.endsWith(".class")) {
 683                         s = scan.processClassFile(a);
 684                     } else if (Files.isDirectory(Paths.get(a))) {
 685                         s = scan.scanDir(a);
 686                     } else {
 687                         s = scan.processClassName(a.replace('.', '/'));
 688                     }
 689                     scanStatus = scanStatus && s;
 690                 }
 691                 break;
 692         }
 693 
 694         return scanStatus;
 695     }
 696 
 697     private void printHelp(PrintStream out) {
 698         JDKPlatformProvider pp = new JDKPlatformProvider();
 699         String supportedReleases =
 700                 String.join("|", pp.getSupportedPlatformNames());
 701         out.println(Messages.get("main.usage", supportedReleases));
 702     }
 703 
 704     /**
 705      * Programmatic main entry point: initializes the tool instance to
 706      * use stdout and stderr; runs the tool, passing command-line args;
 707      * returns an exit status.
 708      *
 709      * @return true on success, false otherwise
 710      */
 711     public static boolean call(PrintStream out, PrintStream err, String... args) {
 712         return new Main(out, err).run(args);
 713     }
 714 
 715     /**
 716      * Calls the main entry point and exits the JVM with an exit
 717      * status determined by the return status.
 718      */
 719     public static void main(String[] args) {
 720         System.exit(call(System.out, System.err, args) ? 0 : 1);
 721     }
 722 }
--- EOF ---