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