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