Arguments.java revision 3573:c4a18ee691c4
1/*
2 * Copyright (c) 1999, 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 com.sun.tools.javac.main;
27
28import java.io.IOException;
29import java.nio.file.Files;
30import java.nio.file.Path;
31import java.nio.file.Paths;
32import java.util.Arrays;
33import java.util.Collection;
34import java.util.Collections;
35import java.util.EnumSet;
36import java.util.HashSet;
37import java.util.Iterator;
38import java.util.LinkedHashMap;
39import java.util.LinkedHashSet;
40import java.util.Map;
41import java.util.Set;
42import java.util.regex.Matcher;
43import java.util.regex.Pattern;
44import java.util.stream.Stream;
45
46import javax.tools.JavaFileManager;
47import javax.tools.JavaFileManager.Location;
48import javax.tools.JavaFileObject;
49import javax.tools.JavaFileObject.Kind;
50import javax.tools.StandardJavaFileManager;
51import javax.tools.StandardLocation;
52
53import com.sun.tools.doclint.DocLint;
54import com.sun.tools.javac.code.Lint.LintCategory;
55import com.sun.tools.javac.code.Source;
56import com.sun.tools.javac.file.BaseFileManager;
57import com.sun.tools.javac.file.JavacFileManager;
58import com.sun.tools.javac.jvm.Profile;
59import com.sun.tools.javac.jvm.Target;
60import com.sun.tools.javac.main.OptionHelper.GrumpyHelper;
61import com.sun.tools.javac.platform.PlatformDescription;
62import com.sun.tools.javac.platform.PlatformUtils;
63import com.sun.tools.javac.resources.CompilerProperties.Errors;
64import com.sun.tools.javac.resources.CompilerProperties.Warnings;
65import com.sun.tools.javac.util.Context;
66import com.sun.tools.javac.util.JCDiagnostic;
67import com.sun.tools.javac.util.List;
68import com.sun.tools.javac.util.ListBuffer;
69import com.sun.tools.javac.util.Log;
70import com.sun.tools.javac.util.Log.PrefixKind;
71import com.sun.tools.javac.util.Log.WriterKind;
72import com.sun.tools.javac.util.Options;
73import com.sun.tools.javac.util.PropagatedException;
74
75/**
76 * Shared option and argument handling for command line and API usage of javac.
77 */
78public class Arguments {
79
80    /**
81     * The context key for the arguments.
82     */
83    public static final Context.Key<Arguments> argsKey = new Context.Key<>();
84
85    private String ownName;
86    private Set<String> classNames;
87    private Set<Path> files;
88    private Map<Option, String> deferredFileManagerOptions;
89    private Set<JavaFileObject> fileObjects;
90    private boolean emptyAllowed;
91    private final Options options;
92
93    private JavaFileManager fileManager;
94    private final Log log;
95    private final Context context;
96
97    private enum ErrorMode { ILLEGAL_ARGUMENT, ILLEGAL_STATE, LOG };
98    private ErrorMode errorMode;
99    private boolean errors;
100
101    /**
102     * Gets the Arguments instance for this context.
103     *
104     * @param context the content
105     * @return the Arguments instance for this context.
106     */
107    public static Arguments instance(Context context) {
108        Arguments instance = context.get(argsKey);
109        if (instance == null) {
110            instance = new Arguments(context);
111        }
112        return instance;
113    }
114
115    protected Arguments(Context context) {
116        context.put(argsKey, this);
117        options = Options.instance(context);
118        log = Log.instance(context);
119        this.context = context;
120
121        // Ideally, we could init this here and update/configure it as
122        // needed, but right now, initializing a file manager triggers
123        // initialization of other items in the context, such as Lint
124        // and FSInfo, which should not be initialized until after
125        // processArgs
126        //        fileManager = context.get(JavaFileManager.class);
127    }
128
129    private final OptionHelper cmdLineHelper = new OptionHelper() {
130        @Override
131        public String get(Option option) {
132            return options.get(option);
133        }
134
135        @Override
136        public void put(String name, String value) {
137            options.put(name, value);
138        }
139
140        @Override
141        public void remove(String name) {
142            options.remove(name);
143        }
144
145        @Override
146        public boolean handleFileManagerOption(Option option, String value) {
147            options.put(option, value);
148            deferredFileManagerOptions.put(option, value);
149            return true;
150        }
151
152        @Override
153        public Log getLog() {
154            return log;
155        }
156
157        @Override
158        public String getOwnName() {
159            return ownName;
160        }
161
162        @Override
163        public void error(String key, Object... args) {
164            Arguments.this.error(key, args);
165        }
166
167        @Override
168        public void error(JCDiagnostic.Error error) {
169            Arguments.this.error(error);
170        }
171
172        @Override
173        public void addFile(Path p) {
174            files.add(p);
175        }
176
177        @Override
178        public void addClassName(String s) {
179            classNames.add(s);
180        }
181
182    };
183
184    /**
185     * Initializes this Args instance with a set of command line args.
186     * The args will be processed in conjunction with the full set of
187     * command line options, including -help, -version etc.
188     * The args may also contain class names and filenames.
189     * Any errors during this call, and later during validate, will be reported
190     * to the log.
191     * @param ownName the name of this tool; used to prefix messages
192     * @param args the args to be processed
193     */
194    public void init(String ownName, String... args) {
195        this.ownName = ownName;
196        errorMode = ErrorMode.LOG;
197        files = new LinkedHashSet<>();
198        deferredFileManagerOptions = new LinkedHashMap<>();
199        fileObjects = null;
200        classNames = new LinkedHashSet<>();
201        processArgs(List.from(args), Option.getJavaCompilerOptions(), cmdLineHelper, true, false);
202        if (errors) {
203            log.printLines(PrefixKind.JAVAC, "msg.usage", ownName);
204        }
205    }
206
207    private final OptionHelper apiHelper = new GrumpyHelper(null) {
208        @Override
209        public String get(Option option) {
210            return options.get(option);
211        }
212
213        @Override
214        public void put(String name, String value) {
215            options.put(name, value);
216        }
217
218        @Override
219        public void remove(String name) {
220            options.remove(name);
221        }
222
223        @Override
224        public void error(String key, Object... args) {
225            Arguments.this.error(key, args);
226        }
227
228        @Override
229        public Log getLog() {
230            return Arguments.this.log;
231        }
232    };
233
234    /**
235     * Initializes this Args instance with the parameters for a JavacTask.
236     * The options will be processed in conjunction with the restricted set
237     * of tool options, which does not include -help, -version, etc,
238     * nor does it include classes and filenames, which should be specified
239     * separately.
240     * File manager options are handled directly by the file manager.
241     * Any errors found while processing individual args will be reported
242     * via IllegalArgumentException.
243     * Any subsequent errors during validate will be reported via IllegalStateException.
244     * @param ownName the name of this tool; used to prefix messages
245     * @param options the options to be processed
246     * @param classNames the classes to be subject to annotation processing
247     * @param files the files to be compiled
248     */
249    public void init(String ownName,
250            Iterable<String> options,
251            Iterable<String> classNames,
252            Iterable<? extends JavaFileObject> files) {
253        this.ownName = ownName;
254        this.classNames = toSet(classNames);
255        this.fileObjects = toSet(files);
256        this.files = null;
257        errorMode = ErrorMode.ILLEGAL_ARGUMENT;
258        if (options != null) {
259            processArgs(toList(options), Option.getJavacToolOptions(), apiHelper, false, true);
260        }
261        errorMode = ErrorMode.ILLEGAL_STATE;
262    }
263
264    /**
265     * Gets the files to be compiled.
266     * @return the files to be compiled
267     */
268    public Set<JavaFileObject> getFileObjects() {
269        if (fileObjects == null) {
270            fileObjects = new LinkedHashSet<>();
271        }
272        if (files != null) {
273            JavacFileManager jfm = (JavacFileManager) getFileManager();
274            for (JavaFileObject fo: jfm.getJavaFileObjectsFromPaths(files))
275                fileObjects.add(fo);
276        }
277        return fileObjects;
278    }
279
280    /**
281     * Gets the classes to be subject to annotation processing.
282     * @return the classes to be subject to annotation processing
283     */
284    public Set<String> getClassNames() {
285        return classNames;
286    }
287
288    /**
289     * Processes strings containing options and operands.
290     * @param args the strings to be processed
291     * @param allowableOpts the set of option declarations that are applicable
292     * @param helper a help for use by Option.process
293     * @param allowOperands whether or not to check for files and classes
294     * @param checkFileManager whether or not to check if the file manager can handle
295     *      options which are not recognized by any of allowableOpts
296     * @return true if all the strings were successfully processed; false otherwise
297     * @throws IllegalArgumentException if a problem occurs and errorMode is set to
298     *      ILLEGAL_ARGUMENT
299     */
300    private boolean processArgs(Iterable<String> args,
301            Set<Option> allowableOpts, OptionHelper helper,
302            boolean allowOperands, boolean checkFileManager) {
303        if (!doProcessArgs(args, allowableOpts, helper, allowOperands, checkFileManager))
304            return false;
305
306        String platformString = options.get(Option.RELEASE);
307
308        checkOptionAllowed(platformString == null,
309                option -> error("err.release.bootclasspath.conflict", option.getPrimaryName()),
310                Option.BOOT_CLASS_PATH, Option.XBOOTCLASSPATH, Option.XBOOTCLASSPATH_APPEND,
311                Option.XBOOTCLASSPATH_PREPEND,
312                Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS,
313                Option.EXTDIRS, Option.DJAVA_EXT_DIRS,
314                Option.SOURCE, Option.TARGET);
315
316        if (platformString != null) {
317            PlatformDescription platformDescription = PlatformUtils.lookupPlatformDescription(platformString);
318
319            if (platformDescription == null) {
320                error("err.unsupported.release.version", platformString);
321                return false;
322            }
323
324            options.put(Option.SOURCE, platformDescription.getSourceVersion());
325            options.put(Option.TARGET, platformDescription.getTargetVersion());
326
327            context.put(PlatformDescription.class, platformDescription);
328
329            if (!doProcessArgs(platformDescription.getAdditionalOptions(), allowableOpts, helper, allowOperands, checkFileManager))
330                return false;
331
332            Collection<Path> platformCP = platformDescription.getPlatformPath();
333
334            if (platformCP != null) {
335                JavaFileManager fm = getFileManager();
336
337                if (!(fm instanceof StandardJavaFileManager)) {
338                    error("err.release.not.standard.file.manager");
339                    return false;
340                }
341
342                try {
343                    StandardJavaFileManager sfm = (StandardJavaFileManager) fm;
344
345                    sfm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, platformCP);
346                } catch (IOException ex) {
347                    log.printLines(PrefixKind.JAVAC, "msg.io");
348                    ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
349                    return false;
350                }
351            }
352        }
353
354        options.notifyListeners();
355
356        return true;
357    }
358
359    private boolean doProcessArgs(Iterable<String> args,
360            Set<Option> allowableOpts, OptionHelper helper,
361            boolean allowOperands, boolean checkFileManager) {
362        JavaFileManager fm = checkFileManager ? getFileManager() : null;
363        Iterator<String> argIter = args.iterator();
364        while (argIter.hasNext()) {
365            String arg = argIter.next();
366            if (arg.isEmpty()) {
367                error("err.invalid.flag", arg);
368                return false;
369            }
370
371            Option option = null;
372
373            // first, check the provided set of javac options
374            if (arg.startsWith("-")) {
375                option = Option.lookup(arg, allowableOpts);
376            } else if (allowOperands && Option.SOURCEFILE.matches(arg)) {
377                option = Option.SOURCEFILE;
378            }
379
380            if (option != null) {
381                if (!option.handleOption(helper, arg, argIter)) {
382                    return false;
383                }
384                continue;
385            }
386
387            // check file manager option
388            if (fm != null && fm.handleOption(arg, argIter)) {
389                continue;
390            }
391
392            // none of the above
393            error("err.invalid.flag", arg);
394            return false;
395        }
396
397        return true;
398    }
399
400    /**
401     * Validates the overall consistency of the options and operands
402     * processed by processOptions.
403     * @return true if all args are successfully validating; false otherwise.
404     * @throws IllegalStateException if a problem is found and errorMode is set to
405     *      ILLEGAL_STATE
406     */
407    public boolean validate() {
408        JavaFileManager fm = getFileManager();
409        if (options.isSet(Option.MODULE)) {
410            if (!fm.hasLocation(StandardLocation.CLASS_OUTPUT)) {
411                log.error(Errors.OutputDirMustBeSpecifiedWithDashMOption);
412            } else if (!fm.hasLocation(StandardLocation.MODULE_SOURCE_PATH)) {
413                log.error(Errors.ModulesourcepathMustBeSpecifiedWithDashMOption);
414            } else {
415                java.util.List<String> modules = Arrays.asList(options.get(Option.MODULE).split(","));
416                try {
417                    for (String module : modules) {
418                        Location sourceLoc = fm.getModuleLocation(StandardLocation.MODULE_SOURCE_PATH, module);
419                        if (sourceLoc == null) {
420                            log.error(Errors.ModuleNotFoundInModuleSourcePath(module));
421                        } else {
422                            Location classLoc = fm.getModuleLocation(StandardLocation.CLASS_OUTPUT, module);
423
424                            for (JavaFileObject file : fm.list(sourceLoc, "", EnumSet.of(JavaFileObject.Kind.SOURCE), true)) {
425                                String className = fm.inferBinaryName(sourceLoc, file);
426                                JavaFileObject classFile = fm.getJavaFileForInput(classLoc, className, Kind.CLASS);
427
428                                if (classFile == null || classFile.getLastModified() < file.getLastModified()) {
429                                    if (fileObjects == null)
430                                        fileObjects = new HashSet<>();
431                                    fileObjects.add(file);
432                                }
433                            }
434                        }
435                    }
436                } catch (IOException ex) {
437                    log.printLines(PrefixKind.JAVAC, "msg.io");
438                    ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
439                    return false;
440                }
441            }
442        }
443
444        if (isEmpty()) {
445            // It is allowed to compile nothing if just asking for help or version info.
446            // But also note that none of these options are supported in API mode.
447            if (options.isSet(Option.HELP)
448                || options.isSet(Option.X)
449                || options.isSet(Option.VERSION)
450                || options.isSet(Option.FULLVERSION)
451                || options.isSet(Option.MODULE))
452                return true;
453
454            if (emptyAllowed)
455                return true;
456
457            if (!errors) {
458                if (JavaCompiler.explicitAnnotationProcessingRequested(options)) {
459                    error("err.no.source.files.classes");
460                } else {
461                    error("err.no.source.files");
462                }
463            }
464
465            return false;
466        }
467
468        if (!checkDirectory(Option.D)) {
469            return false;
470        }
471        if (!checkDirectory(Option.S)) {
472            return false;
473        }
474        if (!checkDirectory(Option.H)) {
475            return false;
476        }
477
478        // The following checks are to help avoid accidental confusion between
479        // directories of modules and exploded module directories.
480        if (fm instanceof StandardJavaFileManager) {
481            StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager;
482            if (sfm.hasLocation(StandardLocation.CLASS_OUTPUT)) {
483                Path outDir = sfm.getLocationAsPaths(StandardLocation.CLASS_OUTPUT).iterator().next();
484                if (sfm.hasLocation(StandardLocation.MODULE_SOURCE_PATH)) {
485                    // multi-module mode
486                    if (Files.exists(outDir.resolve("module-info.class"))) {
487                        log.error(Errors.MultiModuleOutdirCannotBeExplodedModule(outDir));
488                    }
489                } else {
490                    // single-module or legacy mode
491                    boolean lintPaths = options.isUnset(Option.XLINT_CUSTOM,
492                            "-" + LintCategory.PATH.option);
493                    if (lintPaths) {
494                        Path outDirParent = outDir.getParent();
495                        if (outDirParent != null && Files.exists(outDirParent.resolve("module-info.class"))) {
496                            log.warning(LintCategory.PATH, Warnings.OutdirIsInExplodedModule(outDir));
497                        }
498                    }
499                }
500            }
501        }
502
503
504        String sourceString = options.get(Option.SOURCE);
505        Source source = (sourceString != null)
506                ? Source.lookup(sourceString)
507                : Source.DEFAULT;
508        String targetString = options.get(Option.TARGET);
509        Target target = (targetString != null)
510                ? Target.lookup(targetString)
511                : Target.DEFAULT;
512
513        // We don't check source/target consistency for CLDC, as J2ME
514        // profiles are not aligned with J2SE targets; moreover, a
515        // single CLDC target may have many profiles.  In addition,
516        // this is needed for the continued functioning of the JSR14
517        // prototype.
518        if (Character.isDigit(target.name.charAt(0))) {
519            if (target.compareTo(source.requiredTarget()) < 0) {
520                if (targetString != null) {
521                    if (sourceString == null) {
522                        error("warn.target.default.source.conflict",
523                                targetString,
524                                source.requiredTarget().name);
525                    } else {
526                        error("warn.source.target.conflict",
527                                sourceString,
528                                source.requiredTarget().name);
529                    }
530                    return false;
531                } else {
532                    target = source.requiredTarget();
533                    options.put("-target", target.name);
534                }
535            }
536        }
537
538        String profileString = options.get(Option.PROFILE);
539        if (profileString != null) {
540            Profile profile = Profile.lookup(profileString);
541            if (!profile.isValid(target)) {
542                error("warn.profile.target.conflict", profileString, target.name);
543            }
544
545            // This check is only effective in command line mode,
546            // where the file manager options are added to options
547            if (options.get(Option.BOOT_CLASS_PATH) != null) {
548                error("err.profile.bootclasspath.conflict");
549            }
550        }
551
552        if (options.isSet(Option.SOURCE_PATH) && options.isSet(Option.MODULE_SOURCE_PATH)) {
553            error("err.sourcepath.modulesourcepath.conflict");
554        }
555
556        boolean lintOptions = options.isUnset(Option.XLINT_CUSTOM, "-" + LintCategory.OPTIONS.option);
557
558        if (lintOptions && source.compareTo(Source.DEFAULT) < 0 && !options.isSet(Option.RELEASE)) {
559            if (fm instanceof BaseFileManager) {
560                if (((BaseFileManager) fm).isDefaultBootClassPath())
561                    log.warning(LintCategory.OPTIONS, "source.no.bootclasspath", source.name);
562            }
563        }
564
565        boolean obsoleteOptionFound = false;
566
567        if (source.compareTo(Source.MIN) < 0) {
568            log.error(Errors.OptionRemovedSource(source.name, Source.MIN.name));
569        } else if (source == Source.MIN && lintOptions) {
570            log.warning(LintCategory.OPTIONS, Warnings.OptionObsoleteSource(source.name));
571            obsoleteOptionFound = true;
572        }
573
574        if (target.compareTo(Target.MIN) < 0) {
575            log.error(Errors.OptionRemovedTarget(target.name, Target.MIN.name));
576        } else if (target == Target.MIN && lintOptions) {
577            log.warning(LintCategory.OPTIONS, Warnings.OptionObsoleteTarget(target.name));
578            obsoleteOptionFound = true;
579        }
580
581        final Target t = target;
582        checkOptionAllowed(t.compareTo(Target.JDK1_8) <= 0,
583                option -> error("err.option.not.allowed.with.target", option.getPrimaryName(), t.name),
584                Option.BOOT_CLASS_PATH,
585                Option.XBOOTCLASSPATH_PREPEND, Option.XBOOTCLASSPATH, Option.XBOOTCLASSPATH_APPEND,
586                Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS,
587                Option.EXTDIRS, Option.DJAVA_EXT_DIRS);
588
589        checkOptionAllowed(t.compareTo(Target.JDK1_9) >= 0,
590                option -> error("err.option.not.allowed.with.target", option.getPrimaryName(), t.name),
591                Option.MODULE_SOURCE_PATH, Option.UPGRADE_MODULE_PATH,
592                Option.SYSTEM, Option.MODULE_PATH, Option.ADD_MODULES, Option.LIMIT_MODULES,
593                Option.PATCH_MODULE);
594
595        if (fm.hasLocation(StandardLocation.MODULE_SOURCE_PATH)) {
596            if (!options.isSet(Option.PROC, "only")
597                    && !fm.hasLocation(StandardLocation.CLASS_OUTPUT)) {
598                log.error(Errors.NoOutputDir);
599            }
600            if (options.isSet(Option.XMODULE)) {
601                log.error(Errors.XmoduleNoModuleSourcepath);
602            }
603        }
604
605        if (fm.hasLocation(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH) &&
606            fm.hasLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH)) {
607            log.error(Errors.ProcessorpathNoProcessormodulepath);
608        }
609
610        if (obsoleteOptionFound)
611            log.warning(LintCategory.OPTIONS, "option.obsolete.suppression");
612
613        String addExports = options.get(Option.ADD_EXPORTS);
614        if (addExports != null) {
615            // Each entry must be of the form module/package=target-list where target-list is a
616            // comma-separated list of module or ALL-UNNAMED.
617            // All module/package pairs must be unique.
618            Pattern p = Pattern.compile("([^/]+)/([^=]+)=(.*)");
619            Map<String,List<String>> map = new LinkedHashMap<>();
620            for (String e: addExports.split("\0")) {
621                Matcher m = p.matcher(e);
622                if (!m.matches()) {
623                    log.error(Errors.XaddexportsMalformedEntry(e));
624                    continue;
625                }
626                String eModule = m.group(1);  // TODO: check a valid dotted identifier
627                String ePackage = m.group(2); // TODO: check a valid dotted identifier
628                String eTargets = m.group(3);  // TODO: check a valid list of dotted identifier or ALL-UNNAMED
629                String eModPkg = eModule + '/' + ePackage;
630                List<String> l = map.get(eModPkg);
631                map.put(eModPkg, (l == null) ? List.of(eTargets) : l.prepend(eTargets));
632            }
633            map.forEach((key, value) -> {
634                if (value.size() > 1) {
635                    log.error(Errors.XaddexportsTooMany(key));
636                    // TODO: consider adding diag fragments for the entries
637                }
638            });
639        }
640
641        String addReads = options.get(Option.ADD_READS);
642        if (addReads != null) {
643            // Each entry must be of the form module=source-list where source-list is a
644            // comma separated list of module or ALL-UNNAMED.
645            // All target modules (i.e. on left of '=')  must be unique.
646            Pattern p = Pattern.compile("([^=]+)=(.*)");
647            Map<String,List<String>> map = new LinkedHashMap<>();
648            for (String e: addReads.split("\0")) {
649                Matcher m = p.matcher(e);
650                if (!m.matches()) {
651                    log.error(Errors.XaddreadsMalformedEntry(e));
652                    continue;
653                }
654                String eModule = m.group(1);  // TODO: check a valid dotted identifier
655                String eSources = m.group(2);  // TODO: check a valid list of dotted identifier or ALL-UNNAMED
656                List<String> l = map.get(eModule);
657                map.put(eModule, (l == null) ? List.of(eSources) : l.prepend(eSources));
658            }
659            map.forEach((key, value) -> {
660                if (value.size() > 1) {
661                    log.error(Errors.XaddreadsTooMany(key));
662                    // TODO: consider adding diag fragments for the entries
663                }
664            });
665        }
666
667
668        return !errors;
669    }
670
671    /**
672     * Returns true if there are no files or classes specified for use.
673     * @return true if there are no files or classes specified for use
674     */
675    public boolean isEmpty() {
676        return ((files == null) || files.isEmpty())
677                && ((fileObjects == null) || fileObjects.isEmpty())
678                && classNames.isEmpty();
679    }
680
681    public void allowEmpty() {
682        this.emptyAllowed = true;
683    }
684
685    /**
686     * Gets the file manager options which may have been deferred
687     * during processArgs.
688     * @return the deferred file manager options
689     */
690    public Map<Option, String> getDeferredFileManagerOptions() {
691        return deferredFileManagerOptions;
692    }
693
694    /**
695     * Gets any options specifying plugins to be run.
696     * @return options for plugins
697     */
698    public Set<List<String>> getPluginOpts() {
699        String plugins = options.get(Option.PLUGIN);
700        if (plugins == null)
701            return Collections.emptySet();
702
703        Set<List<String>> pluginOpts = new LinkedHashSet<>();
704        for (String plugin: plugins.split("\\x00")) {
705            pluginOpts.add(List.from(plugin.split("\\s+")));
706        }
707        return Collections.unmodifiableSet(pluginOpts);
708    }
709
710    /**
711     * Gets any options specifying how doclint should be run.
712     * An empty list is returned if no doclint options are specified
713     * or if the only doclint option is -Xdoclint:none.
714     * @return options for doclint
715     */
716    public List<String> getDocLintOpts() {
717        String xdoclint = options.get(Option.XDOCLINT);
718        String xdoclintCustom = options.get(Option.XDOCLINT_CUSTOM);
719        if (xdoclint == null && xdoclintCustom == null)
720            return List.nil();
721
722        Set<String> doclintOpts = new LinkedHashSet<>();
723        if (xdoclint != null)
724            doclintOpts.add(DocLint.XMSGS_OPTION);
725        if (xdoclintCustom != null) {
726            for (String s: xdoclintCustom.split("\\s+")) {
727                if (s.isEmpty())
728                    continue;
729                doclintOpts.add(DocLint.XMSGS_CUSTOM_PREFIX + s);
730            }
731        }
732
733        if (doclintOpts.equals(Collections.singleton(DocLint.XMSGS_CUSTOM_PREFIX + "none")))
734            return List.nil();
735
736        String checkPackages = options.get(Option.XDOCLINT_PACKAGE);
737
738        if (checkPackages != null) {
739            for (String s : checkPackages.split("\\s+")) {
740                doclintOpts.add(DocLint.XCHECK_PACKAGE + s);
741            }
742        }
743
744        // standard doclet normally generates H1, H2,
745        // so for now, allow user comments to assume that
746        doclintOpts.add(DocLint.XIMPLICIT_HEADERS + "2");
747        return List.from(doclintOpts.toArray(new String[doclintOpts.size()]));
748    }
749
750    private boolean checkDirectory(Option option) {
751        String value = options.get(option);
752        if (value == null) {
753            return true;
754        }
755        Path file = Paths.get(value);
756        if (Files.exists(file) && !Files.isDirectory(file)) {
757            error("err.file.not.directory", value);
758            return false;
759        }
760        return true;
761    }
762
763    private interface ErrorReporter {
764        void report(Option o);
765    }
766
767    void checkOptionAllowed(boolean allowed, ErrorReporter r, Option... opts) {
768        if (!allowed) {
769            Stream.of(opts)
770                  .filter(options :: isSet)
771                  .forEach(r :: report);
772        }
773    }
774
775    void error(JCDiagnostic.Error error) {
776        errors = true;
777        switch (errorMode) {
778            case ILLEGAL_ARGUMENT: {
779                String msg = log.localize(error);
780                throw new PropagatedException(new IllegalArgumentException(msg));
781            }
782            case ILLEGAL_STATE: {
783                String msg = log.localize(error);
784                throw new PropagatedException(new IllegalStateException(msg));
785            }
786            case LOG:
787                report(error);
788        }
789    }
790
791    void error(String key, Object... args) {
792        errors = true;
793        switch (errorMode) {
794            case ILLEGAL_ARGUMENT: {
795                String msg = log.localize(PrefixKind.JAVAC, key, args);
796                throw new PropagatedException(new IllegalArgumentException(msg));
797            }
798            case ILLEGAL_STATE: {
799                String msg = log.localize(PrefixKind.JAVAC, key, args);
800                throw new PropagatedException(new IllegalStateException(msg));
801            }
802            case LOG:
803                report(key, args);
804        }
805    }
806
807    void warning(String key, Object... args) {
808        report(key, args);
809    }
810
811    private void report(String key, Object... args) {
812        // Would be good to have support for -XDrawDiagnostics here
813        log.printRawLines(ownName + ": " + log.localize(PrefixKind.JAVAC, key, args));
814    }
815
816    private void report(JCDiagnostic.Error error) {
817        // Would be good to have support for -XDrawDiagnostics here
818        log.printRawLines(ownName + ": " + log.localize(error));
819    }
820
821    private JavaFileManager getFileManager() {
822        if (fileManager == null)
823            fileManager = context.get(JavaFileManager.class);
824        return fileManager;
825    }
826
827    <T> ListBuffer<T> toList(Iterable<? extends T> items) {
828        ListBuffer<T> list = new ListBuffer<>();
829        if (items != null) {
830            for (T item : items) {
831                list.add(item);
832            }
833        }
834        return list;
835    }
836
837    <T> Set<T> toSet(Iterable<? extends T> items) {
838        Set<T> set = new LinkedHashSet<>();
839        if (items != null) {
840            for (T item : items) {
841                set.add(item);
842            }
843        }
844        return set;
845    }
846}
847