1 /*
   2  * Copyright (c) 1999, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.tools.javac.main;
  27 
  28 import java.io.IOException;
  29 import java.nio.file.Files;
  30 import java.nio.file.Path;
  31 import java.nio.file.Paths;
  32 import java.util.Arrays;
  33 import java.util.Collection;
  34 import java.util.Collections;
  35 import java.util.EnumSet;
  36 import java.util.HashSet;
  37 import java.util.Iterator;
  38 import java.util.LinkedHashMap;
  39 import java.util.LinkedHashSet;
  40 import java.util.Map;
  41 import java.util.Set;
  42 import java.util.regex.Matcher;
  43 import java.util.regex.Pattern;
  44 import java.util.stream.Stream;
  45 
  46 import javax.tools.JavaFileManager;
  47 import javax.tools.JavaFileManager.Location;
  48 import javax.tools.JavaFileObject;
  49 import javax.tools.JavaFileObject.Kind;
  50 import javax.tools.StandardJavaFileManager;
  51 import javax.tools.StandardLocation;
  52 
  53 import com.sun.tools.doclint.DocLint;
  54 import com.sun.tools.javac.code.Lint.LintCategory;
  55 import com.sun.tools.javac.code.Source;
  56 import com.sun.tools.javac.file.BaseFileManager;
  57 import com.sun.tools.javac.file.JavacFileManager;
  58 import com.sun.tools.javac.jvm.Profile;
  59 import com.sun.tools.javac.jvm.Target;
  60 import com.sun.tools.javac.main.OptionHelper.GrumpyHelper;
  61 import com.sun.tools.javac.platform.PlatformDescription;
  62 import com.sun.tools.javac.platform.PlatformUtils;
  63 import com.sun.tools.javac.resources.CompilerProperties.Errors;
  64 import com.sun.tools.javac.resources.CompilerProperties.Warnings;
  65 import com.sun.tools.javac.util.Context;
  66 import com.sun.tools.javac.util.List;
  67 import com.sun.tools.javac.util.ListBuffer;
  68 import com.sun.tools.javac.util.Log;
  69 import com.sun.tools.javac.util.Log.PrefixKind;
  70 import com.sun.tools.javac.util.Log.WriterKind;
  71 import com.sun.tools.javac.util.Options;
  72 import com.sun.tools.javac.util.PropagatedException;
  73 
  74 /**
  75  * Shared option and argument handling for command line and API usage of javac.
  76  */
  77 public 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 }