1 /*
   2  * Copyright (c) 2006, 2025, 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.FileWriter;
  29 import java.io.PrintWriter;
  30 import java.lang.module.ModuleDescriptor;
  31 import java.nio.file.Files;
  32 import java.nio.file.InvalidPathException;
  33 import java.nio.file.Path;
  34 import java.nio.file.Paths;
  35 import java.text.Collator;
  36 import java.util.Collection;
  37 import java.util.ArrayList;
  38 import java.util.Arrays;
  39 import java.util.Collections;
  40 import java.util.Comparator;
  41 import java.util.EnumSet;
  42 import java.util.Iterator;
  43 import java.util.LinkedHashSet;
  44 import java.util.List;
  45 import java.util.Locale;
  46 import java.util.ServiceLoader;
  47 import java.util.Set;
  48 import java.util.StringJoiner;
  49 import java.util.TreeMap;
  50 import java.util.regex.Pattern;
  51 import java.util.stream.Collectors;
  52 import java.util.stream.Stream;
  53 import java.util.stream.StreamSupport;
  54 
  55 import javax.lang.model.SourceVersion;
  56 
  57 import jdk.internal.misc.VM;
  58 
  59 import com.sun.tools.doclint.DocLint;
  60 import com.sun.tools.javac.code.Lint;
  61 import com.sun.tools.javac.code.Lint.LintCategory;
  62 import com.sun.tools.javac.code.Source;
  63 import com.sun.tools.javac.code.Type;
  64 import com.sun.tools.javac.jvm.Profile;
  65 import com.sun.tools.javac.jvm.Target;
  66 import com.sun.tools.javac.platform.PlatformProvider;
  67 import com.sun.tools.javac.processing.JavacProcessingEnvironment;
  68 import com.sun.tools.javac.resources.CompilerProperties.Errors;
  69 import com.sun.tools.javac.util.Assert;
  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.StringUtils;
  75 
  76 import static com.sun.tools.javac.main.Option.ChoiceKind.*;
  77 import static com.sun.tools.javac.main.Option.OptionGroup.*;
  78 import static com.sun.tools.javac.main.Option.OptionKind.*;
  79 
  80 /**
  81  * Options for javac.
  82  * The specific Option to handle a command-line option can be found by calling
  83  * {@link #lookup}, which search some or all of the members of this enum in order,
  84  * looking for the first {@link #matches match}.
  85  * The action for an Option is performed {@link #handleOption}, which determines
  86  * whether an argument is needed and where to find it;
  87  * {@code handleOption} then calls {@link #process process} providing a suitable
  88  * {@link OptionHelper} to provide access the compiler state.
  89  *
  90  * <p>A subset of options is relevant to the source launcher implementation
  91  * located in {@link com.sun.tools.javac.launcher} package. When an option is
  92  * added, changed, or removed, also update the {@code RelevantJavacOptions} class
  93  * in the launcher package accordingly.
  94  *
  95  * <p>Maintenance note: when adding new annotation processing related
  96  * options, the list of options regarded as requesting explicit
  97  * annotation processing in JavaCompiler should be updated.
  98  *
  99  * <p><b>This is NOT part of any supported API.
 100  * If you write code that depends on this, you do so at your own
 101  * risk.  This code and its internal interfaces are subject to change
 102  * or deletion without notice.</b></p>
 103  */
 104 public enum Option {
 105     G("-g", "opt.g", STANDARD, BASIC),
 106 
 107     G_NONE("-g:none", "opt.g.none", STANDARD, BASIC) {
 108         @Override
 109         public void process(OptionHelper helper, String option) {
 110             helper.put("-g:", "none");
 111         }
 112     },
 113 
 114     G_CUSTOM("-g:",  "opt.g.lines.vars.source",
 115             STANDARD, BASIC, ANYOF, "lines", "vars", "source"),
 116 
 117     XLINT("-Xlint", "opt.Xlint", EXTENDED, BASIC),
 118 
 119     XLINT_CUSTOM("-Xlint:", "opt.arg.Xlint", "opt.Xlint.custom", EXTENDED, BASIC, ANYOF, getXLintChoices()),
 120 
 121     XDOCLINT("-Xdoclint", "opt.Xdoclint", EXTENDED, BASIC),
 122 
 123     XDOCLINT_CUSTOM("-Xdoclint:", "opt.Xdoclint.subopts", "opt.Xdoclint.custom", EXTENDED, BASIC) {
 124         @Override
 125         public boolean matches(String option) {
 126             return DocLint.newDocLint().isValidOption(
 127                     option.replace(XDOCLINT_CUSTOM.primaryName, DocLint.XMSGS_CUSTOM_PREFIX));
 128         }
 129 
 130         @Override
 131         public void process(OptionHelper helper, String option, String arg) {
 132             String prev = helper.get(XDOCLINT_CUSTOM);
 133             String next = (prev == null) ? arg : (prev + " " + arg);
 134             helper.put(XDOCLINT_CUSTOM.primaryName, next);
 135         }
 136     },
 137 
 138     XDOCLINT_PACKAGE("-Xdoclint/package:", "opt.Xdoclint.package.args", "opt.Xdoclint.package.desc", EXTENDED, BASIC) {
 139         @Override
 140         public boolean matches(String option) {
 141             return DocLint.newDocLint().isValidOption(
 142                     option.replace(XDOCLINT_PACKAGE.primaryName, DocLint.XCHECK_PACKAGE));
 143         }
 144 
 145         @Override
 146         public void process(OptionHelper helper, String option, String arg) {
 147             String prev = helper.get(XDOCLINT_PACKAGE);
 148             String next = (prev == null) ? arg : (prev + "," + arg);
 149             helper.put(XDOCLINT_PACKAGE.primaryName, next);
 150         }
 151     },
 152 
 153     NOWARN("-nowarn", "opt.nowarn", STANDARD, BASIC),
 154 
 155     VERBOSE("-verbose", "opt.verbose", STANDARD, BASIC),
 156 
 157     // -deprecation is retained for command-line backward compatibility
 158     DEPRECATION("-deprecation", "opt.deprecation", STANDARD, BASIC) {
 159         @Override
 160         public void process(OptionHelper helper, String option) {
 161             helper.put("-Xlint:deprecation", option);
 162         }
 163     },
 164 
 165     CLASS_PATH("--class-path -classpath -cp", "opt.arg.path", "opt.classpath", STANDARD, FILEMANAGER),
 166 
 167     SOURCE_PATH("--source-path -sourcepath", "opt.arg.path", "opt.sourcepath", STANDARD, FILEMANAGER),
 168 
 169     MODULE_SOURCE_PATH("--module-source-path", "opt.arg.mspath", "opt.modulesourcepath", STANDARD, FILEMANAGER) {
 170         // The deferred file manager diagnostics mechanism assumes a single value per option,
 171         // but --module-source-path-module can be used multiple times, once in the old form
 172         // and once per module in the new form.  Therefore we compose an overall value for the
 173         // option containing the individual values given on the command line, separated by NULL.
 174         // The standard file manager code knows to split apart the NULL-separated components.
 175         @Override
 176         public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
 177             if (arg.isEmpty()) {
 178                 throw helper.newInvalidValueException(Errors.NoValueForOption(option));
 179             }
 180             Pattern moduleSpecificForm = getPattern();
 181             String prev = helper.get(MODULE_SOURCE_PATH);
 182             if (prev == null) {
 183                 super.process(helper, option, arg);
 184             } else  if (moduleSpecificForm.matcher(arg).matches()) {
 185                 String argModule = arg.substring(0, arg.indexOf('='));
 186                 boolean isRepeated = Arrays.stream(prev.split("\0"))
 187                         .filter(s -> moduleSpecificForm.matcher(s).matches())
 188                         .map(s -> s.substring(0, s.indexOf('=')))
 189                         .anyMatch(s -> s.equals(argModule));
 190                 if (isRepeated) {
 191                     throw helper.newInvalidValueException(Errors.RepeatedValueForModuleSourcePath(argModule));
 192                 } else {
 193                     super.process(helper, option, prev + '\0' + arg);
 194                 }
 195             } else {
 196                 boolean isPresent = Arrays.stream(prev.split("\0"))
 197                         .anyMatch(s -> !moduleSpecificForm.matcher(s).matches());
 198                 if (isPresent) {
 199                     throw helper.newInvalidValueException(Errors.MultipleValuesForModuleSourcePath);
 200                 } else {
 201                     super.process(helper, option, prev + '\0' + arg);
 202                 }
 203             }
 204         }
 205 
 206         @Override
 207         public Pattern getPattern() {
 208             return Pattern.compile("([\\p{Alnum}$_.]+)=(.*)");
 209         }
 210     },
 211 
 212     MODULE_PATH("--module-path -p", "opt.arg.path", "opt.modulepath", STANDARD, FILEMANAGER),
 213 
 214     UPGRADE_MODULE_PATH("--upgrade-module-path", "opt.arg.path", "opt.upgrademodulepath", STANDARD, FILEMANAGER),
 215 
 216     SYSTEM("--system", "opt.arg.jdk", "opt.system", STANDARD, FILEMANAGER),
 217 
 218     PATCH_MODULE("--patch-module", "opt.arg.patch", "opt.patch", EXTENDED, FILEMANAGER) {
 219         // The deferred file manager diagnostics mechanism assumes a single value per option,
 220         // but --patch-module can be used multiple times, once per module. Therefore we compose
 221         // a value for the option containing the last value specified for each module, and separate
 222         // the module=path pairs by an invalid path character, NULL.
 223         // The standard file manager code knows to split apart the NULL-separated components.
 224         @Override
 225         public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
 226             if (arg.isEmpty()) {
 227                 throw helper.newInvalidValueException(Errors.NoValueForOption(option));
 228             } else if (getPattern().matcher(arg).matches()) {
 229                 String prev = helper.get(PATCH_MODULE);
 230                 if (prev == null) {
 231                     super.process(helper, option, arg);
 232                 } else {
 233                     String argModulePackage = arg.substring(0, arg.indexOf('='));
 234                     boolean isRepeated = Arrays.stream(prev.split("\0"))
 235                             .map(s -> s.substring(0, s.indexOf('=')))
 236                             .collect(Collectors.toSet())
 237                             .contains(argModulePackage);
 238                     if (isRepeated) {
 239                         throw helper.newInvalidValueException(Errors.RepeatedValueForPatchModule(argModulePackage));
 240                     } else {
 241                         super.process(helper, option, prev + '\0' + arg);
 242                     }
 243                 }
 244             } else {
 245                 throw helper.newInvalidValueException(Errors.BadValueForOption(option, arg));
 246             }
 247         }
 248 
 249         @Override
 250         public Pattern getPattern() {
 251             return Pattern.compile("([^/]+)=(,*[^,].*)");
 252         }
 253     },
 254 
 255     BOOT_CLASS_PATH("--boot-class-path -bootclasspath", "opt.arg.path", "opt.bootclasspath", STANDARD, FILEMANAGER) {
 256         @Override
 257         public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
 258             helper.remove("-Xbootclasspath/p:");
 259             helper.remove("-Xbootclasspath/a:");
 260             super.process(helper, option, arg);
 261         }
 262     },
 263 
 264     XBOOTCLASSPATH_PREPEND("-Xbootclasspath/p:", "opt.arg.path", "opt.Xbootclasspath.p", EXTENDED, FILEMANAGER),
 265 
 266     XBOOTCLASSPATH_APPEND("-Xbootclasspath/a:", "opt.arg.path", "opt.Xbootclasspath.a", EXTENDED, FILEMANAGER),
 267 
 268     XBOOTCLASSPATH("-Xbootclasspath:", "opt.arg.path", "opt.bootclasspath", EXTENDED, FILEMANAGER) {
 269         @Override
 270         public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
 271             helper.remove("-Xbootclasspath/p:");
 272             helper.remove("-Xbootclasspath/a:");
 273             super.process(helper, "-bootclasspath", arg);
 274         }
 275     },
 276 
 277     EXTDIRS("-extdirs", "opt.arg.dirs", "opt.extdirs", STANDARD, FILEMANAGER),
 278 
 279     DJAVA_EXT_DIRS("-Djava.ext.dirs=", "opt.arg.dirs", "opt.extdirs", EXTENDED, FILEMANAGER) {
 280         @Override
 281         public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
 282             EXTDIRS.process(helper, "-extdirs", arg);
 283         }
 284     },
 285 
 286     ENDORSEDDIRS("-endorseddirs", "opt.arg.dirs", "opt.endorseddirs", STANDARD, FILEMANAGER),
 287 
 288     DJAVA_ENDORSED_DIRS("-Djava.endorsed.dirs=", "opt.arg.dirs", "opt.endorseddirs", EXTENDED, FILEMANAGER) {
 289         @Override
 290         public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
 291             ENDORSEDDIRS.process(helper, "-endorseddirs", arg);
 292         }
 293     },
 294 
 295     PROC("-proc:", "opt.proc.none.only", STANDARD, BASIC, ONEOF, "none", "only", "full"),
 296 
 297     PROCESSOR("-processor", "opt.arg.class.list", "opt.processor", STANDARD, BASIC),
 298 
 299     PROCESSOR_PATH("--processor-path -processorpath", "opt.arg.path", "opt.processorpath", STANDARD, FILEMANAGER),
 300 
 301     PROCESSOR_MODULE_PATH("--processor-module-path", "opt.arg.path", "opt.processormodulepath", STANDARD, FILEMANAGER),
 302 
 303     PARAMETERS("-parameters","opt.parameters", STANDARD, BASIC),
 304 
 305     D("-d", "opt.arg.directory", "opt.d", STANDARD, FILEMANAGER),
 306 
 307     S("-s", "opt.arg.directory", "opt.sourceDest", STANDARD, FILEMANAGER),
 308 
 309     H("-h", "opt.arg.directory", "opt.headerDest", STANDARD, FILEMANAGER),
 310 
 311     IMPLICIT("-implicit:", "opt.implicit", STANDARD, BASIC, ONEOF, "none", "class"),
 312 
 313     ENCODING("-encoding", "opt.arg.encoding", "opt.encoding", STANDARD, FILEMANAGER),
 314 
 315     SOURCE("--source -source", "opt.arg.release", "opt.source", STANDARD, BASIC) {
 316         @Override
 317         public void process(OptionHelper helper, String option, String operand) throws InvalidValueException {
 318             Source source = Source.lookup(operand);
 319             if (source == null) {
 320                 throw helper.newInvalidValueException(Errors.InvalidSource(operand));
 321             }
 322             super.process(helper, option, operand);
 323         }
 324 
 325         @Override
 326         protected void help(Log log) {
 327             List<String> releases = new ArrayList<>();
 328             for(Source source :  Source.values()) {
 329                 if (source.isSupported())
 330                     releases.add(source.name);
 331             }
 332             String formatted = formatAbbreviatedList(releases);
 333             super.help(log, log.localize(PrefixKind.JAVAC, descrKey, formatted));
 334         }
 335     },
 336 
 337     TARGET("--target -target", "opt.arg.release", "opt.target", STANDARD, BASIC) {
 338         @Override
 339         public void process(OptionHelper helper, String option, String operand) throws InvalidValueException {
 340             Target target = Target.lookup(operand);
 341             if (target == null) {
 342                 throw helper.newInvalidValueException(Errors.InvalidTarget(operand));
 343             }
 344             super.process(helper, option, operand);
 345         }
 346 
 347         @Override
 348         protected void help(Log log) {
 349             List<String> releases = new ArrayList<>();
 350             for(Target target :  Target.values()) {
 351                 if (target.isSupported())
 352                     releases.add(target.name);
 353             }
 354             String formatted = formatAbbreviatedList(releases);
 355             super.help(log, log.localize(PrefixKind.JAVAC, descrKey, formatted));
 356         }
 357     },
 358 
 359     RELEASE("--release", "opt.arg.release", "opt.release", STANDARD, BASIC) {
 360         @Override
 361         protected void help(Log log) {
 362             Iterable<PlatformProvider> providers =
 363                     ServiceLoader.load(PlatformProvider.class, Arguments.class.getClassLoader());
 364             Set<String> platforms = StreamSupport.stream(providers.spliterator(), false)
 365                                                  .flatMap(provider -> StreamSupport.stream(provider.getSupportedPlatformNames()
 366                                                                                                    .spliterator(),
 367                                                                                            false))
 368                                                  .collect(Collectors.toCollection(LinkedHashSet :: new));
 369 
 370             String formatted = formatAbbreviatedList(platforms);
 371             super.help(log, log.localize(PrefixKind.JAVAC, descrKey, formatted));
 372         }
 373     },
 374 
 375     PREVIEW("--enable-preview", "opt.preview", STANDARD, BASIC),
 376 
 377     DISABLE_LINE_DOC_COMMENTS("--disable-line-doc-comments", "opt.lineDocComments", EXTENDED, BASIC),
 378 
 379     PROFILE("-profile", "opt.arg.profile", "opt.profile", STANDARD, BASIC) {
 380         @Override
 381         public void process(OptionHelper helper, String option, String operand) throws InvalidValueException {
 382             Profile profile = Profile.lookup(operand);
 383             if (profile == null) {
 384                 throw helper.newInvalidValueException(Errors.InvalidProfile(operand));
 385             }
 386             super.process(helper, option, operand);
 387         }
 388     },
 389 
 390     VERSION("--version -version", "opt.version", STANDARD, INFO) {
 391         @Override
 392         public void process(OptionHelper helper, String option) throws InvalidValueException {
 393             Log log = helper.getLog();
 394             String ownName = helper.getOwnName();
 395             log.printLines(WriterKind.STDOUT, PrefixKind.JAVAC, "version", ownName,  JavaCompiler.version());
 396             super.process(helper, option);
 397         }
 398     },
 399 
 400     FULLVERSION("--full-version -fullversion", null, HIDDEN, INFO) {
 401         @Override
 402         public void process(OptionHelper helper, String option) throws InvalidValueException {
 403             Log log = helper.getLog();
 404             String ownName = helper.getOwnName();
 405             log.printLines(WriterKind.STDOUT, PrefixKind.JAVAC, "fullVersion", ownName,  JavaCompiler.fullVersion());
 406             super.process(helper, option);
 407         }
 408     },
 409 
 410     // Note: -h is already taken for "native header output directory".
 411     HELP("--help -help -?", "opt.help", STANDARD, INFO) {
 412         @Override
 413         public void process(OptionHelper helper, String option) throws InvalidValueException {
 414             Log log = helper.getLog();
 415             String ownName = helper.getOwnName();
 416             log.printLines(WriterKind.STDOUT, PrefixKind.JAVAC, "msg.usage.header", ownName);
 417             showHelp(log, OptionKind.STANDARD);
 418             log.printNewline(WriterKind.STDOUT);
 419             super.process(helper, option);
 420         }
 421     },
 422 
 423     A("-A", "opt.arg.key.equals.value", "opt.A", STANDARD, BASIC, ArgKind.ADJACENT) {
 424         @Override
 425         public boolean matches(String arg) {
 426             return arg.startsWith("-A");
 427         }
 428 
 429         @Override
 430         public boolean hasArg() {
 431             return false;
 432         }
 433         // Mapping for processor options created in
 434         // JavacProcessingEnvironment
 435         @Override
 436         public void process(OptionHelper helper, String option) throws InvalidValueException {
 437             int argLength = option.length();
 438             if (argLength == 2) {
 439                 throw helper.newInvalidValueException(Errors.EmptyAArgument);
 440             }
 441             int sepIndex = option.indexOf('=');
 442             String key = option.substring(2, (sepIndex != -1 ? sepIndex : argLength) );
 443             if (!JavacProcessingEnvironment.isValidOptionName(key)) {
 444                 throw helper.newInvalidValueException(Errors.InvalidAKey(option));
 445             }
 446             helper.put(option, option);
 447         }
 448     },
 449 
 450     DEFAULT_MODULE_FOR_CREATED_FILES("--default-module-for-created-files",
 451                                      "opt.arg.default.module.for.created.files",
 452                                      "opt.default.module.for.created.files", EXTENDED, BASIC) {
 453         @Override
 454         public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
 455             String prev = helper.get(DEFAULT_MODULE_FOR_CREATED_FILES);
 456             if (prev != null) {
 457                 throw helper.newInvalidValueException(Errors.OptionTooMany(DEFAULT_MODULE_FOR_CREATED_FILES.primaryName));
 458             } else if (arg.isEmpty()) {
 459                 throw helper.newInvalidValueException(Errors.NoValueForOption(option));
 460             } else if (getPattern().matcher(arg).matches()) {
 461                 helper.put(DEFAULT_MODULE_FOR_CREATED_FILES.primaryName, arg);
 462             } else {
 463                 throw helper.newInvalidValueException(Errors.BadValueForOption(option, arg));
 464             }
 465         }
 466 
 467         @Override
 468         public Pattern getPattern() {
 469             return Pattern.compile("[^,].*");
 470         }
 471     },
 472 
 473     X("--help-extra -X", "opt.X", STANDARD, INFO) {
 474         @Override
 475         public void process(OptionHelper helper, String option) throws InvalidValueException {
 476             Log log = helper.getLog();
 477             showHelp(log, OptionKind.EXTENDED);
 478             log.printNewline(WriterKind.STDOUT);
 479             log.printLines(WriterKind.STDOUT, PrefixKind.JAVAC, "msg.usage.nonstandard.footer");
 480             super.process(helper, option);
 481         }
 482     },
 483 
 484     HELP_LINT("--help-lint", "opt.help.lint", EXTENDED, INFO) {
 485         private final String HELP_INDENT = SMALL_INDENT + SMALL_INDENT;
 486         private final String LINT_KEY_FORMAT = HELP_INDENT + "%-" +
 487                 (DEFAULT_SYNOPSIS_WIDTH - LARGE_INDENT.length()) + "s %s";
 488         @Override
 489         public void process(OptionHelper helper, String option) throws InvalidValueException {
 490             Log log = helper.getLog();
 491 
 492             // Print header
 493             log.printRawLines(WriterKind.STDOUT, log.localize(PrefixKind.JAVAC, "opt.help.lint.header"));
 494 
 495             // Print "all" option
 496             log.printRawLines(WriterKind.STDOUT,
 497                               String.format(LINT_KEY_FORMAT,
 498                                             LINT_CUSTOM_ALL,
 499                                             log.localize(PrefixKind.JAVAC, "opt.Xlint.all")));
 500 
 501             // Alphabetize all the category names and their aliases together, and then list them with their descriptions
 502             TreeMap<String, String> keyMap = new TreeMap<>();
 503             Stream.of(LintCategory.values()).forEach(lc ->
 504               lc.optionList.stream()
 505                 .forEach(key -> keyMap.put(key,
 506                   String.format(LINT_KEY_FORMAT,
 507                                 key,
 508                                 key.equals(lc.option) ?
 509                                   log.localize(PrefixKind.JAVAC, "opt.Xlint.desc." + key) :
 510                                   log.localize(PrefixKind.JAVAC, "opt.Xlint.alias.of", lc.option, key)))));
 511             keyMap.values().forEach(desc -> log.printRawLines(WriterKind.STDOUT, desc));
 512 
 513             // Print "none" option
 514             log.printRawLines(WriterKind.STDOUT,
 515                               String.format(LINT_KEY_FORMAT,
 516                                             LINT_CUSTOM_NONE,
 517                                             log.localize(PrefixKind.JAVAC, "opt.Xlint.none")));
 518 
 519             // Show which lint categories are enabled by default
 520             log.printRawLines(WriterKind.STDOUT, log.localize(PrefixKind.JAVAC, "opt.help.lint.enabled.by.default"));
 521             String defaults = Stream.of(LintCategory.values())
 522               .filter(lc -> lc.enabledByDefault)
 523               .map(lc -> lc.option)
 524               .sorted()
 525               .collect(Collectors.joining(", "));
 526             log.printRawLines(WriterKind.STDOUT,
 527                               String.format("%s%s.", HELP_INDENT, defaults));
 528 
 529             // Add trailing blurb about aliases
 530             List<String> aliasExample = LintCategory.IDENTITY.optionList;
 531             log.printRawLines(WriterKind.STDOUT,
 532                               log.localize(PrefixKind.JAVAC, "opt.help.lint.footer", aliasExample.get(0), aliasExample.get(1)));
 533             super.process(helper, option);
 534         }
 535     },
 536 
 537     // This option exists only for the purpose of documenting itself.
 538     // It's actually implemented by the launcher.
 539     J("-J", "opt.arg.flag", "opt.J", STANDARD, INFO, ArgKind.ADJACENT) {
 540         @Override
 541         public void process(OptionHelper helper, String option) {
 542             throw new AssertionError("the -J flag should be caught by the launcher.");
 543         }
 544 
 545         @Override
 546         public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
 547             throw helper.newInvalidValueException(Errors.InvalidFlag(option + arg));
 548         }
 549     },
 550 
 551     MOREINFO("-moreinfo", null, HIDDEN, BASIC) {
 552         @Override
 553         public void process(OptionHelper helper, String option) throws InvalidValueException {
 554             Type.moreInfo = true;
 555             super.process(helper, option);
 556         }
 557     },
 558 
 559     // treat warnings as errors
 560     WERROR("-Werror", "opt.Werror", STANDARD, BASIC),
 561 
 562     WERROR_CUSTOM("-Werror:", "opt.arg.Werror", "opt.Werror.custom", STANDARD, BASIC, ANYOF, getXLintChoices()),
 563 
 564     // prompt after each error
 565     // new Option("-prompt",                                        "opt.prompt"),
 566     PROMPT("-prompt", null, HIDDEN, BASIC),
 567 
 568     // dump stack on error
 569     DOE("-doe", null, HIDDEN, BASIC),
 570 
 571     // output source after type erasure
 572     PRINTSOURCE("-printsource", null, HIDDEN, BASIC),
 573 
 574     // display warnings for generic unchecked operations
 575     WARNUNCHECKED("-warnunchecked", null, HIDDEN, BASIC) {
 576         @Override
 577         public void process(OptionHelper helper, String option) {
 578             helper.put("-Xlint:unchecked", option);
 579         }
 580     },
 581 
 582     XMAXERRS("-Xmaxerrs", "opt.arg.number", "opt.maxerrs", EXTENDED, BASIC),
 583 
 584     XMAXWARNS("-Xmaxwarns", "opt.arg.number", "opt.maxwarns", EXTENDED, BASIC),
 585 
 586     XSTDOUT("-Xstdout", "opt.arg.file", "opt.Xstdout", EXTENDED, INFO) {
 587         @Override
 588         public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
 589             try {
 590                 Log log = helper.getLog();
 591                 log.setWriters(new PrintWriter(new FileWriter(arg), true));
 592             } catch (java.io.IOException e) {
 593                 throw helper.newInvalidValueException(Errors.ErrorWritingFile(arg, e.getMessage()));
 594             }
 595             super.process(helper, option, arg);
 596         }
 597     },
 598 
 599     XPRINT("-Xprint", "opt.print", EXTENDED, BASIC),
 600 
 601     XPRINTROUNDS("-XprintRounds", "opt.printRounds", EXTENDED, BASIC),
 602 
 603     XPRINTPROCESSORINFO("-XprintProcessorInfo", "opt.printProcessorInfo", EXTENDED, BASIC),
 604 
 605     XPREFER("-Xprefer:", "opt.prefer", EXTENDED, BASIC, ONEOF, "source", "newer"),
 606 
 607     XXUSERPATHSFIRST("-XXuserPathsFirst", "opt.userpathsfirst", HIDDEN, BASIC),
 608 
 609     // see enum PkgInfo
 610     XPKGINFO("-Xpkginfo:", "opt.pkginfo", EXTENDED, BASIC, ONEOF, "always", "legacy", "nonempty"),
 611 
 612     /* -O is a no-op, accepted for backward compatibility. */
 613     O("-O", null, HIDDEN, BASIC),
 614 
 615     /* -Xjcov produces tables to support the code coverage tool jcov. */
 616     XJCOV("-Xjcov", null, HIDDEN, BASIC),
 617 
 618     PLUGIN("-Xplugin:", "opt.arg.plugin", "opt.plugin", EXTENDED, BASIC) {
 619         @Override
 620         public void process(OptionHelper helper, String option, String p) {
 621             String prev = helper.get(PLUGIN);
 622             helper.put(PLUGIN.primaryName, (prev == null) ? p : prev + '\0' + p);
 623         }
 624     },
 625 
 626     XDIAGS("-Xdiags:", "opt.diags", EXTENDED, BASIC, ONEOF, "compact", "verbose"),
 627 
 628     DEBUG("--debug", null, HIDDEN, BASIC, ArgKind.REQUIRED) {
 629         @Override
 630         public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
 631             HiddenGroup.DEBUG.process(helper, option, arg);
 632         }
 633     },
 634 
 635     SHOULDSTOP("--should-stop", null, HIDDEN, BASIC, ArgKind.REQUIRED) {
 636         @Override
 637         public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
 638             HiddenGroup.SHOULDSTOP.process(helper, option, arg);
 639         }
 640     },
 641 
 642     DIAGS("--diags", null, HIDDEN, BASIC, ArgKind.REQUIRED) {
 643         @Override
 644         public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
 645             HiddenGroup.DIAGS.process(helper, option, arg);
 646         }
 647     },
 648 
 649     /* This is a back door to the compiler's option table.
 650      * -XDx=y sets the option x to the value y.
 651      * -XDx sets the option x to the value x.
 652      */
 653     XD("-XD", null, HIDDEN, BASIC) {
 654         @Override
 655         public boolean matches(String s) {
 656             return s.startsWith(primaryName);
 657         }
 658         @Override
 659         public void process(OptionHelper helper, String option) {
 660             process(helper, option, option.substring(primaryName.length()));
 661         }
 662 
 663         @Override
 664         public void process(OptionHelper helper, String option, String arg) {
 665             int eq = arg.indexOf('=');
 666             String key = (eq < 0) ? arg : arg.substring(0, eq);
 667             String value = (eq < 0) ? arg : arg.substring(eq+1);
 668             helper.put(key, value);
 669         }
 670     },
 671 
 672     ADD_EXPORTS("--add-exports", "opt.arg.addExports", "opt.addExports", EXTENDED, BASIC) {
 673         @Override
 674         public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
 675             if (arg.isEmpty()) {
 676                 throw helper.newInvalidValueException(Errors.NoValueForOption(option));
 677             } else if (getPattern().matcher(arg).matches()) {
 678                 String prev = helper.get(ADD_EXPORTS);
 679                 helper.put(ADD_EXPORTS.primaryName, (prev == null) ? arg : prev + '\0' + arg);
 680             } else {
 681                 throw helper.newInvalidValueException(Errors.BadValueForOption(option, arg));
 682             }
 683         }
 684 
 685         @Override
 686         public Pattern getPattern() {
 687             return Pattern.compile("([^/]+)/([^=]+)=(,*[^,].*)");
 688         }
 689     },
 690 
 691     ADD_OPENS("--add-opens", null, null, HIDDEN, BASIC),
 692 
 693     ADD_READS("--add-reads", "opt.arg.addReads", "opt.addReads", EXTENDED, BASIC) {
 694         @Override
 695         public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
 696             if (arg.isEmpty()) {
 697                 throw helper.newInvalidValueException(Errors.NoValueForOption(option));
 698             } else if (getPattern().matcher(arg).matches()) {
 699                 String prev = helper.get(ADD_READS);
 700                 helper.put(ADD_READS.primaryName, (prev == null) ? arg : prev + '\0' + arg);
 701             } else {
 702                 throw helper.newInvalidValueException(Errors.BadValueForOption(option, arg));
 703             }
 704         }
 705 
 706         @Override
 707         public Pattern getPattern() {
 708             return Pattern.compile("([^=]+)=(,*[^,].*)");
 709         }
 710     },
 711 
 712     MODULE("--module -m", "opt.arg.m", "opt.m", STANDARD, BASIC),
 713 
 714     ADD_MODULES("--add-modules", "opt.arg.addmods", "opt.addmods", STANDARD, BASIC) {
 715         @Override
 716         public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
 717             if (arg.isEmpty()) {
 718                 throw helper.newInvalidValueException(Errors.NoValueForOption(option));
 719             } else if (getPattern().matcher(arg).matches()) {
 720                 String prev = helper.get(ADD_MODULES);
 721                 // since the individual values are simple names, we can simply join the
 722                 // values of multiple --add-modules options with ','
 723                 helper.put(ADD_MODULES.primaryName, (prev == null) ? arg : prev + ',' + arg);
 724             } else {
 725                 throw helper.newInvalidValueException(Errors.BadValueForOption(option, arg));
 726             }
 727         }
 728 
 729         @Override
 730         public Pattern getPattern() {
 731             return Pattern.compile(",*[^,].*");
 732         }
 733     },
 734 
 735     LIMIT_MODULES("--limit-modules", "opt.arg.limitmods", "opt.limitmods", STANDARD, BASIC) {
 736         @Override
 737         public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
 738             if (arg.isEmpty()) {
 739                 throw helper.newInvalidValueException(Errors.NoValueForOption(option));
 740             } else if (getPattern().matcher(arg).matches()) {
 741                 helper.put(LIMIT_MODULES.primaryName, arg); // last one wins
 742             } else {
 743                 throw helper.newInvalidValueException(Errors.BadValueForOption(option, arg));
 744             }
 745         }
 746 
 747         @Override
 748         public Pattern getPattern() {
 749             return Pattern.compile(",*[^,].*");
 750         }
 751     },
 752 
 753     MODULE_VERSION("--module-version", "opt.arg.module.version", "opt.module.version", STANDARD, BASIC) {
 754         @Override
 755         public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
 756             if (arg.isEmpty()) {
 757                 throw helper.newInvalidValueException(Errors.NoValueForOption(option));
 758             } else {
 759                 // use official parser if available
 760                 try {
 761                     ModuleDescriptor.Version.parse(arg);
 762                 } catch (IllegalArgumentException e) {
 763                     throw helper.newInvalidValueException(Errors.BadValueForOption(option, arg));
 764                 }
 765             }
 766             super.process(helper, option, arg);
 767         }
 768     },
 769 
 770     // This option exists only for the purpose of documenting itself.
 771     // It's actually implemented by the CommandLine class.
 772     AT("@", "opt.arg.file", "opt.AT", STANDARD, INFO, ArgKind.ADJACENT) {
 773         @Override
 774         public void process(OptionHelper helper, String option) {
 775             throw new AssertionError("the @ flag should be caught by CommandLine.");
 776         }
 777     },
 778 
 779     // Standalone positional argument: source file or type name.
 780     SOURCEFILE("sourcefile", null, HIDDEN, INFO) {
 781         @Override
 782         public boolean matches(String s) {
 783             if (s.endsWith(".java"))  // Java source file
 784                 return true;
 785             int sep = s.indexOf('/');
 786             if (sep != -1) {
 787                 return SourceVersion.isName(s.substring(0, sep))
 788                         && SourceVersion.isName(s.substring(sep + 1));
 789             } else {
 790                 return SourceVersion.isName(s);   // Legal type name
 791             }
 792         }
 793         @Override
 794         public void process(OptionHelper helper, String option) throws InvalidValueException {
 795             if (option.endsWith(".java") ) {
 796                 try {
 797                     Path p = Paths.get(option);
 798                     if (!Files.exists(p)) {
 799                         throw helper.newInvalidValueException(Errors.FileNotFound(p.toString()));
 800                     }
 801                     if (!Files.isRegularFile(p)) {
 802                         throw helper.newInvalidValueException(Errors.FileNotFile(p));
 803                     }
 804                     helper.addFile(p);
 805                 } catch (InvalidPathException ex) {
 806                     throw helper.newInvalidValueException(Errors.InvalidPath(option));
 807                 }
 808             } else {
 809                 helper.addClassName(option);
 810             }
 811         }
 812     },
 813 
 814     MULTIRELEASE("--multi-release", "opt.arg.multi-release", "opt.multi-release", HIDDEN, FILEMANAGER),
 815     PREVIEWMODE("--preview-mode", "opt.arg.preview-mode", "opt.preview-mode", HIDDEN, FILEMANAGER),
 816 
 817     INHERIT_RUNTIME_ENVIRONMENT("--inherit-runtime-environment", "opt.inherit_runtime_environment",
 818             HIDDEN, BASIC) {
 819         @Override
 820         public void process(OptionHelper helper, String option) throws InvalidValueException {
 821             String[] runtimeArgs = VM.getRuntimeArguments();
 822             for (String arg : runtimeArgs) {
 823                 // Handle any supported runtime options; ignore all others.
 824                 // The runtime arguments always use the single token form, e.g. "--name=value".
 825                 for (Option o : getSupportedRuntimeOptions()) {
 826                     if (o.matches(arg)) {
 827                         switch (o) {
 828                             case ADD_MODULES:
 829                                 int eq = arg.indexOf('=');
 830                                 Assert.check(eq > 0, () -> ("invalid runtime option:" + arg));
 831                                 // --add-modules=ALL-DEFAULT is not supported at compile-time
 832                                 // so remove it from list, and only process the rest
 833                                 // if the set is non-empty.
 834                                 // Note that --add-modules=ALL-DEFAULT is automatically added
 835                                 // by the standard javac launcher.
 836                                 String mods = Arrays.stream(arg.substring(eq + 1).split(","))
 837                                         .filter(s -> !s.isEmpty() && !s.equals("ALL-DEFAULT"))
 838                                         .collect(Collectors.joining(","));
 839                                 if (!mods.isEmpty()) {
 840                                     String updatedArg = arg.substring(0, eq + 1) + mods;
 841                                     o.handleOption(helper, updatedArg, Collections.emptyIterator());
 842                                 }
 843                                 break;
 844                             default:
 845                                 o.handleOption(helper, arg, Collections.emptyIterator());
 846                                 break;
 847                         }
 848                         break;
 849                     }
 850                 }
 851             }
 852         }
 853 
 854         private Option[] getSupportedRuntimeOptions() {
 855             Option[] supportedRuntimeOptions = {
 856                 ADD_EXPORTS,
 857                 ADD_MODULES,
 858                 LIMIT_MODULES,
 859                 MODULE_PATH,
 860                 UPGRADE_MODULE_PATH,
 861                 PATCH_MODULE
 862             };
 863             return supportedRuntimeOptions;
 864         }
 865     };
 866 
 867     /**
 868      * Special lint category key meaning "all lint categories".
 869      */
 870     public static final String LINT_CUSTOM_ALL = "all";
 871 
 872     /**
 873      * Special lint category key meaning "no lint categories".
 874      */
 875     public static final String LINT_CUSTOM_NONE = "none";
 876 
 877     /**
 878      * This exception is thrown when an invalid value is given for an option.
 879      * The detail string gives a detailed, localized message, suitable for use
 880      * in error messages reported to the user.
 881      */
 882     public static class InvalidValueException extends Exception {
 883         private static final long serialVersionUID = -1;
 884 
 885         public InvalidValueException(String msg) {
 886             super(msg);
 887         }
 888 
 889         public InvalidValueException(String msg, Throwable cause) {
 890             super(msg, cause);
 891         }
 892     }
 893 
 894     /**
 895      * The kind of argument, if any, accepted by this option. The kind is augmented
 896      * by characters in the name of the option.
 897      */
 898     public enum ArgKind {
 899         /** This option does not take any argument. */
 900         NONE,
 901 
 902 // Not currently supported
 903 //        /**
 904 //         * This option takes an optional argument, which may be provided directly after an '='
 905 //         * separator, or in the following argument position if that word does not itself appear
 906 //         * to be the name of an option.
 907 //         */
 908 //        OPTIONAL,
 909 
 910         /**
 911          * This option takes an argument.
 912          * If the name of option ends with ':' or '=', the argument must be provided directly
 913          * after that separator.
 914          * Otherwise, it may appear after an '=' or in the following argument position.
 915          */
 916         REQUIRED,
 917 
 918         /**
 919          * This option takes an argument immediately after the option name, with no separator
 920          * character.
 921          */
 922         ADJACENT
 923     }
 924 
 925     /**
 926      * The kind of an Option. This is used by the -help and -X options.
 927      */
 928     public enum OptionKind {
 929         /** A standard option, documented by -help. */
 930         STANDARD,
 931         /** An extended option, documented by -X. */
 932         EXTENDED,
 933         /** A hidden option, not documented. */
 934         HIDDEN,
 935     }
 936 
 937     /**
 938      * The group for an Option. This determines the situations in which the
 939      * option is applicable.
 940      */
 941     enum OptionGroup {
 942         /** A basic option, available for use on the command line or via the
 943          *  Compiler API. */
 944         BASIC,
 945         /** An option for javac's standard JavaFileManager. Other file managers
 946          *  may or may not support these options. */
 947         FILEMANAGER,
 948         /** A command-line option that requests information, such as -help. */
 949         INFO,
 950         /** A command-line "option" representing a file or class name. */
 951         OPERAND
 952     }
 953 
 954     /**
 955      * The kind of choice for "choice" options.
 956      */
 957     enum ChoiceKind {
 958         /** The expected value is exactly one of the set of choices. */
 959         ONEOF,
 960         /** The expected value is one of more of the set of choices. */
 961         ANYOF
 962     }
 963 
 964     enum HiddenGroup {
 965         DIAGS("diags"),
 966         DEBUG("debug"),
 967         SHOULDSTOP("should-stop");
 968 
 969         final String text;
 970 
 971         HiddenGroup(String text) {
 972             this.text = text;
 973         }
 974 
 975         public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
 976             String[] subOptions = arg.split(";");
 977             for (String subOption : subOptions) {
 978                 subOption = text + "." + subOption.trim();
 979                 XD.process(helper, subOption, subOption);
 980             }
 981         }
 982     }
 983 
 984     /**
 985      * The "primary name" for this option.
 986      * This is the name that is used to put values in the {@link Options} table.
 987      */
 988     public final String primaryName;
 989 
 990     /**
 991      * The set of names (primary name and aliases) for this option.
 992      * Note that some names may end in a separator, to indicate that an argument must immediately
 993      * follow the separator (and cannot appear in the following argument position.
 994      */
 995     public final String[] names;
 996 
 997     /** Documentation key for arguments. */
 998     protected final String argsNameKey;
 999 
