Start.java revision 3595:81692f730015
1/*
2 * Copyright (c) 1997, 2016, 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
26package jdk.javadoc.internal.tool;
27
28import java.io.File;
29import java.io.FileNotFoundException;
30import java.io.IOException;
31import java.io.PrintWriter;
32import java.nio.file.Path;
33import java.text.BreakIterator;
34import java.util.ArrayList;
35import java.util.Arrays;
36import java.util.Collection;
37import java.util.Collections;
38import java.util.List;
39import java.util.Locale;
40import java.util.Objects;
41import java.util.Set;
42
43import javax.tools.JavaFileManager;
44import javax.tools.JavaFileObject;
45import javax.tools.StandardJavaFileManager;
46import javax.tools.StandardLocation;
47
48import com.sun.tools.javac.api.JavacTrees;
49import com.sun.tools.javac.file.BaseFileManager;
50import com.sun.tools.javac.file.JavacFileManager;
51import com.sun.tools.javac.main.CommandLine;
52import com.sun.tools.javac.main.OptionHelper;
53import com.sun.tools.javac.main.OptionHelper.GrumpyHelper;
54import com.sun.tools.javac.platform.PlatformDescription;
55import com.sun.tools.javac.platform.PlatformUtils;
56import com.sun.tools.javac.util.ClientCodeException;
57import com.sun.tools.javac.util.Context;
58import com.sun.tools.javac.util.Log;
59import com.sun.tools.javac.util.Options;
60
61import jdk.javadoc.doclet.Doclet;
62import jdk.javadoc.doclet.Doclet.Option;
63import jdk.javadoc.doclet.DocletEnvironment;
64
65import static javax.tools.DocumentationTool.Location.*;
66
67import static com.sun.tools.javac.main.Option.*;
68
69/**
70 * Main program of Javadoc.
71 * Previously named "Main".
72 *
73 *  <p><b>This is NOT part of any supported API.
74 *  If you write code that depends on this, you do so at your own risk.
75 *  This code and its internal interfaces are subject to change or
76 *  deletion without notice.</b>
77 *
78 * @author Robert Field
79 * @author Neal Gafter (rewrite)
80 */
81public class Start extends ToolOption.Helper {
82
83    @SuppressWarnings("deprecation")
84    private static final Class<?> OldStdDoclet =
85            com.sun.tools.doclets.standard.Standard.class;
86
87    private static final Class<?> StdDoclet =
88            jdk.javadoc.doclets.StandardDoclet.class;
89    /** Context for this invocation. */
90    private final Context context;
91
92    private static final String ProgramName = "javadoc";
93
94    private Messager messager;
95
96    private final String docletName;
97
98    private final ClassLoader classLoader;
99
100    private Class<?> docletClass;
101
102    private Doclet doclet;
103
104    // used to determine the locale for the messager
105    private Locale locale;
106
107
108    /**
109     * In API mode, exceptions thrown while calling the doclet are
110     * propagated using ClientCodeException.
111     */
112    private boolean apiMode;
113
114    private JavaFileManager fileManager;
115
116    Start() {
117        this(null, null, null, null, null);
118    }
119
120    Start(PrintWriter writer) {
121        this(null, null, writer, null, null);
122    }
123
124    Start(Context context, String programName, PrintWriter writer,
125            String docletName, ClassLoader classLoader) {
126        this.context = context == null ? new Context() : context;
127        String pname = programName == null ? ProgramName : programName;
128        this.messager = writer == null
129                ? new Messager(this.context, pname)
130                : new Messager(this.context, pname, writer, writer);
131        this.docletName = docletName;
132        this.classLoader = classLoader;
133        this.docletClass = null;
134        this.locale = Locale.getDefault();
135    }
136
137    public Start(Context context) {
138        this.docletClass = null;
139        this.context = Objects.requireNonNull(context);
140        this.apiMode = true;
141        this.docletName = null;
142        this.classLoader = null;
143        this.locale = Locale.getDefault();
144    }
145
146    void initMessager() {
147        if (!apiMode)
148            return;
149        if (messager == null) {
150            Log log = context.get(Log.logKey);
151            if (log instanceof Messager) {
152                messager = (Messager) log;
153            } else {
154                PrintWriter out = context.get(Log.errKey);
155                messager = (out == null)
156                        ? new Messager(context, ProgramName)
157                        : new Messager(context, ProgramName, out, out);
158            }
159        }
160    }
161
162    /**
163     * Usage
164     */
165    @Override
166    void usage() {
167        usage(true);
168    }
169
170    void usage(boolean exit) {
171        usage("main.usage", "-help", "main.usage.foot", exit);
172    }
173
174    @Override
175    void Xusage() {
176        Xusage(true);
177    }
178
179    void Xusage(boolean exit) {
180        usage("main.Xusage", "-X", "main.Xusage.foot", exit);
181    }
182
183    private void usage(String main, String option, String foot, boolean exit) {
184        messager.notice(main);
185        // let doclet print usage information (does nothing on error)
186        if (docletClass != null) {
187            String name = doclet.getName();
188            Set<Option> supportedOptions = doclet.getSupportedOptions();
189            messager.notice("main.doclet.usage.header", name);
190            Option.Kind myKind = option.equals("-X")
191                    ? Option.Kind.EXTENDED
192                    : Option.Kind.STANDARD;
193            supportedOptions.stream()
194                    .filter(opt -> opt.getKind() == myKind)
195                    .forEach(opt -> messager.printNotice(opt.toString()));
196        }
197        if (foot != null)
198            messager.notice(foot);
199
200        if (exit)
201            throw new Messager.ExitJavadoc();
202    }
203
204
205    /**
206     * Main program - external wrapper. In order to maintain backward
207     * CLI  compatibility, we dispatch to the old tool or the old doclet's
208     * Start mechanism, based on the options present on the command line
209     * with the following precedence:
210     *   1. presence of -Xold, dispatch to old tool
211     *   2. doclet variant, if old, dispatch to old Start
212     *   3. taglet variant, if old, dispatch to old Start
213     *
214     * Thus the presence of -Xold switches the tool, soon after command files
215     * if any, are expanded, this is performed here, noting that the messager
216     * is available at this point in time.
217     * The doclet/taglet tests are performed in the begin method, further on,
218     * this is to minimize argument processing and most importantly the impact
219     * of class loader creation, needed to detect the doclet/taglet class variants.
220     */
221    @SuppressWarnings("deprecation")
222    int begin(String... argv) {
223        // Preprocess @file arguments
224        try {
225            argv = CommandLine.parse(argv);
226        } catch (FileNotFoundException e) {
227            messager.error("main.cant.read", e.getMessage());
228            throw new Messager.ExitJavadoc();
229        } catch (IOException e) {
230            e.printStackTrace(System.err);
231            throw new Messager.ExitJavadoc();
232        }
233
234        if (argv.length > 0 && "-Xold".equals(argv[0])) {
235            messager.warning("main.legacy_api");
236            String[] nargv = Arrays.copyOfRange(argv, 1, argv.length);
237            return com.sun.tools.javadoc.Main.execute(nargv);
238        }
239        boolean ok = begin(Arrays.asList(argv), Collections.<JavaFileObject> emptySet());
240        return ok ? 0 : 1;
241    }
242
243    // Called by 199 API.
244    public boolean begin(Class<?> docletClass,
245            Iterable<String> options,
246            Iterable<? extends JavaFileObject> fileObjects) {
247        this.docletClass = docletClass;
248        List<String> opts = new ArrayList<>();
249        for (String opt: options)
250            opts.add(opt);
251
252        return begin(opts, fileObjects);
253    }
254
255    @SuppressWarnings("deprecation")
256    private boolean begin(List<String> options, Iterable<? extends JavaFileObject> fileObjects) {
257        fileManager = context.get(JavaFileManager.class);
258        if (fileManager == null) {
259            JavacFileManager.preRegister(context);
260            fileManager = context.get(JavaFileManager.class);
261            if (fileManager instanceof BaseFileManager) {
262                ((BaseFileManager) fileManager).autoClose = true;
263            }
264        }
265        // locale, doclet and maybe taglet, needs to be determined first
266        docletClass = preProcess(fileManager, options);
267        if (jdk.javadoc.doclet.Doclet.class.isAssignableFrom(docletClass)) {
268            // no need to dispatch to old, safe to init now
269            initMessager();
270            messager.setLocale(locale);
271            try {
272                Object o = docletClass.getConstructor().newInstance();
273                doclet = (Doclet) o;
274            } catch (ReflectiveOperationException exc) {
275                exc.printStackTrace();
276                if (!apiMode) {
277                    error("main.could_not_instantiate_class", docletClass);
278                    throw new Messager.ExitJavadoc();
279                }
280                throw new ClientCodeException(exc);
281            }
282        } else {
283            if (this.apiMode) {
284                com.sun.tools.javadoc.main.Start ostart
285                        = new com.sun.tools.javadoc.main.Start(context);
286                return ostart.begin(docletClass, options, fileObjects);
287            }
288            warn("main.legacy_api");
289            String[] array = options.toArray(new String[options.size()]);
290            return com.sun.tools.javadoc.Main.execute(array) == 0;
291        }
292
293        boolean failed = false;
294        try {
295            failed = !parseAndExecute(options, fileObjects);
296        } catch (Messager.ExitJavadoc exc) {
297            // ignore, we just exit this way
298        } catch (OutOfMemoryError ee) {
299            messager.error("main.out.of.memory");
300            failed = true;
301        } catch (ClientCodeException e) {
302            // simply rethrow these exceptions, to be caught and handled by JavadocTaskImpl
303            throw e;
304        } catch (Error ee) {
305            ee.printStackTrace(System.err);
306            messager.error("main.fatal.error");
307            failed = true;
308        } catch (Exception ee) {
309            ee.printStackTrace(System.err);
310            messager.error("main.fatal.exception");
311            failed = true;
312        } finally {
313            if (fileManager != null
314                    && fileManager instanceof BaseFileManager
315                    && ((BaseFileManager) fileManager).autoClose) {
316                try {
317                    fileManager.close();
318                } catch (IOException ignore) {}
319            }
320            boolean haveErrorWarnings = messager.nerrors() > 0 ||
321                    (rejectWarnings && messager.nwarnings() > 0);
322            if (failed && !haveErrorWarnings) {
323                // the doclet failed, but nothing reported, flag it!.
324                messager.error("main.unknown.error");
325            }
326            failed |= haveErrorWarnings;
327            messager.exitNotice();
328            messager.flush();
329        }
330        return !failed;
331    }
332
333    /**
334     * Main program - internal
335     */
336    @SuppressWarnings("unchecked")
337    private boolean parseAndExecute(List<String> argList,
338            Iterable<? extends JavaFileObject> fileObjects) throws IOException {
339        long tm = System.currentTimeMillis();
340
341        List<String> javaNames = new ArrayList<>();
342
343        compOpts = Options.instance(context);
344
345        // Make sure no obsolete source/target messages are reported
346        com.sun.tools.javac.main.Option.XLINT.process(getOptionHelper(), "-Xlint:-options");
347
348        doclet.init(locale, messager);
349        parseArgs(argList, javaNames);
350
351        if (fileManager instanceof BaseFileManager) {
352            ((BaseFileManager) fileManager).handleOptions(fileManagerOpts);
353        }
354
355        String platformString = compOpts.get("--release");
356
357        if (platformString != null) {
358            if (compOpts.isSet("-source")) {
359                usageError("main.release.bootclasspath.conflict", "-source");
360            }
361            if (fileManagerOpts.containsKey(BOOT_CLASS_PATH)) {
362                usageError("main.release.bootclasspath.conflict", BOOT_CLASS_PATH.getPrimaryName());
363            }
364
365            PlatformDescription platformDescription =
366                    PlatformUtils.lookupPlatformDescription(platformString);
367
368            if (platformDescription == null) {
369                usageError("main.unsupported.release.version", platformString);
370            }
371
372            compOpts.put(SOURCE, platformDescription.getSourceVersion());
373
374            context.put(PlatformDescription.class, platformDescription);
375
376            Collection<Path> platformCP = platformDescription.getPlatformPath();
377
378            if (platformCP != null) {
379                if (fileManager instanceof StandardJavaFileManager) {
380                    StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager;
381
382                    sfm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, platformCP);
383                } else {
384                    usageError("main.release.not.standard.file.manager", platformString);
385                }
386            }
387        }
388
389        compOpts.notifyListeners();
390        List<String> modules = (List<String>) jdtoolOpts.computeIfAbsent(ToolOption.MODULE,
391                s -> Collections.EMPTY_LIST);
392
393        if (modules.isEmpty()) {
394            List<String> subpkgs = (List<String>) jdtoolOpts.computeIfAbsent(ToolOption.SUBPACKAGES,
395                    s -> Collections.EMPTY_LIST);
396            if (subpkgs.isEmpty()) {
397                if (javaNames.isEmpty() && isEmpty(fileObjects)) {
398                    usageError("main.No_modules_packages_or_classes_specified");
399                }
400            }
401        }
402
403        JavadocTool comp = JavadocTool.make0(context);
404        if (comp == null) return false;
405
406        DocletEnvironment docEnv = comp.getEnvironment(jdtoolOpts,
407                javaNames,
408                fileObjects);
409
410        // release resources
411        comp = null;
412
413        if (breakiterator || !locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
414            JavacTrees trees = JavacTrees.instance(context);
415            trees.setBreakIterator(BreakIterator.getSentenceInstance(locale));
416        }
417        // pass off control to the doclet
418        boolean ok = docEnv != null;
419        if (ok) ok = doclet.run(docEnv);
420
421        // We're done.
422        if (compOpts.get("-verbose") != null) {
423            tm = System.currentTimeMillis() - tm;
424            messager.notice("main.done_in", Long.toString(tm));
425        }
426
427        return ok;
428    }
429
430    Set<Doclet.Option> docletOptions = null;
431    int handleDocletOptions(int idx, List<String> args, boolean isToolOption) {
432        if (docletOptions == null) {
433            docletOptions = doclet.getSupportedOptions();
434        }
435        String arg = args.get(idx);
436
437        for (Doclet.Option opt : docletOptions) {
438            if (opt.matches(arg)) {
439                if (args.size() - idx < opt.getArgumentCount()) {
440                    usageError("main.requires_argument", arg);
441                }
442                opt.process(arg, args.listIterator(idx + 1));
443                idx += opt.getArgumentCount();
444                return idx;
445            }
446        }
447        // check if arg is accepted by the tool before emitting error
448        if (!isToolOption)
449            usageError("main.invalid_flag", arg);
450        return idx;
451    }
452
453    private Class<?> preProcess(JavaFileManager jfm, List<String> argv) {
454        // doclet specifying arguments
455        String userDocletPath = null;
456        String userDocletName = null;
457
458        // taglet specifying arguments, since tagletpath is a doclet
459        // functionality, assume they are repeated and inspect all.
460        List<File> userTagletPath = new ArrayList<>();
461        List<String> userTagletNames = new ArrayList<>();
462
463        // Step 1: loop through the args, set locale early on, if found.
464        for (int i = 0 ; i < argv.size() ; i++) {
465            String arg = argv.get(i);
466            if (arg.equals(ToolOption.LOCALE.opt)) {
467                checkOneArg(argv, i++);
468                String lname = argv.get(i);
469                locale = getLocale(lname);
470            } else if (arg.equals(ToolOption.DOCLET.opt)) {
471                checkOneArg(argv, i++);
472                if (userDocletName != null) {
473                    usageError("main.more_than_one_doclet_specified_0_and_1",
474                            userDocletName, argv.get(i));
475                }
476                if (docletName != null) {
477                    usageError("main.more_than_one_doclet_specified_0_and_1",
478                            docletName, argv.get(i));
479                }
480                userDocletName = argv.get(i);
481            } else if (arg.equals(ToolOption.DOCLETPATH.opt)) {
482                checkOneArg(argv, i++);
483                if (userDocletPath == null) {
484                    userDocletPath = argv.get(i);
485                } else {
486                    userDocletPath += File.pathSeparator + argv.get(i);
487                }
488            } else if ("-taglet".equals(arg)) {
489                userTagletNames.add(argv.get(i + 1));
490            } else if ("-tagletpath".equals(arg)) {
491                for (String pathname : argv.get(i + 1).split(File.pathSeparator)) {
492                    userTagletPath.add(new File(pathname));
493                }
494            }
495        }
496
497        // Step 2: a doclet is provided, nothing more to do.
498        if (docletClass != null) {
499            return docletClass;
500        }
501
502        // Step 3: doclet name specified ? if so find a ClassLoader,
503        // and load it.
504        if (userDocletName != null) {
505            ClassLoader cl = classLoader;
506            if (cl == null) {
507                if (!fileManager.hasLocation(DOCLET_PATH)) {
508                    List<File> paths = new ArrayList<>();
509                    if (userDocletPath != null) {
510                        for (String pathname : userDocletPath.split(File.pathSeparator)) {
511                            paths.add(new File(pathname));
512                        }
513                    }
514                    try {
515                        ((StandardJavaFileManager)fileManager).setLocation(DOCLET_PATH, paths);
516                    } catch (IOException ioe) {
517                        error("main.doclet_could_not_set_location", paths);
518                        throw new Messager.ExitJavadoc();
519                    }
520                }
521                cl = fileManager.getClassLoader(DOCLET_PATH);
522                if (cl == null) {
523                    // despite doclet specified on cmdline no classloader found!
524                    error("main.doclet_no_classloader_found", userDocletName);
525                    throw new Messager.ExitJavadoc();
526                }
527            }
528            try {
529                Class<?> klass = cl.loadClass(userDocletName);
530                return klass;
531            } catch (ClassNotFoundException cnfe) {
532                error("main.doclet_class_not_found", userDocletName);
533                throw new Messager.ExitJavadoc();
534            }
535        }
536
537        // Step 4: we have a doclet, try loading it
538        if (docletName != null) {
539            try {
540                return Class.forName(docletName, true, getClass().getClassLoader());
541            } catch (ClassNotFoundException cnfe) {
542                error("main.doclet_class_not_found", userDocletName);
543                throw new Messager.ExitJavadoc();
544            }
545        }
546
547        // Step 5: we don't have a doclet specified, do we have taglets ?
548        if (!userTagletNames.isEmpty() && hasOldTaglet(userTagletNames, userTagletPath)) {
549            // found a bogey, return the old doclet
550            return OldStdDoclet;
551        }
552
553        // finally
554        return StdDoclet;
555    }
556
557    /*
558     * This method returns true iff it finds a legacy taglet, but for
559     * all other conditions including errors it returns false, allowing
560     * nature to take its own course.
561     */
562    @SuppressWarnings("deprecation")
563    private boolean hasOldTaglet(List<String> tagletNames, List<File> tagletPaths) {
564        if (!fileManager.hasLocation(TAGLET_PATH)) {
565            try {
566                ((StandardJavaFileManager) fileManager).setLocation(TAGLET_PATH, tagletPaths);
567            } catch (IOException ioe) {
568                error("main.doclet_could_not_set_location", tagletPaths);
569                throw new Messager.ExitJavadoc();
570            }
571        }
572        ClassLoader cl = fileManager.getClassLoader(TAGLET_PATH);
573        if (cl == null) {
574            // no classloader found!
575            error("main.doclet_no_classloader_found", tagletNames.get(0));
576            throw new Messager.ExitJavadoc();
577        }
578        for (String tagletName : tagletNames) {
579            try {
580                Class<?> klass = cl.loadClass(tagletName);
581                if (com.sun.tools.doclets.Taglet.class.isAssignableFrom(klass)) {
582                    return true;
583                }
584            } catch (ClassNotFoundException cnfe) {
585                error("main.doclet_class_not_found", tagletName);
586                throw new Messager.ExitJavadoc();
587            }
588        }
589        return false;
590    }
591
592    private void parseArgs(List<String> args, List<String> javaNames) {
593        for (int i = 0 ; i < args.size() ; i++) {
594            String arg = args.get(i);
595            ToolOption o = ToolOption.get(arg);
596            if (o != null) {
597                // handle a doclet argument that may be needed however
598                // don't increment the index, and allow the tool to consume args
599                handleDocletOptions(i, args, true);
600
601                if (o.hasArg) {
602                    checkOneArg(args, i++);
603                    o.process(this, args.get(i));
604                } else if (o.hasSuffix) {
605                    o.process(this, arg);
606                } else {
607                    o.process(this);
608                }
609            } else if (arg.startsWith("-XD")) {
610                // hidden javac options
611                String s = arg.substring("-XD".length());
612                int eq = s.indexOf('=');
613                String key = (eq < 0) ? s : s.substring(0, eq);
614                String value = (eq < 0) ? s : s.substring(eq+1);
615                compOpts.put(key, value);
616            } else if (arg.startsWith("-")) {
617                i = handleDocletOptions(i, args, false);
618            } else {
619                javaNames.add(arg);
620            }
621        }
622    }
623
624    private <T> boolean isEmpty(Iterable<T> iter) {
625        return !iter.iterator().hasNext();
626    }
627
628    /**
629     * Check the one arg option.
630     * Error and exit if one argument is not provided.
631     */
632    private void checkOneArg(List<String> args, int index) {
633        if ((index + 1) >= args.size() || args.get(index + 1).startsWith("-d")) {
634            usageError("main.requires_argument", args.get(index));
635        }
636    }
637
638    @Override
639    void usageError(String key, Object... args) {
640        error(key, args);
641        usage(true);
642    }
643
644    void error(String key, Object... args) {
645        messager.error(key, args);
646    }
647
648    void warn(String key, Object... args)  {
649        messager.warning(key, args);
650    }
651
652    /**
653     * Get the locale if specified on the command line
654     * else return null and if locale option is not used
655     * then return default locale.
656     */
657    private Locale getLocale(String localeName) {
658        Locale userlocale = null;
659        if (localeName == null || localeName.isEmpty()) {
660            return Locale.getDefault();
661        }
662        int firstuscore = localeName.indexOf('_');
663        int seconduscore = -1;
664        String language = null;
665        String country = null;
666        String variant = null;
667        if (firstuscore == 2) {
668            language = localeName.substring(0, firstuscore);
669            seconduscore = localeName.indexOf('_', firstuscore + 1);
670            if (seconduscore > 0) {
671                if (seconduscore != firstuscore + 3
672                        || localeName.length() <= seconduscore + 1) {
673                    usageError("main.malformed_locale_name", localeName);
674                    return null;
675                }
676                country = localeName.substring(firstuscore + 1,
677                        seconduscore);
678                variant = localeName.substring(seconduscore + 1);
679            } else if (localeName.length() == firstuscore + 3) {
680                country = localeName.substring(firstuscore + 1);
681            } else {
682                usageError("main.malformed_locale_name", localeName);
683                return null;
684            }
685        } else if (firstuscore == -1 && localeName.length() == 2) {
686            language = localeName;
687        } else {
688            usageError("main.malformed_locale_name", localeName);
689            return null;
690        }
691        userlocale = searchLocale(language, country, variant);
692        if (userlocale == null) {
693            usageError("main.illegal_locale_name", localeName);
694            return null;
695        } else {
696            return userlocale;
697        }
698    }
699
700    /**
701     * Search the locale for specified language, specified country and
702     * specified variant.
703     */
704    private Locale searchLocale(String language, String country,
705                                String variant) {
706        for (Locale loc : Locale.getAvailableLocales()) {
707            if (loc.getLanguage().equals(language) &&
708                (country == null || loc.getCountry().equals(country)) &&
709                (variant == null || loc.getVariant().equals(variant))) {
710                return loc;
711            }
712        }
713        return null;
714    }
715
716    @Override
717    OptionHelper getOptionHelper() {
718        return new GrumpyHelper(null) {
719            @Override
720            public String get(com.sun.tools.javac.main.Option option) {
721                return compOpts.get(option);
722            }
723
724            @Override
725            public void put(String name, String value) {
726                compOpts.put(name, value);
727            }
728        };
729    }
730}
731