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