Option.java revision 3778:f6ae0686d664
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 26package com.sun.tools.javac.main; 27 28import java.io.FileWriter; 29import java.io.PrintWriter; 30import java.nio.file.Files; 31import java.nio.file.Path; 32import java.nio.file.Paths; 33import java.text.Collator; 34import java.util.Arrays; 35import java.util.Collections; 36import java.util.Comparator; 37import java.util.EnumSet; 38import java.util.HashSet; 39import java.util.Iterator; 40import java.util.LinkedHashMap; 41import java.util.LinkedHashSet; 42import java.util.Locale; 43import java.util.Map; 44import java.util.ServiceLoader; 45import java.util.Set; 46import java.util.TreeSet; 47import java.util.regex.Matcher; 48import java.util.regex.Pattern; 49import java.util.stream.Collectors; 50import java.util.stream.StreamSupport; 51 52import javax.lang.model.SourceVersion; 53 54import com.sun.tools.doclint.DocLint; 55import com.sun.tools.javac.code.Lint; 56import com.sun.tools.javac.code.Lint.LintCategory; 57import com.sun.tools.javac.code.Source; 58import com.sun.tools.javac.code.Type; 59import com.sun.tools.javac.jvm.Profile; 60import com.sun.tools.javac.jvm.Target; 61import com.sun.tools.javac.platform.PlatformProvider; 62import com.sun.tools.javac.processing.JavacProcessingEnvironment; 63import com.sun.tools.javac.resources.CompilerProperties.Errors; 64import com.sun.tools.javac.util.Assert; 65import com.sun.tools.javac.util.JDK9Wrappers; 66import com.sun.tools.javac.util.Log; 67import com.sun.tools.javac.util.Log.PrefixKind; 68import com.sun.tools.javac.util.Log.WriterKind; 69import com.sun.tools.javac.util.Options; 70import com.sun.tools.javac.util.StringUtils; 71 72import static com.sun.tools.javac.main.Option.ChoiceKind.*; 73import static com.sun.tools.javac.main.Option.OptionGroup.*; 74import 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 */ 91public 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_READS("--add-reads", "opt.arg.addReads", "opt.addReads", EXTENDED, BASIC) { 565 @Override 566 public void process(OptionHelper helper, String option, String arg) throws InvalidValueException { 567 if (arg.isEmpty()) { 568 throw helper.newInvalidValueException("err.no.value.for.option", option); 569 } else if (getPattern().matcher(arg).matches()) { 570 String prev = helper.get(ADD_READS); 571 helper.put(ADD_READS.primaryName, (prev == null) ? arg : prev + '\0' + arg); 572 } else { 573 throw helper.newInvalidValueException("err.bad.value.for.option", option, arg); 574 } 575 } 576 577 @Override 578 public Pattern getPattern() { 579 return Pattern.compile("([^=]+)=(,*[^,].*)"); 580 } 581 }, 582 583 XMODULE("-Xmodule:", "opt.arg.module", "opt.module", EXTENDED, BASIC) { 584 @Override 585 public void process(OptionHelper helper, String option, String arg) throws InvalidValueException { 586 String prev = helper.get(XMODULE); 587 if (prev != null) { 588 throw helper.newInvalidValueException("err.option.too.many", XMODULE.primaryName); 589 } 590 helper.put(XMODULE.primaryName, arg); 591 } 592 }, 593 594 MODULE("--module -m", "opt.arg.m", "opt.m", STANDARD, BASIC), 595 596 ADD_MODULES("--add-modules", "opt.arg.addmods", "opt.addmods", STANDARD, BASIC) { 597 @Override 598 public void process(OptionHelper helper, String option, String arg) throws InvalidValueException { 599 if (arg.isEmpty()) { 600 throw helper.newInvalidValueException("err.no.value.for.option", option); 601 } else if (getPattern().matcher(arg).matches()) { 602 String prev = helper.get(ADD_MODULES); 603 // since the individual values are simple names, we can simply join the 604 // values of multiple --add-modules options with ',' 605 helper.put(ADD_MODULES.primaryName, (prev == null) ? arg : prev + ',' + arg); 606 } else { 607 throw helper.newInvalidValueException("err.bad.value.for.option", option, arg); 608 } 609 } 610 611 @Override 612 public Pattern getPattern() { 613 return Pattern.compile(",*[^,].*"); 614 } 615 }, 616 617 LIMIT_MODULES("--limit-modules", "opt.arg.limitmods", "opt.limitmods", STANDARD, BASIC) { 618 @Override 619 public void process(OptionHelper helper, String option, String arg) throws InvalidValueException { 620 if (arg.isEmpty()) { 621 throw helper.newInvalidValueException("err.no.value.for.option", option); 622 } else if (getPattern().matcher(arg).matches()) { 623 helper.put(LIMIT_MODULES.primaryName, arg); // last one wins 624 } else { 625 throw helper.newInvalidValueException("err.bad.value.for.option", option, arg); 626 } 627 } 628 629 @Override 630 public Pattern getPattern() { 631 return Pattern.compile(",*[^,].*"); 632 } 633 }, 634 635 // This option exists only for the purpose of documenting itself. 636 // It's actually implemented by the CommandLine class. 637 AT("@", "opt.arg.file", "opt.AT", STANDARD, INFO, ArgKind.ADJACENT) { 638 @Override 639 public void process(OptionHelper helper, String option) { 640 throw new AssertionError("the @ flag should be caught by CommandLine."); 641 } 642 }, 643 644 // Standalone positional argument: source file or type name. 645 SOURCEFILE("sourcefile", null, HIDDEN, INFO) { 646 @Override 647 public boolean matches(String s) { 648 if (s.endsWith(".java")) // Java source file 649 return true; 650 int sep = s.indexOf('/'); 651 if (sep != -1) { 652 return SourceVersion.isName(s.substring(0, sep)) 653 && SourceVersion.isName(s.substring(sep + 1)); 654 } else { 655 return SourceVersion.isName(s); // Legal type name 656 } 657 } 658 @Override 659 public void process(OptionHelper helper, String option) throws InvalidValueException { 660 if (option.endsWith(".java") ) { 661 Path p = Paths.get(option); 662 if (!Files.exists(p)) { 663 throw helper.newInvalidValueException("err.file.not.found", p); 664 } 665 if (!Files.isRegularFile(p)) { 666 throw helper.newInvalidValueException("err.file.not.file", p); 667 } 668 helper.addFile(p); 669 } else { 670 helper.addClassName(option); 671 } 672 } 673 }, 674 675 MULTIRELEASE("--multi-release", "opt.arg.multi-release", "opt.multi-release", HIDDEN, FILEMANAGER), 676 677 INHERIT_RUNTIME_ENVIRONMENT("--inherit-runtime-environment", "opt.inherit_runtime_environment", 678 EXTENDED, BASIC) { 679 @Override 680 public void process(OptionHelper helper, String option) throws InvalidValueException { 681 try { 682 Class.forName(JDK9Wrappers.VMHelper.VM_CLASSNAME); 683 String[] runtimeArgs = JDK9Wrappers.VMHelper.getRuntimeArguments(); 684 for (String arg : runtimeArgs) { 685 System.err.println("runtime arg: " + arg); 686 // Handle any supported runtime options; ignore all others. 687 // The runtime arguments always use the single token form, e.g. "--name=value". 688 for (Option o : getSupportedRuntimeOptions()) { 689 if (o.matches(arg)) { 690 switch (o) { 691 case ADD_MODULES: 692 int eq = arg.indexOf('='); 693 Assert.check(eq > 0, () -> ("invalid runtime option:" + arg)); 694 // --add-modules=ALL-DEFAULT is not supported at compile-time 695 // so remove it from list, and only process the rest 696 // if the set is non-empty. 697 // Note that --add-modules=ALL-DEFAULT is automatically added 698 // by the standard javac launcher. 699 String mods = Arrays.stream(arg.substring(eq + 1).split(",")) 700 .filter(s -> !s.isEmpty() && !s.equals("ALL-DEFAULT")) 701 .collect(Collectors.joining(",")); 702 if (!mods.isEmpty()) { 703 String updatedArg = arg.substring(0, eq + 1) + mods; 704 o.handleOption(helper, updatedArg, Collections.emptyIterator()); 705 } 706 break; 707 default: 708 o.handleOption(helper, arg, Collections.emptyIterator()); 709 break; 710 } 711 break; 712 } 713 } 714 } 715 } catch (ClassNotFoundException | SecurityException e) { 716 throw helper.newInvalidValueException("err.cannot.access.runtime.env"); 717 } 718 } 719 720 private Option[] getSupportedRuntimeOptions() { 721 Option[] supportedRuntimeOptions = { 722 ADD_EXPORTS, 723 ADD_MODULES, 724 LIMIT_MODULES, 725 MODULE_PATH, 726 UPGRADE_MODULE_PATH, 727 PATCH_MODULE 728 }; 729 return supportedRuntimeOptions; 730 } 731 }; 732 733 /** 734 * This exception is thrown when an invalid value is given for an option. 735 * The detail string gives a detailed, localized message, suitable for use 736 * in error messages reported to the user. 737 */ 738 public static class InvalidValueException extends Exception { 739 private static final long serialVersionUID = -1; 740 741 public InvalidValueException(String msg) { 742 super(msg); 743 } 744 745 public InvalidValueException(String msg, Throwable cause) { 746 super(msg, cause); 747 } 748 } 749 750 /** 751 * The kind of argument, if any, accepted by this option. The kind is augmented 752 * by characters in the name of the option. 753 */ 754 public enum ArgKind { 755 /** This option does not take any argument. */ 756 NONE, 757 758// Not currently supported 759// /** 760// * This option takes an optional argument, which may be provided directly after an '=' 761// * separator, or in the following argument position if that word does not itself appear 762// * to be the name of an option. 763// */ 764// OPTIONAL, 765 766 /** 767 * This option takes an argument. 768 * If the name of option ends with ':' or '=', the argument must be provided directly 769 * after that separator. 770 * Otherwise, it may appear after an '=' or in the following argument position. 771 */ 772 REQUIRED, 773 774 /** 775 * This option takes an argument immediately after the option name, with no separator 776 * character. 777 */ 778 ADJACENT 779 } 780 781 /** 782 * The kind of an Option. This is used by the -help and -X options. 783 */ 784 public enum OptionKind { 785 /** A standard option, documented by -help. */ 786 STANDARD, 787 /** An extended option, documented by -X. */ 788 EXTENDED, 789 /** A hidden option, not documented. */ 790 HIDDEN, 791 } 792 793 /** 794 * The group for an Option. This determines the situations in which the 795 * option is applicable. 796 */ 797 enum OptionGroup { 798 /** A basic option, available for use on the command line or via the 799 * Compiler API. */ 800 BASIC, 801 /** An option for javac's standard JavaFileManager. Other file managers 802 * may or may not support these options. */ 803 FILEMANAGER, 804 /** A command-line option that requests information, such as -help. */ 805 INFO, 806 /** A command-line "option" representing a file or class name. */ 807 OPERAND 808 } 809 810 /** 811 * The kind of choice for "choice" options. 812 */ 813 enum ChoiceKind { 814 /** The expected value is exactly one of the set of choices. */ 815 ONEOF, 816 /** The expected value is one of more of the set of choices. */ 817 ANYOF 818 } 819 820 enum HiddenGroup { 821 DIAGS("diags"), 822 DEBUG("debug"), 823 SHOULDSTOP("should-stop"); 824 825 static final Set<String> skipSet = new java.util.HashSet<>( 826 Arrays.asList("--diags:", "--debug:", "--should-stop:")); 827 828 final String text; 829 830 HiddenGroup(String text) { 831 this.text = text; 832 } 833 834 public void process(OptionHelper helper, String option) throws InvalidValueException { 835 String p = option.substring(option.indexOf(':') + 1).trim(); 836 String[] subOptions = p.split(";"); 837 for (String subOption : subOptions) { 838 subOption = text + "." + subOption.trim(); 839 XD.process(helper, subOption, subOption); 840 } 841 } 842 843 static boolean skip(String name) { 844 return skipSet.contains(name); 845 } 846 } 847 848 /** 849 * The "primary name" for this option. 850 * This is the name that is used to put values in the {@link Options} table. 851 */ 852 public final String primaryName; 853 854 /** 855 * The set of names (primary name and aliases) for this option. 856 * Note that some names may end in a separator, to indicate that an argument must immediately 857 * follow the separator (and cannot appear in the following argument position. 858 */ 859 public final String[] names; 860 861 /** Documentation key for arguments. */ 862 protected final String argsNameKey; 863 864 /** Documentation key for description. 865 */ 866 protected final String descrKey; 867 868 /** The kind of this option. */ 869 private final OptionKind kind; 870 871 /** The group for this option. */ 872 private final OptionGroup group; 873 874 /** The kind of argument for this option. */ 875 private final ArgKind argKind; 876 877 /** The kind of choices for this option, if any. */ 878 private final ChoiceKind choiceKind; 879 880 /** The choices for this option, if any. */ 881 private final Set<String> choices; 882 883 /** 884 * Looks up the first option matching the given argument in the full set of options. 885 * @param arg the argument to be matches 886 * @return the first option that matches, or null if none. 887 */ 888 public static Option lookup(String arg) { 889 return lookup(arg, EnumSet.allOf(Option.class)); 890 } 891 892 /** 893 * Looks up the first option matching the given argument within a set of options. 894 * @param arg the argument to be matched 895 * @param options the set of possible options 896 * @return the first option that matches, or null if none. 897 */ 898 public static Option lookup(String arg, Set<Option> options) { 899 for (Option option: options) { 900 if (option.matches(arg)) 901 return option; 902 } 903 return null; 904 } 905 906 /** 907 * Writes the "command line help" for given kind of option to the log. 908 * @param log the log 909 * @param kind the kind of options to select 910 */ 911 private static void showHelp(Log log, OptionKind kind) { 912 Comparator<Option> comp = new Comparator<Option>() { 913 final Collator collator = Collator.getInstance(Locale.US); 914 { collator.setStrength(Collator.PRIMARY); } 915 916 @Override 917 public int compare(Option o1, Option o2) { 918 return collator.compare(o1.primaryName, o2.primaryName); 919 } 920 }; 921 922 getJavaCompilerOptions() 923 .stream() 924 .filter(o -> o.kind == kind) 925 .sorted(comp) 926 .forEach(o -> { 927 o.help(log); 928 }); 929 } 930 931 Option(String text, String descrKey, 932 OptionKind kind, OptionGroup group) { 933 this(text, null, descrKey, kind, group, null, null, ArgKind.NONE); 934 } 935 936 Option(String text, String argsNameKey, String descrKey, 937 OptionKind kind, OptionGroup group) { 938 this(text, argsNameKey, descrKey, kind, group, null, null, ArgKind.REQUIRED); 939 } 940 941 Option(String text, String argsNameKey, String descrKey, 942 OptionKind kind, OptionGroup group, ArgKind ak) { 943 this(text, argsNameKey, descrKey, kind, group, null, null, ak); 944 } 945 946 Option(String text, String argsNameKey, String descrKey, OptionKind kind, OptionGroup group, 947 ChoiceKind choiceKind, Set<String> choices) { 948 this(text, argsNameKey, descrKey, kind, group, choiceKind, choices, ArgKind.REQUIRED); 949 } 950 951 Option(String text, String descrKey, 952 OptionKind kind, OptionGroup group, 953 ChoiceKind choiceKind, String... choices) { 954 this(text, null, descrKey, kind, group, choiceKind, 955 new LinkedHashSet<>(Arrays.asList(choices)), ArgKind.REQUIRED); 956 } 957 958 private Option(String text, String argsNameKey, String descrKey, 959 OptionKind kind, OptionGroup group, 960 ChoiceKind choiceKind, Set<String> choices, 961 ArgKind argKind) { 962 this.names = text.trim().split("\\s+"); 963 Assert.check(names.length >= 1); 964 this.primaryName = names[0]; 965 this.argsNameKey = argsNameKey; 966 this.descrKey = descrKey; 967 this.kind = kind; 968 this.group = group; 969 this.choiceKind = choiceKind; 970 this.choices = choices; 971 this.argKind = argKind; 972 } 973 974 public String getPrimaryName() { 975 return primaryName; 976 } 977 978 public OptionKind getKind() { 979 return kind; 980 } 981 982 public ArgKind getArgKind() { 983 return argKind; 984 } 985 986 public boolean hasArg() { 987 return (argKind != ArgKind.NONE); 988 } 989 990 public boolean matches(String option) { 991 for (String name: names) { 992 if (matches(option, name)) 993 return true; 994 } 995 return false; 996 } 997 998 private boolean matches(String option, String name) { 999 if (name.startsWith("--") && !HiddenGroup.skip(name)) { 1000 return option.equals(name) 1001 || hasArg() && option.startsWith(name + "="); 1002 } 1003 1004 boolean hasSuffix = (argKind == ArgKind.ADJACENT) 1005 || name.endsWith(":") || name.endsWith("="); 1006 1007 if (!hasSuffix) 1008 return option.equals(name); 1009 1010 if (!option.startsWith(name)) 1011 return false; 1012 1013 if (choices != null) { 1014 String arg = option.substring(name.length()); 1015 if (choiceKind == ChoiceKind.ONEOF) 1016 return choices.contains(arg); 1017 else { 1018 for (String a: arg.split(",+")) { 1019 if (!choices.contains(a)) 1020 return false; 1021 } 1022 } 1023 } 1024 1025 return true; 1026 } 1027 1028 /** 1029 * Handles an option. 1030 * If an argument for the option is required, depending on spec of the option, it will be found 1031 * as part of the current arg (following ':' or '=') or in the following argument. 1032 * This is the recommended way to handle an option directly, instead of calling the underlying 1033 * {@link #process process} methods. 1034 * @param helper a helper to provide access to the environment 1035 * @param arg the arg string that identified this option 1036 * @param rest the remaining strings to be analysed 1037 * @return true if the operation was successful, and false otherwise 1038 * @implNote The return value is the opposite of that used by {@link #process}. 1039 */ 1040 public void handleOption(OptionHelper helper, String arg, Iterator<String> rest) throws InvalidValueException { 1041 if (hasArg()) { 1042 String option; 1043 String operand; 1044 int sep = findSeparator(arg); 1045 if (getArgKind() == Option.ArgKind.ADJACENT) { 1046 option = primaryName; // aliases not supported 1047 operand = arg.substring(primaryName.length()); 1048 } else if (sep > 0) { 1049 option = arg.substring(0, sep); 1050 operand = arg.substring(sep + 1); 1051 } else { 1052 if (!rest.hasNext()) { 1053 throw helper.newInvalidValueException("err.req.arg", arg); 1054 } 1055 option = arg; 1056 operand = rest.next(); 1057 } 1058 process(helper, option, operand); 1059 } else { 1060 process(helper, arg); 1061 } 1062 } 1063 1064 /** 1065 * Processes an option that either does not need an argument, 1066 * or which contains an argument within it, following a separator. 1067 * @param helper a helper to provide access to the environment 1068 * @param option the option to be processed 1069 * @throws InvalidValueException if an error occurred 1070 */ 1071 public void process(OptionHelper helper, String option) throws InvalidValueException { 1072 if (argKind == ArgKind.NONE) { 1073 process(helper, primaryName, option); 1074 } else { 1075 int sep = findSeparator(option); 1076 process(helper, primaryName, option.substring(sep + 1)); 1077 } 1078 } 1079 1080 /** 1081 * Processes an option by updating the environment via a helper object. 1082 * @param helper a helper to provide access to the environment 1083 * @param option the option to be processed 1084 * @param arg the value to associate with the option, or a default value 1085 * to be used if the option does not otherwise take an argument. 1086 */ 1087 public void process(OptionHelper helper, String option, String arg) throws InvalidValueException { 1088 if (choices != null) { 1089 if (choiceKind == ChoiceKind.ONEOF) { 1090 // some clients like to see just one of option+choice set 1091 for (String s : choices) 1092 helper.remove(primaryName + s); 1093 String opt = primaryName + arg; 1094 helper.put(opt, opt); 1095 // some clients like to see option (without trailing ":") 1096 // set to arg 1097 String nm = primaryName.substring(0, primaryName.length() - 1); 1098 helper.put(nm, arg); 1099 } else { 1100 // set option+word for each word in arg 1101 for (String a: arg.split(",+")) { 1102 String opt = primaryName + a; 1103 helper.put(opt, opt); 1104 } 1105 } 1106 } 1107 helper.put(primaryName, arg); 1108 if (group == OptionGroup.FILEMANAGER) 1109 helper.handleFileManagerOption(this, arg); 1110 } 1111 1112 /** 1113 * Returns a pattern to analyze the value for an option. 1114 * @return the pattern 1115 * @throws UnsupportedOperationException if an option does not provide a pattern. 1116 */ 1117 public Pattern getPattern() { 1118 throw new UnsupportedOperationException(); 1119 } 1120 1121 /** 1122 * Scans a word to find the first separator character, either colon or equals. 1123 * @param word the word to be scanned 1124 * @return the position of the first':' or '=' character in the word, 1125 * or -1 if none found 1126 */ 1127 private static int findSeparator(String word) { 1128 for (int i = 0; i < word.length(); i++) { 1129 switch (word.charAt(i)) { 1130 case ':': case '=': 1131 return i; 1132 } 1133 } 1134 return -1; 1135 } 1136 1137 /** The indent for the option synopsis. */ 1138 private static final String SMALL_INDENT = " "; 1139 /** The automatic indent for the description. */ 1140 private static final String LARGE_INDENT = " "; 1141 /** The space allowed for the synopsis, if the description is to be shown on the same line. */ 1142 private static final int DEFAULT_SYNOPSIS_WIDTH = 28; 1143 /** The nominal maximum line length, when seeing if text will fit on a line. */ 1144 private static final int DEFAULT_MAX_LINE_LENGTH = 80; 1145 /** The format for a single-line help entry. */ 1146 private static final String COMPACT_FORMAT = SMALL_INDENT + "%-" + DEFAULT_SYNOPSIS_WIDTH + "s %s"; 1147 1148 /** 1149 * Writes help text for this option to the log. 1150 * @param log the log 1151 */ 1152 protected void help(Log log) { 1153 help(log, log.localize(PrefixKind.JAVAC, descrKey)); 1154 } 1155 1156 protected void help(Log log, String descr) { 1157 String synopses = Arrays.stream(names) 1158 .map(s -> helpSynopsis(s, log)) 1159 .collect(Collectors.joining(", ")); 1160 1161 // If option synopses and description fit on a single line of reasonable length, 1162 // display using COMPACT_FORMAT 1163 if (synopses.length() < DEFAULT_SYNOPSIS_WIDTH 1164 && !descr.contains("\n") 1165 && (SMALL_INDENT.length() + DEFAULT_SYNOPSIS_WIDTH + 1 + descr.length() <= DEFAULT_MAX_LINE_LENGTH)) { 1166 log.printRawLines(WriterKind.STDOUT, String.format(COMPACT_FORMAT, synopses, descr)); 1167 return; 1168 } 1169 1170 // If option synopses fit on a single line of reasonable length, show that; 1171 // otherwise, show 1 per line 1172 if (synopses.length() <= DEFAULT_MAX_LINE_LENGTH) { 1173 log.printRawLines(WriterKind.STDOUT, SMALL_INDENT + synopses); 1174 } else { 1175 for (String name: names) { 1176 log.printRawLines(WriterKind.STDOUT, SMALL_INDENT + helpSynopsis(name, log)); 1177 } 1178 } 1179 1180 // Finally, show the description 1181 log.printRawLines(WriterKind.STDOUT, LARGE_INDENT + descr.replace("\n", "\n" + LARGE_INDENT)); 1182 } 1183 1184 /** 1185 * Composes the initial synopsis of one of the forms for this option. 1186 * @param name the name of this form of the option 1187 * @param log the log used to localize the description of the arguments 1188 * @return the synopsis 1189 */ 1190 private String helpSynopsis(String name, Log log) { 1191 StringBuilder sb = new StringBuilder(); 1192 sb.append(name); 1193 if (argsNameKey == null) { 1194 if (choices != null) { 1195 String sep = "{"; 1196 for (String choice : choices) { 1197 sb.append(sep); 1198 sb.append(choices); 1199 sep = ","; 1200 } 1201 sb.append("}"); 1202 } 1203 } else { 1204 if (!name.matches(".*[=:]$") && argKind != ArgKind.ADJACENT) 1205 sb.append(" "); 1206 sb.append(log.localize(PrefixKind.JAVAC, argsNameKey)); 1207 } 1208 1209 return sb.toString(); 1210 } 1211 1212 // For -XpkgInfo:value 1213 public enum PkgInfo { 1214 /** 1215 * Always generate package-info.class for every package-info.java file. 1216 * The file may be empty if there annotations with a RetentionPolicy 1217 * of CLASS or RUNTIME. This option may be useful in conjunction with 1218 * build systems (such as Ant) that expect javac to generate at least 1219 * one .class file for every .java file. 1220 */ 1221 ALWAYS, 1222 /** 1223 * Generate a package-info.class file if package-info.java contains 1224 * annotations. The file may be empty if all the annotations have 1225 * a RetentionPolicy of SOURCE. 1226 * This value is just for backwards compatibility with earlier behavior. 1227 * Either of the other two values are to be preferred to using this one. 1228 */ 1229 LEGACY, 1230 /** 1231 * Generate a package-info.class file if and only if there are annotations 1232 * in package-info.java to be written into it. 1233 */ 1234 NONEMPTY; 1235 1236 public static PkgInfo get(Options options) { 1237 String v = options.get(XPKGINFO); 1238 return (v == null 1239 ? PkgInfo.LEGACY 1240 : PkgInfo.valueOf(StringUtils.toUpperCase(v))); 1241 } 1242 } 1243 1244 private static Set<String> getXLintChoices() { 1245 Set<String> choices = new LinkedHashSet<>(); 1246 choices.add("all"); 1247 for (Lint.LintCategory c : Lint.LintCategory.values()) { 1248 choices.add(c.option); 1249 choices.add("-" + c.option); 1250 } 1251 choices.add("none"); 1252 return choices; 1253 } 1254 1255 /** 1256 * Returns the set of options supported by the command line tool. 1257 * @return the set of options. 1258 */ 1259 static Set<Option> getJavaCompilerOptions() { 1260 return EnumSet.allOf(Option.class); 1261 } 1262 1263 /** 1264 * Returns the set of options supported by the built-in file manager. 1265 * @return the set of options. 1266 */ 1267 public static Set<Option> getJavacFileManagerOptions() { 1268 return getOptions(FILEMANAGER); 1269 } 1270 1271 /** 1272 * Returns the set of options supported by this implementation of 1273 * the JavaCompiler API, via {@link JavaCompiler#getTask}. 1274 * @return the set of options. 1275 */ 1276 public static Set<Option> getJavacToolOptions() { 1277 return getOptions(BASIC); 1278 } 1279 1280 private static Set<Option> getOptions(OptionGroup group) { 1281 return Arrays.stream(Option.values()) 1282 .filter(o -> o.group == group) 1283 .collect(Collectors.toCollection(() -> EnumSet.noneOf(Option.class))); 1284 } 1285 1286} 1287