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