Main.java revision 3736:68754738ba9c
11541Srgrimes/*
21541Srgrimes * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
31541Srgrimes * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
41541Srgrimes *
551138Salfred * This code is free software; you can redistribute it and/or modify it
6107914Sdillon * under the terms of the GNU General Public License version 2 only, as
71541Srgrimes * published by the Free Software Foundation.  Oracle designates this
81541Srgrimes * particular file as subject to the "Classpath" exception as provided
9106149Sdwmalone * by Oracle in the LICENSE file that accompanied this code.
101541Srgrimes *
1164002Speter * This code is distributed in the hope that it will be useful, but WITHOUT
121541Srgrimes * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
131541Srgrimes * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
141541Srgrimes * version 2 for more details (a copy is included in the LICENSE file that
151541Srgrimes * accompanied this code).
161541Srgrimes *
171541Srgrimes * You should have received a copy of the GNU General Public License version
181541Srgrimes * 2 along with this work; if not, write to the Free Software Foundation,
191541Srgrimes * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
201541Srgrimes *
211541Srgrimes * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
221541Srgrimes * or visit www.oracle.com if you need additional information or have any
231541Srgrimes * questions.
241541Srgrimes */
251541Srgrimes
261541Srgrimespackage com.sun.tools.jdeprscan;
271541Srgrimes
281541Srgrimesimport java.io.File;
291541Srgrimesimport java.io.IOException;
301541Srgrimesimport java.io.PrintStream;
311541Srgrimesimport java.net.URI;
321541Srgrimesimport java.nio.charset.StandardCharsets;
331541Srgrimesimport java.nio.file.Files;
341541Srgrimesimport java.nio.file.FileSystems;
351541Srgrimesimport java.nio.file.Path;
361541Srgrimesimport java.nio.file.Paths;
371541Srgrimesimport java.util.ArrayDeque;
381541Srgrimesimport java.util.ArrayList;
391541Srgrimesimport java.util.Arrays;
401541Srgrimesimport java.util.Collection;
411541Srgrimesimport java.util.HashSet;
421541Srgrimesimport java.util.List;
431541Srgrimesimport java.util.Map;
441541Srgrimesimport java.util.NoSuchElementException;
451541Srgrimesimport java.util.Set;
461541Srgrimesimport java.util.Queue;
471541Srgrimesimport java.util.stream.Stream;
481541Srgrimesimport java.util.jar.JarEntry;
491541Srgrimesimport java.util.jar.JarFile;
501541Srgrimes
511541Srgrimesimport javax.tools.Diagnostic;
521541Srgrimesimport javax.tools.DiagnosticListener;
531541Srgrimesimport javax.tools.JavaCompiler;
541541Srgrimesimport javax.tools.JavaFileObject;
551541Srgrimesimport javax.tools.StandardJavaFileManager;
5652150Smarcelimport javax.tools.StandardLocation;
571541Srgrimesimport javax.tools.ToolProvider;
5852150Smarcel
591541Srgrimesimport com.sun.tools.javac.file.JavacFileManager;
601541Srgrimes
611541Srgrimesimport com.sun.tools.jdeprscan.scan.Scan;
6252150Smarcel
631541Srgrimesimport static java.util.stream.Collectors.*;
641541Srgrimes
651541Srgrimesimport javax.lang.model.element.PackageElement;
661541Srgrimesimport javax.lang.model.element.TypeElement;
671541Srgrimes
681541Srgrimes/**
691541Srgrimes * Deprecation Scanner tool. Loads API deprecation information from the
701541Srgrimes * JDK image, or optionally, from a jar file or class hierarchy. Then scans
711541Srgrimes * a class library for usages of those APIs.
721541Srgrimes *
731541Srgrimes * TODO:
741541Srgrimes *  - audit error handling throughout, but mainly in scan package
751541Srgrimes *  - handling of covariant overrides
761541Srgrimes *  - handling of override of method found in multiple superinterfaces
771541Srgrimes *  - convert type/method/field output to Java source like syntax, e.g.
781541Srgrimes *      instead of java/lang/Runtime.runFinalizersOnExit(Z)V
791541Srgrimes *      print void java.lang.Runtime.runFinalizersOnExit(boolean)
801541Srgrimes *  - more example output in man page
811541Srgrimes *  - more rigorous GNU style option parsing; use joptsimple?
821541Srgrimes *
831541Srgrimes * FUTURES:
841541Srgrimes *  - add module support: --add-modules, --module-path, module arg
851541Srgrimes *  - load deprecation declarations from a designated class library instead
861541Srgrimes *    of the JDK
871541Srgrimes *  - load deprecation declarations from a module
881541Srgrimes *  - scan a module (but a modular jar can be treated just a like an ordinary jar)
891541Srgrimes *  - multi-version jar
901541Srgrimes */
911541Srgrimespublic class Main implements DiagnosticListener<JavaFileObject> {
921541Srgrimes    final PrintStream out;
931541Srgrimes    final PrintStream err;
941541Srgrimes    final List<File> bootClassPath = new ArrayList<>();
951541Srgrimes    final List<File> classPath = new ArrayList<>();
961541Srgrimes    final List<File> systemModules = new ArrayList<>();
971541Srgrimes    final List<String> options = new ArrayList<>();
981541Srgrimes    final List<String> comments = new ArrayList<>();
991541Srgrimes
1001541Srgrimes    // Valid releases need to match what the compiler supports.
1011541Srgrimes    // Keep these updated manually until there's a compiler API
1021541Srgrimes    // that allows querying of supported releases.
1031541Srgrimes    final Set<String> releasesWithoutForRemoval = Set.of("6", "7", "8");
1041541Srgrimes    final Set<String> releasesWithForRemoval = Set.of("9");
1051541Srgrimes
1061541Srgrimes    final Set<String> validReleases;
1071541Srgrimes    {
1081541Srgrimes        Set<String> temp = new HashSet<>(releasesWithoutForRemoval);
1091541Srgrimes        temp.addAll(releasesWithForRemoval);
1101541Srgrimes        validReleases = Set.of(temp.toArray(new String[0]));
1111541Srgrimes    }
1121541Srgrimes
113105950Speter    boolean verbose = false;
1141541Srgrimes    boolean forRemoval = false;
1151541Srgrimes
1161541Srgrimes    final JavaCompiler compiler;
1171541Srgrimes    final StandardJavaFileManager fm;
1181541Srgrimes
1191541Srgrimes    List<DeprData> deprList; // non-null after successful load phase
1201541Srgrimes
12152150Smarcel    /**
1221541Srgrimes     * Processes a collection of class names. Names should fully qualified
1231541Srgrimes     * names in the form "pkg.pkg.pkg.classname".
1241541Srgrimes     *
1251541Srgrimes     * @param classNames collection of fully qualified classnames to process
1261541Srgrimes     * @return true for success, false for failure
1271541Srgrimes     * @throws IOException if an I/O error occurs
1281541Srgrimes     */
12914220Speter    boolean doClassNames(Collection<String> classNames) throws IOException {
1301541Srgrimes        if (verbose) {
1311541Srgrimes            out.println("List of classes to process:");
1321541Srgrimes            classNames.forEach(out::println);
1331541Srgrimes            out.println("End of class list.");
1341541Srgrimes        }
1351541Srgrimes
1368019Sache        // TODO: not sure this is necessary...
1378019Sache        if (fm instanceof JavacFileManager) {
1381541Srgrimes            ((JavacFileManager)fm).setSymbolFileEnabled(false);
1391541Srgrimes        }
1401541Srgrimes
1411541Srgrimes        fm.setLocation(StandardLocation.CLASS_PATH, classPath);
1421541Srgrimes        if (!bootClassPath.isEmpty()) {
1431541Srgrimes            fm.setLocation(StandardLocation.PLATFORM_CLASS_PATH, bootClassPath);
1441541Srgrimes        }
1451541Srgrimes
1461541Srgrimes        if (!systemModules.isEmpty()) {
1471541Srgrimes            fm.setLocation(StandardLocation.SYSTEM_MODULES, systemModules);
1481541Srgrimes        }
1491541Srgrimes
1501541Srgrimes        LoadProc proc = new LoadProc();
1511541Srgrimes        JavaCompiler.CompilationTask task =
1521541Srgrimes            compiler.getTask(null, fm, this, options, classNames, null);
1531541Srgrimes        task.setProcessors(List.of(proc));
1541541Srgrimes        boolean r = task.call();
1551541Srgrimes        if (r) {
1561541Srgrimes            if (forRemoval) {
1571541Srgrimes                deprList = proc.getDeprecations().stream()
1581541Srgrimes                               .filter(DeprData::isForRemoval)
1591541Srgrimes                               .collect(toList());
1601541Srgrimes            } else {
16114220Speter                deprList = proc.getDeprecations();
16214220Speter            }
16314220Speter        }
1641541Srgrimes        return r;
1651541Srgrimes    }
1661541Srgrimes
1671541Srgrimes    /**
1681541Srgrimes     * Processes a stream of filenames (strings). The strings are in the
1691541Srgrimes     * form pkg/pkg/pkg/classname.class relative to the root of a package
1701541Srgrimes     * hierarchy.
1711541Srgrimes     *
1721549Srgrimes     * @param filenames a Stream of filenames to process
1731549Srgrimes     * @return true for success, false for failure
1741549Srgrimes     * @throws IOException if an I/O error occurs
1751549Srgrimes     */
1762442Sdg    boolean doFileNames(Stream<String> filenames) throws IOException {
1771541Srgrimes        return doClassNames(
1781541Srgrimes            filenames.filter(name -> name.endsWith(".class"))
1792729Sdfr                     .filter(name -> !name.endsWith("package-info.class"))
1802729Sdfr                     .filter(name -> !name.endsWith("module-info.class"))
1811541Srgrimes                     .map(s -> s.replaceAll("\\.class$", ""))
1821541Srgrimes                     .map(s -> s.replace(File.separatorChar, '.'))
18345065Salc                     .collect(toList()));
18445065Salc    }
1852858Swollman
1862297Swollman    /**
18714220Speter     * Replaces all but the first occurrence of '/' with '.'. Assumes
18814220Speter     * that the name is in the format module/pkg/pkg/classname.class.
18914220Speter     * That is, the name should contain at least one '/' character
1901541Srgrimes     * separating the module name from the package-class name.
1911541Srgrimes     *
1921541Srgrimes     * @param filename the input filename
1931541Srgrimes     * @return the modular classname
19432889Sphk     */
19532889Sphk    String convertModularFileName(String filename) {
19632889Sphk        int slash = filename.indexOf('/');
19732889Sphk        return filename.substring(0, slash)
1981541Srgrimes               + "/"
1991541Srgrimes               + filename.substring(slash+1).replace('/', '.');
2001541Srgrimes    }
2011541Srgrimes
2021541Srgrimes    /**
2031541Srgrimes     * Processes a stream of filenames (strings) including a module prefix.
2041541Srgrimes     * The strings are in the form module/pkg/pkg/pkg/classname.class relative
2051541Srgrimes     * to the root of a directory containing modules. The strings are processed
2061541Srgrimes     * into module-qualified class names of the form
2071541Srgrimes     * "module/pkg.pkg.pkg.classname".
2081541Srgrimes     *
2091541Srgrimes     * @param filenames a Stream of filenames to process
2101541Srgrimes     * @return true for success, false for failure
2111541Srgrimes     * @throws IOException if an I/O error occurs
2121541Srgrimes     */
2131541Srgrimes    boolean doModularFileNames(Stream<String> filenames) throws IOException {
2141541Srgrimes        return doClassNames(
21535938Sdyson            filenames.filter(name -> name.endsWith(".class"))
21635938Sdyson                     .filter(name -> !name.endsWith("package-info.class"))
21728400Speter                     .filter(name -> !name.endsWith("module-info.class"))
21825582Speter                     .map(s -> s.replaceAll("\\.class$", ""))
21929349Speter                     .map(this::convertModularFileName)
2202124Sdg                     .collect(toList()));
2212124Sdg    }
2222124Sdg
2232124Sdg    /**
2242124Sdg     * Processes named class files in the given directory. The directory
2252124Sdg     * should be the root of a package hierarchy. If classNames is
2262124Sdg     * empty, walks the directory hierarchy to find all classes.
2272124Sdg     *
2282124Sdg     * @param dirname the name of the directory to process
2292124Sdg     * @param classNames the names of classes to process
23012865Speter     * @return true for success, false for failure
23112865Speter     * @throws IOException if an I/O error occurs
23212865Speter     */
23359829Speter    boolean processDirectory(String dirname, Collection<String> classNames) throws IOException {
23412865Speter        if (!Files.isDirectory(Paths.get(dirname))) {
23512865Speter            err.printf("%s: not a directory%n", dirname);
23612865Speter            return false;
23712865Speter        }
23812865Speter
23912865Speter        classPath.add(0, new File(dirname));
24012865Speter
24112865Speter        if (classNames.isEmpty()) {
24225582Speter            Path base = Paths.get(dirname);
24325582Speter            int baseCount = base.getNameCount();
24425582Speter            try (Stream<Path> paths = Files.walk(base)) {
24525582Speter                Stream<String> files =
24625582Speter                    paths.filter(p -> p.getNameCount() > baseCount)
24725582Speter                         .map(p -> p.subpath(baseCount, p.getNameCount()))
24825582Speter                         .map(Path::toString);
24925582Speter                return doFileNames(files);
25025582Speter            }
25114220Speter        } else {
25214220Speter            return doClassNames(classNames);
25314220Speter        }
25414220Speter    }
25514220Speter
25614220Speter    /**
25714220Speter     * Processes all class files in the given jar file.
25814220Speter     *
25914220Speter     * @param jarname the name of the jar file to process
26014220Speter     * @return true for success, false for failure
26114220Speter     * @throws IOException if an I/O error occurs
26229349Speter     */
26324452Speter    boolean doJarFile(String jarname) throws IOException {
26424440Speter        try (JarFile jf = new JarFile(jarname)) {
26525537Sdfr            Stream<String> files =
26625537Sdfr                jf.stream()
26725537Sdfr                  .map(JarEntry::getName);
26825537Sdfr            return doFileNames(files);
26925537Sdfr        }
27025537Sdfr    }
27125537Sdfr
27225537Sdfr    /**
27325537Sdfr     * Processes named class files from the given jar file,
27425537Sdfr     * or all classes if classNames is empty.
27525537Sdfr     *
27625537Sdfr     * @param jarname the name of the jar file to process
27725537Sdfr     * @param classNames the names of classes to process
27825537Sdfr     * @return true for success, false for failure
27925537Sdfr     * @throws IOException if an I/O error occurs
28025537Sdfr     */
28125537Sdfr    boolean processJarFile(String jarname, Collection<String> classNames) throws IOException {
28235938Sdyson        classPath.add(0, new File(jarname));
28325537Sdfr
28435938Sdyson        if (classNames.isEmpty()) {
28535938Sdyson            return doJarFile(jarname);
28635938Sdyson        } else {
28735938Sdyson            return doClassNames(classNames);
28835938Sdyson        }
28935938Sdyson    }
29035938Sdyson
29125537Sdfr    /**
29225537Sdfr     * Processes named class files from rt.jar of a JDK version 7 or 8.
29325537Sdfr     * If classNames is empty, processes all classes.
29425537Sdfr     *
29525537Sdfr     * @param jdkHome the path to the "home" of the JDK to process
29625537Sdfr     * @param classNames the names of classes to process
29725537Sdfr     * @return true for success, false for failure
29825537Sdfr     * @throws IOException if an I/O error occurs
29925537Sdfr     */
30025537Sdfr    boolean processOldJdk(String jdkHome, Collection<String> classNames) throws IOException {
30125537Sdfr        String RTJAR = jdkHome + "/jre/lib/rt.jar";
30225537Sdfr        String CSJAR = jdkHome + "/jre/lib/charsets.jar";
30325537Sdfr
30425537Sdfr        bootClassPath.add(0, new File(RTJAR));
30525537Sdfr        bootClassPath.add(1, new File(CSJAR));
30625537Sdfr        options.add("-source");
30751138Salfred        options.add("8");
30851138Salfred
30951138Salfred        if (classNames.isEmpty()) {
31025537Sdfr            return doJarFile(RTJAR);
31125537Sdfr        } else {
31225537Sdfr            return doClassNames(classNames);
31325537Sdfr        }
31425537Sdfr    }
31525537Sdfr
31625537Sdfr    /**
31725537Sdfr     * Processes listed classes given a JDK 9 home.
31825537Sdfr     */
31925537Sdfr    boolean processJdk9(String jdkHome, Collection<String> classes) throws IOException {
32028400Speter        systemModules.add(new File(jdkHome));
32156115Speter        return doClassNames(classes);
32256115Speter    }
32336034Speter
32426671Sdyson    /**
32526671Sdyson     * Processes the class files from the currently running JDK,
32626671Sdyson     * using the jrt: filesystem.
32726671Sdyson     *
32826671Sdyson     * @return true for success, false for failure
32926671Sdyson     * @throws IOException if an I/O error occurs
33026671Sdyson     */
33126671Sdyson    boolean processSelf(Collection<String> classes) throws IOException {
33269514Sjake        options.add("--add-modules");
33369514Sjake        options.add("java.se.ee,jdk.xml.bind"); // TODO why jdk.xml.bind?
33426671Sdyson
33526671Sdyson        if (classes.isEmpty()) {
33629391Sphk            Path modules = FileSystems.getFileSystem(URI.create("jrt:/"))
33734925Sdufault                                      .getPath("/modules");
33834925Sdufault
33934925Sdufault            // names are /modules/<modulename>/pkg/.../Classname.class
34034925Sdufault            try (Stream<Path> paths = Files.walk(modules)) {
34134925Sdufault                Stream<String> files =
34234925Sdufault                    paths.filter(p -> p.getNameCount() > 2)
34334925Sdufault                         .map(p -> p.subpath(1, p.getNameCount()))
34434925Sdufault                         .map(Path::toString);
34535938Sdyson                return doModularFileNames(files);
34699856Salfred            }
34741089Speter        } else {
34846155Sphk            return doClassNames(classes);
34949420Sjkh        }
35051791Smarcel    }
35151791Smarcel
352105950Speter    /**
35351791Smarcel     * Process classes from a particular JDK release, using only information
354105950Speter     * in this JDK.
35551791Smarcel     *
35651791Smarcel     * @param release "6", "7", "8", or "9"
35756271Srwatson     * @param classes collection of classes to process, may be empty
35856271Srwatson     * @return success value
35956271Srwatson     */
36056271Srwatson    boolean processRelease(String release, Collection<String> classes) throws IOException {
36156271Srwatson        options.addAll(List.of("--release", release));
36256271Srwatson
36356271Srwatson        if (release.equals("9")) {
36456271Srwatson            List<String> rootMods = List.of("java.se", "java.se.ee");
36554803Srwatson            TraverseProc proc = new TraverseProc(rootMods);
36654803Srwatson            JavaCompiler.CompilationTask task =
36754803Srwatson                compiler.getTask(null, fm, this,
36854803Srwatson                                 // options
36955943Sjasone                                 List.of("--add-modules", String.join(",", rootMods)),
37056115Speter                                 // classes
37156115Speter                                 List.of("java.lang.Object"),
37259288Sjlemon                                 null);
37359288Sjlemon            task.setProcessors(List.of(proc));
37498198Srwatson            if (!task.call()) {
37598198Srwatson                return false;
37698198Srwatson            }
37798198Srwatson            Map<PackageElement, List<TypeElement>> types = proc.getPublicTypes();
37898198Srwatson            options.add("--add-modules");
37998198Srwatson            options.add(String.join(",", rootMods));
38069449Salfred            return doClassNames(
38175039Srwatson                types.values().stream()
38275039Srwatson                     .flatMap(List::stream)
38375039Srwatson                     .map(TypeElement::toString)
38475427Srwatson                     .collect(toList()));
38583652Speter        } else {
38683796Srwatson            // TODO: kind of a hack...
38784884Srwatson            // Create a throwaway compilation task with options "--release N"
38885891Sphk            // which has the side effect of setting the file manager's
38990889Sjulian            // PLATFORM_CLASS_PATH to the right value.
39090889Sjulian            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
391103972Sarchie            StandardJavaFileManager fm =
392103972Sarchie                compiler.getStandardFileManager(this, null, StandardCharsets.UTF_8);
393103972Sarchie            JavaCompiler.CompilationTask task =
394100897Srwatson                compiler.getTask(null, fm, this, List.of("--release", release), null, null);
395100897Srwatson            List<Path> paths = new ArrayList<>();
396100897Srwatson            for (Path p : fm.getLocationAsPaths(StandardLocation.PLATFORM_CLASS_PATH)) {
397100897Srwatson                try (Stream<Path> str = Files.walk(p)) {
398100897Srwatson                    str.forEachOrdered(paths::add);
399100897Srwatson                }
40094936Smux            }
40196084Smux
40297372Smarcel            options.add("-Xlint:-options");
40399856Salfred
404101426Srwatson            return doClassNames(
405103575Salfred                paths.stream()
406103575Salfred                     .filter(path -> path.toString().endsWith(".sig"))
407103575Salfred                     .map(path -> path.subpath(1, path.getNameCount()))
408103575Salfred                     .map(Path::toString)
409103575Salfred                     .map(s -> s.replaceAll("\\.sig$", ""))
410103575Salfred                     .map(s -> s.replace('/', '.'))
411103575Salfred                     .collect(toList()));
412103575Salfred        }
413103575Salfred    }
414103575Salfred
415103575Salfred    /**
416103575Salfred     * An enum denoting the mode in which the tool is running.
417103575Salfred     * Different modes correspond to the different process* methods.
418103575Salfred     * The exception is UNKNOWN, which indicates that a mode wasn't
419105692Srwatson     * specified on the command line, which is an error.
420105692Srwatson     */
421105692Srwatson    static enum LoadMode {
422104731Srwatson        CLASSES, DIR, JAR, OLD_JDK, JDK9, SELF, RELEASE, LOAD_CSV
423104731Srwatson    }
424104731Srwatson
425106467Srwatson    static enum ScanMode {
426105950Speter        ARGS, LIST, PRINT_CSV
427105950Speter    }
428105692Srwatson
429105692Srwatson    /**
430105692Srwatson     * A checked exception that's thrown if a command-line syntax error
431106978Sdeischen     * is detected.
432106978Sdeischen     */
433106978Sdeischen    static class UsageException extends Exception {
434107914Sdillon        private static final long serialVersionUID = 3611828659572908743L;
4351541Srgrimes    }
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 = "9";
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