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