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