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
816 INHERIT_RUNTIME_ENVIRONMENT("--inherit-runtime-environment", "opt.inherit_runtime_environment",
817 HIDDEN, BASIC) {
818 @Override
819 public void process(OptionHelper helper, String option) throws InvalidValueException {
820 String[] runtimeArgs = VM.getRuntimeArguments();
821 for (String arg : runtimeArgs) {
822 // Handle any supported runtime options; ignore all others.
823 // The runtime arguments always use the single token form, e.g. "--name=value".
824 for (Option o : getSupportedRuntimeOptions()) {
825 if (o.matches(arg)) {
826 switch (o) {
827 case ADD_MODULES:
828 int eq = arg.indexOf('=');
829 Assert.check(eq > 0, () -> ("invalid runtime option:" + arg));
830 // --add-modules=ALL-DEFAULT is not supported at compile-time
831 // so remove it from list, and only process the rest
832 // if the set is non-empty.
833 // Note that --add-modules=ALL-DEFAULT is automatically added
834 // by the standard javac launcher.
835 String mods = Arrays.stream(arg.substring(eq + 1).split(","))
836 .filter(s -> !s.isEmpty() && !s.equals("ALL-DEFAULT"))
837 .collect(Collectors.joining(","));
838 if (!mods.isEmpty()) {
839 String updatedArg = arg.substring(0, eq + 1) + mods;
840 o.handleOption(helper, updatedArg, Collections.emptyIterator());
841 }
842 break;
843 default:
844 o.handleOption(helper, arg, Collections.emptyIterator());
845 break;
846 }
847 break;
848 }
849 }
850 }
851 }
852
853 private Option[] getSupportedRuntimeOptions() {
854 Option[] supportedRuntimeOptions = {
855 ADD_EXPORTS,
856 ADD_MODULES,
857 LIMIT_MODULES,
858 MODULE_PATH,
859 UPGRADE_MODULE_PATH,
860 PATCH_MODULE
861 };
862 return supportedRuntimeOptions;
863 }
864 };
865
866 /**
867 * Special lint category key meaning "all lint categories".
868 */
869 public static final String LINT_CUSTOM_ALL = "all";
870
871 /**
872 * Special lint category key meaning "no lint categories".
873 */
874 public static final String LINT_CUSTOM_NONE = "none";
875
876 /**
877 * This exception is thrown when an invalid value is given for an option.
878 * The detail string gives a detailed, localized message, suitable for use
879 * in error messages reported to the user.
880 */
881 public static class InvalidValueException extends Exception {
882 private static final long serialVersionUID = -1;
883
884 public InvalidValueException(String msg) {
885 super(msg);
886 }
887
888 public InvalidValueException(String msg, Throwable cause) {
889 super(msg, cause);
890 }
891 }
892
893 /**
894 * The kind of argument, if any, accepted by this option. The kind is augmented
895 * by characters in the name of the option.
896 */
897 public enum ArgKind {
898 /** This option does not take any argument. */
899 NONE,
900
901 // Not currently supported
902 // /**
903 // * This option takes an optional argument, which may be provided directly after an '='
904 // * separator, or in the following argument position if that word does not itself appear
905 // * to be the name of an option.
906 // */
907 // OPTIONAL,
908
909 /**
910 * This option takes an argument.
911 * If the name of option ends with ':' or '=', the argument must be provided directly
912 * after that separator.
913 * Otherwise, it may appear after an '=' or in the following argument position.
914 */
915 REQUIRED,
916
917 /**
918 * This option takes an argument immediately after the option name, with no separator
919 * character.
920 */
921 ADJACENT
922 }
923
924 /**
925 * The kind of an Option. This is used by the -help and -X options.
926 */
927 public enum OptionKind {
928 /** A standard option, documented by -help. */
929 STANDARD,
930 /** An extended option, documented by -X. */
931 EXTENDED,
932 /** A hidden option, not documented. */
933 HIDDEN,
934 }
935
936 /**
937 * The group for an Option. This determines the situations in which the
938 * option is applicable.
939 */
940 enum OptionGroup {
941 /** A basic option, available for use on the command line or via the
942 * Compiler API. */
943 BASIC,
944 /** An option for javac's standard JavaFileManager. Other file managers
945 * may or may not support these options. */
946 FILEMANAGER,
947 /** A command-line option that requests information, such as -help. */
948 INFO,
949 /** A command-line "option" representing a file or class name. */
950 OPERAND
951 }
952
953 /**
954 * The kind of choice for "choice" options.
955 */
956 enum ChoiceKind {
957 /** The expected value is exactly one of the set of choices. */
958 ONEOF,
959 /** The expected value is one of more of the set of choices. */
960 ANYOF
961 }
962
963 enum HiddenGroup {
964 DIAGS("diags"),
965 DEBUG("debug"),
966 SHOULDSTOP("should-stop");
967
968 final String text;
969
970 HiddenGroup(String text) {
971 this.text = text;
972 }
973
974 public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
975 String[] subOptions = arg.split(";");
976 for (String subOption : subOptions) {
977 subOption = text + "." + subOption.trim();
978 XD.process(helper, subOption, subOption);
979 }
980 }
981 }
982
983 /**
984 * The "primary name" for this option.
985 * This is the name that is used to put values in the {@link Options} table.
986 */
987 public final String primaryName;
988
989 /**
990 * The set of names (primary name and aliases) for this option.
991 * Note that some names may end in a separator, to indicate that an argument must immediately
992 * follow the separator (and cannot appear in the following argument position.
993 */
994 public final String[] names;
995
996 /** Documentation key for arguments. */
997 protected final String argsNameKey;
998
999 /** Documentation key for description.
1000 */
1001 protected final String descrKey;
1002
1003 /** The kind of this option. */
1004 private final OptionKind kind;
1005
1006 /** The group for this option. */
1007 private final OptionGroup group;
1008
1009 /** The kind of argument for this option. */
1010 private final ArgKind argKind;
1011
1012 /** The kind of choices for this option, if any. */
1013 private final ChoiceKind choiceKind;
1014
1015 /** The choices for this option, if any. */
1016 private final Set<String> choices;
1017
1018 /**
1019 * Looks up the first option matching the given argument in the full set of options.
1020 * @param arg the argument to be matches
1021 * @return the first option that matches, or null if none.
1022 */
1023 public static Option lookup(String arg) {
1024 return lookup(arg, EnumSet.allOf(Option.class));
1025 }
1026
1027 /**
1028 * Looks up the first option matching the given argument within a set of options.
1029 * @param arg the argument to be matched
1030 * @param options the set of possible options
1031 * @return the first option that matches, or null if none.
1032 */
1033 public static Option lookup(String arg, Set<Option> options) {
1034 for (Option option: options) {
1035 if (option.matches(arg))
1036 return option;
1037 }
1038 return null;
1039 }
1040
1041 /**
1042 * Writes the "command line help" for given kind of option to the log.
1043 * @param log the log
1044 * @param kind the kind of options to select
1045 */
1046 private static void showHelp(Log log, OptionKind kind) {
1047 Comparator<Option> comp = new Comparator<Option>() {
1048 final Collator collator = Collator.getInstance(Locale.US);
1049 { collator.setStrength(Collator.PRIMARY); }
1050
1051 @Override
1052 public int compare(Option o1, Option o2) {
1053 return collator.compare(o1.primaryName, o2.primaryName);
1054 }
1055 };
1056
1057 getJavaCompilerOptions()
1058 .stream()
1059 .filter(o -> o.kind == kind)
1060 .sorted(comp)
1061 .forEach(o -> {
1062 o.help(log);
1063 });
1064 }
1065
1066 Option(String text, String descrKey,
1067 OptionKind kind, OptionGroup group) {
1068 this(text, null, descrKey, kind, group, null, null, ArgKind.NONE);
1069 }
1070
1071 Option(String text, String descrKey,
1072 OptionKind kind, OptionGroup group, ArgKind argKind) {
1073 this(text, null, descrKey, kind, group, null, null, argKind);
1074 }
1075
1076 Option(String text, String argsNameKey, String descrKey,
1077 OptionKind kind, OptionGroup group) {
1078 this(text, argsNameKey, descrKey, kind, group, null, null, ArgKind.REQUIRED);
1079 }
1080
1081 Option(String text, String argsNameKey, String descrKey,
1082 OptionKind kind, OptionGroup group, ArgKind ak) {
1083 this(text, argsNameKey, descrKey, kind, group, null, null, ak);
1084 }
1085
1086 Option(String text, String argsNameKey, String descrKey, OptionKind kind, OptionGroup group,
1087 ChoiceKind choiceKind, Set<String> choices) {
1088 this(text, argsNameKey, descrKey, kind, group, choiceKind, choices, ArgKind.REQUIRED);
1089 }
1090
1091 Option(String text, String descrKey,
1092 OptionKind kind, OptionGroup group,
1093 ChoiceKind choiceKind, String... choices) {
1094 this(text, null, descrKey, kind, group, choiceKind,
1095 new LinkedHashSet<>(Arrays.asList(choices)), ArgKind.REQUIRED);
1096 }
1097
1098 private Option(String text, String argsNameKey, String descrKey,
1099 OptionKind kind, OptionGroup group,
1100 ChoiceKind choiceKind, Set<String> choices,
1101 ArgKind argKind) {
1102 this.names = text.trim().split("\\s+");
1103 Assert.check(names.length >= 1);
1104 this.primaryName = names[0];
1105 this.argsNameKey = argsNameKey;
1106 this.descrKey = descrKey;
1107 this.kind = kind;
1108 this.group = group;
1109 this.choiceKind = choiceKind;
1110 this.choices = choices;
1111 this.argKind = argKind;
1112 }
1113
1114 public String getPrimaryName() {
1115 return primaryName;
1116 }
1117
1118 public OptionKind getKind() {
1119 return kind;
1120 }
1121
1122 /**
1123 * If this option is named {@code FOO}, obtain the option named {@code FOO_CUSTOM}.
1124 *
1125 * @param option regular option
1126 * @return corresponding custom option
1127 * @throws IllegalArgumentException if no such option exists
1128 */
1129 public Option getCustom() {
1130 return Option.valueOf(name() + "_CUSTOM");
1131 }
1132
1133 /**
1134 * Like {@link #getCustom} but also requires that the custom option supports lint categories.
1135 *
1136 * <p>
1137 * In practice, that means {@code option} must be {@link Option#LINT} or {@link Option#WERROR}.
1138 *
1139 * @param option regular option
1140 * @return corresponding lint custom option
1141 * @throws IllegalArgumentException if no such option exists
1142 */
1143 public Option getLintCustom() {
1144 if (this == XLINT || this == WERROR)
1145 return getCustom();
1146 throw new IllegalArgumentException();
1147 }
1148
1149 public boolean isInBasicOptionGroup() {
1150 return group == BASIC;
1151 }
1152
1153 public ArgKind getArgKind() {
1154 return argKind;
1155 }
1156
1157 public boolean hasArg() {
1158 return (argKind != ArgKind.NONE);
1159 }
1160
1161 public boolean hasSeparateArg() {
1162 return getArgKind() == ArgKind.REQUIRED &&
1163 !primaryName.endsWith(":") && !primaryName.endsWith("=");
1164 }
1165
1166 public boolean matches(String option) {
1167 for (String name: names) {
1168 if (matches(option, name))
1169 return true;
1170 }
1171 return false;
1172 }
1173
1174 private boolean matches(String option, String name) {
1175 if (name.startsWith("--")) {
1176 return option.equals(name)
1177 || hasArg() && option.startsWith(name + "=");
1178 }
1179
1180 boolean hasSuffix = (argKind == ArgKind.ADJACENT)
1181 || name.endsWith(":") || name.endsWith("=");
1182
1183 if (!hasSuffix)
1184 return option.equals(name);
1185
1186 if (!option.startsWith(name))
1187 return false;
1188
1189 if (choices != null) {
1190 String arg = option.substring(name.length());
1191 if (choiceKind == ChoiceKind.ONEOF)
1192 return choices.contains(arg);
1193 else {
1194 for (String a: arg.split(",+")) {
1195 if (!choices.contains(a))
1196 return false;
1197 }
1198 }
1199 }
1200
1201 return true;
1202 }
1203
1204 /**
1205 * Handles an option.
1206 * If an argument for the option is required, depending on spec of the option, it will be found
1207 * as part of the current arg (following ':' or '=') or in the following argument.
1208 * This is the recommended way to handle an option directly, instead of calling the underlying
1209 * {@link #process process} methods.
1210 * @param helper a helper to provide access to the environment
1211 * @param arg the arg string that identified this option
1212 * @param rest the remaining strings to be analysed
1213 * @throws InvalidValueException if the value of the option was invalid
1214 * @implNote The return value is the opposite of that used by {@link #process}.
1215 */
1216 public void handleOption(OptionHelper helper, String arg, Iterator<String> rest) throws InvalidValueException {
1217 helper.initialize();
1218 if (hasArg()) {
1219 String option;
1220 String operand;
1221 int sep = findSeparator(arg);
1222 if (getArgKind() == Option.ArgKind.ADJACENT) {
1223 option = primaryName; // aliases not supported
1224 operand = arg.substring(primaryName.length());
1225 } else if (sep > 0) {
1226 option = arg.substring(0, sep);
1227 operand = arg.substring(sep + 1);
1228 } else {
1229 if (!rest.hasNext()) {
1230 throw helper.newInvalidValueException(Errors.ReqArg(this.primaryName));
1231 }
1232 option = arg;
1233 operand = rest.next();
1234 }
1235 process(helper, option, operand);
1236 } else {
1237 if ((this == HELP || this == X || this == HELP_LINT || this == VERSION || this == FULLVERSION)
1238 && (helper.get(this) != null)) {
1239 // avoid processing the info options repeatedly
1240 return;
1241 }
1242 process(helper, arg);
1243 }
1244 }
1245
1246 /**
1247 * Processes an option that either does not need an argument,
1248 * or which contains an argument within it, following a separator.
1249 * @param helper a helper to provide access to the environment
1250 * @param option the option to be processed
1251 * @throws InvalidValueException if an error occurred
1252 */
1253 public void process(OptionHelper helper, String option) throws InvalidValueException {
1254 if (argKind == ArgKind.NONE) {
1255 process(helper, primaryName, option);
1256 } else {
1257 int sep = findSeparator(option);
1258 process(helper, primaryName, option.substring(sep + 1));
1259 }
1260 }
1261
1262 /**
1263 * Processes an option by updating the environment via a helper object.
1264 * @param helper a helper to provide access to the environment
1265 * @param option the option to be processed
1266 * @param arg the value to associate with the option, or a default value
1267 * to be used if the option does not otherwise take an argument.
1268 * @throws InvalidValueException if an error occurred
1269 */
1270 public void process(OptionHelper helper, String option, String arg) throws InvalidValueException {
1271 helper.initialize();
1272 if (choices != null) {
1273 if (choiceKind == ChoiceKind.ONEOF) {
1274 // some clients like to see just one of option+choice set
1275 for (String s : choices)
1276 helper.remove(primaryName + s);
1277 String opt = primaryName + arg;
1278 helper.put(opt, opt);
1279 // some clients like to see option (without trailing ":")
1280 // set to arg
1281 String nm = primaryName.substring(0, primaryName.length() - 1);
1282 helper.put(nm, arg);
1283 } else {
1284 // set option+word for each word in arg
1285 for (String a: arg.split(",+")) {
1286 String opt = primaryName + a;
1287 helper.put(opt, opt);
1288 }
1289 }
1290 }
1291 helper.put(primaryName, arg);
1292 if (group == OptionGroup.FILEMANAGER)
1293 helper.handleFileManagerOption(this, arg);
1294 }
1295
1296 /**
1297 * Returns a pattern to analyze the value for an option.
1298 * @return the pattern
1299 * @throws UnsupportedOperationException if an option does not provide a pattern.
1300 */
1301 public Pattern getPattern() {
1302 throw new UnsupportedOperationException();
1303 }
1304
1305 /**
1306 * Scans a word to find the first separator character, either colon or equals.
1307 * @param word the word to be scanned
1308 * @return the position of the first':' or '=' character in the word,
1309 * or -1 if none found
1310 */
1311 private static int findSeparator(String word) {
1312 for (int i = 0; i < word.length(); i++) {
1313 switch (word.charAt(i)) {
1314 case ':': case '=':
1315 return i;
1316 }
1317 }
1318 return -1;
1319 }
1320
1321 /** The indent for the option synopsis. */
1322 private static final String SMALL_INDENT = " ";
1323 /** The automatic indent for the description. */
1324 private static final String LARGE_INDENT = " ";
1325 /** The space allowed for the synopsis, if the description is to be shown on the same line. */
1326 private static final int DEFAULT_SYNOPSIS_WIDTH = 28;
1327 /** The nominal maximum line length, when seeing if text will fit on a line. */
1328 private static final int DEFAULT_MAX_LINE_LENGTH = 80;
1329 /** The format for a single-line help entry. */
1330 private static final String COMPACT_FORMAT = SMALL_INDENT + "%-" + DEFAULT_SYNOPSIS_WIDTH + "s %s";
1331
1332 /**
1333 * Writes help text for this option to the log.
1334 * @param log the log
1335 */
1336 protected void help(Log log) {
1337 help(log, log.localize(PrefixKind.JAVAC, descrKey));
1338 }
1339
1340 protected void help(Log log, String descr) {
1341 String synopses = Arrays.stream(names)
1342 .map(s -> helpSynopsis(s, log))
1343 .collect(Collectors.joining(", "));
1344
1345 // If option synopses and description fit on a single line of reasonable length,
1346 // display using COMPACT_FORMAT
1347 if (synopses.length() < DEFAULT_SYNOPSIS_WIDTH
1348 && !descr.contains("\n")
1349 && (SMALL_INDENT.length() + DEFAULT_SYNOPSIS_WIDTH + 1 + descr.length() <= DEFAULT_MAX_LINE_LENGTH)) {
1350 log.printRawLines(WriterKind.STDOUT, String.format(COMPACT_FORMAT, synopses, descr));
1351 return;
1352 }
1353
1354 // If option synopses fit on a single line of reasonable length, show that;
1355 // otherwise, show 1 per line
1356 if (synopses.length() <= DEFAULT_MAX_LINE_LENGTH) {
1357 log.printRawLines(WriterKind.STDOUT, SMALL_INDENT + synopses);
1358 } else {
1359 for (String name: names) {
1360 log.printRawLines(WriterKind.STDOUT, SMALL_INDENT + helpSynopsis(name, log));
1361 }
1362 }
1363
1364 // Finally, show the description
1365 log.printRawLines(WriterKind.STDOUT, LARGE_INDENT + descr.replace("\n", "\n" + LARGE_INDENT));
1366 }
1367
1368 /**
1369 * Formats a collection of values as an abbreviated, comma separated list
1370 * for use in javac help output.
1371 *
1372 * This helper assumes that the supported values form a dense sequence
1373 * between the fourth and the (n - 3)rd entries.
1374 * That matches the current policy for these values but is not
1375 * guaranteed, and should be reconsidered if the structure of the values changes.
1376 *
1377 * @param values the values to format
1378 * @return a comma separated representation of the values
1379 */
1380 private static String formatAbbreviatedList(Collection<String> values) {
1381 List<String> list = (values instanceof List)
1382 ? (List<String>) values
1383 : new ArrayList<>(values);
1384
1385 int size = list.size();
1386 if (size == 0) {
1387 return "";
1388 }
1389 if (size <= 6) {
1390 return String.join(", ", list);
1391 }
1392 StringJoiner sj = new StringJoiner(", ");
1393 for (int i = 0; i < 3; i++) {
1394 sj.add(list.get(i));
1395 }
1396 sj.add("...");
1397 for (int i = size - 3; i < size; i++) {
1398 sj.add(list.get(i));
1399 }
1400 return sj.toString();
1401 }
1402
1403 /**
1404 * Composes the initial synopsis of one of the forms for this option.
1405 * @param name the name of this form of the option
1406 * @param log the log used to localize the description of the arguments
1407 * @return the synopsis
1408 */
1409 private String helpSynopsis(String name, Log log) {
1410 StringBuilder sb = new StringBuilder();
1411 sb.append(name);
1412 if (argsNameKey == null) {
1413 if (choices != null) {
1414 if (!name.endsWith(":"))
1415 sb.append(" ");
1416 String sep = "{";
1417 for (String choice : choices) {
1418 sb.append(sep);
1419 sb.append(choice);
1420 sep = ",";
1421 }
1422 sb.append("}");
1423 }
1424 } else {
1425 if (!name.matches(".*[=:]$") && argKind != ArgKind.ADJACENT)
1426 sb.append(" ");
1427 sb.append(log.localize(PrefixKind.JAVAC, argsNameKey));
1428 }
1429
1430 return sb.toString();
1431 }
1432
1433 // For -XpkgInfo:value
1434 public enum PkgInfo {
1435 /**
1436 * Always generate package-info.class for every package-info.java file.
1437 * The file may be empty if there annotations with a RetentionPolicy
1438 * of CLASS or RUNTIME. This option may be useful in conjunction with
1439 * build systems (such as Ant) that expect javac to generate at least
1440 * one .class file for every .java file.
1441 */
1442 ALWAYS,
1443 /**
1444 * Generate a package-info.class file if package-info.java contains
1445 * annotations. The file may be empty if all the annotations have
1446 * a RetentionPolicy of SOURCE.
1447 * This value is just for backwards compatibility with earlier behavior.
1448 * Either of the other two values are to be preferred to using this one.
1449 */
1450 LEGACY,
1451 /**
1452 * Generate a package-info.class file if and only if there are annotations
1453 * in package-info.java to be written into it.
1454 */
1455 NONEMPTY;
1456
1457 public static PkgInfo get(Options options) {
1458 String v = options.get(XPKGINFO);
1459 return (v == null
1460 ? PkgInfo.LEGACY
1461 : PkgInfo.valueOf(StringUtils.toUpperCase(v)));
1462 }
1463 }
1464
1465 private static Set<String> getXLintChoices() {
1466 Set<String> choices = new LinkedHashSet<>();
1467 choices.add(LINT_CUSTOM_ALL);
1468 Lint.LintCategory.options().stream()
1469 .flatMap(ident -> Stream.of(ident, "-" + ident))
1470 .forEach(choices::add);
1471 choices.add(LINT_CUSTOM_NONE);
1472 return choices;
1473 }
1474
1475 /**
1476 * Returns the set of options supported by the command line tool.
1477 * @return the set of options.
1478 */
1479 static Set<Option> getJavaCompilerOptions() {
1480 return EnumSet.allOf(Option.class);
1481 }
1482
1483 /**
1484 * Returns the set of options supported by the built-in file manager.
1485 * @return the set of options.
1486 */
1487 public static Set<Option> getJavacFileManagerOptions() {
1488 return getOptions(FILEMANAGER);
1489 }
1490
1491 /**
1492 * Returns the set of options supported by this implementation of
1493 * the JavaCompiler API, via {@link JavaCompiler#getTask}.
1494 * @return the set of options.
1495 */
1496 public static Set<Option> getJavacToolOptions() {
1497 return getOptions(BASIC);
1498 }
1499
1500 private static Set<Option> getOptions(OptionGroup group) {
1501 return Arrays.stream(Option.values())
1502 .filter(o -> o.group == group)
1503 .collect(Collectors.toCollection(() -> EnumSet.noneOf(Option.class)));
1504 }
1505
1506 }