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