1000     /** Documentation key for description.
1001      */
1002     protected final String descrKey;
1003 
1004     /** The kind of this option. */
1005     private final OptionKind kind;
1006 
1007     /** The group for this option. */
1008     private final OptionGroup group;
1009 
1010     /** The kind of argument for this option. */
1011     private final ArgKind argKind;
1012 
1013     /** The kind of choices for this option, if any. */
1014     private final ChoiceKind choiceKind;
1015 
1016     /** The choices for this option, if any. */
1017     private final Set<String> choices;
1018 
1019     /**
1020      * Looks up the first option matching the given argument in the full set of options.
1021      * @param arg the argument to be matches
1022      * @return the first option that matches, or null if none.
1023      */
1024     public static Option lookup(String arg) {
1025         return lookup(arg, EnumSet.allOf(Option.class));
1026     }
1027 
1028     /**
1029      * Looks up the first option matching the given argument within a set of options.
1030      * @param arg the argument to be matched
1031      * @param options the set of possible options
1032      * @return the first option that matches, or null if none.
1033      */
1034     public static Option lookup(String arg, Set<Option> options) {
1035         for (Option option: options) {
1036             if (option.matches(arg))
1037                 return option;
1038         }
1039         return null;
1040     }
1041 
1042     /**
1043      * Writes the "command line help" for given kind of option to the log.
1044      * @param log the log
1045      * @param kind  the kind of options to select
1046      */
1047     private static void showHelp(Log log, OptionKind kind) {
1048         Comparator<Option> comp = new Comparator<Option>() {
1049             final Collator collator = Collator.getInstance(Locale.US);
1050             { collator.setStrength(Collator.PRIMARY); }
1051 
1052             @Override
1053             public int compare(Option o1, Option o2) {
1054                 return collator.compare(o1.primaryName, o2.primaryName);
1055             }
1056         };
1057 
1058         getJavaCompilerOptions()
1059                 .stream()
1060                 .filter(o -> o.kind == kind)
1061                 .sorted(comp)
1062                 .forEach(o -> {
1063                     o.help(log);
1064                 });
1065     }
1066 
1067     Option(String text, String descrKey,
1068             OptionKind kind, OptionGroup group) {
1069         this(text, null, descrKey, kind, group, null, null, ArgKind.NONE);
1070     }
1071 
1072     Option(String text, String descrKey,
1073             OptionKind kind, OptionGroup group, ArgKind argKind) {
1074         this(text, null, descrKey, kind, group, null, null, argKind);
1075     }
1076 
1077     Option(String text, String argsNameKey, String descrKey,
1078             OptionKind kind, OptionGroup group) {
1079         this(text, argsNameKey, descrKey, kind, group, null, null, ArgKind.REQUIRED);
1080     }
1081 
1082     Option(String text, String argsNameKey, String descrKey,
1083             OptionKind kind, OptionGroup group, ArgKind ak) {
1084         this(text, argsNameKey, descrKey, kind, group, null, null, ak);
1085     }
1086 
1087     Option(String text, String argsNameKey, String descrKey, OptionKind kind, OptionGroup group,
1088             ChoiceKind choiceKind, Set<String> choices) {
1089         this(text, argsNameKey, descrKey, kind, group, choiceKind, choices, ArgKind.REQUIRED);
1090     }
1091 
1092     Option(String text, String descrKey,
1093             OptionKind kind, OptionGroup group,
1094             ChoiceKind choiceKind, String... choices) {
1095         this(text, null, descrKey, kind, group, choiceKind,
1096                 new LinkedHashSet<>(Arrays.asList(choices)), ArgKind.REQUIRED);
1097     }
1098 
1099     private Option(String text, String argsNameKey, String descrKey,
1100             OptionKind kind, OptionGroup group,
1101             ChoiceKind choiceKind, Set<String> choices,
1102             ArgKind argKind) {
1103         this.names = text.trim().split("\\s+");
1104         Assert.check(names.length >= 1);
1105         this.primaryName = names[0];
1106         this.argsNameKey = argsNameKey;
1107         this.descrKey = descrKey;
1108         this.kind = kind;
1109         this.group = group;
1110         this.choiceKind = choiceKind;
1111         this.choices = choices;
1112         this.argKind = argKind;
1113     }
1114 
1115     public String getPrimaryName() {
1116         return primaryName;
1117     }
1118 
1119     public OptionKind getKind() {
1120         return kind;
1121     }
1122 
1123     /**
1124      * If this option is named {@code FOO}, obtain the option named {@code FOO_CUSTOM}.
1125      *
1126      * @param option regular option
1127      * @return corresponding custom option
1128      * @throws IllegalArgumentException if no such option exists
1129      */
1130     public Option getCustom() {
1131         return Option.valueOf(name() + "_CUSTOM");
1132     }
1133 
1134     /**
1135      * Like {@link #getCustom} but also requires that the custom option supports lint categories.
1136      *
1137      * <p>
1138      * In practice, that means {@code option} must be {@link Option#LINT} or {@link Option#WERROR}.
1139      *
1140      * @param option regular option
1141      * @return corresponding lint custom option
1142      * @throws IllegalArgumentException if no such option exists
1143      */
1144     public Option getLintCustom() {
1145         if (this == XLINT || this == WERROR)
1146             return getCustom();
1147         throw new IllegalArgumentException();
1148     }
1149 
1150     public boolean isInBasicOptionGroup() {
1151         return group == BASIC;
1152     }
1153 
1154     public ArgKind getArgKind() {
1155         return argKind;
1156     }
1157 
1158     public boolean hasArg() {
1159         return (argKind != ArgKind.NONE);
1160     }
1161 
1162     public boolean hasSeparateArg() {
1163         return getArgKind() == ArgKind.REQUIRED &&
1164                !primaryName.endsWith(":") && !primaryName.endsWith("=");
1165     }
1166 
1167     public boolean matches(String option) {
1168         for (String name: names) {
1169             if (matches(option, name))
1170                 return true;
1171         }
1172         return false;
1173     }
1174 
1175     private boolean matches(String option, String name) {
1176         if (name.startsWith("--")) {
1177             return option.equals(name)
1178                     || hasArg() && option.startsWith(name + "=");
1179         }
1180 
1181         boolean hasSuffix = (argKind == ArgKind.ADJACENT)
1182                 || name.endsWith(":") || name.endsWith("=");
1183 
1184         if (!hasSuffix)
1185             return option.equals(name);
1186 
1187         if (!option.startsWith(name))
1188             return false;
1189 
1190         if (choices != null) {
1191             String arg = option.substring(name.length());
1192             if (choiceKind == ChoiceKind.ONEOF)
1193                 return choices.contains(arg);
1194             else {
1195                 for (String a: arg.split(",+")) {
1196                     if (!choices.contains(a))
1197                         return false;
1198                 }
1199             }
1200         }
1201 
1202         return true;
1203     }
1204 
1205     /**
1206      * Handles an option.
1207      * If an argument for the option is required, depending on spec of the option, it will be found
1208      * as part of the current arg (following ':' or '=') or in the following argument.
1209      * This is the recommended way to handle an option directly, instead of calling the underlying
1210      * {@link #process process} methods.
1211      * @param helper a helper to provide access to the environment
1212      * @param arg the arg string that identified this option
1213      * @param rest the remaining strings to be analysed
1214      * @throws InvalidValueException if the value of the option was invalid
1215      * @implNote The return value is the opposite of that used by {@link #process}.
1216      */
1217     public void handleOption(OptionHelper helper, String arg, Iterator<String> rest) throws InvalidValueException {
1218         helper.initialize();
1219         if (hasArg()) {
1220             String option;
1221             String operand;
1222             int sep = findSeparator(arg);
1223             if (getArgKind() == Option.ArgKind.ADJACENT) {
1224                 option = primaryName; // aliases not supported
1225                 operand = arg.substring(primaryName.length());
1226             } else if (sep > 0) {
1227                 option = arg.substring(0, sep);
1228                 operand = arg.substring(sep + 1);
1229             } else {
1230                 if (!rest.hasNext()) {
1231                     throw helper.newInvalidValueException(Errors.ReqArg(this.primaryName));
1232                 }
1233                 option = arg;
1234                 operand = rest.next();
1235             }
1236             process(helper, option, operand);
1237         } else {
1238             if ((this == HELP || this == X || this == HELP_LINT || this == VERSION || this == FULLVERSION)
1239                     && (helper.get(this) != null)) {
1240                 // avoid processing the info options repeatedly
1241                 return;
1242             }
1243             process(helper, arg);
1244         }
1245     }
1246 
1247     /**
1248      * Processes an option that either does not need an argument,
1249      * or which contains an argument within it, following a separator.
1250      * @param helper a helper to provide access to the environment
1251      * @param option the option to be processed
1252      * @throws InvalidValueException if an error occurred
1253      */
1254     public void process(OptionHelper helper, String option) throws InvalidValueException {
1255         if (argKind == ArgKind.NONE) {
1256             process(helper, primaryName, option);
1257         } else {
1258             int sep = findSeparator(option);
1259             process(helper, primaryName, option.substring(sep + 1));
1260         }
1261     }
1262 
1263     /**
1264      * Processes an option by updating the environment via a helper object.
1265      * @param helper a helper to provide access to the environment
1266      * @param option the option to be processed
1267      * @param arg the value to associate with the option, or a default value
1268      *  to be used if the option does not otherwise take an argument.
1269      * @throws InvalidValueException if an error occurred
1270      */
1271     public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
1272         helper.initialize();
1273         if (choices != null) {
1274             if (choiceKind == ChoiceKind.ONEOF) {
1275                 // some clients like to see just one of option+choice set
1276                 for (String s : choices)
1277                     helper.remove(primaryName + s);
1278                 String opt = primaryName + arg;
1279                 helper.put(opt, opt);
1280                 // some clients like to see option (without trailing ":")
1281                 // set to arg
1282                 String nm = primaryName.substring(0, primaryName.length() - 1);
1283                 helper.put(nm, arg);
1284             } else {
1285                 // set option+word for each word in arg
1286                 for (String a: arg.split(",+")) {
1287                     String opt = primaryName + a;
1288                     helper.put(opt, opt);
1289                 }
1290             }
1291         }
1292         helper.put(primaryName, arg);
1293         if (group == OptionGroup.FILEMANAGER)
1294             helper.handleFileManagerOption(this, arg);
1295     }
1296 
1297     /**
1298      * Returns a pattern to analyze the value for an option.
1299      * @return the pattern
1300      * @throws UnsupportedOperationException if an option does not provide a pattern.
1301      */
1302     public Pattern getPattern() {
1303         throw new UnsupportedOperationException();
1304     }
1305 
1306     /**
1307      * Scans a word to find the first separator character, either colon or equals.
1308      * @param word the word to be scanned
1309      * @return the position of the first':' or '=' character in the word,
1310      *  or -1 if none found
1311      */
1312     private static int findSeparator(String word) {
1313         for (int i = 0; i < word.length(); i++) {
1314             switch (word.charAt(i)) {
1315                 case ':': case '=':
1316                     return i;
1317             }
1318         }
1319         return -1;
1320     }
1321 
1322     /** The indent for the option synopsis. */
1323     private static final String SMALL_INDENT = "  ";
1324     /** The automatic indent for the description. */
1325     private static final String LARGE_INDENT = "        ";
1326     /** The space allowed for the synopsis, if the description is to be shown on the same line. */
1327     private static final int DEFAULT_SYNOPSIS_WIDTH = 28;
1328     /** The nominal maximum line length, when seeing if text will fit on a line. */
1329     private static final int DEFAULT_MAX_LINE_LENGTH = 80;
1330     /** The format for a single-line help entry. */
1331     private static final String COMPACT_FORMAT = SMALL_INDENT + "%-" + DEFAULT_SYNOPSIS_WIDTH + "s %s";
1332 
1333     /**
1334      * Writes help text for this option to the log.
1335      * @param log the log
1336      */
1337     protected void help(Log log) {
1338         help(log, log.localize(PrefixKind.JAVAC, descrKey));
1339     }
1340 
1341     protected void help(Log log, String descr) {
1342         String synopses = Arrays.stream(names)
1343                 .map(s -> helpSynopsis(s, log))
1344                 .collect(Collectors.joining(", "));
1345 
1346         // If option synopses and description fit on a single line of reasonable length,
1347         // display using COMPACT_FORMAT
1348         if (synopses.length() < DEFAULT_SYNOPSIS_WIDTH
1349                 && !descr.contains("\n")
1350                 && (SMALL_INDENT.length() + DEFAULT_SYNOPSIS_WIDTH + 1 + descr.length() <= DEFAULT_MAX_LINE_LENGTH)) {
1351             log.printRawLines(WriterKind.STDOUT, String.format(COMPACT_FORMAT, synopses, descr));
1352             return;
1353         }
1354 
1355         // If option synopses fit on a single line of reasonable length, show that;
1356         // otherwise, show 1 per line
1357         if (synopses.length() <= DEFAULT_MAX_LINE_LENGTH) {
1358             log.printRawLines(WriterKind.STDOUT, SMALL_INDENT + synopses);
1359         } else {
1360             for (String name: names) {
1361                 log.printRawLines(WriterKind.STDOUT, SMALL_INDENT + helpSynopsis(name, log));
1362             }
1363         }
1364 
1365         // Finally, show the description
1366         log.printRawLines(WriterKind.STDOUT, LARGE_INDENT + descr.replace("\n", "\n" + LARGE_INDENT));
1367     }
1368 
1369     /**
1370      * Formats a collection of values as an abbreviated, comma separated list
1371      * for use in javac help output.
1372      *
1373      * This helper assumes that the supported values form a dense sequence
1374      * between the fourth and the (n - 3)rd entries.
1375      * That matches the current policy for these values but is not
1376      * guaranteed, and should be reconsidered if the structure of the values changes.
1377      *
1378      * @param values the values to format
1379      * @return a comma separated representation of the values
1380      */
1381     private static String formatAbbreviatedList(Collection<String> values) {
1382         List<String> list = (values instanceof List)
1383                 ? (List<String>) values
1384                 : new ArrayList<>(values);
1385 
1386         int size = list.size();
1387         if (size == 0) {
1388             return "";
1389         }
1390         if (size <= 6) {
1391             return String.join(", ", list);
1392         }
1393         StringJoiner sj = new StringJoiner(", ");
1394         for (int i = 0; i < 3; i++) {
1395             sj.add(list.get(i));
1396         }
1397         sj.add("...");
1398         for (int i = size - 3; i < size; i++) {
1399             sj.add(list.get(i));
1400         }
1401         return sj.toString();
1402     }
1403 
1404     /**
1405      * Composes the initial synopsis of one of the forms for this option.
1406      * @param name the name of this form of the option
1407      * @param log the log used to localize the description of the arguments
1408      * @return  the synopsis
1409      */
1410     private String helpSynopsis(String name, Log log) {
1411         StringBuilder sb = new StringBuilder();
1412         sb.append(name);
1413         if (argsNameKey == null) {
1414             if (choices != null) {
1415                 if (!name.endsWith(":"))
1416                     sb.append(" ");
1417                 String sep = "{";
1418                 for (String choice : choices) {
1419                     sb.append(sep);
1420                     sb.append(choice);
1421                     sep = ",";
1422                 }
1423                 sb.append("}");
1424             }
1425         } else {
1426             if (!name.matches(".*[=:]$") && argKind != ArgKind.ADJACENT)
1427                 sb.append(" ");
1428             sb.append(log.localize(PrefixKind.JAVAC, argsNameKey));
1429         }
1430 
1431         return sb.toString();
1432     }
1433 
1434     // For -XpkgInfo:value
1435     public enum PkgInfo {
1436         /**
1437          * Always generate package-info.class for every package-info.java file.
1438          * The file may be empty if there annotations with a RetentionPolicy
1439          * of CLASS or RUNTIME.  This option may be useful in conjunction with
1440          * build systems (such as Ant) that expect javac to generate at least
1441          * one .class file for every .java file.
1442          */
1443         ALWAYS,
1444         /**
1445          * Generate a package-info.class file if package-info.java contains
1446          * annotations. The file may be empty if all the annotations have
1447          * a RetentionPolicy of SOURCE.
1448          * This value is just for backwards compatibility with earlier behavior.
1449          * Either of the other two values are to be preferred to using this one.
1450          */
1451         LEGACY,
1452         /**
1453          * Generate a package-info.class file if and only if there are annotations
1454          * in package-info.java to be written into it.
1455          */
1456         NONEMPTY;
1457 
1458         public static PkgInfo get(Options options) {
1459             String v = options.get(XPKGINFO);
1460             return (v == null
1461                     ? PkgInfo.LEGACY
1462                     : PkgInfo.valueOf(StringUtils.toUpperCase(v)));
1463         }
1464     }
1465 
1466     private static Set<String> getXLintChoices() {
1467         Set<String> choices = new LinkedHashSet<>();
1468         choices.add(LINT_CUSTOM_ALL);
1469         Lint.LintCategory.options().stream()
1470           .flatMap(ident -> Stream.of(ident, "-" + ident))
1471           .forEach(choices::add);
1472         choices.add(LINT_CUSTOM_NONE);
1473         return choices;
1474     }
1475 
1476     /**
1477      * Returns the set of options supported by the command line tool.
1478      * @return the set of options.
1479      */
1480     static Set<Option> getJavaCompilerOptions() {
1481         return EnumSet.allOf(Option.class);
1482     }
1483 
1484     /**
1485      * Returns the set of options supported by the built-in file manager.
1486      * @return the set of options.
1487      */
1488     public static Set<Option> getJavacFileManagerOptions() {
1489         return getOptions(FILEMANAGER);
1490     }
1491 
1492     /**
1493      * Returns the set of options supported by this implementation of
1494      * the JavaCompiler API, via {@link JavaCompiler#getTask}.
1495      * @return the set of options.
1496      */
1497     public static Set<Option> getJavacToolOptions() {
1498         return getOptions(BASIC);
1499     }
1500 
1501     private static Set<Option> getOptions(OptionGroup group) {
1502         return Arrays.stream(Option.values())
1503                 .filter(o -> o.group == group)
1504                 .collect(Collectors.toCollection(() -> EnumSet.noneOf(Option.class)));
1505     }
1506 
1507 }