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