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