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