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