rev 47329 : 8189102: All tools should support -?, -h and --help

   1 /*
   2  * Copyright (c) 2016, 2017, 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.HashSet;
  42 import java.util.List;
  43 import java.util.Map;
  44 import java.util.NoSuchElementException;
  45 import java.util.Set;
  46 import java.util.Queue;
  47 import java.util.stream.Stream;
  48 import java.util.jar.JarEntry;
  49 import java.util.jar.JarFile;
  50 
  51 import javax.tools.Diagnostic;
  52 import javax.tools.DiagnosticListener;
  53 import javax.tools.JavaCompiler;
  54 import javax.tools.JavaFileObject;
  55 import javax.tools.StandardJavaFileManager;
  56 import javax.tools.StandardLocation;
  57 import javax.tools.ToolProvider;
  58 
  59 import com.sun.tools.javac.file.JavacFileManager;
  60 
  61 import com.sun.tools.jdeprscan.scan.Scan;
  62 
  63 import static java.util.stream.Collectors.*;
  64 
  65 import javax.lang.model.element.PackageElement;
  66 import javax.lang.model.element.TypeElement;
  67 
  68 /**
  69  * Deprecation Scanner tool. Loads API deprecation information from the
  70  * JDK image, or optionally, from a jar file or class hierarchy. Then scans
  71  * a class library for usages of those APIs.
  72  *
  73  * TODO:
  74  *  - audit error handling throughout, but mainly in scan package
  75  *  - handling of covariant overrides
  76  *  - handling of override of method found in multiple superinterfaces
  77  *  - convert type/method/field output to Java source like syntax, e.g.
  78  *      instead of java/lang/Runtime.runFinalizersOnExit(Z)V
  79  *      print void java.lang.Runtime.runFinalizersOnExit(boolean)
  80  *  - more example output in man page
  81  *  - more rigorous GNU style option parsing; use joptsimple?
  82  *
  83  * FUTURES:
  84  *  - add module support: --add-modules, --module-path, module arg
  85  *  - load deprecation declarations from a designated class library instead
  86  *    of the JDK
  87  *  - load deprecation declarations from a module
  88  *  - scan a module (but a modular jar can be treated just a like an ordinary jar)
  89  *  - multi-version jar
  90  */
  91 public class Main implements DiagnosticListener<JavaFileObject> {
  92     final PrintStream out;
  93     final PrintStream err;
  94     final List<File> bootClassPath = new ArrayList<>();
  95     final List<File> classPath = new ArrayList<>();
  96     final List<File> systemModules = new ArrayList<>();
  97     final List<String> options = new ArrayList<>();
  98     final List<String> comments = new ArrayList<>();
  99 
 100     // Valid releases need to match what the compiler supports.
 101     // Keep these updated manually until there's a compiler API
 102     // that allows querying of supported releases.
 103     final Set<String> releasesWithoutForRemoval = Set.of("6", "7", "8");
 104     final Set<String> releasesWithForRemoval = Set.of("9", "10");
 105 
 106     final Set<String> validReleases;
 107     {
 108         Set<String> temp = new HashSet<>(releasesWithoutForRemoval);
 109         temp.addAll(releasesWithForRemoval);
 110         validReleases = Set.of(temp.toArray(new String[0]));
 111     }
 112 
 113     boolean verbose = false;
 114     boolean forRemoval = false;
 115 
 116     final JavaCompiler compiler;
 117     final StandardJavaFileManager fm;
 118 
 119     List<DeprData> deprList; // non-null after successful load phase
 120 
 121     /**
 122      * Processes a collection of class names. Names should fully qualified
 123      * names in the form "pkg.pkg.pkg.classname".
 124      *
 125      * @param classNames collection of fully qualified classnames to process
 126      * @return true for success, false for failure
 127      * @throws IOException if an I/O error occurs
 128      */
 129     boolean doClassNames(Collection<String> classNames) throws IOException {
 130         if (verbose) {
 131             out.println("List of classes to process:");
 132             classNames.forEach(out::println);
 133             out.println("End of class list.");
 134         }
 135 
 136         // TODO: not sure this is necessary...
 137         if (fm instanceof JavacFileManager) {
 138             ((JavacFileManager)fm).setSymbolFileEnabled(false);
 139         }
 140 
 141         fm.setLocation(StandardLocation.CLASS_PATH, classPath);
 142         if (!bootClassPath.isEmpty()) {
 143             fm.setLocation(StandardLocation.PLATFORM_CLASS_PATH, bootClassPath);
 144         }
 145 
 146         if (!systemModules.isEmpty()) {
 147             fm.setLocation(StandardLocation.SYSTEM_MODULES, systemModules);
 148         }
 149 
 150         LoadProc proc = new LoadProc();
 151         JavaCompiler.CompilationTask task =
 152             compiler.getTask(null, fm, this, options, classNames, null);
 153         task.setProcessors(List.of(proc));
 154         boolean r = task.call();
 155         if (r) {
 156             if (forRemoval) {
 157                 deprList = proc.getDeprecations().stream()
 158                                .filter(DeprData::isForRemoval)
 159                                .collect(toList());
 160             } else {
 161                 deprList = proc.getDeprecations();
 162             }
 163         }
 164         return r;
 165     }
 166 
 167     /**
 168      * Processes a stream of filenames (strings). The strings are in the
 169      * form pkg/pkg/pkg/classname.class relative to the root of a package
 170      * hierarchy.
 171      *
 172      * @param filenames a Stream of filenames to process
 173      * @return true for success, false for failure
 174      * @throws IOException if an I/O error occurs
 175      */
 176     boolean doFileNames(Stream<String> filenames) throws IOException {
 177         return doClassNames(
 178             filenames.filter(name -> name.endsWith(".class"))
 179                      .filter(name -> !name.endsWith("package-info.class"))
 180                      .filter(name -> !name.endsWith("module-info.class"))
 181                      .map(s -> s.replaceAll("\\.class$", ""))
 182                      .map(s -> s.replace(File.separatorChar, '.'))
 183                      .collect(toList()));
 184     }
 185 
 186     /**
 187      * Replaces all but the first occurrence of '/' with '.'. Assumes
 188      * that the name is in the format module/pkg/pkg/classname.class.
 189      * That is, the name should contain at least one '/' character
 190      * separating the module name from the package-class name.
 191      *
 192      * @param filename the input filename
 193      * @return the modular classname
 194      */
 195     String convertModularFileName(String filename) {
 196         int slash = filename.indexOf('/');
 197         return filename.substring(0, slash)
 198                + "/"
 199                + filename.substring(slash+1).replace('/', '.');
 200     }
 201 
 202     /**
 203      * Processes a stream of filenames (strings) including a module prefix.
 204      * The strings are in the form module/pkg/pkg/pkg/classname.class relative
 205      * to the root of a directory containing modules. The strings are processed
 206      * into module-qualified class names of the form
 207      * "module/pkg.pkg.pkg.classname".
 208      *
 209      * @param filenames a Stream of filenames to process
 210      * @return true for success, false for failure
 211      * @throws IOException if an I/O error occurs
 212      */
 213     boolean doModularFileNames(Stream<String> filenames) throws IOException {
 214         return doClassNames(
 215             filenames.filter(name -> name.endsWith(".class"))
 216                      .filter(name -> !name.endsWith("package-info.class"))
 217                      .filter(name -> !name.endsWith("module-info.class"))
 218                      .map(s -> s.replaceAll("\\.class$", ""))
 219                      .map(this::convertModularFileName)
 220                      .collect(toList()));
 221     }
 222 
 223     /**
 224      * Processes named class files in the given directory. The directory
 225      * should be the root of a package hierarchy. If classNames is
 226      * empty, walks the directory hierarchy to find all classes.
 227      *
 228      * @param dirname the name of the directory to process
 229      * @param classNames the names of classes to process
 230      * @return true for success, false for failure
 231      * @throws IOException if an I/O error occurs
 232      */
 233     boolean processDirectory(String dirname, Collection<String> classNames) throws IOException {
 234         if (!Files.isDirectory(Paths.get(dirname))) {
 235             err.printf("%s: not a directory%n", dirname);
 236             return false;
 237         }
 238 
 239         classPath.add(0, new File(dirname));
 240 
 241         if (classNames.isEmpty()) {
 242             Path base = Paths.get(dirname);
 243             int baseCount = base.getNameCount();
 244             try (Stream<Path> paths = Files.walk(base)) {
 245                 Stream<String> files =
 246                     paths.filter(p -> p.getNameCount() > baseCount)
 247                          .map(p -> p.subpath(baseCount, p.getNameCount()))
 248                          .map(Path::toString);
 249                 return doFileNames(files);
 250             }
 251         } else {
 252             return doClassNames(classNames);
 253         }
 254     }
 255 
 256     /**
 257      * Processes all class files in the given jar file.
 258      *
 259      * @param jarname the name of the jar file to process
 260      * @return true for success, false for failure
 261      * @throws IOException if an I/O error occurs
 262      */
 263     boolean doJarFile(String jarname) throws IOException {
 264         try (JarFile jf = new JarFile(jarname)) {
 265             Stream<String> files =
 266                 jf.stream()
 267                   .map(JarEntry::getName);
 268             return doFileNames(files);
 269         }
 270     }
 271 
 272     /**
 273      * Processes named class files from the given jar file,
 274      * or all classes if classNames is empty.
 275      *
 276      * @param jarname the name of the jar file to process
 277      * @param classNames the names of classes to process
 278      * @return true for success, false for failure
 279      * @throws IOException if an I/O error occurs
 280      */
 281     boolean processJarFile(String jarname, Collection<String> classNames) throws IOException {
 282         classPath.add(0, new File(jarname));
 283 
 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                         case "-?":
 512                             out.println(Messages.get("main.usage"));
 513                             out.println();
 514                             out.println(Messages.get("main.help"));
 515                             return false;
 516                         case "-l":
 517                         case "--list":
 518                             require(scanMode == ScanMode.ARGS);
 519                             scanMode = ScanMode.LIST;
 520                             break;
 521                         case "--release":
 522                             loadMode = LoadMode.RELEASE;
 523                             release = args.remove();
 524                             if (!validReleases.contains(release)) {
 525                                 throw new UsageException();
 526                             }
 527                             break;
 528                         case "-v":
 529                         case "--verbose":
 530                             verbose = true;
 531                             break;
 532                         case "--version":
 533                             out.println(System.getProperty("java.version"));
 534                             return false;
 535                         case "--Xcompiler-arg":
 536                             options.add(args.remove());
 537                             break;
 538                         case "--Xcsv-comment":
 539                             comments.add(args.remove());
 540                             break;
 541                         case "--Xhelp":
 542                             out.println(Messages.get("main.xhelp"));
 543                             return false;
 544                         case "--Xload-class":
 545                             loadMode = LoadMode.CLASSES;
 546                             loadClasses.add(args.remove());
 547                             break;
 548                         case "--Xload-csv":
 549                             loadMode = LoadMode.LOAD_CSV;
 550                             csvFile = args.remove();
 551                             break;
 552                         case "--Xload-dir":
 553                             loadMode = LoadMode.DIR;
 554                             dir = args.remove();
 555                             break;
 556                         case "--Xload-jar":
 557                             loadMode = LoadMode.JAR;
 558                             jar = args.remove();
 559                             break;
 560                         case "--Xload-jdk9":
 561                             loadMode = LoadMode.JDK9;
 562                             jdkHome = args.remove();
 563                             break;
 564                         case "--Xload-old-jdk":
 565                             loadMode = LoadMode.OLD_JDK;
 566                             jdkHome = args.remove();
 567                             break;
 568                         case "--Xload-self":
 569                             loadMode = LoadMode.SELF;
 570                             break;
 571                         case "--Xprint-csv":
 572                             require(scanMode == ScanMode.ARGS);
 573                             scanMode = ScanMode.PRINT_CSV;
 574                             break;
 575                         default:
 576                             throw new UsageException();
 577                     }
 578                 } else {
 579                     break;
 580                 }
 581             }
 582 
 583             if ((scanMode == ScanMode.ARGS) == args.isEmpty()) {
 584                 throw new UsageException();
 585             }
 586 
 587             if (    forRemoval && loadMode == LoadMode.RELEASE &&
 588                     releasesWithoutForRemoval.contains(release)) {
 589                 throw new UsageException();
 590             }
 591 
 592             boolean success = false;
 593 
 594             switch (loadMode) {
 595                 case CLASSES:
 596                     success = doClassNames(loadClasses);
 597                     break;
 598                 case DIR:
 599                     success = processDirectory(dir, loadClasses);
 600                     break;
 601                 case JAR:
 602                     success = processJarFile(jar, loadClasses);
 603                     break;
 604                 case JDK9:
 605                     require(!args.isEmpty());
 606                     success = processJdk9(jdkHome, loadClasses);
 607                     break;
 608                 case LOAD_CSV:
 609                     deprList = DeprDB.loadFromFile(csvFile);
 610                     success = true;
 611                     break;
 612                 case OLD_JDK:
 613                     success = processOldJdk(jdkHome, loadClasses);
 614                     break;
 615                 case RELEASE:
 616                     success = processRelease(release, loadClasses);
 617                     break;
 618                 case SELF:
 619                     success = processSelf(loadClasses);
 620                     break;
 621                 default:
 622                     throw new UsageException();
 623             }
 624 
 625             if (!success) {
 626                 return false;
 627             }
 628         } catch (NoSuchElementException | UsageException ex) {
 629             err.println(Messages.get("main.usage"));
 630             return false;
 631         } catch (IOException ioe) {
 632             if (verbose) {
 633                 ioe.printStackTrace(err);
 634             } else {
 635                 err.println(ioe);
 636             }
 637             return false;
 638         }
 639 
 640         // now the scanning phase
 641 
 642         boolean scanStatus = true;
 643 
 644         switch (scanMode) {
 645             case LIST:
 646                 for (DeprData dd : deprList) {
 647                     if (!forRemoval || dd.isForRemoval()) {
 648                         out.println(Pretty.print(dd));
 649                     }
 650                 }
 651                 break;
 652             case PRINT_CSV:
 653                 out.println("#jdepr1");
 654                 comments.forEach(s -> out.println("# " + s));
 655                 for (DeprData dd : deprList) {
 656                     CSV.write(out, dd.kind, dd.typeName, dd.nameSig, dd.since, dd.forRemoval);
 657                 }
 658                 break;
 659             case ARGS:
 660                 DeprDB db = DeprDB.loadFromList(deprList);
 661                 List<String> cp = classPath.stream()
 662                                            .map(File::toString)
 663                                            .collect(toList());
 664                 Scan scan = new Scan(out, err, cp, db, verbose);
 665 
 666                 for (String a : args) {
 667                     boolean s;
 668                     if (a.endsWith(".jar")) {
 669                         s = scan.scanJar(a);
 670                     } else if (a.endsWith(".class")) {
 671                         s = scan.processClassFile(a);
 672                     } else if (Files.isDirectory(Paths.get(a))) {
 673                         s = scan.scanDir(a);
 674                     } else {
 675                         s = scan.processClassName(a.replace('.', '/'));
 676                     }
 677                     scanStatus = scanStatus && s;
 678                 }
 679                 break;
 680         }
 681 
 682         return scanStatus;
 683     }
 684 
 685     /**
 686      * Programmatic main entry point: initializes the tool instance to
 687      * use stdout and stderr; runs the tool, passing command-line args;
 688      * returns an exit status.
 689      *
 690      * @return true on success, false otherwise
 691      */
 692     public static boolean call(PrintStream out, PrintStream err, String... args) {
 693         return new Main(out, err).run(args);
 694     }
 695 
 696     /**
 697      * Calls the main entry point and exits the JVM with an exit
 698      * status determined by the return status.
 699      */
 700     public static void main(String[] args) {
 701         System.exit(call(System.out, System.err, args) ? 0 : 1);
 702     }
 703 }
--- EOF ---