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