Arguments.java revision 4202:2bd34895dda2
1/*
2 * Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
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.function.Predicate;
43import java.util.regex.Matcher;
44import java.util.regex.Pattern;
45import java.util.stream.Stream;
46
47import javax.lang.model.SourceVersion;
48import javax.tools.JavaFileManager;
49import javax.tools.JavaFileManager.Location;
50import javax.tools.JavaFileObject;
51import javax.tools.JavaFileObject.Kind;
52import javax.tools.StandardJavaFileManager;
53import javax.tools.StandardLocation;
54
55import com.sun.tools.doclint.DocLint;
56import com.sun.tools.javac.code.Lint.LintCategory;
57import com.sun.tools.javac.code.Source;
58import com.sun.tools.javac.file.BaseFileManager;
59import com.sun.tools.javac.file.JavacFileManager;
60import com.sun.tools.javac.jvm.Profile;
61import com.sun.tools.javac.jvm.Target;
62import com.sun.tools.javac.main.OptionHelper.GrumpyHelper;
63import com.sun.tools.javac.platform.PlatformDescription;
64import com.sun.tools.javac.platform.PlatformUtils;
65import com.sun.tools.javac.resources.CompilerProperties.Errors;
66import com.sun.tools.javac.resources.CompilerProperties.Warnings;
67import com.sun.tools.javac.util.Context;
68import com.sun.tools.javac.util.JCDiagnostic;
69import com.sun.tools.javac.util.List;
70import com.sun.tools.javac.util.ListBuffer;
71import com.sun.tools.javac.util.Log;
72import com.sun.tools.javac.util.Log.PrefixKind;
73import com.sun.tools.javac.util.Log.WriterKind;
74import com.sun.tools.javac.util.Options;
75import com.sun.tools.javac.util.PropagatedException;
76
77/**
78 * Shared option and argument handling for command line and API usage of javac.
79 */
80public class Arguments {
81
82    /**
83     * The context key for the arguments.
84     */
85    public static final Context.Key<Arguments> argsKey = new Context.Key<>();
86
87    private String ownName;
88    private Set<String> classNames;
89    private Set<Path> files;
90    private Map<Option, String> deferredFileManagerOptions;
91    private Set<JavaFileObject> fileObjects;
92    private boolean emptyAllowed;
93    private final Options options;
94
95    private JavaFileManager fileManager;
96    private final Log log;
97    private final Context context;
98
99    private enum ErrorMode { ILLEGAL_ARGUMENT, ILLEGAL_STATE, LOG };
100    private ErrorMode errorMode;
101    private boolean errors;
102
103    /**
104     * Gets the Arguments instance for this context.
105     *
106     * @param context the content
107     * @return the Arguments instance for this context.
108     */
109    public static Arguments instance(Context context) {
110        Arguments instance = context.get(argsKey);
111        if (instance == null) {
112            instance = new Arguments(context);
113        }
114        return instance;
115    }
116
117    protected Arguments(Context context) {
118        context.put(argsKey, this);
119        options = Options.instance(context);
120        log = Log.instance(context);
121        this.context = context;
122
123        // Ideally, we could init this here and update/configure it as
124        // needed, but right now, initializing a file manager triggers
125        // initialization of other items in the context, such as Lint
126        // and FSInfo, which should not be initialized until after
127        // processArgs
128        //        fileManager = context.get(JavaFileManager.class);
129    }
130
131    private final OptionHelper cmdLineHelper = new OptionHelper() {
132        @Override
133        public String get(Option option) {
134            return options.get(option);
135        }
136
137        @Override
138        public void put(String name, String value) {
139            options.put(name, value);
140        }
141
142        @Override
143        public void remove(String name) {
144            options.remove(name);
145        }
146
147        @Override
148        public boolean handleFileManagerOption(Option option, String value) {
149            options.put(option, value);
150            deferredFileManagerOptions.put(option, value);
151            return true;
152        }
153
154        @Override
155        public Log getLog() {
156            return log;
157        }
158
159        @Override
160        public String getOwnName() {
161            return ownName;
162        }
163
164        @Override
165        public void addFile(Path p) {
166            files.add(p);
167        }
168
169        @Override
170        public void addClassName(String s) {
171            classNames.add(s);
172        }
173
174    };
175
176    /**
177     * Initializes this Args instance with a set of command line args.
178     * The args will be processed in conjunction with the full set of
179     * command line options, including -help, -version etc.
180     * The args may also contain class names and filenames.
181     * Any errors during this call, and later during validate, will be reported
182     * to the log.
183     * @param ownName the name of this tool; used to prefix messages
184     * @param args the args to be processed
185     */
186    public void init(String ownName, String... args) {
187        this.ownName = ownName;
188        errorMode = ErrorMode.LOG;
189        files = new LinkedHashSet<>();
190        deferredFileManagerOptions = new LinkedHashMap<>();
191        fileObjects = null;
192        classNames = new LinkedHashSet<>();
193        processArgs(List.from(args), Option.getJavaCompilerOptions(), cmdLineHelper, true, false);
194        if (errors) {
195            log.printLines(PrefixKind.JAVAC, "msg.usage", ownName);
196        }
197    }
198
199    private final OptionHelper apiHelper = new GrumpyHelper(null) {
200        @Override
201        public String get(Option option) {
202            return options.get(option);
203        }
204
205        @Override
206        public void put(String name, String value) {
207            options.put(name, value);
208        }
209
210        @Override
211        public void remove(String name) {
212            options.remove(name);
213        }
214
215        @Override
216        public Log getLog() {
217            return Arguments.this.log;
218        }
219    };
220
221    /**
222     * Initializes this Args instance with the parameters for a JavacTask.
223     * The options will be processed in conjunction with the restricted set
224     * of tool options, which does not include -help, -version, etc,
225     * nor does it include classes and filenames, which should be specified
226     * separately.
227     * File manager options are handled directly by the file manager.
228     * Any errors found while processing individual args will be reported
229     * via IllegalArgumentException.
230     * Any subsequent errors during validate will be reported via IllegalStateException.
231     * @param ownName the name of this tool; used to prefix messages
232     * @param options the options to be processed
233     * @param classNames the classes to be subject to annotation processing
234     * @param files the files to be compiled
235     */
236    public void init(String ownName,
237            Iterable<String> options,
238            Iterable<String> classNames,
239            Iterable<? extends JavaFileObject> files) {
240        this.ownName = ownName;
241        this.classNames = toSet(classNames);
242        this.fileObjects = toSet(files);
243        this.files = null;
244        errorMode = ErrorMode.ILLEGAL_ARGUMENT;
245        if (options != null) {
246            processArgs(toList(options), Option.getJavacToolOptions(), apiHelper, false, true);
247        }
248        errorMode = ErrorMode.ILLEGAL_STATE;
249    }
250
251    /**
252     * Minimal initialization for tools, like javadoc,
253     * to be able to process javac options for themselves,
254     * and then call validate.
255     * @param ownName  the name of this tool; used to prefix messages
256     */
257    public void init(String ownName) {
258        this.ownName = ownName;
259        errorMode = ErrorMode.LOG;
260    }
261
262    /**
263     * Gets the files to be compiled.
264     * @return the files to be compiled
265     */
266    public Set<JavaFileObject> getFileObjects() {
267        if (fileObjects == null) {
268            fileObjects = new LinkedHashSet<>();
269        }
270        if (files != null) {
271            JavacFileManager jfm = (JavacFileManager) getFileManager();
272            for (JavaFileObject fo: jfm.getJavaFileObjectsFromPaths(files))
273                fileObjects.add(fo);
274        }
275        return fileObjects;
276    }
277
278    /**
279     * Gets the classes to be subject to annotation processing.
280     * @return the classes to be subject to annotation processing
281     */
282    public Set<String> getClassNames() {
283        return classNames;
284    }
285
286    /**
287     * Handles the {@code --release} option.
288     *
289     * @param additionalOptions a predicate to handle additional options implied by the
290     * {@code --release} option. The predicate should return true if all the additional
291     * options were processed successfully.
292     * @return true if successful, false otherwise
293     */
294    public boolean handleReleaseOptions(Predicate<Iterable<String>> additionalOptions) {
295        String platformString = options.get(Option.RELEASE);
296
297        checkOptionAllowed(platformString == null,
298                option -> error("err.release.bootclasspath.conflict", option.getPrimaryName()),
299                Option.BOOT_CLASS_PATH, Option.XBOOTCLASSPATH, Option.XBOOTCLASSPATH_APPEND,
300                Option.XBOOTCLASSPATH_PREPEND,
301                Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS,
302                Option.EXTDIRS, Option.DJAVA_EXT_DIRS,
303                Option.SOURCE, Option.TARGET,
304                Option.SYSTEM, Option.UPGRADE_MODULE_PATH);
305
306        if (platformString != null) {
307            PlatformDescription platformDescription = PlatformUtils.lookupPlatformDescription(platformString);
308
309            if (platformDescription == null) {
310                error("err.unsupported.release.version", platformString);
311                return false;
312            }
313
314            options.put(Option.SOURCE, platformDescription.getSourceVersion());
315            options.put(Option.TARGET, platformDescription.getTargetVersion());
316
317            context.put(PlatformDescription.class, platformDescription);
318
319            if (!additionalOptions.test(platformDescription.getAdditionalOptions()))
320                return false;
321
322            Collection<Path> platformCP = platformDescription.getPlatformPath();
323
324            if (platformCP != null) {
325                JavaFileManager fm = getFileManager();
326
327                if (!(fm instanceof StandardJavaFileManager)) {
328                    error("err.release.not.standard.file.manager");
329                    return false;
330                }
331
332                try {
333                    StandardJavaFileManager sfm = (StandardJavaFileManager) fm;
334
335                    if (Source.instance(context).allowModules()) {
336                        sfm.handleOption("--system", Arrays.asList("none").iterator());
337                        sfm.setLocationFromPaths(StandardLocation.UPGRADE_MODULE_PATH, platformCP);
338                    } else {
339                        sfm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, platformCP);
340                    }
341                } catch (IOException ex) {
342                    log.printLines(PrefixKind.JAVAC, "msg.io");
343                    ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
344                    return false;
345                }
346            }
347        }
348
349        return true;
350    }
351
352    /**
353     * Processes strings containing options and operands.
354     * @param args the strings to be processed
355     * @param allowableOpts the set of option declarations that are applicable
356     * @param helper a help for use by Option.process
357     * @param allowOperands whether or not to check for files and classes
358     * @param checkFileManager whether or not to check if the file manager can handle
359     *      options which are not recognized by any of allowableOpts
360     * @return true if all the strings were successfully processed; false otherwise
361     * @throws IllegalArgumentException if a problem occurs and errorMode is set to
362     *      ILLEGAL_ARGUMENT
363     */
364    private boolean processArgs(Iterable<String> args,
365            Set<Option> allowableOpts, OptionHelper helper,
366            boolean allowOperands, boolean checkFileManager) {
367        if (!doProcessArgs(args, allowableOpts, helper, allowOperands, checkFileManager))
368            return false;
369
370        if (!handleReleaseOptions(extra -> doProcessArgs(extra, allowableOpts, helper, allowOperands, checkFileManager)))
371            return false;
372
373        options.notifyListeners();
374
375        return true;
376    }
377
378    private boolean doProcessArgs(Iterable<String> args,
379            Set<Option> allowableOpts, OptionHelper helper,
380            boolean allowOperands, boolean checkFileManager) {
381        JavaFileManager fm = checkFileManager ? getFileManager() : null;
382        Iterator<String> argIter = args.iterator();
383        while (argIter.hasNext()) {
384            String arg = argIter.next();
385            if (arg.isEmpty()) {
386                error("err.invalid.flag", arg);
387                return false;
388            }
389
390            Option option = null;
391
392            // first, check the provided set of javac options
393            if (arg.startsWith("-")) {
394                option = Option.lookup(arg, allowableOpts);
395            } else if (allowOperands && Option.SOURCEFILE.matches(arg)) {
396                option = Option.SOURCEFILE;
397            }
398
399            if (option != null) {
400                try {
401                    option.handleOption(helper, arg, argIter);
402                } catch (Option.InvalidValueException e) {
403                    error(e);
404                    return false;
405                }
406                continue;
407            }
408
409            // check file manager option
410            if (fm != null && fm.handleOption(arg, argIter)) {
411                continue;
412            }
413
414            // none of the above
415            error("err.invalid.flag", arg);
416            return false;
417        }
418
419        return true;
420    }
421
422    /**
423     * Validates the overall consistency of the options and operands
424     * processed by processOptions.
425     * @return true if all args are successfully validated; false otherwise.
426     * @throws IllegalStateException if a problem is found and errorMode is set to
427     *      ILLEGAL_STATE
428     */
429    public boolean validate() {
430        JavaFileManager fm = getFileManager();
431        if (options.isSet(Option.MODULE)) {
432            if (!fm.hasLocation(StandardLocation.CLASS_OUTPUT)) {
433                log.error(Errors.OutputDirMustBeSpecifiedWithDashMOption);
434            } else if (!fm.hasLocation(StandardLocation.MODULE_SOURCE_PATH)) {
435                log.error(Errors.ModulesourcepathMustBeSpecifiedWithDashMOption);
436            } else {
437                java.util.List<String> modules = Arrays.asList(options.get(Option.MODULE).split(","));
438                try {
439                    for (String module : modules) {
440                        Location sourceLoc = fm.getLocationForModule(StandardLocation.MODULE_SOURCE_PATH, module);
441                        if (sourceLoc == null) {
442                            log.error(Errors.ModuleNotFoundInModuleSourcePath(module));
443                        } else {
444                            Location classLoc = fm.getLocationForModule(StandardLocation.CLASS_OUTPUT, module);
445
446                            for (JavaFileObject file : fm.list(sourceLoc, "", EnumSet.of(JavaFileObject.Kind.SOURCE), true)) {
447                                String className = fm.inferBinaryName(sourceLoc, file);
448                                JavaFileObject classFile = fm.getJavaFileForInput(classLoc, className, Kind.CLASS);
449
450                                if (classFile == null || classFile.getLastModified() < file.getLastModified()) {
451                                    if (fileObjects == null)
452                                        fileObjects = new HashSet<>();
453                                    fileObjects.add(file);
454                                }
455                            }
456                        }
457                    }
458                } catch (IOException ex) {
459                    log.printLines(PrefixKind.JAVAC, "msg.io");
460                    ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
461                    return false;
462                }
463            }
464        }
465
466        if (isEmpty()) {
467            // It is allowed to compile nothing if just asking for help or version info.
468            // But also note that none of these options are supported in API mode.
469            if (options.isSet(Option.HELP)
470                    || options.isSet(Option.X)
471                    || options.isSet(Option.VERSION)
472                    || options.isSet(Option.FULLVERSION)
473                    || options.isSet(Option.MODULE)) {
474                return true;
475            }
476
477            if (!emptyAllowed) {
478                if (!errors) {
479                    if (JavaCompiler.explicitAnnotationProcessingRequested(options)) {
480                        error("err.no.source.files.classes");
481                    } else {
482                        error("err.no.source.files");
483                    }
484                }
485                return false;
486            }
487        }
488
489        if (!checkDirectory(Option.D)) {
490            return false;
491        }
492        if (!checkDirectory(Option.S)) {
493            return false;
494        }
495        if (!checkDirectory(Option.H)) {
496            return false;
497        }
498
499        // The following checks are to help avoid accidental confusion between
500        // directories of modules and exploded module directories.
501        if (fm instanceof StandardJavaFileManager) {
502            StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager;
503            if (sfm.hasLocation(StandardLocation.CLASS_OUTPUT)) {
504                Path outDir = sfm.getLocationAsPaths(StandardLocation.CLASS_OUTPUT).iterator().next();
505                if (sfm.hasLocation(StandardLocation.MODULE_SOURCE_PATH)) {
506                    // multi-module mode
507                    if (Files.exists(outDir.resolve("module-info.class"))) {
508                        log.error(Errors.MultiModuleOutdirCannotBeExplodedModule(outDir));
509                    }
510                } else {
511                    // single-module or legacy mode
512                    boolean lintPaths = options.isUnset(Option.XLINT_CUSTOM,
513                            "-" + LintCategory.PATH.option);
514                    if (lintPaths) {
515                        Path outDirParent = outDir.getParent();
516                        if (outDirParent != null && Files.exists(outDirParent.resolve("module-info.class"))) {
517                            log.warning(LintCategory.PATH, Warnings.OutdirIsInExplodedModule(outDir));
518                        }
519                    }
520                }
521            }
522        }
523
524
525        String sourceString = options.get(Option.SOURCE);
526        Source source = (sourceString != null)
527                ? Source.lookup(sourceString)
528                : Source.DEFAULT;
529        String targetString = options.get(Option.TARGET);
530        Target target = (targetString != null)
531                ? Target.lookup(targetString)
532                : Target.DEFAULT;
533
534        // We don't check source/target consistency for CLDC, as J2ME
535        // profiles are not aligned with J2SE targets; moreover, a
536        // single CLDC target may have many profiles.  In addition,
537        // this is needed for the continued functioning of the JSR14
538        // prototype.
539        if (Character.isDigit(target.name.charAt(0))) {
540            if (target.compareTo(source.requiredTarget()) < 0) {
541                if (targetString != null) {
542                    if (sourceString == null) {
543                        error("warn.target.default.source.conflict",
544                                targetString,
545                                source.requiredTarget().name);
546                    } else {
547                        error("warn.source.target.conflict",
548                                sourceString,
549                                source.requiredTarget().name);
550                    }
551                    return false;
552                } else {
553                    target = source.requiredTarget();
554                    options.put("-target", target.name);
555                }
556            }
557        }
558
559        String profileString = options.get(Option.PROFILE);
560        if (profileString != null) {
561            Profile profile = Profile.lookup(profileString);
562            if (!profile.isValid(target)) {
563                error("warn.profile.target.conflict", profileString, target.name);
564            }
565
566            // This check is only effective in command line mode,
567            // where the file manager options are added to options
568            if (options.get(Option.BOOT_CLASS_PATH) != null) {
569                error("err.profile.bootclasspath.conflict");
570            }
571        }
572
573        if (options.isSet(Option.SOURCE_PATH) && options.isSet(Option.MODULE_SOURCE_PATH)) {
574            error("err.sourcepath.modulesourcepath.conflict");
575        }
576
577        boolean lintOptions = options.isUnset(Option.XLINT_CUSTOM, "-" + LintCategory.OPTIONS.option);
578        if (lintOptions && source.compareTo(Source.DEFAULT) < 0 && !options.isSet(Option.RELEASE)) {
579            if (fm instanceof BaseFileManager) {
580                if (((BaseFileManager) fm).isDefaultBootClassPath())
581                    log.warning(LintCategory.OPTIONS, Warnings.SourceNoBootclasspath(source.name));
582            }
583        }
584
585        boolean obsoleteOptionFound = false;
586
587        if (source.compareTo(Source.MIN) < 0) {
588            log.error(Errors.OptionRemovedSource(source.name, Source.MIN.name));
589        } else if (source == Source.MIN && lintOptions) {
590            log.warning(LintCategory.OPTIONS, Warnings.OptionObsoleteSource(source.name));
591            obsoleteOptionFound = true;
592        }
593
594        if (target.compareTo(Target.MIN) < 0) {
595            log.error(Errors.OptionRemovedTarget(target.name, Target.MIN.name));
596        } else if (target == Target.MIN && lintOptions) {
597            log.warning(LintCategory.OPTIONS, Warnings.OptionObsoleteTarget(target.name));
598            obsoleteOptionFound = true;
599        }
600
601        final Target t = target;
602        checkOptionAllowed(t.compareTo(Target.JDK1_8) <= 0,
603                option -> error("err.option.not.allowed.with.target", option.getPrimaryName(), t.name),
604                Option.BOOT_CLASS_PATH,
605                Option.XBOOTCLASSPATH_PREPEND, Option.XBOOTCLASSPATH, Option.XBOOTCLASSPATH_APPEND,
606                Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS,
607                Option.EXTDIRS, Option.DJAVA_EXT_DIRS,
608                Option.PROFILE);
609
610        checkOptionAllowed(t.compareTo(Target.JDK1_9) >= 0,
611                option -> error("err.option.not.allowed.with.target", option.getPrimaryName(), t.name),
612                Option.MODULE_SOURCE_PATH, Option.UPGRADE_MODULE_PATH,
613                Option.SYSTEM, Option.MODULE_PATH, Option.ADD_MODULES,
614                Option.ADD_EXPORTS, Option.ADD_OPENS, Option.ADD_READS,
615                Option.LIMIT_MODULES,
616                Option.PATCH_MODULE);
617
618        if (fm.hasLocation(StandardLocation.MODULE_SOURCE_PATH)) {
619            if (!options.isSet(Option.PROC, "only")
620                    && !fm.hasLocation(StandardLocation.CLASS_OUTPUT)) {
621                log.error(Errors.NoOutputDir);
622            }
623        }
624
625        if (fm.hasLocation(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH) &&
626            fm.hasLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH)) {
627            log.error(Errors.ProcessorpathNoProcessormodulepath);
628        }
629
630        if (obsoleteOptionFound && lintOptions) {
631            log.warning(LintCategory.OPTIONS, Warnings.OptionObsoleteSuppression);
632        }
633
634        SourceVersion sv = Source.toSourceVersion(source);
635        validateAddExports(sv);
636        validateAddModules(sv);
637        validateAddReads(sv);
638        validateLimitModules(sv);
639        validateDefaultModuleForCreatedFiles(sv);
640
641        if (lintOptions && options.isSet(Option.ADD_OPENS)) {
642            log.warning(LintCategory.OPTIONS, Warnings.AddopensIgnored);
643        }
644
645        return !errors && (log.nerrors == 0);
646    }
647
648    private void validateAddExports(SourceVersion sv) {
649        String addExports = options.get(Option.ADD_EXPORTS);
650        if (addExports != null) {
651            // Each entry must be of the form sourceModule/sourcePackage=target-list where
652            // target-list is a comma separated list of module or ALL-UNNAMED.
653            // Empty items in the target-list are ignored.
654            // There must be at least one item in the list; this is handled in Option.ADD_EXPORTS.
655            Pattern p = Option.ADD_EXPORTS.getPattern();
656            for (String e : addExports.split("\0")) {
657                Matcher m = p.matcher(e);
658                if (m.matches()) {
659                    String sourceModuleName = m.group(1);
660                    if (!SourceVersion.isName(sourceModuleName, sv)) {
661                        // syntactically invalid source name:  e.g. --add-exports m!/p1=m2
662                        log.warning(Warnings.BadNameForOption(Option.ADD_EXPORTS, sourceModuleName));
663                    }
664                    String sourcePackageName = m.group(2);
665                    if (!SourceVersion.isName(sourcePackageName, sv)) {
666                        // syntactically invalid source name:  e.g. --add-exports m1/p!=m2
667                        log.warning(Warnings.BadNameForOption(Option.ADD_EXPORTS, sourcePackageName));
668                    }
669
670                    String targetNames = m.group(3);
671                    for (String targetName : targetNames.split(",")) {
672                        switch (targetName) {
673                            case "":
674                            case "ALL-UNNAMED":
675                                break;
676
677                            default:
678                                if (!SourceVersion.isName(targetName, sv)) {
679                                    // syntactically invalid target name:  e.g. --add-exports m1/p1=m!
680                                    log.warning(Warnings.BadNameForOption(Option.ADD_EXPORTS, targetName));
681                                }
682                                break;
683                        }
684                    }
685                }
686            }
687        }
688    }
689
690    private void validateAddReads(SourceVersion sv) {
691        String addReads = options.get(Option.ADD_READS);
692        if (addReads != null) {
693            // Each entry must be of the form source=target-list where target-list is a
694            // comma-separated list of module or ALL-UNNAMED.
695            // Empty items in the target list are ignored.
696            // There must be at least one item in the list; this is handled in Option.ADD_READS.
697            Pattern p = Option.ADD_READS.getPattern();
698            for (String e : addReads.split("\0")) {
699                Matcher m = p.matcher(e);
700                if (m.matches()) {
701                    String sourceName = m.group(1);
702                    if (!SourceVersion.isName(sourceName, sv)) {
703                        // syntactically invalid source name:  e.g. --add-reads m!=m2
704                        log.warning(Warnings.BadNameForOption(Option.ADD_READS, sourceName));
705                    }
706
707                    String targetNames = m.group(2);
708                    for (String targetName : targetNames.split(",", -1)) {
709                        switch (targetName) {
710                            case "":
711                            case "ALL-UNNAMED":
712                                break;
713
714                            default:
715                                if (!SourceVersion.isName(targetName, sv)) {
716                                    // syntactically invalid target name:  e.g. --add-reads m1=m!
717                                    log.warning(Warnings.BadNameForOption(Option.ADD_READS, targetName));
718                                }
719                                break;
720                        }
721                    }
722                }
723            }
724        }
725    }
726
727    private void validateAddModules(SourceVersion sv) {
728        String addModules = options.get(Option.ADD_MODULES);
729        if (addModules != null) {
730            // Each entry must be of the form target-list where target-list is a
731            // comma separated list of module names, or ALL-DEFAULT, ALL-SYSTEM,
732            // or ALL-MODULE_PATH.
733            // Empty items in the target list are ignored.
734            // There must be at least one item in the list; this is handled in Option.ADD_MODULES.
735            for (String moduleName : addModules.split(",")) {
736                switch (moduleName) {
737                    case "":
738                    case "ALL-SYSTEM":
739                    case "ALL-MODULE-PATH":
740                        break;
741
742                    default:
743                        if (!SourceVersion.isName(moduleName, sv)) {
744                            // syntactically invalid module name:  e.g. --add-modules m1,m!
745                            log.error(Errors.BadNameForOption(Option.ADD_MODULES, moduleName));
746                        }
747                        break;
748                }
749            }
750        }
751    }
752
753    private void validateLimitModules(SourceVersion sv) {
754        String limitModules = options.get(Option.LIMIT_MODULES);
755        if (limitModules != null) {
756            // Each entry must be of the form target-list where target-list is a
757            // comma separated list of module names, or ALL-DEFAULT, ALL-SYSTEM,
758            // or ALL-MODULE_PATH.
759            // Empty items in the target list are ignored.
760            // There must be at least one item in the list; this is handled in Option.LIMIT_EXPORTS.
761            for (String moduleName : limitModules.split(",")) {
762                switch (moduleName) {
763                    case "":
764                        break;
765
766                    default:
767                        if (!SourceVersion.isName(moduleName, sv)) {
768                            // syntactically invalid module name:  e.g. --limit-modules m1,m!
769                            log.error(Errors.BadNameForOption(Option.LIMIT_MODULES, moduleName));
770                        }
771                        break;
772                }
773            }
774        }
775    }
776
777    private void validateDefaultModuleForCreatedFiles(SourceVersion sv) {
778        String moduleName = options.get(Option.DEFAULT_MODULE_FOR_CREATED_FILES);
779        if (moduleName != null) {
780            if (!SourceVersion.isName(moduleName, sv)) {
781                // syntactically invalid module name:  e.g. --default-module-for-created-files m!
782                log.error(Errors.BadNameForOption(Option.DEFAULT_MODULE_FOR_CREATED_FILES,
783                                                  moduleName));
784            }
785        }
786    }
787
788    /**
789     * Returns true if there are no files or classes specified for use.
790     * @return true if there are no files or classes specified for use
791     */
792    public boolean isEmpty() {
793        return ((files == null) || files.isEmpty())
794                && ((fileObjects == null) || fileObjects.isEmpty())
795                && (classNames == null || classNames.isEmpty());
796    }
797
798    public void allowEmpty() {
799        this.emptyAllowed = true;
800    }
801
802    /**
803     * Gets the file manager options which may have been deferred
804     * during processArgs.
805     * @return the deferred file manager options
806     */
807    public Map<Option, String> getDeferredFileManagerOptions() {
808        return deferredFileManagerOptions;
809    }
810
811    /**
812     * Gets any options specifying plugins to be run.
813     * @return options for plugins
814     */
815    public Set<List<String>> getPluginOpts() {
816        String plugins = options.get(Option.PLUGIN);
817        if (plugins == null)
818            return Collections.emptySet();
819
820        Set<List<String>> pluginOpts = new LinkedHashSet<>();
821        for (String plugin: plugins.split("\\x00")) {
822            pluginOpts.add(List.from(plugin.split("\\s+")));
823        }
824        return Collections.unmodifiableSet(pluginOpts);
825    }
826
827    /**
828     * Gets any options specifying how doclint should be run.
829     * An empty list is returned if no doclint options are specified
830     * or if the only doclint option is -Xdoclint:none.
831     * @return options for doclint
832     */
833    public List<String> getDocLintOpts() {
834        String xdoclint = options.get(Option.XDOCLINT);
835        String xdoclintCustom = options.get(Option.XDOCLINT_CUSTOM);
836        if (xdoclint == null && xdoclintCustom == null)
837            return List.nil();
838
839        Set<String> doclintOpts = new LinkedHashSet<>();
840        if (xdoclint != null)
841            doclintOpts.add(DocLint.XMSGS_OPTION);
842        if (xdoclintCustom != null) {
843            for (String s: xdoclintCustom.split("\\s+")) {
844                if (s.isEmpty())
845                    continue;
846                doclintOpts.add(DocLint.XMSGS_CUSTOM_PREFIX + s);
847            }
848        }
849
850        if (doclintOpts.equals(Collections.singleton(DocLint.XMSGS_CUSTOM_PREFIX + "none")))
851            return List.nil();
852
853        String checkPackages = options.get(Option.XDOCLINT_PACKAGE);
854        if (checkPackages != null) {
855            for (String s : checkPackages.split("\\s+")) {
856                doclintOpts.add(DocLint.XCHECK_PACKAGE + s);
857            }
858        }
859
860        String format = options.get(Option.DOCLINT_FORMAT);
861        if (format != null) {
862            doclintOpts.add(DocLint.XHTML_VERSION_PREFIX + format);
863        }
864
865        // standard doclet normally generates H1, H2,
866        // so for now, allow user comments to assume that
867        doclintOpts.add(DocLint.XIMPLICIT_HEADERS + "2");
868        return List.from(doclintOpts.toArray(new String[doclintOpts.size()]));
869    }
870
871    private boolean checkDirectory(Option option) {
872        String value = options.get(option);
873        if (value == null) {
874            return true;
875        }
876        Path file = Paths.get(value);
877        if (Files.exists(file) && !Files.isDirectory(file)) {
878            error("err.file.not.directory", value);
879            return false;
880        }
881        return true;
882    }
883
884    private interface ErrorReporter {
885        void report(Option o);
886    }
887
888    void checkOptionAllowed(boolean allowed, ErrorReporter r, Option... opts) {
889        if (!allowed) {
890            Stream.of(opts)
891                  .filter(options :: isSet)
892                  .forEach(r :: report);
893        }
894    }
895
896    void error(JCDiagnostic.Error error) {
897        errors = true;
898        switch (errorMode) {
899            case ILLEGAL_ARGUMENT: {
900                String msg = log.localize(error);
901                throw new PropagatedException(new IllegalArgumentException(msg));
902            }
903            case ILLEGAL_STATE: {
904                String msg = log.localize(error);
905                throw new PropagatedException(new IllegalStateException(msg));
906            }
907            case LOG:
908                report(error);
909        }
910    }
911
912    void error(String key, Object... args) {
913        errors = true;
914        switch (errorMode) {
915            case ILLEGAL_ARGUMENT: {
916                String msg = log.localize(PrefixKind.JAVAC, key, args);
917                throw new PropagatedException(new IllegalArgumentException(msg));
918            }
919            case ILLEGAL_STATE: {
920                String msg = log.localize(PrefixKind.JAVAC, key, args);
921                throw new PropagatedException(new IllegalStateException(msg));
922            }
923            case LOG:
924                report(key, args);
925        }
926    }
927
928    void error(Option.InvalidValueException f) {
929        String msg = f.getMessage();
930        errors = true;
931        switch (errorMode) {
932            case ILLEGAL_ARGUMENT: {
933                throw new PropagatedException(new IllegalArgumentException(msg, f.getCause()));
934            }
935            case ILLEGAL_STATE: {
936                throw new PropagatedException(new IllegalStateException(msg, f.getCause()));
937            }
938            case LOG:
939                log.printRawLines(ownName + ": " + msg);
940        }
941    }
942
943    void warning(String key, Object... args) {
944        report(key, args);
945    }
946
947    private void report(String key, Object... args) {
948        // Would be good to have support for -XDrawDiagnostics here
949        log.printRawLines(ownName + ": " + log.localize(PrefixKind.JAVAC, key, args));
950    }
951
952    private void report(JCDiagnostic.Error error) {
953        // Would be good to have support for -XDrawDiagnostics here
954        log.printRawLines(ownName + ": " + log.localize(error));
955    }
956
957    private JavaFileManager getFileManager() {
958        if (fileManager == null)
959            fileManager = context.get(JavaFileManager.class);
960        return fileManager;
961    }
962
963    <T> ListBuffer<T> toList(Iterable<? extends T> items) {
964        ListBuffer<T> list = new ListBuffer<>();
965        if (items != null) {
966            for (T item : items) {
967                list.add(item);
968            }
969        }
970        return list;
971    }
972
973    <T> Set<T> toSet(Iterable<? extends T> items) {
974        Set<T> set = new LinkedHashSet<>();
975        if (items != null) {
976            for (T item : items) {
977                set.add(item);
978            }
979        }
980        return set;
981    }
982}
983