Option.java revision 3769:9b74986367e3
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 // Handle any supported runtime options; ignore all others. 686 // The runtime arguments always use the single token form, e.g. "--name=value". 687 for (Option o : getSupportedRuntimeOptions()) { 688 if (o.matches(arg)) { 689 o.handleOption(helper, arg, Collections.emptyIterator()); 690 break; 691 } 692 } 693 } 694 } catch (ClassNotFoundException | SecurityException e) { 695 throw helper.newInvalidValueException("err.cannot.access.runtime.env"); 696 } 697 } 698 699 private Option[] getSupportedRuntimeOptions() { 700 Option[] supportedRuntimeOptions = { 701 ADD_EXPORTS, 702 ADD_MODULES, 703 LIMIT_MODULES, 704 MODULE_PATH, 705 UPGRADE_MODULE_PATH, 706 PATCH_MODULE 707 }; 708 return supportedRuntimeOptions; 709 } 710 }; 711 712 /** 713 * This exception is thrown when an invalid value is given for an option. 714 * The detail string gives a detailed, localized message, suitable for use 715 * in error messages reported to the user. 716 */ 717 public static class InvalidValueException extends Exception { 718 private static final long serialVersionUID = -1; 719 720 public InvalidValueException(String msg) { 721 super(msg); 722 } 723 724 public InvalidValueException(String msg, Throwable cause) { 725 super(msg, cause); 726 } 727 } 728 729 /** 730 * The kind of argument, if any, accepted by this option. The kind is augmented 731 * by characters in the name of the option. 732 */ 733 public enum ArgKind { 734 /** This option does not take any argument. */ 735 NONE, 736 737// Not currently supported 738// /** 739// * This option takes an optional argument, which may be provided directly after an '=' 740// * separator, or in the following argument position if that word does not itself appear 741// * to be the name of an option. 742// */ 743// OPTIONAL, 744 745 /** 746 * This option takes an argument. 747 * If the name of option ends with ':' or '=', the argument must be provided directly 748 * after that separator. 749 * Otherwise, it may appear after an '=' or in the following argument position. 750 */ 751 REQUIRED, 752 753 /** 754 * This option takes an argument immediately after the option name, with no separator 755 * character. 756 */ 757 ADJACENT 758 } 759 760 /** 761 * The kind of an Option. This is used by the -help and -X options. 762 */ 763 public enum OptionKind { 764 /** A standard option, documented by -help. */ 765 STANDARD, 766 /** An extended option, documented by -X. */ 767 EXTENDED, 768 /** A hidden option, not documented. */ 769 HIDDEN, 770 } 771 772 /** 773 * The group for an Option. This determines the situations in which the 774 * option is applicable. 775 */ 776 enum OptionGroup { 777 /** A basic option, available for use on the command line or via the 778 * Compiler API. */ 779 BASIC, 780 /** An option for javac's standard JavaFileManager. Other file managers 781 * may or may not support these options. */ 782 FILEMANAGER, 783 /** A command-line option that requests information, such as -help. */ 784 INFO, 785 /** A command-line "option" representing a file or class name. */ 786 OPERAND 787 } 788 789 /** 790 * The kind of choice for "choice" options. 791 */ 792 enum ChoiceKind { 793 /** The expected value is exactly one of the set of choices. */ 794 ONEOF, 795 /** The expected value is one of more of the set of choices. */ 796 ANYOF 797 } 798 799 enum HiddenGroup { 800 DIAGS("diags"), 801 DEBUG("debug"), 802 SHOULDSTOP("should-stop"); 803 804 static final Set<String> skipSet = new java.util.HashSet<>( 805 Arrays.asList("--diags:", "--debug:", "--should-stop:")); 806 807 final String text; 808 809 HiddenGroup(String text) { 810 this.text = text; 811 } 812 813 public void process(OptionHelper helper, String option) throws InvalidValueException { 814 String p = option.substring(option.indexOf(':') + 1).trim(); 815 String[] subOptions = p.split(";"); 816 for (String subOption : subOptions) { 817 subOption = text + "." + subOption.trim(); 818 XD.process(helper, subOption, subOption); 819 } 820 } 821 822 static boolean skip(String name) { 823 return skipSet.contains(name); 824 } 825 } 826 827 /** 828 * The "primary name" for this option. 829 * This is the name that is used to put values in the {@link Options} table. 830 */ 831 public final String primaryName; 832 833 /** 834 * The set of names (primary name and aliases) for this option. 835 * Note that some names may end in a separator, to indicate that an argument must immediately 836 * follow the separator (and cannot appear in the following argument position. 837 */ 838 public final String[] names; 839 840 /** Documentation key for arguments. */ 841 protected final String argsNameKey; 842 843 /** Documentation key for description. 844 */ 845 protected final String descrKey; 846 847 /** The kind of this option. */ 848 private final OptionKind kind; 849 850 /** The group for this option. */ 851 private final OptionGroup group; 852 853 /** The kind of argument for this option. */ 854 private final ArgKind argKind; 855 856 /** The kind of choices for this option, if any. */ 857 private final ChoiceKind choiceKind; 858 859 /** The choices for this option, if any. */ 860 private final Set<String> choices; 861 862 /** 863 * Looks up the first option matching the given argument in the full set of options. 864 * @param arg the argument to be matches 865 * @return the first option that matches, or null if none. 866 */ 867 public static Option lookup(String arg) { 868 return lookup(arg, EnumSet.allOf(Option.class)); 869 } 870 871 /** 872 * Looks up the first option matching the given argument within a set of options. 873 * @param arg the argument to be matched 874 * @param options the set of possible options 875 * @return the first option that matches, or null if none. 876 */ 877 public static Option lookup(String arg, Set<Option> options) { 878 for (Option option: options) { 879 if (option.matches(arg)) 880 return option; 881 } 882 return null; 883 } 884 885 /** 886 * Writes the "command line help" for given kind of option to the log. 887 * @param log the log 888 * @param kind the kind of options to select 889 */ 890 private static void showHelp(Log log, OptionKind kind) { 891 Comparator<Option> comp = new Comparator<Option>() { 892 final Collator collator = Collator.getInstance(Locale.US); 893 { collator.setStrength(Collator.PRIMARY); } 894 895 @Override 896 public int compare(Option o1, Option o2) { 897 return collator.compare(o1.primaryName, o2.primaryName); 898 } 899 }; 900 901 getJavaCompilerOptions() 902 .stream() 903 .filter(o -> o.kind == kind) 904 .sorted(comp) 905 .forEach(o -> { 906 o.help(log); 907 }); 908 } 909 910 Option(String text, String descrKey, 911 OptionKind kind, OptionGroup group) { 912 this(text, null, descrKey, kind, group, null, null, ArgKind.NONE); 913 } 914 915 Option(String text, String argsNameKey, String descrKey, 916 OptionKind kind, OptionGroup group) { 917 this(text, argsNameKey, descrKey, kind, group, null, null, ArgKind.REQUIRED); 918 } 919 920 Option(String text, String argsNameKey, String descrKey, 921 OptionKind kind, OptionGroup group, ArgKind ak) { 922 this(text, argsNameKey, descrKey, kind, group, null, null, ak); 923 } 924 925 Option(String text, String argsNameKey, String descrKey, OptionKind kind, OptionGroup group, 926 ChoiceKind choiceKind, Set<String> choices) { 927 this(text, argsNameKey, descrKey, kind, group, choiceKind, choices, ArgKind.REQUIRED); 928 } 929 930 Option(String text, String descrKey, 931 OptionKind kind, OptionGroup group, 932 ChoiceKind choiceKind, String... choices) { 933 this(text, null, descrKey, kind, group, choiceKind, 934 new LinkedHashSet<>(Arrays.asList(choices)), ArgKind.REQUIRED); 935 } 936 937 private Option(String text, String argsNameKey, String descrKey, 938 OptionKind kind, OptionGroup group, 939 ChoiceKind choiceKind, Set<String> choices, 940 ArgKind argKind) { 941 this.names = text.trim().split("\\s+"); 942 Assert.check(names.length >= 1); 943 this.primaryName = names[0]; 944 this.argsNameKey = argsNameKey; 945 this.descrKey = descrKey; 946 this.kind = kind; 947 this.group = group; 948 this.choiceKind = choiceKind; 949 this.choices = choices; 950 this.argKind = argKind; 951 } 952 953 public String getPrimaryName() { 954 return primaryName; 955 } 956 957 public OptionKind getKind() { 958 return kind; 959 } 960 961 public ArgKind getArgKind() { 962 return argKind; 963 } 964 965 public boolean hasArg() { 966 return (argKind != ArgKind.NONE); 967 } 968 969 public boolean matches(String option) { 970 for (String name: names) { 971 if (matches(option, name)) 972 return true; 973 } 974 return false; 975 } 976 977 private boolean matches(String option, String name) { 978 if (name.startsWith("--") && !HiddenGroup.skip(name)) { 979 return option.equals(name) 980 || hasArg() && option.startsWith(name + "="); 981 } 982 983 boolean hasSuffix = (argKind == ArgKind.ADJACENT) 984 || name.endsWith(":") || name.endsWith("="); 985 986 if (!hasSuffix) 987 return option.equals(name); 988 989 if (!option.startsWith(name)) 990 return false; 991 992 if (choices != null) { 993 String arg = option.substring(name.length()); 994 if (choiceKind == ChoiceKind.ONEOF) 995 return choices.contains(arg); 996 else { 997 for (String a: arg.split(",+")) { 998 if (!choices.contains(a)) 999 return false; 1000 } 1001 } 1002 } 1003 1004 return true; 1005 } 1006 1007 /** 1008 * Handles an option. 1009 * If an argument for the option is required, depending on spec of the option, it will be found 1010 * as part of the current arg (following ':' or '=') or in the following argument. 1011 * This is the recommended way to handle an option directly, instead of calling the underlying 1012 * {@link #process process} methods. 1013 * @param helper a helper to provide access to the environment 1014 * @param arg the arg string that identified this option 1015 * @param rest the remaining strings to be analysed 1016 * @return true if the operation was successful, and false otherwise 1017 * @implNote The return value is the opposite of that used by {@link #process}. 1018 */ 1019 public void handleOption(OptionHelper helper, String arg, Iterator<String> rest) throws InvalidValueException { 1020 if (hasArg()) { 1021 String option; 1022 String operand; 1023 int sep = findSeparator(arg); 1024 if (getArgKind() == Option.ArgKind.ADJACENT) { 1025 option = primaryName; // aliases not supported 1026 operand = arg.substring(primaryName.length()); 1027 } else if (sep > 0) { 1028 option = arg.substring(0, sep); 1029 operand = arg.substring(sep + 1); 1030 } else { 1031 if (!rest.hasNext()) { 1032 throw helper.newInvalidValueException("err.req.arg", arg); 1033 } 1034 option = arg; 1035 operand = rest.next(); 1036 } 1037 process(helper, option, operand); 1038 } else { 1039 process(helper, arg); 1040 } 1041 } 1042 1043 /** 1044 * Processes an option that either does not need an argument, 1045 * or which contains an argument within it, following a separator. 1046 * @param helper a helper to provide access to the environment 1047 * @param option the option to be processed 1048 * @throws InvalidValueException if an error occurred 1049 */ 1050 public void process(OptionHelper helper, String option) throws InvalidValueException { 1051 if (argKind == ArgKind.NONE) { 1052 process(helper, primaryName, option); 1053 } else { 1054 int sep = findSeparator(option); 1055 process(helper, primaryName, option.substring(sep + 1)); 1056 } 1057 } 1058 1059 /** 1060 * Processes an option by updating the environment via a helper object. 1061 * @param helper a helper to provide access to the environment 1062 * @param option the option to be processed 1063 * @param arg the value to associate with the option, or a default value 1064 * to be used if the option does not otherwise take an argument. 1065 */ 1066 public void process(OptionHelper helper, String option, String arg) throws InvalidValueException { 1067 if (choices != null) { 1068 if (choiceKind == ChoiceKind.ONEOF) { 1069 // some clients like to see just one of option+choice set 1070 for (String s : choices) 1071 helper.remove(primaryName + s); 1072 String opt = primaryName + arg; 1073 helper.put(opt, opt); 1074 // some clients like to see option (without trailing ":") 1075 // set to arg 1076 String nm = primaryName.substring(0, primaryName.length() - 1); 1077 helper.put(nm, arg); 1078 } else { 1079 // set option+word for each word in arg 1080 for (String a: arg.split(",+")) { 1081 String opt = primaryName + a; 1082 helper.put(opt, opt); 1083 } 1084 } 1085 } 1086 helper.put(primaryName, arg); 1087 if (group == OptionGroup.FILEMANAGER) 1088 helper.handleFileManagerOption(this, arg); 1089 } 1090 1091 /** 1092 * Returns a pattern to analyze the value for an option. 1093 * @return the pattern 1094 * @throws UnsupportedOperationException if an option does not provide a pattern. 1095 */ 1096 public Pattern getPattern() { 1097 throw new UnsupportedOperationException(); 1098 } 1099 1100 /** 1101 * Scans a word to find the first separator character, either colon or equals. 1102 * @param word the word to be scanned 1103 * @return the position of the first':' or '=' character in the word, 1104 * or -1 if none found 1105 */ 1106 private static int findSeparator(String word) { 1107 for (int i = 0; i < word.length(); i++) { 1108 switch (word.charAt(i)) { 1109 case ':': case '=': 1110 return i; 1111 } 1112 } 1113 return -1; 1114 } 1115 1116 /** The indent for the option synopsis. */ 1117 private static final String SMALL_INDENT = " "; 1118 /** The automatic indent for the description. */ 1119 private static final String LARGE_INDENT = " "; 1120 /** The space allowed for the synopsis, if the description is to be shown on the same line. */ 1121 private static final int DEFAULT_SYNOPSIS_WIDTH = 28; 1122 /** The nominal maximum line length, when seeing if text will fit on a line. */ 1123 private static final int DEFAULT_MAX_LINE_LENGTH = 80; 1124 /** The format for a single-line help entry. */ 1125 private static final String COMPACT_FORMAT = SMALL_INDENT + "%-" + DEFAULT_SYNOPSIS_WIDTH + "s %s"; 1126 1127 /** 1128 * Writes help text for this option to the log. 1129 * @param log the log 1130 */ 1131 protected void help(Log log) { 1132 help(log, log.localize(PrefixKind.JAVAC, descrKey)); 1133 } 1134 1135 protected void help(Log log, String descr) { 1136 String synopses = Arrays.stream(names) 1137 .map(s -> helpSynopsis(s, log)) 1138 .collect(Collectors.joining(", ")); 1139 1140 // If option synopses and description fit on a single line of reasonable length, 1141 // display using COMPACT_FORMAT 1142 if (synopses.length() < DEFAULT_SYNOPSIS_WIDTH 1143 && !descr.contains("\n") 1144 && (SMALL_INDENT.length() + DEFAULT_SYNOPSIS_WIDTH + 1 + descr.length() <= DEFAULT_MAX_LINE_LENGTH)) { 1145 log.printRawLines(WriterKind.STDOUT, String.format(COMPACT_FORMAT, synopses, descr)); 1146 return; 1147 } 1148 1149 // If option synopses fit on a single line of reasonable length, show that; 1150 // otherwise, show 1 per line 1151 if (synopses.length() <= DEFAULT_MAX_LINE_LENGTH) { 1152 log.printRawLines(WriterKind.STDOUT, SMALL_INDENT + synopses); 1153 } else { 1154 for (String name: names) { 1155 log.printRawLines(WriterKind.STDOUT, SMALL_INDENT + helpSynopsis(name, log)); 1156 } 1157 } 1158 1159 // Finally, show the description 1160 log.printRawLines(WriterKind.STDOUT, LARGE_INDENT + descr.replace("\n", "\n" + LARGE_INDENT)); 1161 } 1162 1163 /** 1164 * Composes the initial synopsis of one of the forms for this option. 1165 * @param name the name of this form of the option 1166 * @param log the log used to localize the description of the arguments 1167 * @return the synopsis 1168 */ 1169 private String helpSynopsis(String name, Log log) { 1170 StringBuilder sb = new StringBuilder(); 1171 sb.append(name); 1172 if (argsNameKey == null) { 1173 if (choices != null) { 1174 String sep = "{"; 1175 for (String choice : choices) { 1176 sb.append(sep); 1177 sb.append(choices); 1178 sep = ","; 1179 } 1180 sb.append("}"); 1181 } 1182 } else { 1183 if (!name.matches(".*[=:]$") && argKind != ArgKind.ADJACENT) 1184 sb.append(" "); 1185 sb.append(log.localize(PrefixKind.JAVAC, argsNameKey)); 1186 } 1187 1188 return sb.toString(); 1189 } 1190 1191 // For -XpkgInfo:value 1192 public enum PkgInfo { 1193 /** 1194 * Always generate package-info.class for every package-info.java file. 1195 * The file may be empty if there annotations with a RetentionPolicy 1196 * of CLASS or RUNTIME. This option may be useful in conjunction with 1197 * build systems (such as Ant) that expect javac to generate at least 1198 * one .class file for every .java file. 1199 */ 1200 ALWAYS, 1201 /** 1202 * Generate a package-info.class file if package-info.java contains 1203 * annotations. The file may be empty if all the annotations have 1204 * a RetentionPolicy of SOURCE. 1205 * This value is just for backwards compatibility with earlier behavior. 1206 * Either of the other two values are to be preferred to using this one. 1207 */ 1208 LEGACY, 1209 /** 1210 * Generate a package-info.class file if and only if there are annotations 1211 * in package-info.java to be written into it. 1212 */ 1213 NONEMPTY; 1214 1215 public static PkgInfo get(Options options) { 1216 String v = options.get(XPKGINFO); 1217 return (v == null 1218 ? PkgInfo.LEGACY 1219 : PkgInfo.valueOf(StringUtils.toUpperCase(v))); 1220 } 1221 } 1222 1223 private static Set<String> getXLintChoices() { 1224 Set<String> choices = new LinkedHashSet<>(); 1225 choices.add("all"); 1226 for (Lint.LintCategory c : Lint.LintCategory.values()) { 1227 choices.add(c.option); 1228 choices.add("-" + c.option); 1229 } 1230 choices.add("none"); 1231 return choices; 1232 } 1233 1234 /** 1235 * Returns the set of options supported by the command line tool. 1236 * @return the set of options. 1237 */ 1238 static Set<Option> getJavaCompilerOptions() { 1239 return EnumSet.allOf(Option.class); 1240 } 1241 1242 /** 1243 * Returns the set of options supported by the built-in file manager. 1244 * @return the set of options. 1245 */ 1246 public static Set<Option> getJavacFileManagerOptions() { 1247 return getOptions(FILEMANAGER); 1248 } 1249 1250 /** 1251 * Returns the set of options supported by this implementation of 1252 * the JavaCompiler API, via {@link JavaCompiler#getTask}. 1253 * @return the set of options. 1254 */ 1255 public static Set<Option> getJavacToolOptions() { 1256 return getOptions(BASIC); 1257 } 1258 1259 private static Set<Option> getOptions(OptionGroup group) { 1260 return Arrays.stream(Option.values()) 1261 .filter(o -> o.group == group) 1262 .collect(Collectors.toCollection(() -> EnumSet.noneOf(Option.class))); 1263 } 1264 1265} 1266