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