picocli/CommandLine.java (view raw)
1/*
2 Copyright 2017 Remko Popma
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16package picocli;
17
18import java.io.*;
19import java.lang.annotation.Annotation;
20import java.lang.annotation.ElementType;
21import java.lang.annotation.Retention;
22import java.lang.annotation.RetentionPolicy;
23import java.lang.annotation.Target;
24import java.lang.reflect.*;
25import java.math.BigDecimal;
26import java.math.BigInteger;
27import java.net.InetAddress;
28import java.net.MalformedURLException;
29import java.net.NetworkInterface;
30import java.net.URI;
31import java.net.URISyntaxException;
32import java.net.URL;
33import java.nio.ByteOrder;
34import java.nio.charset.Charset;
35import java.text.BreakIterator;
36import java.text.ParseException;
37import java.text.SimpleDateFormat;
38import java.util.*;
39import java.util.concurrent.Callable;
40import java.util.regex.Pattern;
41import picocli.CommandLine.Help.Ansi.IStyle;
42import picocli.CommandLine.Help.Ansi.Style;
43import picocli.CommandLine.Help.Ansi.Text;
44import picocli.CommandLine.Model.*;
45import picocli.CommandLine.ParseResult.MatchedGroup;
46
47import static java.util.Locale.ENGLISH;
48import static picocli.CommandLine.Help.Column.Overflow.SPAN;
49import static picocli.CommandLine.Help.Column.Overflow.TRUNCATE;
50import static picocli.CommandLine.Help.Column.Overflow.WRAP;
51
52/**
53 * <p>
54 * CommandLine interpreter that uses reflection to initialize an annotated domain object with values obtained from the
55 * command line arguments.
56 * </p><h2>Example</h2>
57 * <pre>import static picocli.CommandLine.*;
58 *
59 * @Command(mixinStandardHelpOptions = true, version = "v3.0.0",
60 * header = "Encrypt FILE(s), or standard input, to standard output or to the output file.")
61 * public class Encrypt {
62 *
63 * @Parameters(type = File.class, description = "Any number of input files")
64 * private List<File> files = new ArrayList<File>();
65 *
66 * @Option(names = { "-o", "--out" }, description = "Output file (default: print to console)")
67 * private File outputFile;
68 *
69 * @Option(names = { "-v", "--verbose"}, description = "Verbose mode. Helpful for troubleshooting. Multiple -v options increase the verbosity.")
70 * private boolean[] verbose;
71 * }
72 * </pre>
73 * <p>
74 * Use {@code CommandLine} to initialize a domain object as follows:
75 * </p><pre>
76 * public static void main(String... args) {
77 * Encrypt encrypt = new Encrypt();
78 * try {
79 * ParseResult parseResult = new CommandLine(encrypt).parseArgs(args);
80 * if (!CommandLine.printHelpIfRequested(parseResult)) {
81 * runProgram(encrypt);
82 * }
83 * } catch (ParameterException ex) { // command line arguments could not be parsed
84 * System.err.println(ex.getMessage());
85 * ex.getCommandLine().usage(System.err);
86 * }
87 * }
88 * </pre><p>
89 * Invoke the above program with some command line arguments. The below are all equivalent:
90 * </p>
91 * <pre>
92 * --verbose --out=outfile in1 in2
93 * --verbose --out outfile in1 in2
94 * -v --out=outfile in1 in2
95 * -v -o outfile in1 in2
96 * -v -o=outfile in1 in2
97 * -vo outfile in1 in2
98 * -vo=outfile in1 in2
99 * -v -ooutfile in1 in2
100 * -vooutfile in1 in2
101 * </pre>
102 * <p>
103 * Another example that implements {@code Callable} and uses the {@link #call(Callable, String...) CommandLine.call} convenience API to run in a single line of code:
104 * </p>
105 * <pre>
106 * @Command(description = "Prints the checksum (MD5 by default) of a file to STDOUT.",
107 * name = "checksum", mixinStandardHelpOptions = true, version = "checksum 3.0")
108 * class CheckSum implements Callable<Void> {
109 *
110 * @Parameters(index = "0", description = "The file whose checksum to calculate.")
111 * private File file;
112 *
113 * @Option(names = {"-a", "--algorithm"}, description = "MD5, SHA-1, SHA-256, ...")
114 * private String algorithm = "MD5";
115 *
116 * public static void main(String[] args) throws Exception {
117 * // CheckSum implements Callable, so parsing, error handling and handling user
118 * // requests for usage help or version help can be done with one line of code.
119 * CommandLine.call(new CheckSum(), args);
120 * }
121 *
122 * @Override
123 * public Void call() throws Exception {
124 * // your business logic goes here...
125 * byte[] fileContents = Files.readAllBytes(file.toPath());
126 * byte[] digest = MessageDigest.getInstance(algorithm).digest(fileContents);
127 * System.out.println(javax.xml.bind.DatatypeConverter.printHexBinary(digest));
128 * return null;
129 * }
130 * }
131 * </pre>
132 * <h2>Classes and Interfaces for Defining a CommandSpec Model</h2>
133 * <p>
134 * <img src="doc-files/class-diagram-definition.png" alt="Classes and Interfaces for Defining a CommandSpec Model">
135 * </p>
136 * <h2>Classes Related to Parsing Command Line Arguments</h2>
137 * <p>
138 * <img src="doc-files/class-diagram-parsing.png" alt="Classes Related to Parsing Command Line Arguments">
139 * </p>
140 */
141public class CommandLine {
142
143 /** This is picocli version {@value}. */
144 public static final String VERSION = "4.0.0-alpha-2-SNAPSHOT";
145
146 private final Tracer tracer = new Tracer();
147 private final CommandSpec commandSpec;
148 private final Interpreter interpreter;
149 private final IFactory factory;
150
151 /**
152 * Constructs a new {@code CommandLine} interpreter with the specified object (which may be an annotated user object or a {@link CommandSpec CommandSpec}) and a default subcommand factory.
153 * <p>The specified object may be a {@link CommandSpec CommandSpec} object, or it may be a {@code @Command}-annotated
154 * user object with {@code @Option} and {@code @Parameters}-annotated fields, in which case picocli automatically
155 * constructs a {@code CommandSpec} from this user object.
156 * </p><p>
157 * When the {@link #parse(String...)} method is called, the {@link CommandSpec CommandSpec} object will be
158 * initialized based on command line arguments. If the commandSpec is created from an annotated user object, this
159 * user object will be initialized based on the command line arguments.</p>
160 * @param command an annotated user object or a {@code CommandSpec} object to initialize from the command line arguments
161 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
162 */
163 public CommandLine(Object command) {
164 this(command, new DefaultFactory());
165 }
166 /**
167 * Constructs a new {@code CommandLine} interpreter with the specified object (which may be an annotated user object or a {@link CommandSpec CommandSpec}) and object factory.
168 * <p>The specified object may be a {@link CommandSpec CommandSpec} object, or it may be a {@code @Command}-annotated
169 * user object with {@code @Option} and {@code @Parameters}-annotated fields, in which case picocli automatically
170 * constructs a {@code CommandSpec} from this user object.
171 * </p><p> If the specified command object is an interface {@code Class} with {@code @Option} and {@code @Parameters}-annotated methods,
172 * picocli creates a {@link java.lang.reflect.Proxy Proxy} whose methods return the matched command line values.
173 * If the specified command object is a concrete {@code Class}, picocli delegates to the {@linkplain IFactory factory} to get an instance.
174 * </p><p>
175 * When the {@link #parse(String...)} method is called, the {@link CommandSpec CommandSpec} object will be
176 * initialized based on command line arguments. If the commandSpec is created from an annotated user object, this
177 * user object will be initialized based on the command line arguments.</p>
178 * @param command an annotated user object or a {@code CommandSpec} object to initialize from the command line arguments
179 * @param factory the factory used to create instances of {@linkplain Command#subcommands() subcommands}, {@linkplain Option#converter() converters}, etc., that are registered declaratively with annotation attributes
180 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
181 * @since 2.2 */
182 public CommandLine(Object command, IFactory factory) {
183 this.factory = Assert.notNull(factory, "factory");
184 interpreter = new Interpreter();
185 commandSpec = CommandSpec.forAnnotatedObject(command, factory);
186 commandSpec.commandLine(this);
187 commandSpec.validate();
188 if (commandSpec.unmatchedArgsBindings().size() > 0) { setUnmatchedArgumentsAllowed(true); }
189 }
190
191 /**
192 * Returns the {@code CommandSpec} model that this {@code CommandLine} was constructed with.
193 * @return the {@code CommandSpec} model
194 * @since 3.0 */
195 public CommandSpec getCommandSpec() { return commandSpec; }
196
197 /**
198 * Adds the options and positional parameters in the specified mixin to this command.
199 * <p>The specified object may be a {@link CommandSpec CommandSpec} object, or it may be a user object with
200 * {@code @Option} and {@code @Parameters}-annotated fields, in which case picocli automatically
201 * constructs a {@code CommandSpec} from this user object.
202 * </p>
203 * @param name the name by which the mixin object may later be retrieved
204 * @param mixin an annotated user object or a {@link CommandSpec CommandSpec} object whose options and positional parameters to add to this command
205 * @return this CommandLine object, to allow method chaining
206 * @since 3.0 */
207 public CommandLine addMixin(String name, Object mixin) {
208 getCommandSpec().addMixin(name, CommandSpec.forAnnotatedObject(mixin, factory));
209 return this;
210 }
211
212 /**
213 * Returns a map of user objects whose options and positional parameters were added to ("mixed in" with) this command.
214 * @return a new Map containing the user objects mixed in with this command. If {@code CommandSpec} objects without
215 * user objects were programmatically added, use the {@link CommandSpec#mixins() underlying model} directly.
216 * @since 3.0 */
217 public Map<String, Object> getMixins() {
218 Map<String, CommandSpec> mixins = getCommandSpec().mixins();
219 Map<String, Object> result = new LinkedHashMap<String, Object>();
220 for (String name : mixins.keySet()) { result.put(name, mixins.get(name).userObject); }
221 return result;
222 }
223
224 /** Registers a subcommand with the specified name. For example:
225 * <pre>
226 * CommandLine commandLine = new CommandLine(new Git())
227 * .addSubcommand("status", new GitStatus())
228 * .addSubcommand("commit", new GitCommit();
229 * .addSubcommand("add", new GitAdd())
230 * .addSubcommand("branch", new GitBranch())
231 * .addSubcommand("checkout", new GitCheckout())
232 * //...
233 * ;
234 * </pre>
235 *
236 * <p>The specified object can be an annotated object or a
237 * {@code CommandLine} instance with its own nested subcommands. For example:</p>
238 * <pre>
239 * CommandLine commandLine = new CommandLine(new MainCommand())
240 * .addSubcommand("cmd1", new ChildCommand1()) // subcommand
241 * .addSubcommand("cmd2", new ChildCommand2())
242 * .addSubcommand("cmd3", new CommandLine(new ChildCommand3()) // subcommand with nested sub-subcommands
243 * .addSubcommand("cmd3sub1", new GrandChild3Command1())
244 * .addSubcommand("cmd3sub2", new GrandChild3Command2())
245 * .addSubcommand("cmd3sub3", new CommandLine(new GrandChild3Command3()) // deeper nesting
246 * .addSubcommand("cmd3sub3sub1", new GreatGrandChild3Command3_1())
247 * .addSubcommand("cmd3sub3sub2", new GreatGrandChild3Command3_2())
248 * )
249 * );
250 * </pre>
251 * <p>The default type converters are available on all subcommands and nested sub-subcommands, but custom type
252 * converters are registered only with the subcommand hierarchy as it existed when the custom type was registered.
253 * To ensure a custom type converter is available to all subcommands, register the type converter last, after
254 * adding subcommands.</p>
255 * <p>See also the {@link Command#subcommands()} annotation to register subcommands declaratively.</p>
256 *
257 * @param name the string to recognize on the command line as a subcommand
258 * @param command the object to initialize with command line arguments following the subcommand name.
259 * This may be a {@code CommandLine} instance with its own (nested) subcommands
260 * @return this CommandLine object, to allow method chaining
261 * @see #registerConverter(Class, ITypeConverter)
262 * @since 0.9.7
263 * @see Command#subcommands()
264 */
265 public CommandLine addSubcommand(String name, Object command) {
266 return addSubcommand(name, command, new String[0]);
267 }
268
269 /** Registers a subcommand with the specified name and all specified aliases. See also {@link #addSubcommand(String, Object)}.
270 *
271 *
272 * @param name the string to recognize on the command line as a subcommand
273 * @param command the object to initialize with command line arguments following the subcommand name.
274 * This may be a {@code CommandLine} instance with its own (nested) subcommands
275 * @param aliases zero or more alias names that are also recognized on the command line as this subcommand
276 * @return this CommandLine object, to allow method chaining
277 * @since 3.1
278 * @see #addSubcommand(String, Object)
279 */
280 public CommandLine addSubcommand(String name, Object command, String... aliases) {
281 CommandLine subcommandLine = toCommandLine(command, factory);
282 subcommandLine.getCommandSpec().aliases.addAll(Arrays.asList(aliases));
283 getCommandSpec().addSubcommand(name, subcommandLine);
284 CommandLine.Model.CommandReflection.initParentCommand(subcommandLine.getCommandSpec().userObject(), getCommandSpec().userObject());
285 return this;
286 }
287 /** Returns a map with the subcommands {@linkplain #addSubcommand(String, Object) registered} on this instance.
288 * @return a map with the registered subcommands
289 * @since 0.9.7
290 */
291 public Map<String, CommandLine> getSubcommands() {
292 return new LinkedHashMap<String, CommandLine>(getCommandSpec().subcommands());
293 }
294 /**
295 * Returns the command that this is a subcommand of, or {@code null} if this is a top-level command.
296 * @return the command that this is a subcommand of, or {@code null} if this is a top-level command
297 * @see #addSubcommand(String, Object)
298 * @see Command#subcommands()
299 * @since 0.9.8
300 */
301 public CommandLine getParent() {
302 CommandSpec parent = getCommandSpec().parent();
303 return parent == null ? null : parent.commandLine();
304 }
305
306 /** Returns the annotated user object that this {@code CommandLine} instance was constructed with.
307 * @param <T> the type of the variable that the return value is being assigned to
308 * @return the annotated object that this {@code CommandLine} instance was constructed with
309 * @since 0.9.7
310 */
311 @SuppressWarnings("unchecked")
312 public <T> T getCommand() {
313 return (T) getCommandSpec().userObject();
314 }
315
316 /** Returns {@code true} if an option annotated with {@link Option#usageHelp()} was specified on the command line.
317 * @return whether the parser encountered an option annotated with {@link Option#usageHelp()}.
318 * @since 0.9.8 */
319 public boolean isUsageHelpRequested() { return interpreter.parseResultBuilder != null && interpreter.parseResultBuilder.usageHelpRequested; }
320
321 /** Returns {@code true} if an option annotated with {@link Option#versionHelp()} was specified on the command line.
322 * @return whether the parser encountered an option annotated with {@link Option#versionHelp()}.
323 * @since 0.9.8 */
324 public boolean isVersionHelpRequested() { return interpreter.parseResultBuilder != null && interpreter.parseResultBuilder.versionHelpRequested; }
325
326 /** Returns the {@code IHelpFactory} that is used to construct the usage help message.
327 * @see #setHelpFactory(IHelpFactory)
328 * @since 3.9
329 */
330 public IHelpFactory getHelpFactory() {
331 return getCommandSpec().usageMessage().helpFactory();
332 }
333
334 /** Sets a new {@code IHelpFactory} to customize the usage help message.
335 * @param helpFactory the new help factory. Must be non-{@code null}.
336 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
337 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
338 * later will have the default setting. To ensure a setting is applied to all
339 * subcommands, call the setter last, after adding subcommands.</p>
340 * @return this {@code CommandLine} object, to allow method chaining
341 * @since 3.9
342 */
343 public CommandLine setHelpFactory(IHelpFactory helpFactory) {
344 getCommandSpec().usageMessage().helpFactory(helpFactory);
345 for (CommandLine command : getCommandSpec().subcommands().values()) {
346 command.setHelpFactory(helpFactory);
347 }
348 return this;
349 }
350
351 /**
352 * Returns the section keys in the order that the usage help message should render the sections.
353 * This ordering may be modified with {@link #setHelpSectionKeys(List) setSectionKeys}. The default keys are (in order):
354 * <ol>
355 * <li>{@link UsageMessageSpec#SECTION_KEY_HEADER_HEADING SECTION_KEY_HEADER_HEADING}</li>
356 * <li>{@link UsageMessageSpec#SECTION_KEY_HEADER SECTION_KEY_HEADER}</li>
357 * <li>{@link UsageMessageSpec#SECTION_KEY_SYNOPSIS_HEADING SECTION_KEY_SYNOPSIS_HEADING}</li>
358 * <li>{@link UsageMessageSpec#SECTION_KEY_SYNOPSIS SECTION_KEY_SYNOPSIS}</li>
359 * <li>{@link UsageMessageSpec#SECTION_KEY_DESCRIPTION_HEADING SECTION_KEY_DESCRIPTION_HEADING}</li>
360 * <li>{@link UsageMessageSpec#SECTION_KEY_DESCRIPTION SECTION_KEY_DESCRIPTION}</li>
361 * <li>{@link UsageMessageSpec#SECTION_KEY_PARAMETER_LIST_HEADING SECTION_KEY_PARAMETER_LIST_HEADING}</li>
362 * <li>{@link UsageMessageSpec#SECTION_KEY_PARAMETER_LIST SECTION_KEY_PARAMETER_LIST}</li>
363 * <li>{@link UsageMessageSpec#SECTION_KEY_OPTION_LIST_HEADING SECTION_KEY_OPTION_LIST_HEADING}</li>
364 * <li>{@link UsageMessageSpec#SECTION_KEY_OPTION_LIST SECTION_KEY_OPTION_LIST}</li>
365 * <li>{@link UsageMessageSpec#SECTION_KEY_COMMAND_LIST_HEADING SECTION_KEY_COMMAND_LIST_HEADING}</li>
366 * <li>{@link UsageMessageSpec#SECTION_KEY_COMMAND_LIST SECTION_KEY_COMMAND_LIST}</li>
367 * <li>{@link UsageMessageSpec#SECTION_KEY_FOOTER_HEADING SECTION_KEY_FOOTER_HEADING}</li>
368 * <li>{@link UsageMessageSpec#SECTION_KEY_FOOTER SECTION_KEY_FOOTER}</li>
369 * </ol>
370 * @since 3.9
371 */
372 public List<String> getHelpSectionKeys() { return getCommandSpec().usageMessage().sectionKeys(); }
373
374 /**
375 * Sets the section keys in the order that the usage help message should render the sections.
376 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
377 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
378 * later will have the default setting. To ensure a setting is applied to all
379 * subcommands, call the setter last, after adding subcommands.</p>
380 * <p>Use {@link UsageMessageSpec#sectionKeys(List)} to customize a command without affecting its subcommands.</p>
381 * @see #getHelpSectionKeys
382 * @since 3.9
383 */
384 public CommandLine setHelpSectionKeys(List<String> keys) {
385 getCommandSpec().usageMessage().sectionKeys(keys);
386 for (CommandLine command : getCommandSpec().subcommands().values()) {
387 command.setHelpSectionKeys(keys);
388 }
389 return this;
390 }
391
392 /**
393 * Returns the map of section keys and renderers used to construct the usage help message.
394 * The usage help message can be customized by adding, replacing and removing section renderers from this map.
395 * Sections can be reordered with {@link #setHelpSectionKeys(List) setSectionKeys}.
396 * Sections that are either not in this map or not in the list returned by {@link #getHelpSectionKeys() getSectionKeys} are omitted.
397 * <p>
398 * NOTE: By modifying the returned {@code Map}, only the usage help message <em>of this command</em> is affected.
399 * Use {@link #setHelpSectionMap(Map)} to customize the usage help message for this command <em>and all subcommands</em>.
400 * </p>
401 * @since 3.9
402 */
403 public Map<String, IHelpSectionRenderer> getHelpSectionMap() { return getCommandSpec().usageMessage().sectionMap(); }
404
405 /**
406 * Sets the map of section keys and renderers used to construct the usage help message.
407 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
408 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
409 * later will have the default setting. To ensure a setting is applied to all
410 * subcommands, call the setter last, after adding subcommands.</p>
411 * <p>Use {@link UsageMessageSpec#sectionMap(Map)} to customize a command without affecting its subcommands.</p>
412 * @see #getHelpSectionMap
413 * @since 3.9
414 */
415 public CommandLine setHelpSectionMap(Map<String, IHelpSectionRenderer> map) {
416 getCommandSpec().usageMessage().sectionMap(map);
417 for (CommandLine command : getCommandSpec().subcommands().values()) {
418 command.setHelpSectionMap(map);
419 }
420 return this;
421 }
422
423 /** Returns whether the value of boolean flag options should be "toggled" when the option is matched.
424 * By default, flags are toggled, so if the value is {@code true} it is set to {@code false}, and when the value is
425 * {@code false} it is set to {@code true}. If toggling is off, flags are simply set to {@code true}.
426 * @return {@code true} the value of boolean flag options should be "toggled" when the option is matched, {@code false} otherwise
427 * @since 3.0
428 */
429 public boolean isToggleBooleanFlags() {
430 return getCommandSpec().parser().toggleBooleanFlags();
431 }
432
433 /** Sets whether the value of boolean flag options should be "toggled" when the option is matched. The default is {@code true}.
434 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
435 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
436 * later will have the default setting. To ensure a setting is applied to all
437 * subcommands, call the setter last, after adding subcommands.</p>
438 * @param newValue the new setting
439 * @return this {@code CommandLine} object, to allow method chaining
440 * @since 3.0
441 */
442 public CommandLine setToggleBooleanFlags(boolean newValue) {
443 getCommandSpec().parser().toggleBooleanFlags(newValue);
444 for (CommandLine command : getCommandSpec().subcommands().values()) {
445 command.setToggleBooleanFlags(newValue);
446 }
447 return this;
448 }
449
450 /** Returns whether options for single-value fields can be specified multiple times on the command line.
451 * The default is {@code false} and a {@link OverwrittenOptionException} is thrown if this happens.
452 * When {@code true}, the last specified value is retained.
453 * @return {@code true} if options for single-value fields can be specified multiple times on the command line, {@code false} otherwise
454 * @since 0.9.7
455 */
456 public boolean isOverwrittenOptionsAllowed() {
457 return getCommandSpec().parser().overwrittenOptionsAllowed();
458 }
459
460 /** Sets whether options for single-value fields can be specified multiple times on the command line without a {@link OverwrittenOptionException} being thrown.
461 * The default is {@code false}.
462 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
463 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
464 * later will have the default setting. To ensure a setting is applied to all
465 * subcommands, call the setter last, after adding subcommands.</p>
466 * @param newValue the new setting
467 * @return this {@code CommandLine} object, to allow method chaining
468 * @since 0.9.7
469 */
470 public CommandLine setOverwrittenOptionsAllowed(boolean newValue) {
471 getCommandSpec().parser().overwrittenOptionsAllowed(newValue);
472 for (CommandLine command : getCommandSpec().subcommands().values()) {
473 command.setOverwrittenOptionsAllowed(newValue);
474 }
475 return this;
476 }
477
478 /** Returns whether the parser accepts clustered short options. The default is {@code true}.
479 * @return {@code true} if short options like {@code -x -v -f SomeFile} can be clustered together like {@code -xvfSomeFile}, {@code false} otherwise
480 * @since 3.0 */
481 public boolean isPosixClusteredShortOptionsAllowed() { return getCommandSpec().parser().posixClusteredShortOptionsAllowed(); }
482
483 /** Sets whether short options like {@code -x -v -f SomeFile} can be clustered together like {@code -xvfSomeFile}. The default is {@code true}.
484 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
485 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
486 * later will have the default setting. To ensure a setting is applied to all
487 * subcommands, call the setter last, after adding subcommands.</p>
488 * @param newValue the new setting
489 * @return this {@code CommandLine} object, to allow method chaining
490 * @since 3.0
491 */
492 public CommandLine setPosixClusteredShortOptionsAllowed(boolean newValue) {
493 getCommandSpec().parser().posixClusteredShortOptionsAllowed(newValue);
494 for (CommandLine command : getCommandSpec().subcommands().values()) {
495 command.setPosixClusteredShortOptionsAllowed(newValue);
496 }
497 return this;
498 }
499
500 /** Returns whether the parser should ignore case when converting arguments to {@code enum} values. The default is {@code false}.
501 * @return {@code true} if enum values can be specified that don't match the {@code toString()} value of the enum constant, {@code false} otherwise;
502 * e.g., for an option of type <a href="https://docs.oracle.com/javase/8/docs/api/java/time/DayOfWeek.html">java.time.DayOfWeek</a>,
503 * values {@code MonDaY}, {@code monday} and {@code MONDAY} are all recognized if {@code true}.
504 * @since 3.4 */
505 public boolean isCaseInsensitiveEnumValuesAllowed() { return getCommandSpec().parser().caseInsensitiveEnumValuesAllowed(); }
506
507 /** Sets whether the parser should ignore case when converting arguments to {@code enum} values. The default is {@code false}.
508 * When set to true, for example, for an option of type <a href="https://docs.oracle.com/javase/8/docs/api/java/time/DayOfWeek.html">java.time.DayOfWeek</a>,
509 * values {@code MonDaY}, {@code monday} and {@code MONDAY} are all recognized if {@code true}.
510 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
511 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
512 * later will have the default setting. To ensure a setting is applied to all
513 * subcommands, call the setter last, after adding subcommands.</p>
514 * @param newValue the new setting
515 * @return this {@code CommandLine} object, to allow method chaining
516 * @since 3.4
517 */
518 public CommandLine setCaseInsensitiveEnumValuesAllowed(boolean newValue) {
519 getCommandSpec().parser().caseInsensitiveEnumValuesAllowed(newValue);
520 for (CommandLine command : getCommandSpec().subcommands().values()) {
521 command.setCaseInsensitiveEnumValuesAllowed(newValue);
522 }
523 return this;
524 }
525
526 /** Returns whether the parser should trim quotes from command line arguments before processing them. The default is
527 * read from the system property "picocli.trimQuotes" and will be {@code true} if the property is present and empty,
528 * or if its value is "true".
529 * @return {@code true} if the parser should trim quotes from command line arguments before processing them, {@code false} otherwise;
530 * @since 3.7 */
531 public boolean isTrimQuotes() { return getCommandSpec().parser().trimQuotes(); }
532
533 /** Sets whether the parser should trim quotes from command line arguments before processing them. The default is
534 * read from the system property "picocli.trimQuotes" and will be {@code true} if the property is set and empty, or
535 * if its value is "true".
536 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
537 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
538 * later will have the default setting. To ensure a setting is applied to all
539 * subcommands, call the setter last, after adding subcommands.</p>
540 * <p>Calling this method will cause the "picocli.trimQuotes" property to have no effect.</p>
541 * @param newValue the new setting
542 * @return this {@code CommandLine} object, to allow method chaining
543 * @since 3.7
544 */
545 public CommandLine setTrimQuotes(boolean newValue) {
546 getCommandSpec().parser().trimQuotes(newValue);
547 for (CommandLine command : getCommandSpec().subcommands().values()) {
548 command.setTrimQuotes(newValue);
549 }
550 return this;
551 }
552
553 /** Returns whether the parser is allowed to split quoted Strings or not. The default is {@code false},
554 * so quoted strings are treated as a single value that cannot be split.
555 * @return {@code true} if the parser is allowed to split quoted Strings, {@code false} otherwise;
556 * @see ArgSpec#splitRegex()
557 * @since 3.7 */
558 public boolean isSplitQuotedStrings() { return getCommandSpec().parser().splitQuotedStrings(); }
559
560 /** Sets whether the parser is allowed to split quoted Strings. The default is {@code false}.
561 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
562 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
563 * later will have the default setting. To ensure a setting is applied to all
564 * subcommands, call the setter last, after adding subcommands.</p>
565 * @param newValue the new setting
566 * @return this {@code CommandLine} object, to allow method chaining
567 * @see ArgSpec#splitRegex()
568 * @since 3.7
569 */
570 public CommandLine setSplitQuotedStrings(boolean newValue) {
571 getCommandSpec().parser().splitQuotedStrings(newValue);
572 for (CommandLine command : getCommandSpec().subcommands().values()) {
573 command.setSplitQuotedStrings(newValue);
574 }
575 return this;
576 }
577
578 /** Returns the end-of-options delimiter that signals that the remaining command line arguments should be treated as positional parameters.
579 * @return the end-of-options delimiter. The default is {@code "--"}.
580 * @since 3.5 */
581 public String getEndOfOptionsDelimiter() { return getCommandSpec().parser().endOfOptionsDelimiter(); }
582
583 /** Sets the end-of-options delimiter that signals that the remaining command line arguments should be treated as positional parameters.
584 * @param delimiter the end-of-options delimiter; must not be {@code null}. The default is {@code "--"}.
585 * @return this {@code CommandLine} object, to allow method chaining
586 * @since 3.5 */
587 public CommandLine setEndOfOptionsDelimiter(String delimiter) {
588 getCommandSpec().parser().endOfOptionsDelimiter(delimiter);
589 for (CommandLine command : getCommandSpec().subcommands().values()) {
590 command.setEndOfOptionsDelimiter(delimiter);
591 }
592 return this;
593 }
594
595 /** Returns the default value provider for the command, or {@code null} if none has been set.
596 * @return the default value provider for this command, or {@code null}
597 * @since 3.6
598 * @see Command#defaultValueProvider()
599 * @see CommandSpec#defaultValueProvider()
600 * @see ArgSpec#defaultValueString()
601 */
602 public IDefaultValueProvider getDefaultValueProvider() {
603 return getCommandSpec().defaultValueProvider();
604 }
605
606 /** Sets a default value provider for the command and sub-commands
607 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
608 * sub-commands and nested sub-subcommands <em>at the moment this method is called</em>. Sub-commands added
609 * later will have the default setting. To ensure a setting is applied to all
610 * sub-commands, call the setter last, after adding sub-commands.</p>
611 * @param newValue the default value provider to use
612 * @return this {@code CommandLine} object, to allow method chaining
613 * @since 3.6
614 */
615 public CommandLine setDefaultValueProvider(IDefaultValueProvider newValue) {
616 getCommandSpec().defaultValueProvider(newValue);
617 for (CommandLine command : getCommandSpec().subcommands().values()) {
618 command.setDefaultValueProvider(newValue);
619 }
620 return this;
621 }
622
623 /** Returns whether the parser interprets the first positional parameter as "end of options" so the remaining
624 * arguments are all treated as positional parameters. The default is {@code false}.
625 * @return {@code true} if all values following the first positional parameter should be treated as positional parameters, {@code false} otherwise
626 * @since 2.3
627 */
628 public boolean isStopAtPositional() {
629 return getCommandSpec().parser().stopAtPositional();
630 }
631
632 /** Sets whether the parser interprets the first positional parameter as "end of options" so the remaining
633 * arguments are all treated as positional parameters. The default is {@code false}.
634 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
635 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
636 * later will have the default setting. To ensure a setting is applied to all
637 * subcommands, call the setter last, after adding subcommands.</p>
638 * @param newValue {@code true} if all values following the first positional parameter should be treated as positional parameters, {@code false} otherwise
639 * @return this {@code CommandLine} object, to allow method chaining
640 * @since 2.3
641 */
642 public CommandLine setStopAtPositional(boolean newValue) {
643 getCommandSpec().parser().stopAtPositional(newValue);
644 for (CommandLine command : getCommandSpec().subcommands().values()) {
645 command.setStopAtPositional(newValue);
646 }
647 return this;
648 }
649
650 /** Returns whether the parser should stop interpreting options and positional parameters as soon as it encounters an
651 * unmatched option. Unmatched options are arguments that look like an option but are not one of the known options, or
652 * positional arguments for which there is no available slots (the command has no positional parameters or their size is limited).
653 * The default is {@code false}.
654 * <p>Setting this flag to {@code true} automatically sets the {@linkplain #isUnmatchedArgumentsAllowed() unmatchedArgumentsAllowed} flag to {@code true} also.</p>
655 * @return {@code true} when an unmatched option should result in the remaining command line arguments to be added to the
656 * {@linkplain #getUnmatchedArguments() unmatchedArguments list}
657 * @since 2.3
658 */
659 public boolean isStopAtUnmatched() {
660 return getCommandSpec().parser().stopAtUnmatched();
661 }
662
663 /** Sets whether the parser should stop interpreting options and positional parameters as soon as it encounters an
664 * unmatched option. Unmatched options are arguments that look like an option but are not one of the known options, or
665 * positional arguments for which there is no available slots (the command has no positional parameters or their size is limited).
666 * The default is {@code false}.
667 * <p>Setting this flag to {@code true} automatically sets the {@linkplain #setUnmatchedArgumentsAllowed(boolean) unmatchedArgumentsAllowed} flag to {@code true} also.</p>
668 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
669 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
670 * later will have the default setting. To ensure a setting is applied to all
671 * subcommands, call the setter last, after adding subcommands.</p>
672 * @param newValue {@code true} when an unmatched option should result in the remaining command line arguments to be added to the
673 * {@linkplain #getUnmatchedArguments() unmatchedArguments list}
674 * @return this {@code CommandLine} object, to allow method chaining
675 * @since 2.3
676 */
677 public CommandLine setStopAtUnmatched(boolean newValue) {
678 getCommandSpec().parser().stopAtUnmatched(newValue);
679 for (CommandLine command : getCommandSpec().subcommands().values()) {
680 command.setStopAtUnmatched(newValue);
681 }
682 if (newValue) { setUnmatchedArgumentsAllowed(true); }
683 return this;
684 }
685
686 /** Returns whether arguments on the command line that resemble an option should be treated as positional parameters.
687 * The default is {@code false} and the parser behaviour depends on {@link #isUnmatchedArgumentsAllowed()}.
688 * @return {@code true} arguments on the command line that resemble an option should be treated as positional parameters, {@code false} otherwise
689 * @see #getUnmatchedArguments()
690 * @since 3.0
691 */
692 public boolean isUnmatchedOptionsArePositionalParams() {
693 return getCommandSpec().parser().unmatchedOptionsArePositionalParams();
694 }
695
696 /** Sets whether arguments on the command line that resemble an option should be treated as positional parameters.
697 * The default is {@code false}.
698 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
699 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
700 * later will have the default setting. To ensure a setting is applied to all
701 * subcommands, call the setter last, after adding subcommands.</p>
702 * @param newValue the new setting. When {@code true}, arguments on the command line that resemble an option should be treated as positional parameters.
703 * @return this {@code CommandLine} object, to allow method chaining
704 * @since 3.0
705 * @see #getUnmatchedArguments()
706 * @see #isUnmatchedArgumentsAllowed
707 */
708 public CommandLine setUnmatchedOptionsArePositionalParams(boolean newValue) {
709 getCommandSpec().parser().unmatchedOptionsArePositionalParams(newValue);
710 for (CommandLine command : getCommandSpec().subcommands().values()) {
711 command.setUnmatchedOptionsArePositionalParams(newValue);
712 }
713 return this;
714 }
715
716 /** Returns whether the end user may specify arguments on the command line that are not matched to any option or parameter fields.
717 * The default is {@code false} and a {@link UnmatchedArgumentException} is thrown if this happens.
718 * When {@code true}, the last unmatched arguments are available via the {@link #getUnmatchedArguments()} method.
719 * @return {@code true} if the end use may specify unmatched arguments on the command line, {@code false} otherwise
720 * @see #getUnmatchedArguments()
721 * @since 0.9.7
722 */
723 public boolean isUnmatchedArgumentsAllowed() {
724 return getCommandSpec().parser().unmatchedArgumentsAllowed();
725 }
726
727 /** Sets whether the end user may specify unmatched arguments on the command line without a {@link UnmatchedArgumentException} being thrown.
728 * The default is {@code false}.
729 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
730 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
731 * later will have the default setting. To ensure a setting is applied to all
732 * subcommands, call the setter last, after adding subcommands.</p>
733 * @param newValue the new setting. When {@code true}, the last unmatched arguments are available via the {@link #getUnmatchedArguments()} method.
734 * @return this {@code CommandLine} object, to allow method chaining
735 * @since 0.9.7
736 * @see #getUnmatchedArguments()
737 */
738 public CommandLine setUnmatchedArgumentsAllowed(boolean newValue) {
739 getCommandSpec().parser().unmatchedArgumentsAllowed(newValue);
740 for (CommandLine command : getCommandSpec().subcommands().values()) {
741 command.setUnmatchedArgumentsAllowed(newValue);
742 }
743 return this;
744 }
745
746 /** Returns the list of unmatched command line arguments, if any.
747 * @return the list of unmatched command line arguments or an empty list
748 * @see #isUnmatchedArgumentsAllowed()
749 * @since 0.9.7
750 */
751 public List<String> getUnmatchedArguments() {
752 return interpreter.parseResultBuilder == null ? Collections.<String>emptyList() : UnmatchedArgumentException.stripErrorMessage(interpreter.parseResultBuilder.unmatched);
753 }
754
755 /**
756 * <p>
757 * Convenience method that initializes the specified annotated object from the specified command line arguments.
758 * </p><p>
759 * This is equivalent to
760 * </p><pre>
761 * CommandLine cli = new CommandLine(command);
762 * cli.parse(args);
763 * return command;
764 * </pre>
765 *
766 * @param command the object to initialize. This object contains fields annotated with
767 * {@code @Option} or {@code @Parameters}.
768 * @param args the command line arguments to parse
769 * @param <T> the type of the annotated object
770 * @return the specified annotated object
771 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
772 * @throws ParameterException if the specified command line arguments are invalid
773 * @since 0.9.7
774 */
775 public static <T> T populateCommand(T command, String... args) {
776 CommandLine cli = toCommandLine(command, new DefaultFactory());
777 cli.parse(args);
778 return command;
779 }
780
781 /**
782 * <p>
783 * Convenience method that derives the command specification from the specified interface class, and returns an
784 * instance of the specified interface. The interface is expected to have annotated getter methods. Picocli will
785 * instantiate the interface and the getter methods will return the option and positional parameter values matched on the command line.
786 * </p><p>
787 * This is equivalent to
788 * </p><pre>
789 * CommandLine cli = new CommandLine(spec);
790 * cli.parse(args);
791 * return cli.getCommand();
792 * </pre>
793 *
794 * @param spec the interface that defines the command specification. This object contains getter methods annotated with
795 * {@code @Option} or {@code @Parameters}.
796 * @param args the command line arguments to parse
797 * @param <T> the type of the annotated object
798 * @return an instance of the specified annotated interface
799 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
800 * @throws ParameterException if the specified command line arguments are invalid
801 * @since 3.1
802 */
803 public static <T> T populateSpec(Class<T> spec, String... args) {
804 CommandLine cli = toCommandLine(spec, new DefaultFactory());
805 cli.parse(args);
806 return cli.getCommand();
807 }
808
809 /** Parses the specified command line arguments and returns a list of {@code CommandLine} objects representing the
810 * top-level command and any subcommands (if any) that were recognized and initialized during the parsing process.
811 * <p>
812 * If parsing succeeds, the first element in the returned list is always {@code this CommandLine} object. The
813 * returned list may contain more elements if subcommands were {@linkplain #addSubcommand(String, Object) registered}
814 * and these subcommands were initialized by matching command line arguments. If parsing fails, a
815 * {@link ParameterException} is thrown.
816 * </p>
817 *
818 * @param args the command line arguments to parse
819 * @return a list with the top-level command and any subcommands initialized by this method
820 * @throws ParameterException if the specified command line arguments are invalid; use
821 * {@link ParameterException#getCommandLine()} to get the command or subcommand whose user input was invalid
822 */
823 public List<CommandLine> parse(String... args) {
824 return interpreter.parse(args);
825 }
826 /** Parses the specified command line arguments and returns a list of {@code ParseResult} with the options, positional
827 * parameters, and subcommands (if any) that were recognized and initialized during the parsing process.
828 * <p>If parsing fails, a {@link ParameterException} is thrown.</p>
829 *
830 * @param args the command line arguments to parse
831 * @return a list with the top-level command and any subcommands initialized by this method
832 * @throws ParameterException if the specified command line arguments are invalid; use
833 * {@link ParameterException#getCommandLine()} to get the command or subcommand whose user input was invalid
834 */
835 public ParseResult parseArgs(String... args) {
836 interpreter.parse(args);
837 return getParseResult();
838 }
839 public ParseResult getParseResult() { return interpreter.parseResultBuilder == null ? null : interpreter.parseResultBuilder.build(); }
840 /**
841 * Represents a function that can process a List of {@code CommandLine} objects resulting from successfully
842 * {@linkplain #parse(String...) parsing} the command line arguments. This is a
843 * <a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">functional interface</a>
844 * whose functional method is {@link #handleParseResult(List, PrintStream, CommandLine.Help.Ansi)}.
845 * <p>
846 * Implementations of this functions can be passed to the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) CommandLine::parseWithHandler}
847 * methods to take some next step after the command line was successfully parsed.
848 * </p>
849 * @see RunFirst
850 * @see RunLast
851 * @see RunAll
852 * @deprecated Use {@link IParseResultHandler2} instead.
853 * @since 2.0 */
854 @Deprecated public static interface IParseResultHandler {
855 /** Processes a List of {@code CommandLine} objects resulting from successfully
856 * {@linkplain #parse(String...) parsing} the command line arguments and optionally returns a list of results.
857 * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
858 * @param out the {@code PrintStream} to print help to if requested
859 * @param ansi for printing help messages using ANSI styles and colors
860 * @return a list of results, or an empty list if there are no results
861 * @throws ParameterException if a help command was invoked for an unknown subcommand. Any {@code ParameterExceptions}
862 * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler}
863 * @throws ExecutionException if a problem occurred while processing the parse results; use
864 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
865 */
866 List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) throws ExecutionException;
867 }
868
869 /**
870 * Represents a function that can process the {@code ParseResult} object resulting from successfully
871 * {@linkplain #parseArgs(String...) parsing} the command line arguments. This is a
872 * <a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">functional interface</a>
873 * whose functional method is {@link IParseResultHandler2#handleParseResult(CommandLine.ParseResult)}.
874 * <p>
875 * Implementations of this function can be passed to the {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) CommandLine::parseWithHandlers}
876 * methods to take some next step after the command line was successfully parsed.
877 * </p><p>
878 * This interface replaces the {@link IParseResultHandler} interface; it takes the parse result as a {@code ParseResult}
879 * object instead of a List of {@code CommandLine} objects, and it has the freedom to select the {@link Help.Ansi} style
880 * to use and what {@code PrintStreams} to print to.
881 * </p>
882 * @param <R> the return type of this handler
883 * @see RunFirst
884 * @see RunLast
885 * @see RunAll
886 * @since 3.0 */
887 public static interface IParseResultHandler2<R> {
888 /** Processes the {@code ParseResult} object resulting from successfully
889 * {@linkplain CommandLine#parseArgs(String...) parsing} the command line arguments and returns a return value.
890 * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments
891 * @throws ParameterException if a help command was invoked for an unknown subcommand. Any {@code ParameterExceptions}
892 * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler2}
893 * @throws ExecutionException if a problem occurred while processing the parse results; use
894 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
895 */
896 R handleParseResult(ParseResult parseResult) throws ExecutionException;
897 }
898 /**
899 * Represents a function that can handle a {@code ParameterException} that occurred while
900 * {@linkplain #parse(String...) parsing} the command line arguments. This is a
901 * <a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">functional interface</a>
902 * whose functional method is {@link #handleException(CommandLine.ParameterException, PrintStream, CommandLine.Help.Ansi, String...)}.
903 * <p>
904 * Implementations of this function can be passed to the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) CommandLine::parseWithHandlers}
905 * methods to handle situations when the command line could not be parsed.
906 * </p>
907 * @deprecated Use {@link IExceptionHandler2} instead.
908 * @see DefaultExceptionHandler
909 * @since 2.0 */
910 @Deprecated public static interface IExceptionHandler {
911 /** Handles a {@code ParameterException} that occurred while {@linkplain #parse(String...) parsing} the command
912 * line arguments and optionally returns a list of results.
913 * @param ex the ParameterException describing the problem that occurred while parsing the command line arguments,
914 * and the CommandLine representing the command or subcommand whose input was invalid
915 * @param out the {@code PrintStream} to print help to if requested
916 * @param ansi for printing help messages using ANSI styles and colors
917 * @param args the command line arguments that could not be parsed
918 * @return a list of results, or an empty list if there are no results
919 */
920 List<Object> handleException(ParameterException ex, PrintStream out, Help.Ansi ansi, String... args);
921 }
922 /**
923 * Classes implementing this interface know how to handle {@code ParameterExceptions} (usually from invalid user input)
924 * and {@code ExecutionExceptions} that occurred while executing the {@code Runnable} or {@code Callable} command.
925 * <p>
926 * Implementations of this interface can be passed to the
927 * {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) CommandLine::parseWithHandlers} method.
928 * </p><p>
929 * This interface replaces the {@link IParseResultHandler} interface.
930 * </p>
931 * @param <R> the return type of this handler
932 * @see DefaultExceptionHandler
933 * @since 3.0 */
934 public static interface IExceptionHandler2<R> {
935 /** Handles a {@code ParameterException} that occurred while {@linkplain #parseArgs(String...) parsing} the command
936 * line arguments and optionally returns a list of results.
937 * @param ex the ParameterException describing the problem that occurred while parsing the command line arguments,
938 * and the CommandLine representing the command or subcommand whose input was invalid
939 * @param args the command line arguments that could not be parsed
940 * @return an object resulting from handling the exception
941 */
942 R handleParseException(ParameterException ex, String[] args);
943 /** Handles a {@code ExecutionException} that occurred while executing the {@code Runnable} or
944 * {@code Callable} command and optionally returns a list of results.
945 * @param ex the ExecutionException describing the problem that occurred while executing the {@code Runnable} or
946 * {@code Callable} command, and the CommandLine representing the command or subcommand that was being executed
947 * @param parseResult the result of parsing the command line arguments
948 * @return an object resulting from handling the exception
949 */
950 R handleExecutionException(ExecutionException ex, ParseResult parseResult);
951 }
952
953 /** Abstract superclass for {@link IParseResultHandler2} and {@link IExceptionHandler2} implementations.
954 * <p>Note that {@code AbstractHandler} is a generic type. This, along with the abstract {@code self} method,
955 * allows method chaining to work properly in subclasses, without the need for casts. An example subclass can look like this:</p>
956 * <pre>{@code
957 * class MyResultHandler extends AbstractHandler<MyReturnType, MyResultHandler> implements IParseResultHandler2<MyReturnType> {
958 *
959 * public MyReturnType handleParseResult(ParseResult parseResult) { ... }
960 *
961 * protected MyResultHandler self() { return this; }
962 * }
963 * }</pre>
964 * @param <R> the return type of this handler
965 * @param <T> The type of the handler subclass; for fluent API method chaining
966 * @since 3.0 */
967 public static abstract class AbstractHandler<R, T extends AbstractHandler<R, T>> {
968 private Help.Ansi ansi = Help.Ansi.AUTO;
969 private Integer exitCode;
970 private PrintStream out = System.out;
971 private PrintStream err = System.err;
972
973 /** Returns the stream to print command output to. Defaults to {@code System.out}, unless {@link #useOut(PrintStream)}
974 * was called with a different stream.
975 * <p>{@code IParseResultHandler2} implementations should use this stream.
976 * By <a href="http://www.gnu.org/prep/standards/html_node/_002d_002dhelp.html">convention</a>, when the user requests
977 * help with a {@code --help} or similar option, the usage help message is printed to the standard output stream so that it can be easily searched and paged.</p> */
978 public PrintStream out() { return out; }
979 /** Returns the stream to print diagnostic messages to. Defaults to {@code System.err}, unless {@link #useErr(PrintStream)}
980 * was called with a different stream. <p>{@code IExceptionHandler2} implementations should use this stream to print error
981 * messages (which may include a usage help message) when an unexpected error occurs.</p> */
982 public PrintStream err() { return err; }
983 /** Returns the ANSI style to use. Defaults to {@code Help.Ansi.AUTO}, unless {@link #useAnsi(CommandLine.Help.Ansi)} was called with a different setting. */
984 public Help.Ansi ansi() { return ansi; }
985 /** Returns the exit code to use as the termination status, or {@code null} (the default) if the handler should
986 * not call {@link System#exit(int)} after processing completes.
987 * @see #andExit(int) */
988 public Integer exitCode() { return exitCode; }
989 /** Returns {@code true} if an exit code was set with {@link #andExit(int)}, or {@code false} (the default) if
990 * the handler should not call {@link System#exit(int)} after processing completes. */
991 public boolean hasExitCode() { return exitCode != null; }
992
993 /** Convenience method for subclasses that returns the specified result object if no exit code was set,
994 * or otherwise, if an exit code {@linkplain #andExit(int) was set}, calls {@code System.exit} with the configured
995 * exit code to terminate the currently running Java virtual machine. */
996 protected R returnResultOrExit(R result) {
997 if (hasExitCode()) { exit(exitCode()); }
998 return result;
999 }
1000
1001 /** Convenience method for subclasses that throws the specified ExecutionException if no exit code was set,
1002 * or otherwise, if an exit code {@linkplain #andExit(int) was set}, prints the stacktrace of the specified exception
1003 * to the diagnostic error stream and calls {@code System.exit} with the configured
1004 * exit code to terminate the currently running Java virtual machine. */
1005 protected R throwOrExit(ExecutionException ex) {
1006 if (hasExitCode()) {
1007 ex.printStackTrace(this.err());
1008 exit(exitCode());
1009 }
1010 throw ex;
1011 }
1012 /** Calls {@code System.exit(int)} with the specified exit code. */
1013 protected void exit(int exitCode) { System.exit(exitCode); }
1014
1015 /** Returns {@code this} to allow method chaining when calling the setters for a fluent API. */
1016 protected abstract T self();
1017
1018 /** Sets the stream to print command output to. For use by {@code IParseResultHandler2} implementations.
1019 * @see #out() */
1020 public T useOut(PrintStream out) { this.out = Assert.notNull(out, "out"); return self(); }
1021 /** Sets the stream to print diagnostic messages to. For use by {@code IExceptionHandler2} implementations.
1022 * @see #err()*/
1023 public T useErr(PrintStream err) { this.err = Assert.notNull(err, "err"); return self(); }
1024 /** Sets the ANSI style to use.
1025 * @see #ansi() */
1026 public T useAnsi(Help.Ansi ansi) { this.ansi = Assert.notNull(ansi, "ansi"); return self(); }
1027 /** Indicates that the handler should call {@link System#exit(int)} after processing completes and sets the exit code to use as the termination status. */
1028 public T andExit(int exitCode) { this.exitCode = exitCode; return self(); }
1029 }
1030
1031 /**
1032 * Default exception handler that handles invalid user input by printing the exception message, followed by the usage
1033 * message for the command or subcommand whose input was invalid.
1034 * <p>{@code ParameterExceptions} (invalid user input) is handled like this:</p>
1035 * <pre>
1036 * err().println(paramException.getMessage());
1037 * paramException.getCommandLine().usage(err(), ansi());
1038 * if (hasExitCode()) System.exit(exitCode()); else return returnValue;
1039 * </pre>
1040 * <p>{@code ExecutionExceptions} that occurred while executing the {@code Runnable} or {@code Callable} command are simply rethrown and not handled.</p>
1041 * @since 2.0 */
1042 @SuppressWarnings("deprecation")
1043 public static class DefaultExceptionHandler<R> extends AbstractHandler<R, DefaultExceptionHandler<R>> implements IExceptionHandler, IExceptionHandler2<R> {
1044 public List<Object> handleException(ParameterException ex, PrintStream out, Help.Ansi ansi, String... args) {
1045 internalHandleParseException(ex, out, ansi, args); return Collections.<Object>emptyList(); }
1046
1047 /** Prints the message of the specified exception, followed by the usage message for the command or subcommand
1048 * whose input was invalid, to the stream returned by {@link #err()}.
1049 * @param ex the ParameterException describing the problem that occurred while parsing the command line arguments,
1050 * and the CommandLine representing the command or subcommand whose input was invalid
1051 * @param args the command line arguments that could not be parsed
1052 * @return the empty list
1053 * @since 3.0 */
1054 public R handleParseException(ParameterException ex, String[] args) {
1055 internalHandleParseException(ex, err(), ansi(), args); return returnResultOrExit(null); }
1056
1057 private void internalHandleParseException(ParameterException ex, PrintStream out, Help.Ansi ansi, String[] args) {
1058 out.println(ex.getMessage());
1059 if (!UnmatchedArgumentException.printSuggestions(ex, out)) {
1060 ex.getCommandLine().usage(out, ansi);
1061 }
1062 }
1063 /** This implementation always simply rethrows the specified exception.
1064 * @param ex the ExecutionException describing the problem that occurred while executing the {@code Runnable} or {@code Callable} command
1065 * @param parseResult the result of parsing the command line arguments
1066 * @return nothing: this method always rethrows the specified exception
1067 * @throws ExecutionException always rethrows the specified exception
1068 * @since 3.0 */
1069 public R handleExecutionException(ExecutionException ex, ParseResult parseResult) { return throwOrExit(ex); }
1070
1071 @Override protected DefaultExceptionHandler<R> self() { return this; }
1072 }
1073 /** Convenience method that returns {@code new DefaultExceptionHandler<List<Object>>()}. */
1074 public static DefaultExceptionHandler<List<Object>> defaultExceptionHandler() { return new DefaultExceptionHandler<List<Object>>(); }
1075
1076 /** @deprecated use {@link #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi)} instead
1077 * @since 2.0 */
1078 @Deprecated public static boolean printHelpIfRequested(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
1079 return printHelpIfRequested(parsedCommands, out, out, ansi);
1080 }
1081
1082 /** Delegates to {@link #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi)} with
1083 * {@code parseResult.asCommandLineList(), System.out, System.err, Help.Ansi.AUTO}.
1084 * @since 3.0 */
1085 public static boolean printHelpIfRequested(ParseResult parseResult) {
1086 return printHelpIfRequested(parseResult.asCommandLineList(), System.out, System.err, Help.Ansi.AUTO);
1087 }
1088 /**
1089 * Helper method that may be useful when processing the list of {@code CommandLine} objects that result from successfully
1090 * {@linkplain #parse(String...) parsing} command line arguments. This method prints out
1091 * {@linkplain #usage(PrintStream, Help.Ansi) usage help} if {@linkplain #isUsageHelpRequested() requested}
1092 * or {@linkplain #printVersionHelp(PrintStream, Help.Ansi) version help} if {@linkplain #isVersionHelpRequested() requested}
1093 * and returns {@code true}. If the command is a {@link Command#helpCommand()} and {@code runnable} or {@code callable},
1094 * that command is executed and this method returns {@code true}.
1095 * Otherwise, if none of the specified {@code CommandLine} objects have help requested,
1096 * this method returns {@code false}.<p>
1097 * Note that this method <em>only</em> looks at the {@link Option#usageHelp() usageHelp} and
1098 * {@link Option#versionHelp() versionHelp} attributes. The {@link Option#help() help} attribute is ignored.
1099 * </p><p><b>Implementation note:</b></p><p>
1100 * When an error occurs while processing the help request, it is recommended custom Help commands throw a
1101 * {@link ParameterException} with a reference to the parent command. This will print the error message and the
1102 * usage for the parent command, and will use the exit code of the exception handler if one was set.
1103 * </p>
1104 * @param parsedCommands the list of {@code CommandLine} objects to check if help was requested
1105 * @param out the {@code PrintStream} to print help to if requested
1106 * @param err the error string to print diagnostic messages to, in addition to the output from the exception handler
1107 * @param ansi for printing help messages using ANSI styles and colors
1108 * @return {@code true} if help was printed, {@code false} otherwise
1109 * @see IHelpCommandInitializable
1110 * @since 3.0 */
1111 public static boolean printHelpIfRequested(List<CommandLine> parsedCommands, PrintStream out, PrintStream err, Help.Ansi ansi) {
1112 return printHelpIfRequested(parsedCommands, out, err, Help.defaultColorScheme(ansi));
1113 }
1114 /**
1115 * Helper method that may be useful when processing the list of {@code CommandLine} objects that result from successfully
1116 * {@linkplain #parse(String...) parsing} command line arguments. This method prints out
1117 * {@linkplain #usage(PrintStream, Help.ColorScheme) usage help} if {@linkplain #isUsageHelpRequested() requested}
1118 * or {@linkplain #printVersionHelp(PrintStream, Help.Ansi) version help} if {@linkplain #isVersionHelpRequested() requested}
1119 * and returns {@code true}. If the command is a {@link Command#helpCommand()} and {@code runnable} or {@code callable},
1120 * that command is executed and this method returns {@code true}.
1121 * Otherwise, if none of the specified {@code CommandLine} objects have help requested,
1122 * this method returns {@code false}.<p>
1123 * Note that this method <em>only</em> looks at the {@link Option#usageHelp() usageHelp} and
1124 * {@link Option#versionHelp() versionHelp} attributes. The {@link Option#help() help} attribute is ignored.
1125 * </p><p><b>Implementation note:</b></p><p>
1126 * When an error occurs while processing the help request, it is recommended custom Help commands throw a
1127 * {@link ParameterException} with a reference to the parent command. This will print the error message and the
1128 * usage for the parent command, and will use the exit code of the exception handler if one was set.
1129 * </p>
1130 * @param parsedCommands the list of {@code CommandLine} objects to check if help was requested
1131 * @param out the {@code PrintStream} to print help to if requested
1132 * @param err the error string to print diagnostic messages to, in addition to the output from the exception handler
1133 * @param colorScheme for printing help messages using ANSI styles and colors
1134 * @return {@code true} if help was printed, {@code false} otherwise
1135 * @see IHelpCommandInitializable
1136 * @since 3.6 */
1137 public static boolean printHelpIfRequested(List<CommandLine> parsedCommands, PrintStream out, PrintStream err, Help.ColorScheme colorScheme) {
1138 for (int i = 0; i < parsedCommands.size(); i++) {
1139 CommandLine parsed = parsedCommands.get(i);
1140 if (parsed.isUsageHelpRequested()) {
1141 parsed.usage(out, colorScheme);
1142 return true;
1143 } else if (parsed.isVersionHelpRequested()) {
1144 parsed.printVersionHelp(out, colorScheme.ansi);
1145 return true;
1146 } else if (parsed.getCommandSpec().helpCommand()) {
1147 if (parsed.getCommand() instanceof IHelpCommandInitializable) {
1148 ((IHelpCommandInitializable) parsed.getCommand()).init(parsed, colorScheme.ansi, out, err);
1149 }
1150 execute(parsed, new ArrayList<Object>());
1151 return true;
1152 }
1153 }
1154 return false;
1155 }
1156 private static List<Object> execute(CommandLine parsed, List<Object> executionResult) {
1157 Object command = parsed.getCommand();
1158 if (command instanceof Runnable) {
1159 try {
1160 ((Runnable) command).run();
1161 executionResult.add(null); // for compatibility with picocli 2.x
1162 return executionResult;
1163 } catch (ParameterException ex) {
1164 throw ex;
1165 } catch (ExecutionException ex) {
1166 throw ex;
1167 } catch (Exception ex) {
1168 throw new ExecutionException(parsed, "Error while running command (" + command + "): " + ex, ex);
1169 }
1170 } else if (command instanceof Callable) {
1171 try {
1172 @SuppressWarnings("unchecked") Callable<Object> callable = (Callable<Object>) command;
1173 executionResult.add(callable.call());
1174 return executionResult;
1175 } catch (ParameterException ex) {
1176 throw ex;
1177 } catch (ExecutionException ex) {
1178 throw ex;
1179 } catch (Exception ex) {
1180 throw new ExecutionException(parsed, "Error while calling command (" + command + "): " + ex, ex);
1181 }
1182 } else if (command instanceof Method) {
1183 try {
1184 if (Modifier.isStatic(((Method) command).getModifiers())) {
1185 // invoke static method
1186 executionResult.add(((Method) command).invoke(null, parsed.getCommandSpec().argValues()));
1187 return executionResult;
1188 } else if (parsed.getCommandSpec().parent() != null) {
1189 executionResult.add(((Method) command).invoke(parsed.getCommandSpec().parent().userObject(), parsed.getCommandSpec().argValues()));
1190 return executionResult;
1191 } else {
1192 for (Constructor<?> constructor : ((Method) command).getDeclaringClass().getDeclaredConstructors()) {
1193 if (constructor.getParameterTypes().length == 0) {
1194 executionResult.add(((Method) command).invoke(constructor.newInstance(), parsed.getCommandSpec().argValues()));
1195 return executionResult;
1196 }
1197 }
1198 throw new UnsupportedOperationException("Invoking non-static method without default constructor not implemented");
1199 }
1200 } catch (InvocationTargetException ex) {
1201 Throwable t = ex.getTargetException();
1202 if (t instanceof ParameterException) {
1203 throw (ParameterException) t;
1204 } else if (t instanceof ExecutionException) {
1205 throw (ExecutionException) t;
1206 } else {
1207 throw new ExecutionException(parsed, "Error while calling command (" + command + "): " + t, t);
1208 }
1209 } catch (Exception ex) {
1210 throw new ExecutionException(parsed, "Unhandled error while calling command (" + command + "): " + ex, ex);
1211 }
1212 }
1213 throw new ExecutionException(parsed, "Parsed command (" + command + ") is not Method, Runnable or Callable");
1214 }
1215 /** Command line parse result handler that returns a value. This handler prints help if requested, and otherwise calls
1216 * {@link #handle(CommandLine.ParseResult)} with the parse result. Facilitates implementation of the {@link IParseResultHandler2} interface.
1217 * <p>Note that {@code AbstractParseResultHandler} is a generic type. This, along with the abstract {@code self} method,
1218 * allows method chaining to work properly in subclasses, without the need for casts. An example subclass can look like this:</p>
1219 * <pre>{@code
1220 * class MyResultHandler extends AbstractParseResultHandler<MyReturnType> {
1221 *
1222 * protected MyReturnType handle(ParseResult parseResult) throws ExecutionException { ... }
1223 *
1224 * protected MyResultHandler self() { return this; }
1225 * }
1226 * }</pre>
1227 * @since 3.0 */
1228 public abstract static class AbstractParseResultHandler<R> extends AbstractHandler<R, AbstractParseResultHandler<R>> implements IParseResultHandler2<R> {
1229 /** Prints help if requested, and otherwise calls {@link #handle(CommandLine.ParseResult)}.
1230 * Finally, either a list of result objects is returned, or the JVM is terminated if an exit code {@linkplain #andExit(int) was set}.
1231 *
1232 * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments
1233 * @return the result of {@link #handle(ParseResult) processing parse results}
1234 * @throws ParameterException if the {@link HelpCommand HelpCommand} was invoked for an unknown subcommand. Any {@code ParameterExceptions}
1235 * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler2}
1236 * @throws ExecutionException if a problem occurred while processing the parse results; client code can use
1237 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1238 */
1239 public R handleParseResult(ParseResult parseResult) throws ExecutionException {
1240 if (printHelpIfRequested(parseResult.asCommandLineList(), out(), err(), ansi())) {
1241 return returnResultOrExit(null);
1242 }
1243 return returnResultOrExit(handle(parseResult));
1244 }
1245
1246 /** Processes the specified {@code ParseResult} and returns the result as a list of objects.
1247 * Implementations are responsible for catching any exceptions thrown in the {@code handle} method, and
1248 * rethrowing an {@code ExecutionException} that details the problem and captures the offending {@code CommandLine} object.
1249 *
1250 * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments
1251 * @return the result of processing parse results
1252 * @throws ExecutionException if a problem occurred while processing the parse results; client code can use
1253 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1254 */
1255 protected abstract R handle(ParseResult parseResult) throws ExecutionException;
1256 }
1257 /**
1258 * Command line parse result handler that prints help if requested, and otherwise executes the top-level
1259 * {@code Runnable} or {@code Callable} command.
1260 * For use in the {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) parseWithHandler} methods.
1261 * @since 2.0 */
1262 public static class RunFirst extends AbstractParseResultHandler<List<Object>> implements IParseResultHandler {
1263 /** Prints help if requested, and otherwise executes the top-level {@code Runnable} or {@code Callable} command.
1264 * Finally, either a list of result objects is returned, or the JVM is terminated if an exit code {@linkplain #andExit(int) was set}.
1265 * If the top-level command does not implement either {@code Runnable} or {@code Callable}, an {@code ExecutionException}
1266 * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
1267 *
1268 * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
1269 * @param out the {@code PrintStream} to print help to if requested
1270 * @param ansi for printing help messages using ANSI styles and colors
1271 * @return an empty list if help was requested, or a list containing a single element: the result of calling the
1272 * {@code Callable}, or a {@code null} element if the top-level command was a {@code Runnable}
1273 * @throws ParameterException if the {@link HelpCommand HelpCommand} was invoked for an unknown subcommand. Any {@code ParameterExceptions}
1274 * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler}
1275 * @throws ExecutionException if a problem occurred while processing the parse results; use
1276 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1277 */
1278 public List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
1279 if (printHelpIfRequested(parsedCommands, out, err(), ansi)) { return returnResultOrExit(Collections.emptyList()); }
1280 return returnResultOrExit(execute(parsedCommands.get(0), new ArrayList<Object>()));
1281 }
1282 /** Executes the top-level {@code Runnable} or {@code Callable} subcommand.
1283 * If the top-level command does not implement either {@code Runnable} or {@code Callable}, an {@code ExecutionException}
1284 * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
1285 *
1286 * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments
1287 * @return an empty list if help was requested, or a list containing a single element: the result of calling the
1288 * {@code Callable}, or a {@code null} element if the last (sub)command was a {@code Runnable}
1289 * @throws ExecutionException if a problem occurred while processing the parse results; use
1290 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1291 * @since 3.0 */
1292 protected List<Object> handle(ParseResult parseResult) throws ExecutionException {
1293 return execute(parseResult.commandSpec().commandLine(), new ArrayList<Object>()); // first
1294 }
1295 @Override protected RunFirst self() { return this; }
1296 }
1297 /**
1298 * Command line parse result handler that prints help if requested, and otherwise executes the most specific
1299 * {@code Runnable} or {@code Callable} subcommand.
1300 * For use in the {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) parseWithHandler} methods.
1301 * <p>
1302 * Something like this:</p>
1303 * <pre>{@code
1304 * // RunLast implementation: print help if requested, otherwise execute the most specific subcommand
1305 * List<CommandLine> parsedCommands = parseResult.asCommandLineList();
1306 * if (CommandLine.printHelpIfRequested(parsedCommands, out(), err(), ansi())) {
1307 * return emptyList();
1308 * }
1309 * CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
1310 * Object command = last.getCommand();
1311 * Object result = null;
1312 * if (command instanceof Runnable) {
1313 * try {
1314 * ((Runnable) command).run();
1315 * } catch (Exception ex) {
1316 * throw new ExecutionException(last, "Error in runnable " + command, ex);
1317 * }
1318 * } else if (command instanceof Callable) {
1319 * try {
1320 * result = ((Callable) command).call();
1321 * } catch (Exception ex) {
1322 * throw new ExecutionException(last, "Error in callable " + command, ex);
1323 * }
1324 * } else {
1325 * throw new ExecutionException(last, "Parsed command (" + command + ") is not Runnable or Callable");
1326 * }
1327 * if (hasExitCode()) { System.exit(exitCode()); }
1328 * return Arrays.asList(result);
1329 * }</pre>
1330 * <p>
1331 * From picocli v2.0, {@code RunLast} is used to implement the {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...) run}
1332 * and {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...) call} convenience methods.
1333 * </p>
1334 * @since 2.0 */
1335 public static class RunLast extends AbstractParseResultHandler<List<Object>> implements IParseResultHandler {
1336 /** Prints help if requested, and otherwise executes the most specific {@code Runnable} or {@code Callable} subcommand.
1337 * Finally, either a list of result objects is returned, or the JVM is terminated if an exit code {@linkplain #andExit(int) was set}.
1338 * If the last (sub)command does not implement either {@code Runnable} or {@code Callable}, an {@code ExecutionException}
1339 * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
1340 *
1341 * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
1342 * @param out the {@code PrintStream} to print help to if requested
1343 * @param ansi for printing help messages using ANSI styles and colors
1344 * @return an empty list if help was requested, or a list containing a single element: the result of calling the
1345 * {@code Callable}, or a {@code null} element if the last (sub)command was a {@code Runnable}
1346 * @throws ParameterException if the {@link HelpCommand HelpCommand} was invoked for an unknown subcommand. Any {@code ParameterExceptions}
1347 * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler}
1348 * @throws ExecutionException if a problem occurred while processing the parse results; use
1349 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1350 */
1351 public List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
1352 if (printHelpIfRequested(parsedCommands, out, err(), ansi)) { return returnResultOrExit(Collections.emptyList()); }
1353 return returnResultOrExit(execute(parsedCommands.get(parsedCommands.size() - 1), new ArrayList<Object>()));
1354 }
1355 /** Executes the most specific {@code Runnable} or {@code Callable} subcommand.
1356 * If the last (sub)command does not implement either {@code Runnable} or {@code Callable}, an {@code ExecutionException}
1357 * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
1358 *
1359 * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments
1360 * @return an empty list if help was requested, or a list containing a single element: the result of calling the
1361 * {@code Callable}, or a {@code null} element if the last (sub)command was a {@code Runnable}
1362 * @throws ExecutionException if a problem occurred while processing the parse results; use
1363 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1364 * @since 3.0 */
1365 protected List<Object> handle(ParseResult parseResult) throws ExecutionException {
1366 List<CommandLine> parsedCommands = parseResult.asCommandLineList();
1367 return execute(parsedCommands.get(parsedCommands.size() - 1), new ArrayList<Object>());
1368 }
1369 @Override protected RunLast self() { return this; }
1370 }
1371 /**
1372 * Command line parse result handler that prints help if requested, and otherwise executes the top-level command and
1373 * all subcommands as {@code Runnable} or {@code Callable}.
1374 * For use in the {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) parseWithHandler} methods.
1375 * @since 2.0 */
1376 public static class RunAll extends AbstractParseResultHandler<List<Object>> implements IParseResultHandler {
1377 /** Prints help if requested, and otherwise executes the top-level command and all subcommands as {@code Runnable}
1378 * or {@code Callable}. Finally, either a list of result objects is returned, or the JVM is terminated if an exit
1379 * code {@linkplain #andExit(int) was set}. If any of the {@code CommandLine} commands does not implement either
1380 * {@code Runnable} or {@code Callable}, an {@code ExecutionException}
1381 * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
1382 *
1383 * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
1384 * @param out the {@code PrintStream} to print help to if requested
1385 * @param ansi for printing help messages using ANSI styles and colors
1386 * @return an empty list if help was requested, or a list containing the result of executing all commands:
1387 * the return values from calling the {@code Callable} commands, {@code null} elements for commands that implement {@code Runnable}
1388 * @throws ParameterException if the {@link HelpCommand HelpCommand} was invoked for an unknown subcommand. Any {@code ParameterExceptions}
1389 * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler}
1390 * @throws ExecutionException if a problem occurred while processing the parse results; use
1391 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1392 */
1393 public List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
1394 if (printHelpIfRequested(parsedCommands, out, err(), ansi)) { return returnResultOrExit(Collections.emptyList()); }
1395 List<Object> result = new ArrayList<Object>();
1396 for (CommandLine parsed : parsedCommands) {
1397 execute(parsed, result);
1398 }
1399 return returnResultOrExit(result);
1400 }
1401 /** Executes the top-level command and all subcommands as {@code Runnable} or {@code Callable}.
1402 * If any of the {@code CommandLine} commands does not implement either {@code Runnable} or {@code Callable}, an {@code ExecutionException}
1403 * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
1404 *
1405 * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments
1406 * @return an empty list if help was requested, or a list containing the result of executing all commands:
1407 * the return values from calling the {@code Callable} commands, {@code null} elements for commands that implement {@code Runnable}
1408 * @throws ExecutionException if a problem occurred while processing the parse results; use
1409 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1410 * @since 3.0 */
1411 protected List<Object> handle(ParseResult parseResult) throws ExecutionException {
1412 List<Object> result = new ArrayList<Object>();
1413 execute(parseResult.commandSpec().commandLine(), result);
1414 while (parseResult.hasSubcommand()) {
1415 parseResult = parseResult.subcommand();
1416 execute(parseResult.commandSpec().commandLine(), result);
1417 }
1418 return returnResultOrExit(result);
1419 }
1420 @Override protected RunAll self() { return this; }
1421 }
1422
1423 /** @deprecated use {@link #parseWithHandler(IParseResultHandler2, String[])} instead
1424 * @since 2.0 */
1425 @Deprecated public List<Object> parseWithHandler(IParseResultHandler handler, PrintStream out, String... args) {
1426 return parseWithHandlers(handler, out, Help.Ansi.AUTO, defaultExceptionHandler(), args);
1427 }
1428 /**
1429 * Returns the result of calling {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)} with
1430 * a new {@link DefaultExceptionHandler} in addition to the specified parse result handler and the specified command line arguments.
1431 * <p>
1432 * This is a convenience method intended to offer the same ease of use as the {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...) run}
1433 * and {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...) call} methods, but with more flexibility and better
1434 * support for nested subcommands.
1435 * </p>
1436 * <p>Calling this method roughly expands to:</p>
1437 * <pre>{@code
1438 * try {
1439 * ParseResult parseResult = parseArgs(args);
1440 * return handler.handleParseResult(parseResult);
1441 * } catch (ParameterException ex) {
1442 * return new DefaultExceptionHandler<R>().handleParseException(ex, args);
1443 * }
1444 * }</pre>
1445 * <p>
1446 * Picocli provides some default handlers that allow you to accomplish some common tasks with very little code.
1447 * The following handlers are available:</p>
1448 * <ul>
1449 * <li>{@link RunLast} handler prints help if requested, and otherwise gets the last specified command or subcommand
1450 * and tries to execute it as a {@code Runnable} or {@code Callable}.</li>
1451 * <li>{@link RunFirst} handler prints help if requested, and otherwise executes the top-level command as a {@code Runnable} or {@code Callable}.</li>
1452 * <li>{@link RunAll} handler prints help if requested, and otherwise executes all recognized commands and subcommands as {@code Runnable} or {@code Callable} tasks.</li>
1453 * <li>{@link DefaultExceptionHandler} prints the error message followed by usage help</li>
1454 * </ul>
1455 * @param <R> the return type of this handler
1456 * @param handler the function that will handle the result of successfully parsing the command line arguments
1457 * @param args the command line arguments
1458 * @return an object resulting from handling the parse result or the exception that occurred while parsing the input
1459 * @throws ExecutionException if the command line arguments were parsed successfully but a problem occurred while processing the
1460 * parse results; use {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1461 * @see RunLast
1462 * @see RunAll
1463 * @since 3.0 */
1464 public <R> R parseWithHandler(IParseResultHandler2<R> handler, String[] args) {
1465 return parseWithHandlers(handler, new DefaultExceptionHandler<R>(), args);
1466 }
1467
1468 /** @deprecated use {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)} instead
1469 * @since 2.0 */
1470 @Deprecated public List<Object> parseWithHandlers(IParseResultHandler handler, PrintStream out, Help.Ansi ansi, IExceptionHandler exceptionHandler, String... args) {
1471 try {
1472 List<CommandLine> result = parse(args);
1473 return handler.handleParseResult(result, out, ansi);
1474 } catch (ParameterException ex) {
1475 return exceptionHandler.handleException(ex, out, ansi, args);
1476 }
1477 }
1478 /**
1479 * Tries to {@linkplain #parseArgs(String...) parse} the specified command line arguments, and if successful, delegates
1480 * the processing of the resulting {@code ParseResult} object to the specified {@linkplain IParseResultHandler2 handler}.
1481 * If the command line arguments were invalid, the {@code ParameterException} thrown from the {@code parse} method
1482 * is caught and passed to the specified {@link IExceptionHandler2}.
1483 * <p>
1484 * This is a convenience method intended to offer the same ease of use as the {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...) run}
1485 * and {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...) call} methods, but with more flexibility and better
1486 * support for nested subcommands.
1487 * </p>
1488 * <p>Calling this method roughly expands to:</p>
1489 * <pre>
1490 * ParseResult parseResult = null;
1491 * try {
1492 * parseResult = parseArgs(args);
1493 * return handler.handleParseResult(parseResult);
1494 * } catch (ParameterException ex) {
1495 * return exceptionHandler.handleParseException(ex, (String[]) args);
1496 * } catch (ExecutionException ex) {
1497 * return exceptionHandler.handleExecutionException(ex, parseResult);
1498 * }
1499 * </pre>
1500 * <p>
1501 * Picocli provides some default handlers that allow you to accomplish some common tasks with very little code.
1502 * The following handlers are available:</p>
1503 * <ul>
1504 * <li>{@link RunLast} handler prints help if requested, and otherwise gets the last specified command or subcommand
1505 * and tries to execute it as a {@code Runnable} or {@code Callable}.</li>
1506 * <li>{@link RunFirst} handler prints help if requested, and otherwise executes the top-level command as a {@code Runnable} or {@code Callable}.</li>
1507 * <li>{@link RunAll} handler prints help if requested, and otherwise executes all recognized commands and subcommands as {@code Runnable} or {@code Callable} tasks.</li>
1508 * <li>{@link DefaultExceptionHandler} prints the error message followed by usage help</li>
1509 * </ul>
1510 *
1511 * @param handler the function that will handle the result of successfully parsing the command line arguments
1512 * @param exceptionHandler the function that can handle the {@code ParameterException} thrown when the command line arguments are invalid
1513 * @param args the command line arguments
1514 * @return an object resulting from handling the parse result or the exception that occurred while parsing the input
1515 * @throws ExecutionException if the command line arguments were parsed successfully but a problem occurred while processing the parse
1516 * result {@code ParseResult} object; use {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1517 * @param <R> the return type of the result handler and exception handler
1518 * @see RunLast
1519 * @see RunAll
1520 * @see DefaultExceptionHandler
1521 * @since 3.0 */
1522 public <R> R parseWithHandlers(IParseResultHandler2<R> handler, IExceptionHandler2<R> exceptionHandler, String... args) {
1523 ParseResult parseResult = null;
1524 try {
1525 parseResult = parseArgs(args);
1526 return handler.handleParseResult(parseResult);
1527 } catch (ParameterException ex) {
1528 return exceptionHandler.handleParseException(ex, args);
1529 } catch (ExecutionException ex) {
1530 return exceptionHandler.handleExecutionException(ex, parseResult);
1531 }
1532 }
1533 static String versionString() {
1534 return String.format("%s, JVM: %s (%s %s %s), OS: %s %s %s", VERSION,
1535 System.getProperty("java.version"), System.getProperty("java.vendor"), System.getProperty("java.vm.name"), System.getProperty("java.vm.version"),
1536 System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch"));
1537 }
1538 /**
1539 * Equivalent to {@code new CommandLine(command).usage(out)}. See {@link #usage(PrintStream)} for details.
1540 * @param command the object annotated with {@link Command}, {@link Option} and {@link Parameters}
1541 * @param out the print stream to print the help message to
1542 * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1543 */
1544 public static void usage(Object command, PrintStream out) {
1545 toCommandLine(command, new DefaultFactory()).usage(out);
1546 }
1547
1548 /**
1549 * Equivalent to {@code new CommandLine(command).usage(out, ansi)}.
1550 * See {@link #usage(PrintStream, Help.Ansi)} for details.
1551 * @param command the object annotated with {@link Command}, {@link Option} and {@link Parameters}
1552 * @param out the print stream to print the help message to
1553 * @param ansi whether the usage message should contain ANSI escape codes or not
1554 * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1555 */
1556 public static void usage(Object command, PrintStream out, Help.Ansi ansi) {
1557 toCommandLine(command, new DefaultFactory()).usage(out, ansi);
1558 }
1559
1560 /**
1561 * Equivalent to {@code new CommandLine(command).usage(out, colorScheme)}.
1562 * See {@link #usage(PrintStream, Help.ColorScheme)} for details.
1563 * @param command the object annotated with {@link Command}, {@link Option} and {@link Parameters}
1564 * @param out the print stream to print the help message to
1565 * @param colorScheme the {@code ColorScheme} defining the styles for options, parameters and commands when ANSI is enabled
1566 * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1567 */
1568 public static void usage(Object command, PrintStream out, Help.ColorScheme colorScheme) {
1569 toCommandLine(command, new DefaultFactory()).usage(out, colorScheme);
1570 }
1571
1572 /**
1573 * Delegates to {@link #usage(PrintStream, Help.Ansi)} with the {@linkplain Help.Ansi#AUTO platform default}.
1574 * @param out the printStream to print to
1575 * @see #usage(PrintStream, Help.ColorScheme)
1576 */
1577 public void usage(PrintStream out) { usage(out, Help.Ansi.AUTO); }
1578 /**
1579 * Delegates to {@link #usage(PrintWriter, Help.Ansi)} with the {@linkplain Help.Ansi#AUTO platform default}.
1580 * @param writer the PrintWriter to print to
1581 * @see #usage(PrintWriter, Help.ColorScheme)
1582 * @since 3.0 */
1583 public void usage(PrintWriter writer) { usage(writer, Help.Ansi.AUTO); }
1584
1585 /**
1586 * Delegates to {@link #usage(PrintStream, Help.ColorScheme)} with the {@linkplain Help#defaultColorScheme(CommandLine.Help.Ansi) default color scheme}.
1587 * @param out the printStream to print to
1588 * @param ansi whether the usage message should include ANSI escape codes or not
1589 * @see #usage(PrintStream, Help.ColorScheme)
1590 */
1591 public void usage(PrintStream out, Help.Ansi ansi) { usage(out, Help.defaultColorScheme(ansi)); }
1592 /** Similar to {@link #usage(PrintStream, Help.Ansi)} but with the specified {@code PrintWriter} instead of a {@code PrintStream}.
1593 * @since 3.0 */
1594 public void usage(PrintWriter writer, Help.Ansi ansi) { usage(writer, Help.defaultColorScheme(ansi)); }
1595
1596 /**
1597 * Prints a usage help message for the annotated command class to the specified {@code PrintStream}.
1598 * Delegates construction of the usage help message to the {@link Help} inner class and is equivalent to:
1599 * <pre>
1600 * Help.ColorScheme colorScheme = Help.defaultColorScheme(Help.Ansi.AUTO);
1601 * Help help = getHelpFactory().create(getCommandSpec(), colorScheme)
1602 * StringBuilder sb = new StringBuilder();
1603 * for (String key : getHelpSectionKeys()) {
1604 * IHelpSectionRenderer renderer = getHelpSectionMap().get(key);
1605 * if (renderer != null) { sb.append(renderer.render(help)); }
1606 * }
1607 * out.print(sb);
1608 * </pre>
1609 * <p>Annotate your class with {@link Command} to control many aspects of the usage help message, including
1610 * the program name, text of section headings and section contents, and some aspects of the auto-generated sections
1611 * of the usage help message.
1612 * <p>To customize the auto-generated sections of the usage help message, like how option details are displayed,
1613 * instantiate a {@link Help} object and use a {@link Help.TextTable} with more of fewer columns, a custom
1614 * {@linkplain Help.Layout layout}, and/or a custom option {@linkplain Help.IOptionRenderer renderer}
1615 * for ultimate control over which aspects of an Option or Field are displayed where.</p>
1616 * @param out the {@code PrintStream} to print the usage help message to
1617 * @param colorScheme the {@code ColorScheme} defining the styles for options, parameters and commands when ANSI is enabled
1618 * @see UsageMessageSpec
1619 */
1620 public void usage(PrintStream out, Help.ColorScheme colorScheme) {
1621 out.print(usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), colorScheme)));
1622 }
1623 /** Similar to {@link #usage(PrintStream, Help.ColorScheme)}, but with the specified {@code PrintWriter} instead of a {@code PrintStream}.
1624 * @since 3.0 */
1625 public void usage(PrintWriter writer, Help.ColorScheme colorScheme) {
1626 writer.print(usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), colorScheme)));
1627 }
1628 /** Similar to {@link #usage(PrintStream)}, but returns the usage help message as a String instead of printing it to the {@code PrintStream}.
1629 * @since 3.2 */
1630 public String getUsageMessage() {
1631 return usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), Help.defaultColorScheme(Help.Ansi.AUTO))).toString();
1632 }
1633 /** Similar to {@link #usage(PrintStream, Help.Ansi)}, but returns the usage help message as a String instead of printing it to the {@code PrintStream}.
1634 * @since 3.2 */
1635 public String getUsageMessage(Help.Ansi ansi) {
1636 return usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), Help.defaultColorScheme(ansi))).toString();
1637 }
1638 /** Similar to {@link #usage(PrintStream, Help.ColorScheme)}, but returns the usage help message as a String instead of printing it to the {@code PrintStream}.
1639 * @since 3.2 */
1640 public String getUsageMessage(Help.ColorScheme colorScheme) {
1641 return usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), colorScheme)).toString();
1642 }
1643
1644 private StringBuilder usage(StringBuilder sb, Help help) {
1645 for (String key : getHelpSectionKeys()) {
1646 IHelpSectionRenderer renderer = getHelpSectionMap().get(key);
1647 if (renderer != null) { sb.append(renderer.render(help)); }
1648 }
1649 return sb;
1650 }
1651
1652 /**
1653 * Delegates to {@link #printVersionHelp(PrintStream, Help.Ansi)} with the {@linkplain Help.Ansi#AUTO platform default}.
1654 * @param out the printStream to print to
1655 * @see #printVersionHelp(PrintStream, Help.Ansi)
1656 * @since 0.9.8
1657 */
1658 public void printVersionHelp(PrintStream out) { printVersionHelp(out, Help.Ansi.AUTO); }
1659
1660 /**
1661 * Prints version information from the {@link Command#version()} annotation to the specified {@code PrintStream}.
1662 * Each element of the array of version strings is printed on a separate line. Version strings may contain
1663 * <a href="http://picocli.info/#_usage_help_with_styles_and_colors">markup for colors and style</a>.
1664 * @param out the printStream to print to
1665 * @param ansi whether the usage message should include ANSI escape codes or not
1666 * @see Command#version()
1667 * @see Option#versionHelp()
1668 * @see #isVersionHelpRequested()
1669 * @since 0.9.8
1670 */
1671 public void printVersionHelp(PrintStream out, Help.Ansi ansi) {
1672 for (String versionInfo : getCommandSpec().version()) {
1673 out.println(ansi.new Text(versionInfo));
1674 }
1675 }
1676 /**
1677 * Prints version information from the {@link Command#version()} annotation to the specified {@code PrintStream}.
1678 * Each element of the array of version strings is {@linkplain String#format(String, Object...) formatted} with the
1679 * specified parameters, and printed on a separate line. Both version strings and parameters may contain
1680 * <a href="http://picocli.info/#_usage_help_with_styles_and_colors">markup for colors and style</a>.
1681 * @param out the printStream to print to
1682 * @param ansi whether the usage message should include ANSI escape codes or not
1683 * @param params Arguments referenced by the format specifiers in the version strings
1684 * @see Command#version()
1685 * @see Option#versionHelp()
1686 * @see #isVersionHelpRequested()
1687 * @since 1.0.0
1688 */
1689 public void printVersionHelp(PrintStream out, Help.Ansi ansi, Object... params) {
1690 for (String versionInfo : getCommandSpec().version()) {
1691 out.println(ansi.new Text(format(versionInfo, params)));
1692 }
1693 }
1694
1695 /**
1696 * Delegates to {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.out} for
1697 * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
1698 * @param callable the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
1699 * @param args the command line arguments to parse
1700 * @param <C> the annotated object must implement Callable
1701 * @param <T> the return type of the most specific command (must implement {@code Callable})
1702 * @see #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)
1703 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1704 * @throws ExecutionException if the Callable throws an exception
1705 * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable
1706 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1707 * @since 3.0
1708 */
1709 public static <C extends Callable<T>, T> T call(C callable, String... args) {
1710 return call(callable, System.out, System.err, Help.Ansi.AUTO, args);
1711 }
1712
1713 /**
1714 * Delegates to {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.err} for
1715 * diagnostic error messages and {@link Help.Ansi#AUTO}.
1716 * @param callable the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
1717 * @param out the printStream to print the usage help message to when the user requested help
1718 * @param args the command line arguments to parse
1719 * @param <C> the annotated object must implement Callable
1720 * @param <T> the return type of the most specific command (must implement {@code Callable})
1721 * @see #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)
1722 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1723 * @throws ExecutionException if the Callable throws an exception
1724 * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable
1725 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1726 * @see RunLast
1727 */
1728 public static <C extends Callable<T>, T> T call(C callable, PrintStream out, String... args) {
1729 return call(callable, out, System.err, Help.Ansi.AUTO, args);
1730 }
1731 /**
1732 * Delegates to {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.err} for diagnostic error messages.
1733 * @param callable the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
1734 * @param out the printStream to print the usage help message to when the user requested help
1735 * @param ansi the ANSI style to use
1736 * @param args the command line arguments to parse
1737 * @param <C> the annotated object must implement Callable
1738 * @param <T> the return type of the most specific command (must implement {@code Callable})
1739 * @see #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)
1740 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1741 * @throws ExecutionException if the Callable throws an exception
1742 * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable
1743 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1744 * @see RunLast
1745 */
1746 public static <C extends Callable<T>, T> T call(C callable, PrintStream out, Help.Ansi ansi, String... args) {
1747 return call(callable, out, System.err, ansi, args);
1748 }
1749 /**
1750 * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
1751 * The annotated object needs to implement {@link Callable}. Calling this method is equivalent to:
1752 * <pre>{@code
1753 * CommandLine cmd = new CommandLine(callable);
1754 * List<Object> results = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi),
1755 * new DefaultExceptionHandler().useErr(err).useAnsi(ansi),
1756 * args);
1757 * T result = results == null || results.isEmpty() ? null : (T) results.get(0);
1758 * return result;
1759 * }</pre>
1760 * <p>
1761 * If the specified Callable command has subcommands, the {@linkplain RunLast last} subcommand specified on the
1762 * command line is executed.
1763 * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler2, String[]) parseWithHandler}
1764 * method with the {@link RunAll} handler or a custom handler.
1765 * </p><p>
1766 * Use {@link #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...) call(Class, IFactory, ...)} instead of this method
1767 * if you want to use a factory that performs Dependency Injection.
1768 * </p>
1769 * @param callable the command to call when {@linkplain #parse(String...) parsing} succeeds.
1770 * @param out the printStream to print the usage help message to when the user requested help
1771 * @param err the printStream to print diagnostic messages to
1772 * @param ansi whether the usage message should include ANSI escape codes or not
1773 * @param args the command line arguments to parse
1774 * @param <C> the annotated object must implement Callable
1775 * @param <T> the return type of the specified {@code Callable}
1776 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1777 * @throws ExecutionException if the Callable throws an exception
1778 * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable
1779 * @see #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
1780 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1781 * @see RunLast
1782 * @since 3.0
1783 */
1784 public static <C extends Callable<T>, T> T call(C callable, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) {
1785 CommandLine cmd = new CommandLine(callable);
1786 List<Object> results = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler<List<Object>>().useErr(err).useAnsi(ansi), args);
1787 @SuppressWarnings("unchecked") T result = (results == null || results.isEmpty()) ? null : (T) results.get(0);
1788 return result;
1789 }
1790 /**
1791 * Delegates to {@link #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.out} for
1792 * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
1793 * @param callableClass class of the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
1794 * @param factory the factory responsible for instantiating the specified callable class and potentially inject other components
1795 * @param args the command line arguments to parse
1796 * @param <C> the annotated class must implement Callable
1797 * @param <T> the return type of the most specific command (must implement {@code Callable})
1798 * @see #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
1799 * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1800 * @throws ExecutionException if the Callable throws an exception
1801 * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable
1802 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1803 * @since 3.2
1804 */
1805 public static <C extends Callable<T>, T> T call(Class<C> callableClass, IFactory factory, String... args) {
1806 return call(callableClass, factory, System.out, System.err, Help.Ansi.AUTO, args);
1807 }
1808 /**
1809 * Delegates to {@link #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with
1810 * {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
1811 * @param callableClass class of the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
1812 * @param factory the factory responsible for instantiating the specified callable class and potentially injecting other components
1813 * @param out the printStream to print the usage help message to when the user requested help
1814 * @param args the command line arguments to parse
1815 * @param <C> the annotated class must implement Callable
1816 * @param <T> the return type of the most specific command (must implement {@code Callable})
1817 * @see #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
1818 * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1819 * @throws ExecutionException if the Callable throws an exception
1820 * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable
1821 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1822 * @since 3.2
1823 */
1824 public static <C extends Callable<T>, T> T call(Class<C> callableClass, IFactory factory, PrintStream out, String... args) {
1825 return call(callableClass, factory, out, System.err, Help.Ansi.AUTO, args);
1826 }
1827 /**
1828 * Delegates to {@link #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with
1829 * {@code System.err} for diagnostic error messages.
1830 * @param callableClass class of the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
1831 * @param factory the factory responsible for instantiating the specified callable class and potentially injecting other components
1832 * @param out the printStream to print the usage help message to when the user requested help
1833 * @param ansi the ANSI style to use
1834 * @param args the command line arguments to parse
1835 * @param <C> the annotated class must implement Callable
1836 * @param <T> the return type of the most specific command (must implement {@code Callable})
1837 * @see #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
1838 * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1839 * @throws ExecutionException if the Callable throws an exception
1840 * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable
1841 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1842 * @since 3.2
1843 */
1844 public static <C extends Callable<T>, T> T call(Class<C> callableClass, IFactory factory, PrintStream out, Help.Ansi ansi, String... args) {
1845 return call(callableClass, factory, out, System.err, ansi, args);
1846 }
1847 /**
1848 * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
1849 * The specified {@linkplain IFactory factory} will create an instance of the specified {@code callableClass};
1850 * use this method instead of {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...) call(Callable, ...)}
1851 * if you want to use a factory that performs Dependency Injection.
1852 * The annotated class needs to implement {@link Callable}. Calling this method is equivalent to:
1853 * <pre>{@code
1854 * CommandLine cmd = new CommandLine(callableClass, factory);
1855 * List<Object> results = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi),
1856 * new DefaultExceptionHandler().useErr(err).useAnsi(ansi),
1857 * args);
1858 * T result = results == null || results.isEmpty() ? null : (T) results.get(0);
1859 * return result;
1860 * }</pre>
1861 * <p>
1862 * If the specified Callable command has subcommands, the {@linkplain RunLast last} subcommand specified on the
1863 * command line is executed.
1864 * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler2, String[]) parseWithHandler}
1865 * method with the {@link RunAll} handler or a custom handler.
1866 * </p>
1867 * @param callableClass class of the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
1868 * @param factory the factory responsible for instantiating the specified callable class and potentially injecting other components
1869 * @param out the printStream to print the usage help message to when the user requested help
1870 * @param err the printStream to print diagnostic messages to
1871 * @param ansi the ANSI style to use
1872 * @param args the command line arguments to parse
1873 * @param <C> the annotated class must implement Callable
1874 * @param <T> the return type of the most specific command (must implement {@code Callable})
1875 * @see #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
1876 * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1877 * @throws ExecutionException if the Callable throws an exception
1878 * @return {@code null} if an error occurred while parsing the command line options, or if help was requested and printed. Otherwise returns the result of calling the Callable
1879 * @see #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)
1880 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1881 * @since 3.2
1882 */
1883 public static <C extends Callable<T>, T> T call(Class<C> callableClass, IFactory factory, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) {
1884 CommandLine cmd = new CommandLine(callableClass, factory);
1885 List<Object> results = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler<List<Object>>().useErr(err).useAnsi(ansi), args);
1886 @SuppressWarnings("unchecked") T result = (results == null || results.isEmpty()) ? null : (T) results.get(0);
1887 return result;
1888 }
1889
1890 /**
1891 * Delegates to {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.out} for
1892 * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
1893 * @param runnable the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
1894 * @param args the command line arguments to parse
1895 * @param <R> the annotated object must implement Runnable
1896 * @see #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)
1897 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1898 * @throws ExecutionException if the Runnable throws an exception
1899 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1900 * @see RunLast
1901 * @since 3.0
1902 */
1903 public static <R extends Runnable> void run(R runnable, String... args) {
1904 run(runnable, System.out, System.err, Help.Ansi.AUTO, args);
1905 }
1906
1907 /**
1908 * Delegates to {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.err} for diagnostic error messages and {@link Help.Ansi#AUTO}.
1909 * @param runnable the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
1910 * @param out the printStream to print the usage help message to when the user requested help
1911 * @param args the command line arguments to parse
1912 * @param <R> the annotated object must implement Runnable
1913 * @see #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)
1914 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1915 * @throws ExecutionException if the Runnable throws an exception
1916 * @see #parseWithHandler(IParseResultHandler2, String[])
1917 * @see RunLast
1918 */
1919 public static <R extends Runnable> void run(R runnable, PrintStream out, String... args) {
1920 run(runnable, out, System.err, Help.Ansi.AUTO, args);
1921 }
1922 /**
1923 * Delegates to {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.err} for diagnostic error messages.
1924 * @param runnable the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
1925 * @param out the printStream to print the usage help message to when the user requested help
1926 * @param ansi whether the usage message should include ANSI escape codes or not
1927 * @param args the command line arguments to parse
1928 * @param <R> the annotated object must implement Runnable
1929 * @see #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)
1930 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1931 * @throws ExecutionException if the Runnable throws an exception
1932 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1933 * @see RunLast
1934 */
1935 public static <R extends Runnable> void run(R runnable, PrintStream out, Help.Ansi ansi, String... args) {
1936 run(runnable, out, System.err, ansi, args);
1937 }
1938 /**
1939 * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
1940 * The annotated object needs to implement {@link Runnable}. Calling this method is equivalent to:
1941 * <pre>{@code
1942 * CommandLine cmd = new CommandLine(runnable);
1943 * cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi),
1944 * new DefaultExceptionHandler().useErr(err).useAnsi(ansi),
1945 * args);
1946 * }</pre>
1947 * <p>
1948 * If the specified Runnable command has subcommands, the {@linkplain RunLast last} subcommand specified on the
1949 * command line is executed.
1950 * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler2, String[]) parseWithHandler}
1951 * method with the {@link RunAll} handler or a custom handler.
1952 * </p><p>
1953 * From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi) requested},
1954 * and any exceptions thrown by the {@code Runnable} are caught and rethrown wrapped in an {@code ExecutionException}.
1955 * </p><p>
1956 * Use {@link #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...) run(Class, IFactory, ...)} instead of this method
1957 * if you want to use a factory that performs Dependency Injection.
1958 * </p>
1959 * @param runnable the command to run when {@linkplain #parse(String...) parsing} succeeds.
1960 * @param out the printStream to print the usage help message to when the user requested help
1961 * @param err the printStream to print diagnostic messages to
1962 * @param ansi whether the usage message should include ANSI escape codes or not
1963 * @param args the command line arguments to parse
1964 * @param <R> the annotated object must implement Runnable
1965 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1966 * @throws ExecutionException if the Runnable throws an exception
1967 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1968 * @see RunLast
1969 * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
1970 * @since 3.0
1971 */
1972 public static <R extends Runnable> void run(R runnable, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) {
1973 CommandLine cmd = new CommandLine(runnable);
1974 cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler<List<Object>>().useErr(err).useAnsi(ansi), args);
1975 }
1976 /**
1977 * Delegates to {@link #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.out} for
1978 * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
1979 * @param runnableClass class of the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
1980 * @param factory the factory responsible for instantiating the specified Runnable class and potentially injecting other components
1981 * @param args the command line arguments to parse
1982 * @param <R> the annotated class must implement Runnable
1983 * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
1984 * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1985 * @throws ExecutionException if the Runnable throws an exception
1986 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1987 * @see RunLast
1988 * @since 3.2
1989 */
1990 public static <R extends Runnable> void run(Class<R> runnableClass, IFactory factory, String... args) {
1991 run(runnableClass, factory, System.out, System.err, Help.Ansi.AUTO, args);
1992 }
1993 /**
1994 * Delegates to {@link #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with
1995 * {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
1996 * @param runnableClass class of the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
1997 * @param factory the factory responsible for instantiating the specified Runnable class and potentially injecting other components
1998 * @param out the printStream to print the usage help message to when the user requested help
1999 * @param args the command line arguments to parse
2000 * @param <R> the annotated class must implement Runnable
2001 * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
2002 * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
2003 * @throws ExecutionException if the Runnable throws an exception
2004 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
2005 * @see RunLast
2006 * @since 3.2
2007 */
2008 public static <R extends Runnable> void run(Class<R> runnableClass, IFactory factory, PrintStream out, String... args) {
2009 run(runnableClass, factory, out, System.err, Help.Ansi.AUTO, args);
2010 }
2011 /**
2012 * Delegates to {@link #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with
2013 * {@code System.err} for diagnostic error messages.
2014 * @param runnableClass class of the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
2015 * @param factory the factory responsible for instantiating the specified Runnable class and potentially injecting other components
2016 * @param out the printStream to print the usage help message to when the user requested help
2017 * @param ansi whether the usage message should include ANSI escape codes or not
2018 * @param args the command line arguments to parse
2019 * @param <R> the annotated class must implement Runnable
2020 * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
2021 * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
2022 * @throws ExecutionException if the Runnable throws an exception
2023 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
2024 * @see RunLast
2025 * @since 3.2
2026 */
2027 public static <R extends Runnable> void run(Class<R> runnableClass, IFactory factory, PrintStream out, Help.Ansi ansi, String... args) {
2028 run(runnableClass, factory, out, System.err, ansi, args);
2029 }
2030 /**
2031 * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
2032 * The specified {@linkplain IFactory factory} will create an instance of the specified {@code runnableClass};
2033 * use this method instead of {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...) run(Runnable, ...)}
2034 * if you want to use a factory that performs Dependency Injection.
2035 * The annotated class needs to implement {@link Runnable}. Calling this method is equivalent to:
2036 * <pre>{@code
2037 * CommandLine cmd = new CommandLine(runnableClass, factory);
2038 * cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi),
2039 * new DefaultExceptionHandler().useErr(err).useAnsi(ansi),
2040 * args);
2041 * }</pre>
2042 * <p>
2043 * If the specified Runnable command has subcommands, the {@linkplain RunLast last} subcommand specified on the
2044 * command line is executed.
2045 * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler2, String[]) parseWithHandler}
2046 * method with the {@link RunAll} handler or a custom handler.
2047 * </p><p>
2048 * This method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi) requested},
2049 * and any exceptions thrown by the {@code Runnable} are caught and rethrown wrapped in an {@code ExecutionException}.
2050 * </p>
2051 * @param runnableClass class of the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
2052 * @param factory the factory responsible for instantiating the specified Runnable class and potentially injecting other components
2053 * @param out the printStream to print the usage help message to when the user requested help
2054 * @param err the printStream to print diagnostic messages to
2055 * @param ansi whether the usage message should include ANSI escape codes or not
2056 * @param args the command line arguments to parse
2057 * @param <R> the annotated class must implement Runnable
2058 * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
2059 * @throws InitializationException if the specified class cannot be instantiated by the factory, or does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
2060 * @throws ExecutionException if the Runnable throws an exception
2061 * @see #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)
2062 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
2063 * @see RunLast
2064 * @since 3.2
2065 */
2066 public static <R extends Runnable> void run(Class<R> runnableClass, IFactory factory, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) {
2067 CommandLine cmd = new CommandLine(runnableClass, factory);
2068 cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler<List<Object>>().useErr(err).useAnsi(ansi), args);
2069 }
2070
2071 /**
2072 * Delegates to {@link #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.out} for
2073 * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
2074 * @param methodName the {@code @Command}-annotated method to build a {@link CommandSpec} model from,
2075 * and run when {@linkplain #parseArgs(String...) parsing} succeeds.
2076 * @param cls the class where the {@code @Command}-annotated method is declared, or a subclass
2077 * @param args the command line arguments to parse
2078 * @see #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)
2079 * @throws InitializationException if the specified method does not have a {@link Command} annotation,
2080 * or if the specified class contains multiple {@code @Command}-annotated methods with the specified name
2081 * @throws ExecutionException if the Runnable throws an exception
2082 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
2083 * @since 3.6
2084 */
2085 public static Object invoke(String methodName, Class<?> cls, String... args) {
2086 return invoke(methodName, cls, System.out, System.err, Help.Ansi.AUTO, args);
2087 }
2088 /**
2089 * Delegates to {@link #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)} with the specified stream for
2090 * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
2091 * @param methodName the {@code @Command}-annotated method to build a {@link CommandSpec} model from,
2092 * and run when {@linkplain #parseArgs(String...) parsing} succeeds.
2093 * @param cls the class where the {@code @Command}-annotated method is declared, or a subclass
2094 * @param out the printstream to print requested help message to
2095 * @param args the command line arguments to parse
2096 * @see #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)
2097 * @throws InitializationException if the specified method does not have a {@link Command} annotation,
2098 * or if the specified class contains multiple {@code @Command}-annotated methods with the specified name
2099 * @throws ExecutionException if the Runnable throws an exception
2100 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
2101 * @since 3.6
2102 */
2103 public static Object invoke(String methodName, Class<?> cls, PrintStream out, String... args) {
2104 return invoke(methodName, cls, out, System.err, Help.Ansi.AUTO, args);
2105 }
2106 /**
2107 * Delegates to {@link #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)} with the specified stream for
2108 * requested usage help messages, {@code System.err} for diagnostic error messages, and the specified Ansi mode.
2109 * @param methodName the {@code @Command}-annotated method to build a {@link CommandSpec} model from,
2110 * and run when {@linkplain #parseArgs(String...) parsing} succeeds.
2111 * @param cls the class where the {@code @Command}-annotated method is declared, or a subclass
2112 * @param out the printstream to print requested help message to
2113 * @param ansi whether the usage message should include ANSI escape codes or not
2114 * @param args the command line arguments to parse
2115 * @see #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)
2116 * @throws InitializationException if the specified method does not have a {@link Command} annotation,
2117 * or if the specified class contains multiple {@code @Command}-annotated methods with the specified name
2118 * @throws ExecutionException if the Runnable throws an exception
2119 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
2120 * @since 3.6
2121 */
2122 public static Object invoke(String methodName, Class<?> cls, PrintStream out, Help.Ansi ansi, String... args) {
2123 return invoke(methodName, cls, out, System.err, ansi, args);
2124 }
2125 /**
2126 * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
2127 * Constructs a {@link CommandSpec} model from the {@code @Option} and {@code @Parameters}-annotated method parameters
2128 * of the {@code @Command}-annotated method, parses the specified command line arguments and invokes the specified method.
2129 * Calling this method is equivalent to:
2130 * <pre>{@code
2131 * Method commandMethod = getCommandMethods(cls, methodName).get(0);
2132 * CommandLine cmd = new CommandLine(commandMethod);
2133 * List<Object> list = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi),
2134 * new DefaultExceptionHandler().useErr(err).useAnsi(ansi),
2135 * args);
2136 * return list == null ? null : list.get(0);
2137 * }</pre>
2138 * @param methodName the {@code @Command}-annotated method to build a {@link CommandSpec} model from,
2139 * and run when {@linkplain #parseArgs(String...) parsing} succeeds.
2140 * @param cls the class where the {@code @Command}-annotated method is declared, or a subclass
2141 * @param out the printStream to print the usage help message to when the user requested help
2142 * @param err the printStream to print diagnostic messages to
2143 * @param ansi whether the usage message should include ANSI escape codes or not
2144 * @param args the command line arguments to parse
2145 * @throws InitializationException if the specified method does not have a {@link Command} annotation,
2146 * or if the specified class contains multiple {@code @Command}-annotated methods with the specified name
2147 * @throws ExecutionException if the method throws an exception
2148 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
2149 * @since 3.6
2150 */
2151 public static Object invoke(String methodName, Class<?> cls, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) {
2152 List<Method> candidates = getCommandMethods(cls, methodName);
2153 if (candidates.size() != 1) { throw new InitializationException("Expected exactly one @Command-annotated method for " + cls.getName() + "::" + methodName + "(...), but got: " + candidates); }
2154 Method method = candidates.get(0);
2155 CommandLine cmd = new CommandLine(method);
2156 List<Object> list = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler<List<Object>>().useErr(err).useAnsi(ansi), args);
2157 return list == null ? null : list.get(0);
2158 }
2159
2160 /**
2161 * Helper to get methods of a class annotated with {@link Command @Command} via reflection, optionally filtered by method name (not {@link Command#name() @Command.name}).
2162 * Methods have to be either public (inherited) members or be declared by {@code cls}, that is "inherited" static or protected methods will not be picked up.
2163 *
2164 * @param cls the class to search for methods annotated with {@code @Command}
2165 * @param methodName if not {@code null}, return only methods whose method name (not {@link Command#name() @Command.name}) equals this string. Ignored if {@code null}.
2166 * @return the matching command methods, or an empty list
2167 * @see #invoke(String, Class, String...)
2168 * @since 3.6.0
2169 */
2170 public static List<Method> getCommandMethods(Class<?> cls, String methodName) {
2171 Set<Method> candidates = new HashSet<Method>();
2172 // traverse public member methods (excludes static/non-public, includes inherited)
2173 candidates.addAll(Arrays.asList(Assert.notNull(cls, "class").getMethods()));
2174 // traverse directly declared methods (includes static/non-public, excludes inherited)
2175 candidates.addAll(Arrays.asList(Assert.notNull(cls, "class").getDeclaredMethods()));
2176
2177 List<Method> result = new ArrayList<Method>();
2178 for (Method method : candidates) {
2179 if (method.isAnnotationPresent(Command.class)) {
2180 if (methodName == null || methodName.equals(method.getName())) { result.add(method); }
2181 }
2182 }
2183 Collections.sort(result, new Comparator<Method>() {
2184 public int compare(Method o1, Method o2) { return o1.getName().compareTo(o2.getName()); }
2185 });
2186 return result;
2187 }
2188
2189 /**
2190 * Registers the specified type converter for the specified class. When initializing fields annotated with
2191 * {@link Option}, the field's type is used as a lookup key to find the associated type converter, and this
2192 * type converter converts the original command line argument string value to the correct type.
2193 * <p>
2194 * Java 8 lambdas make it easy to register custom type converters:
2195 * </p>
2196 * <pre>
2197 * commandLine.registerConverter(java.nio.file.Path.class, s -> java.nio.file.Paths.get(s));
2198 * commandLine.registerConverter(java.time.Duration.class, s -> java.time.Duration.parse(s));</pre>
2199 * <p>
2200 * Built-in type converters are pre-registered for the following java 1.5 types:
2201 * </p>
2202 * <ul>
2203 * <li>all primitive types</li>
2204 * <li>all primitive wrapper types: Boolean, Byte, Character, Double, Float, Integer, Long, Short</li>
2205 * <li>any enum</li>
2206 * <li>java.io.File</li>
2207 * <li>java.math.BigDecimal</li>
2208 * <li>java.math.BigInteger</li>
2209 * <li>java.net.InetAddress</li>
2210 * <li>java.net.URI</li>
2211 * <li>java.net.URL</li>
2212 * <li>java.nio.charset.Charset</li>
2213 * <li>java.sql.Time</li>
2214 * <li>java.util.Date</li>
2215 * <li>java.util.UUID</li>
2216 * <li>java.util.regex.Pattern</li>
2217 * <li>StringBuilder</li>
2218 * <li>CharSequence</li>
2219 * <li>String</li>
2220 * </ul>
2221 * <p>The specified converter will be registered with this {@code CommandLine} and the full hierarchy of its
2222 * subcommands and nested sub-subcommands <em>at the moment the converter is registered</em>. Subcommands added
2223 * later will not have this converter added automatically. To ensure a custom type converter is available to all
2224 * subcommands, register the type converter last, after adding subcommands.</p>
2225 *
2226 * @param cls the target class to convert parameter string values to
2227 * @param converter the class capable of converting string values to the specified target type
2228 * @param <K> the target type
2229 * @return this CommandLine object, to allow method chaining
2230 * @see #addSubcommand(String, Object)
2231 */
2232 public <K> CommandLine registerConverter(Class<K> cls, ITypeConverter<K> converter) {
2233 interpreter.converterRegistry.put(Assert.notNull(cls, "class"), Assert.notNull(converter, "converter"));
2234 for (CommandLine command : getCommandSpec().commands.values()) {
2235 command.registerConverter(cls, converter);
2236 }
2237 return this;
2238 }
2239
2240 /** Returns the String that separates option names from option values when parsing command line options.
2241 * @return the String the parser uses to separate option names from option values
2242 * @see ParserSpec#separator() */
2243 public String getSeparator() { return getCommandSpec().parser().separator(); }
2244
2245 /** Sets the String the parser uses to separate option names from option values to the specified value.
2246 * The separator may also be set declaratively with the {@link CommandLine.Command#separator()} annotation attribute.
2247 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
2248 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
2249 * later will have the default setting. To ensure a setting is applied to all
2250 * subcommands, call the setter last, after adding subcommands.</p>
2251 * @param separator the String that separates option names from option values
2252 * @see ParserSpec#separator(String)
2253 * @return this {@code CommandLine} object, to allow method chaining */
2254 public CommandLine setSeparator(String separator) {
2255 getCommandSpec().parser().separator(Assert.notNull(separator, "separator"));
2256 for (CommandLine command : getCommandSpec().subcommands().values()) {
2257 command.setSeparator(separator);
2258 }
2259 return this;
2260 }
2261
2262 /** Returns the ResourceBundle of this command or {@code null} if no resource bundle is set.
2263 * @see Command#resourceBundle()
2264 * @see CommandSpec#resourceBundle()
2265 * @since 3.6 */
2266 public ResourceBundle getResourceBundle() { return getCommandSpec().resourceBundle(); }
2267
2268 /** Sets the ResourceBundle containing usage help message strings.
2269 * <p>The specified bundle will be registered with this {@code CommandLine} and the full hierarchy of its
2270 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
2271 * later will not be impacted. To ensure a setting is applied to all
2272 * subcommands, call the setter last, after adding subcommands.</p>
2273 * @param bundle the ResourceBundle containing usage help message strings
2274 * @return this {@code CommandLine} object, to allow method chaining
2275 * @see Command#resourceBundle()
2276 * @see CommandSpec#resourceBundle(ResourceBundle)
2277 * @since 3.6 */
2278 public CommandLine setResourceBundle(ResourceBundle bundle) {
2279 getCommandSpec().resourceBundle(bundle);
2280 for (CommandLine command : getCommandSpec().subcommands().values()) {
2281 command.getCommandSpec().resourceBundle(bundle);
2282 }
2283 return this;
2284 }
2285
2286 /** Returns the maximum width of the usage help message. The default is 80.
2287 * @see UsageMessageSpec#width() */
2288 public int getUsageHelpWidth() { return getCommandSpec().usageMessage().width(); }
2289
2290 /** Sets the maximum width of the usage help message. Longer lines are wrapped.
2291 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
2292 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
2293 * later will have the default setting. To ensure a setting is applied to all
2294 * subcommands, call the setter last, after adding subcommands.</p>
2295 * @param width the maximum width of the usage help message
2296 * @see UsageMessageSpec#width(int)
2297 * @return this {@code CommandLine} object, to allow method chaining */
2298 public CommandLine setUsageHelpWidth(int width) {
2299 getCommandSpec().usageMessage().width(width);
2300 for (CommandLine command : getCommandSpec().subcommands().values()) {
2301 command.setUsageHelpWidth(width);
2302 }
2303 return this;
2304 }
2305
2306 /** Returns the command name (also called program name) displayed in the usage help synopsis.
2307 * @return the command name (also called program name) displayed in the usage
2308 * @see CommandSpec#name()
2309 * @since 2.0 */
2310 public String getCommandName() { return getCommandSpec().name(); }
2311
2312 /** Sets the command name (also called program name) displayed in the usage help synopsis to the specified value.
2313 * Note that this method only modifies the usage help message, it does not impact parsing behaviour.
2314 * The command name may also be set declaratively with the {@link CommandLine.Command#name()} annotation attribute.
2315 * @param commandName command name (also called program name) displayed in the usage help synopsis
2316 * @return this {@code CommandLine} object, to allow method chaining
2317 * @see CommandSpec#name(String)
2318 * @since 2.0 */
2319 public CommandLine setCommandName(String commandName) {
2320 getCommandSpec().name(Assert.notNull(commandName, "commandName"));
2321 return this;
2322 }
2323
2324 /** Returns whether arguments starting with {@code '@'} should be treated as the path to an argument file and its
2325 * contents should be expanded into separate arguments for each line in the specified file.
2326 * This property is {@code true} by default.
2327 * @return whether "argument files" or {@code @files} should be expanded into their content
2328 * @since 2.1 */
2329 public boolean isExpandAtFiles() { return getCommandSpec().parser().expandAtFiles(); }
2330
2331 /** Sets whether arguments starting with {@code '@'} should be treated as the path to an argument file and its
2332 * contents should be expanded into separate arguments for each line in the specified file. ({@code true} by default.)
2333 * @param expandAtFiles whether "argument files" or {@code @files} should be expanded into their content
2334 * @return this {@code CommandLine} object, to allow method chaining
2335 * @since 2.1 */
2336 public CommandLine setExpandAtFiles(boolean expandAtFiles) {
2337 getCommandSpec().parser().expandAtFiles(expandAtFiles);
2338 return this;
2339 }
2340
2341 /** Returns the character that starts a single-line comment or {@code null} if all content of argument files should
2342 * be interpreted as arguments (without comments).
2343 * If specified, all characters from the comment character to the end of the line are ignored.
2344 * @return the character that starts a single-line comment or {@code null}. The default is {@code '#'}.
2345 * @since 3.5 */
2346 public Character getAtFileCommentChar() { return getCommandSpec().parser().atFileCommentChar(); }
2347
2348 /** Sets the character that starts a single-line comment or {@code null} if all content of argument files should
2349 * be interpreted as arguments (without comments).
2350 * If specified, all characters from the comment character to the end of the line are ignored.
2351 * @param atFileCommentChar the character that starts a single-line comment or {@code null}. The default is {@code '#'}.
2352 * @return this {@code CommandLine} object, to allow method chaining
2353 * @since 3.5 */
2354 public CommandLine setAtFileCommentChar(Character atFileCommentChar) {
2355 getCommandSpec().parser().atFileCommentChar(atFileCommentChar);
2356 for (CommandLine command : getCommandSpec().subcommands().values()) {
2357 command.setAtFileCommentChar(atFileCommentChar);
2358 }
2359 return this;
2360 }
2361
2362 /** Returns whether to use a simplified argument file format that is compatible with JCommander.
2363 * In this format, every line (except empty lines and comment lines)
2364 * is interpreted as a single argument. Arguments containing whitespace do not need to be quoted.
2365 * When system property {@code "picocli.useSimplifiedAtFiles"} is defined, the system property value overrides the programmatically set value.
2366 * @return whether to use a simplified argument file format. The default is {@code false}.
2367 * @since 3.9 */
2368 public boolean isUseSimplifiedAtFiles() { return getCommandSpec().parser().useSimplifiedAtFiles(); }
2369
2370 /** Sets whether to use a simplified argument file format that is compatible with JCommander.
2371 * In this format, every line (except empty lines and comment lines)
2372 * is interpreted as a single argument. Arguments containing whitespace do not need to be quoted.
2373 * When system property {@code "picocli.useSimplifiedAtFiles"} is defined, the system property value overrides the programmatically set value.
2374 * @param simplifiedAtFiles whether to use a simplified argument file format. The default is {@code false}.
2375 * @return this {@code CommandLine} object, to allow method chaining
2376 * @since 3.9 */
2377 public CommandLine setUseSimplifiedAtFiles(boolean simplifiedAtFiles) {
2378 getCommandSpec().parser().useSimplifiedAtFiles(simplifiedAtFiles);
2379 for (CommandLine command : getCommandSpec().subcommands().values()) {
2380 command.setUseSimplifiedAtFiles(simplifiedAtFiles);
2381 }
2382 return this;
2383 }
2384 private static boolean empty(String str) { return str == null || str.trim().length() == 0; }
2385 private static boolean empty(Object[] array) { return array == null || array.length == 0; }
2386 private static String str(String[] arr, int i) { return (arr == null || arr.length <= i) ? "" : arr[i]; }
2387 private static boolean isBoolean(Class<?> type) { return type == Boolean.class || type == Boolean.TYPE; }
2388 private static CommandLine toCommandLine(Object obj, IFactory factory) { return obj instanceof CommandLine ? (CommandLine) obj : new CommandLine(obj, factory);}
2389 private static boolean isMultiValue(Class<?> cls) { return cls.isArray() || Collection.class.isAssignableFrom(cls) || Map.class.isAssignableFrom(cls); }
2390 private static String format(String formatString, Object... params) {
2391 try {
2392 return formatString == null ? "" : String.format(formatString, params);
2393 } catch (IllegalFormatException ex) {
2394 new Tracer().warn("Could not format '%s' (Underlying error: %s). " +
2395 "Using raw String: '%%n' format strings have not been replaced with newlines. " +
2396 "Please ensure to escape '%%' characters with another '%%'.%n", formatString, ex.getMessage());
2397 return formatString;
2398 }
2399 }
2400
2401 private static class NoCompletionCandidates implements Iterable<String> {
2402 public Iterator<String> iterator() { throw new UnsupportedOperationException(); }
2403 }
2404 /**
2405 * <p>
2406 * Annotate fields in your class with {@code @Option} and picocli will initialize these fields when matching
2407 * arguments are specified on the command line. In the case of command methods (annotated with {@code @Command}),
2408 * command options can be defined by annotating method parameters with {@code @Option}.
2409 * </p><p>
2410 * Command class example:
2411 * </p>
2412 * <pre>
2413 * import static picocli.CommandLine.*;
2414 *
2415 * public class MyClass {
2416 * @Parameters(description = "Any number of input files")
2417 * private List<File> files = new ArrayList<File>();
2418 *
2419 * @Option(names = { "-o", "--out" }, description = "Output file (default: print to console)")
2420 * private File outputFile;
2421 *
2422 * @Option(names = { "-v", "--verbose"}, description = "Verbose mode. Helpful for troubleshooting. Multiple -v options increase the verbosity.")
2423 * private boolean[] verbose;
2424 *
2425 * @Option(names = { "-h", "--help", "-?", "-help"}, usageHelp = true, description = "Display this help and exit")
2426 * private boolean help;
2427 * }
2428 * </pre>
2429 * <p>
2430 * A field cannot be annotated with both {@code @Parameters} and {@code @Option} or a
2431 * {@code ParameterException} is thrown.
2432 * </p>
2433 */
2434 @Retention(RetentionPolicy.RUNTIME)
2435 @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
2436 public @interface Option {
2437 /**
2438 * One or more option names. At least one option name is required.
2439 * <p>
2440 * Different environments have different conventions for naming options, but usually options have a prefix
2441 * that sets them apart from parameters.
2442 * Picocli supports all of the below styles. The default separator is {@code '='}, but this can be configured.
2443 * </p><p>
2444 * <b>*nix</b>
2445 * </p><p>
2446 * In Unix and Linux, options have a short (single-character) name, a long name or both.
2447 * Short options
2448 * (<a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02">POSIX
2449 * style</a> are single-character and are preceded by the {@code '-'} character, e.g., {@code `-v'}.
2450 * <a href="https://www.gnu.org/software/tar/manual/html_node/Long-Options.html">GNU-style</a> long
2451 * (or <em>mnemonic</em>) options start with two dashes in a row, e.g., {@code `--file'}.
2452 * </p><p>Picocli supports the POSIX convention that short options can be grouped, with the last option
2453 * optionally taking a parameter, which may be attached to the option name or separated by a space or
2454 * a {@code '='} character. The below examples are all equivalent:
2455 * </p><pre>
2456 * -xvfFILE
2457 * -xvf FILE
2458 * -xvf=FILE
2459 * -xv --file FILE
2460 * -xv --file=FILE
2461 * -x -v --file FILE
2462 * -x -v --file=FILE
2463 * </pre><p>
2464 * <b>DOS</b>
2465 * </p><p>
2466 * DOS options mostly have upper case single-character names and start with a single slash {@code '/'} character.
2467 * Option parameters are separated by a {@code ':'} character. Options cannot be grouped together but
2468 * must be specified separately. For example:
2469 * </p><pre>
2470 * DIR /S /A:D /T:C
2471 * </pre><p>
2472 * <b>PowerShell</b>
2473 * </p><p>
2474 * Windows PowerShell options generally are a word preceded by a single {@code '-'} character, e.g., {@code `-Help'}.
2475 * Option parameters are separated by a space or by a {@code ':'} character.
2476 * </p>
2477 * @return one or more option names
2478 */
2479 String[] names();
2480
2481 /**
2482 * Indicates whether this option is required. By default this is false.
2483 * <p>If an option is required, but a user invokes the program without specifying the required option,
2484 * a {@link MissingParameterException} is thrown from the {@link #parse(String...)} method.</p>
2485 * <p>Required options that are part of a {@linkplain ArgGroup group} are required <em>within the group</em>, not required within the command:
2486 * the group's {@linkplain ArgGroup#multiplicity() multiplicity} determines whether the group itself is required or optional.</p>
2487 * @return whether this option is required
2488 */
2489 boolean required() default false;
2490
2491 /**
2492 * Set {@code help=true} if this option should disable validation of the remaining arguments:
2493 * If the {@code help} option is specified, no error message is generated for missing required options.
2494 * <p>
2495 * This attribute is useful for special options like help ({@code -h} and {@code --help} on unix,
2496 * {@code -?} and {@code -Help} on Windows) or version ({@code -V} and {@code --version} on unix,
2497 * {@code -Version} on Windows).
2498 * </p>
2499 * <p>
2500 * Note that the {@link #parse(String...)} method will not print help documentation. It will only set
2501 * the value of the annotated field. It is the responsibility of the caller to inspect the annotated fields
2502 * and take the appropriate action.
2503 * </p>
2504 * @return whether this option disables validation of the other arguments
2505 * @deprecated Use {@link #usageHelp()} and {@link #versionHelp()} instead. See {@link #printHelpIfRequested(List, PrintStream, CommandLine.Help.Ansi)}
2506 */
2507 @Deprecated boolean help() default false;
2508
2509 /**
2510 * Set {@code usageHelp=true} for the {@code --help} option that triggers display of the usage help message.
2511 * The <a href="http://picocli.info/#_printing_help_automatically">convenience methods</a> {@code Commandline.call},
2512 * {@code Commandline.run}, and {@code Commandline.parseWithHandler(s)} will automatically print usage help
2513 * when an option with {@code usageHelp=true} was specified on the command line.
2514 * <p>
2515 * By default, <em>all</em> options and positional parameters are included in the usage help message
2516 * <em>except when explicitly marked {@linkplain #hidden() hidden}.</em>
2517 * </p><p>
2518 * If this option is specified on the command line, picocli will not validate the remaining arguments (so no "missing required
2519 * option" errors) and the {@link CommandLine#isUsageHelpRequested()} method will return {@code true}.
2520 * </p><p>
2521 * Alternatively, consider annotating your command with {@linkplain Command#mixinStandardHelpOptions() @Command(mixinStandardHelpOptions = true)}.
2522 * </p>
2523 * @return whether this option allows the user to request usage help
2524 * @since 0.9.8
2525 * @see #hidden()
2526 * @see #run(Runnable, String...)
2527 * @see #call(Callable, String...)
2528 * @see #parseWithHandler(IParseResultHandler2, String[])
2529 * @see #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi)
2530 */
2531 boolean usageHelp() default false;
2532
2533 /**
2534 * Set {@code versionHelp=true} for the {@code --version} option that triggers display of the version information.
2535 * The <a href="http://picocli.info/#_printing_help_automatically">convenience methods</a> {@code Commandline.call},
2536 * {@code Commandline.run}, and {@code Commandline.parseWithHandler(s)} will automatically print version information
2537 * when an option with {@code versionHelp=true} was specified on the command line.
2538 * <p>
2539 * The version information string is obtained from the command's {@linkplain Command#version() version} annotation
2540 * or from the {@linkplain Command#versionProvider() version provider}.
2541 * </p><p>
2542 * If this option is specified on the command line, picocli will not validate the remaining arguments (so no "missing required
2543 * option" errors) and the {@link CommandLine#isUsageHelpRequested()} method will return {@code true}.
2544 * </p><p>
2545 * Alternatively, consider annotating your command with {@linkplain Command#mixinStandardHelpOptions() @Command(mixinStandardHelpOptions = true)}.
2546 * </p>
2547 * @return whether this option allows the user to request version information
2548 * @since 0.9.8
2549 * @see #hidden()
2550 * @see #run(Runnable, String...)
2551 * @see #call(Callable, String...)
2552 * @see #parseWithHandler(IParseResultHandler2, String[])
2553 * @see #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi)
2554 */
2555 boolean versionHelp() default false;
2556
2557 /**
2558 * Description of this option, used when generating the usage documentation. Each element of the array is rendered on a separate line.
2559 * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.
2560 * </p><p>
2561 * The description may contain variables that are rendered when help is requested.
2562 * The string {@code ${DEFAULT-VALUE}} is replaced with the default value of the option. This is regardless of
2563 * the command's {@link Command#showDefaultValues() showDefaultValues} setting or the option's {@link #showDefaultValue() showDefaultValue} setting.
2564 * The string {@code ${COMPLETION-CANDIDATES}} is replaced with the completion candidates generated by
2565 * {@link #completionCandidates()} in the description for this option.
2566 * Also, embedded {@code %n} newline markers are converted to actual newlines.
2567 * </p>
2568 * @return the description of this option
2569 */
2570 String[] description() default {};
2571
2572 /**
2573 * Specifies the minimum number of required parameters and the maximum number of accepted parameters.
2574 * If an option declares a positive arity, and the user specifies an insufficient number of parameters on the
2575 * command line, a {@link MissingParameterException} is thrown by the {@link #parse(String...)} method.
2576 * <p>
2577 * In many cases picocli can deduce the number of required parameters from the field's type.
2578 * By default, flags (boolean options) have arity zero,
2579 * and single-valued type fields (String, int, Integer, double, Double, File, Date, etc) have arity one.
2580 * Generally, fields with types that cannot hold multiple values can omit the {@code arity} attribute.
2581 * </p><p>
2582 * Fields used to capture options with arity two or higher should have a type that can hold multiple values,
2583 * like arrays or Collections. See {@link #type()} for strongly-typed Collection fields.
2584 * </p><p>
2585 * For example, if an option has 2 required parameters and any number of optional parameters,
2586 * specify {@code @Option(names = "-example", arity = "2..*")}.
2587 * </p>
2588 * <b>A note on boolean options</b>
2589 * <p>
2590 * By default picocli does not expect boolean options (also called "flags" or "switches") to have a parameter.
2591 * You can make a boolean option take a required parameter by annotating your field with {@code arity="1"}.
2592 * For example: </p>
2593 * <pre>@Option(names = "-v", arity = "1") boolean verbose;</pre>
2594 * <p>
2595 * Because this boolean field is defined with arity 1, the user must specify either {@code <program> -v false}
2596 * or {@code <program> -v true}
2597 * on the command line, or a {@link MissingParameterException} is thrown by the {@link #parse(String...)}
2598 * method.
2599 * </p><p>
2600 * To make the boolean parameter possible but optional, define the field with {@code arity = "0..1"}.
2601 * For example: </p>
2602 * <pre>@Option(names="-v", arity="0..1") boolean verbose;</pre>
2603 * <p>This will accept any of the below without throwing an exception:</p>
2604 * <pre>
2605 * -v
2606 * -v true
2607 * -v false
2608 * </pre>
2609 * @return how many arguments this option requires
2610 */
2611 String arity() default "";
2612
2613 /**
2614 * Specify a {@code paramLabel} for the option parameter to be used in the usage help message. If omitted,
2615 * picocli uses the field name in fish brackets ({@code '<'} and {@code '>'}) by default. Example:
2616 * <pre>class Example {
2617 * @Option(names = {"-o", "--output"}, paramLabel="FILE", description="path of the output file")
2618 * private File out;
2619 * @Option(names = {"-j", "--jobs"}, arity="0..1", description="Allow N jobs at once; infinite jobs with no arg.")
2620 * private int maxJobs = -1;
2621 * }</pre>
2622 * <p>By default, the above gives a usage help message like the following:</p><pre>
2623 * Usage: <main class> [OPTIONS]
2624 * -o, --output FILE path of the output file
2625 * -j, --jobs [<maxJobs>] Allow N jobs at once; infinite jobs with no arg.
2626 * </pre>
2627 * @return name of the option parameter used in the usage help message
2628 */
2629 String paramLabel() default "";
2630
2631 /** Returns whether usage syntax decorations around the {@linkplain #paramLabel() paramLabel} should be suppressed.
2632 * The default is {@code false}: by default, the paramLabel is surrounded with {@code '['} and {@code ']'} characters
2633 * if the value is optional and followed by ellipses ("...") when multiple values can be specified.
2634 * @since 3.6.0 */
2635 boolean hideParamSyntax() default false;
2636
2637 /** <p>
2638 * Optionally specify a {@code type} to control exactly what Class the option parameter should be converted
2639 * to. This may be useful when the field type is an interface or an abstract class. For example, a field can
2640 * be declared to have type {@code java.lang.Number}, and annotating {@code @Option(type=Short.class)}
2641 * ensures that the option parameter value is converted to a {@code Short} before setting the field value.
2642 * </p><p>
2643 * For array fields whose <em>component</em> type is an interface or abstract class, specify the concrete <em>component</em> type.
2644 * For example, a field with type {@code Number[]} may be annotated with {@code @Option(type=Short.class)}
2645 * to ensure that option parameter values are converted to {@code Short} before adding an element to the array.
2646 * </p><p>
2647 * Picocli will use the {@link ITypeConverter} that is
2648 * {@linkplain #registerConverter(Class, ITypeConverter) registered} for the specified type to convert
2649 * the raw String values before modifying the field value.
2650 * </p><p>
2651 * Prior to 2.0, the {@code type} attribute was necessary for {@code Collection} and {@code Map} fields,
2652 * but starting from 2.0 picocli will infer the component type from the generic type's type arguments.
2653 * For example, for a field of type {@code Map<TimeUnit, Long>} picocli will know the option parameter
2654 * should be split up in key=value pairs, where the key should be converted to a {@code java.util.concurrent.TimeUnit}
2655 * enum value, and the value should be converted to a {@code Long}. No {@code @Option(type=...)} type attribute
2656 * is required for this. For generic types with wildcards, picocli will take the specified upper or lower bound
2657 * as the Class to convert to, unless the {@code @Option} annotation specifies an explicit {@code type} attribute.
2658 * </p><p>
2659 * If the field type is a raw collection or a raw map, and you want it to contain other values than Strings,
2660 * or if the generic type's type arguments are interfaces or abstract classes, you may
2661 * specify a {@code type} attribute to control the Class that the option parameter should be converted to.
2662 * @return the type(s) to convert the raw String values
2663 */
2664 Class<?>[] type() default {};
2665
2666 /**
2667 * Optionally specify one or more {@link ITypeConverter} classes to use to convert the command line argument into
2668 * a strongly typed value (or key-value pair for map fields). This is useful when a particular field should
2669 * use a custom conversion that is different from the normal conversion for the field's type.
2670 * <p>For example, for a specific field you may want to use a converter that maps the constant names defined
2671 * in {@link java.sql.Types java.sql.Types} to the {@code int} value of these constants, but any other {@code int} fields should
2672 * not be affected by this and should continue to use the standard int converter that parses numeric values.</p>
2673 * @return the type converter(s) to use to convert String values to strongly typed values for this field
2674 * @see CommandLine#registerConverter(Class, ITypeConverter)
2675 */
2676 Class<? extends ITypeConverter<?>>[] converter() default {};
2677
2678 /**
2679 * Specify a regular expression to use to split option parameter values before applying them to the field.
2680 * All elements resulting from the split are added to the array or Collection. Ignored for single-value fields.
2681 * @return a regular expression to split option parameter values or {@code ""} if the value should not be split
2682 * @see String#split(String)
2683 */
2684 String split() default "";
2685
2686 /**
2687 * Set {@code hidden=true} if this option should not be included in the usage help message.
2688 * @return whether this option should be excluded from the usage documentation
2689 */
2690 boolean hidden() default false;
2691
2692 /** Returns the default value of this option, before splitting and type conversion.
2693 * @return a String that (after type conversion) will be used as the value for this option if no value was specified on the command line
2694 * @since 3.2 */
2695 String defaultValue() default "__no_default_value__";
2696
2697 /** Use this attribute to control for a specific option whether its default value should be shown in the usage
2698 * help message. If not specified, the default value is only shown when the {@link Command#showDefaultValues()}
2699 * is set {@code true} on the command. Use this attribute to specify whether the default value
2700 * for this specific option should always be shown or never be shown, regardless of the command setting.
2701 * <p>Note that picocli 3.2 allows {@linkplain #description() embedding default values} anywhere in the description that ignores this setting.</p>
2702 * @return whether this option's default value should be shown in the usage help message
2703 */
2704 Help.Visibility showDefaultValue() default Help.Visibility.ON_DEMAND;
2705
2706 /** Use this attribute to specify an {@code Iterable<String>} class that generates completion candidates for this option.
2707 * For map fields, completion candidates should be in {@code key=value} form.
2708 * <p>
2709 * Completion candidates are used in bash completion scripts generated by the {@code picocli.AutoComplete} class.
2710 * Bash has special completion options to generate file names and host names, and the bash completion scripts
2711 * generated by {@code AutoComplete} delegate to these bash built-ins for {@code @Options} whose {@code type} is
2712 * {@code java.io.File}, {@code java.nio.file.Path} or {@code java.net.InetAddress}.
2713 * </p><p>
2714 * For {@code @Options} whose {@code type} is a Java {@code enum}, {@code AutoComplete} can generate completion
2715 * candidates from the type. For other types, use this attribute to specify completion candidates.
2716 * </p>
2717 *
2718 * @return a class whose instances can iterate over the completion candidates for this option
2719 * @see picocli.CommandLine.IFactory
2720 * @since 3.2 */
2721 Class<? extends Iterable<String>> completionCandidates() default NoCompletionCandidates.class;
2722
2723 /**
2724 * Set {@code interactive=true} if this option will prompt the end user for a value (like a password).
2725 * Only supported for single-value options (not arrays, collections or maps).
2726 * When running on Java 6 or greater, this will use the {@link Console#readPassword()} API to get a value without echoing input to the console.
2727 * @return whether this option prompts the end user for a value to be entered on the command line
2728 * @since 3.5
2729 */
2730 boolean interactive() default false;
2731
2732 /** ResourceBundle key for this option. If not specified, (and a ResourceBundle {@linkplain Command#resourceBundle() exists for this command}) an attempt
2733 * is made to find the option description using any of the option names (without leading hyphens) as key.
2734 * @see OptionSpec#description()
2735 * @since 3.6
2736 */
2737 String descriptionKey() default "";
2738
2739 /**
2740 * When {@link Command#sortOptions() @Command(sortOptions = false)} is specified, this attribute can be used to control the order in which options are listed in the usage help message.
2741 * @return the position in the options list at which this option should be shown. Options with a lower number are shown before options with a higher number. Gaps are allowed.
2742 * @since 3.9
2743 */
2744 int order() default -1;
2745
2746 /**
2747 * Specify the name of one or more options that this option is mutually exclusive with.
2748 * Picocli will internally create a mutually exclusive {@linkplain ArgGroup group} with all specified options (and
2749 * any options that the specified options are mutually exclusive with).
2750 * <p>
2751 * Options cannot be part of multiple groups to avoid ambiguity for the parser. Constructions
2752 * where an option is part of multiple groups must be simplified so that the option is in just one group.
2753 * For example: {@code (-a | -b) | (-a -x)} can be simplified to {@code (-a [-x] | -b)}.
2754 * </p>
2755 * @return the name or names of the option(s) that this option is mutually exclusive with.
2756 * @since 4.0
2757 */
2758 String[] excludes() default {};
2759
2760 /**
2761 * Specify the name of one or more options that this option must co-occur with.
2762 * Picocli will internally create a co-occurring {@linkplain ArgGroup group} with all specified options (and
2763 * any options that the specified options must co-occur with).
2764 * <p>
2765 * Options cannot be part of multiple groups to avoid ambiguity for the parser. Constructions
2766 * where an option is part of multiple groups must be simplified so that the option is in just one group.
2767 * For example: {@code (-a -x) | (-a -y)} can be simplified to {@code (-a [-x | -y])}.
2768 * </p>
2769 * @return the name or names of the option(s) that this option must co-occur with.
2770 * @since 4.0
2771 */
2772 String[] needs() default {};
2773 }
2774 /**
2775 * <p>
2776 * Fields annotated with {@code @Parameters} will be initialized with positional parameters. By specifying the
2777 * {@link #index()} attribute you can pick the exact position or a range of positional parameters to apply. If no
2778 * index is specified, the field will get all positional parameters (and so it should be an array or a collection).
2779 * </p><p>
2780 * In the case of command methods (annotated with {@code @Command}), method parameters may be annotated with {@code @Parameters},
2781 * but are are considered positional parameters by default, unless they are annotated with {@code @Option}.
2782 * </p><p>
2783 * Command class example:
2784 * </p>
2785 * <pre>
2786 * import static picocli.CommandLine.*;
2787 *
2788 * public class MyCalcParameters {
2789 * @Parameters(description = "Any number of input numbers")
2790 * private List<BigDecimal> files = new ArrayList<BigDecimal>();
2791 *
2792 * @Option(names = { "-h", "--help" }, usageHelp = true, description = "Display this help and exit")
2793 * private boolean help;
2794 * }
2795 * </pre><p>
2796 * A field cannot be annotated with both {@code @Parameters} and {@code @Option} or a {@code ParameterException}
2797 * is thrown.</p>
2798 */
2799 @Retention(RetentionPolicy.RUNTIME)
2800 @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
2801 public @interface Parameters {
2802 /** Specify an index ("0", or "1", etc.) to pick which of the command line arguments should be assigned to this
2803 * field. For array or Collection fields, you can also specify an index range ("0..3", or "2..*", etc.) to assign
2804 * a subset of the command line arguments to this field. The default is "*", meaning all command line arguments.
2805 * @return an index or range specifying which of the command line arguments should be assigned to this field
2806 */
2807 String index() default "";
2808
2809 /** Description of the parameter(s), used when generating the usage documentation. Each element of the array is rendered on a separate line.
2810 * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.
2811 * </p><p>
2812 * The description may contain variables that are rendered when help is requested.
2813 * The string {@code ${DEFAULT-VALUE}} is replaced with the default value of the positional parameter. This is regardless of
2814 * the command's {@link Command#showDefaultValues() showDefaultValues} setting or the positional parameter's {@link #showDefaultValue() showDefaultValue} setting.
2815 * The string {@code ${COMPLETION-CANDIDATES}} is replaced with the completion candidates generated by
2816 * {@link #completionCandidates()} in the description for this positional parameter.
2817 * Also, embedded {@code %n} newline markers are converted to actual newlines.
2818 * </p>
2819 * @return the description of the parameter(s)
2820 */
2821 String[] description() default {};
2822
2823 /**
2824 * Specifies the minimum number of required parameters and the maximum number of accepted parameters. If a
2825 * positive arity is declared, and the user specifies an insufficient number of parameters on the command line,
2826 * {@link MissingParameterException} is thrown by the {@link #parse(String...)} method.
2827 * <p>The default depends on the type of the parameter: booleans require no parameters, arrays and Collections
2828 * accept zero to any number of parameters, and any other type accepts one parameter.</p>
2829 * <p>For single-value parameters, setting {@code arity = "0..1"} makes a positional parameter optional, while setting {@code arity = "1"} makes it required.</p>
2830 * <p>Required parameters that are part of a {@linkplain ArgGroup group} are required <em>within the group</em>, not required within the command:
2831 * the group's {@linkplain ArgGroup#multiplicity() multiplicity} determines whether the group itself is required or optional.</p>
2832 * @return the range of minimum and maximum parameters accepted by this command
2833 */
2834 String arity() default "";
2835
2836 /**
2837 * Specify a {@code paramLabel} for the parameter to be used in the usage help message. If omitted,
2838 * picocli uses the field name in fish brackets ({@code '<'} and {@code '>'}) by default. Example:
2839 * <pre>class Example {
2840 * @Parameters(paramLabel="FILE", description="path of the input FILE(s)")
2841 * private File[] inputFiles;
2842 * }</pre>
2843 * <p>By default, the above gives a usage help message like the following:</p><pre>
2844 * Usage: <main class> [FILE...]
2845 * [FILE...] path of the input FILE(s)
2846 * </pre>
2847 * @return name of the positional parameter used in the usage help message
2848 */
2849 String paramLabel() default "";
2850
2851 /** Returns whether usage syntax decorations around the {@linkplain #paramLabel() paramLabel} should be suppressed.
2852 * The default is {@code false}: by default, the paramLabel is surrounded with {@code '['} and {@code ']'} characters
2853 * if the value is optional and followed by ellipses ("...") when multiple values can be specified.
2854 * @since 3.6.0 */
2855 boolean hideParamSyntax() default false;
2856
2857 /**
2858 * <p>
2859 * Optionally specify a {@code type} to control exactly what Class the positional parameter should be converted
2860 * to. This may be useful when the field type is an interface or an abstract class. For example, a field can
2861 * be declared to have type {@code java.lang.Number}, and annotating {@code @Parameters(type=Short.class)}
2862 * ensures that the positional parameter value is converted to a {@code Short} before setting the field value.
2863 * </p><p>
2864 * For array fields whose <em>component</em> type is an interface or abstract class, specify the concrete <em>component</em> type.
2865 * For example, a field with type {@code Number[]} may be annotated with {@code @Parameters(type=Short.class)}
2866 * to ensure that positional parameter values are converted to {@code Short} before adding an element to the array.
2867 * </p><p>
2868 * Picocli will use the {@link ITypeConverter} that is
2869 * {@linkplain #registerConverter(Class, ITypeConverter) registered} for the specified type to convert
2870 * the raw String values before modifying the field value.
2871 * </p><p>
2872 * Prior to 2.0, the {@code type} attribute was necessary for {@code Collection} and {@code Map} fields,
2873 * but starting from 2.0 picocli will infer the component type from the generic type's type arguments.
2874 * For example, for a field of type {@code Map<TimeUnit, Long>} picocli will know the positional parameter
2875 * should be split up in key=value pairs, where the key should be converted to a {@code java.util.concurrent.TimeUnit}
2876 * enum value, and the value should be converted to a {@code Long}. No {@code @Parameters(type=...)} type attribute
2877 * is required for this. For generic types with wildcards, picocli will take the specified upper or lower bound
2878 * as the Class to convert to, unless the {@code @Parameters} annotation specifies an explicit {@code type} attribute.
2879 * </p><p>
2880 * If the field type is a raw collection or a raw map, and you want it to contain other values than Strings,
2881 * or if the generic type's type arguments are interfaces or abstract classes, you may
2882 * specify a {@code type} attribute to control the Class that the positional parameter should be converted to.
2883 * @return the type(s) to convert the raw String values
2884 */
2885 Class<?>[] type() default {};
2886
2887 /**
2888 * Optionally specify one or more {@link ITypeConverter} classes to use to convert the command line argument into
2889 * a strongly typed value (or key-value pair for map fields). This is useful when a particular field should
2890 * use a custom conversion that is different from the normal conversion for the field's type.
2891 * <p>For example, for a specific field you may want to use a converter that maps the constant names defined
2892 * in {@link java.sql.Types java.sql.Types} to the {@code int} value of these constants, but any other {@code int} fields should
2893 * not be affected by this and should continue to use the standard int converter that parses numeric values.</p>
2894 * @return the type converter(s) to use to convert String values to strongly typed values for this field
2895 * @see CommandLine#registerConverter(Class, ITypeConverter)
2896 */
2897 Class<? extends ITypeConverter<?>>[] converter() default {};
2898
2899 /**
2900 * Specify a regular expression to use to split positional parameter values before applying them to the field.
2901 * All elements resulting from the split are added to the array or Collection. Ignored for single-value fields.
2902 * @return a regular expression to split operand values or {@code ""} if the value should not be split
2903 * @see String#split(String)
2904 */
2905 String split() default "";
2906
2907 /**
2908 * Set {@code hidden=true} if this parameter should not be included in the usage message.
2909 * @return whether this parameter should be excluded from the usage message
2910 */
2911 boolean hidden() default false;
2912
2913 /** Returns the default value of this positional parameter, before splitting and type conversion.
2914 * @return a String that (after type conversion) will be used as the value for this positional parameter if no value was specified on the command line
2915 * @since 3.2 */
2916 String defaultValue() default "__no_default_value__";
2917
2918 /** Use this attribute to control for a specific positional parameter whether its default value should be shown in the usage
2919 * help message. If not specified, the default value is only shown when the {@link Command#showDefaultValues()}
2920 * is set {@code true} on the command. Use this attribute to specify whether the default value
2921 * for this specific positional parameter should always be shown or never be shown, regardless of the command setting.
2922 * <p>Note that picocli 3.2 allows {@linkplain #description() embedding default values} anywhere in the description that ignores this setting.</p>
2923 * @return whether this positional parameter's default value should be shown in the usage help message
2924 */
2925 Help.Visibility showDefaultValue() default Help.Visibility.ON_DEMAND;
2926
2927 /** Use this attribute to specify an {@code Iterable<String>} class that generates completion candidates for
2928 * this positional parameter. For map fields, completion candidates should be in {@code key=value} form.
2929 * <p>
2930 * Completion candidates are used in bash completion scripts generated by the {@code picocli.AutoComplete} class.
2931 * Unfortunately, {@code picocli.AutoComplete} is not very good yet at generating completions for positional parameters.
2932 * </p>
2933 *
2934 * @return a class whose instances can iterate over the completion candidates for this positional parameter
2935 * @see picocli.CommandLine.IFactory
2936 * @since 3.2 */
2937 Class<? extends Iterable<String>> completionCandidates() default NoCompletionCandidates.class;
2938
2939 /**
2940 * Set {@code interactive=true} if this positional parameter will prompt the end user for a value (like a password).
2941 * Only supported for single-value positional parameters (not arrays, collections or maps).
2942 * When running on Java 6 or greater, this will use the {@link Console#readPassword()} API to get a value without echoing input to the console.
2943 * @return whether this positional parameter prompts the end user for a value to be entered on the command line
2944 * @since 3.5
2945 */
2946 boolean interactive() default false;
2947
2948 /** ResourceBundle key for this option. If not specified, (and a ResourceBundle {@linkplain Command#resourceBundle() exists for this command}) an attempt
2949 * is made to find the positional parameter description using {@code paramLabel() + "[" + index() + "]"} as key.
2950 *
2951 * @see PositionalParamSpec#description()
2952 * @since 3.6
2953 */
2954 String descriptionKey() default "";
2955
2956 /**
2957 * Specify the name of one or more options that this positional parameter is mutually exclusive with.
2958 * Picocli will internally create a mutually exclusive {@linkplain ArgGroup group} with all specified options (and
2959 * any options and positional parameters that the specified options are mutually exclusive with).
2960 * <p>
2961 * An option or positional parameter cannot be part of multiple groups to avoid ambiguity for the parser. Constructions
2962 * where an option is part of multiple groups must be simplified so that the option is in just one group.
2963 * For example: {@code (-a | -b) | (-a -x)} can be simplified to {@code (-a [-x] | -b)}.
2964 * </p>
2965 * @return the name or names of the option(s) that this positional parameter is mutually exclusive with.
2966 * @since 4.0
2967 */
2968 String[] excludes() default {};
2969
2970 /**
2971 * Specify the name of one or more options that this option must co-occur with.
2972 * Picocli will internally create a co-occurring {@linkplain ArgGroup group} with all specified options (and
2973 * any options that the specified options must co-occur with).
2974 * <p>
2975 * Options cannot be part of multiple groups to avoid ambiguity for the parser. Constructions
2976 * where an option is part of multiple groups must be simplified so that the option is in just one group.
2977 * For example: {@code (-a -x) | (-a -y)} can be simplified to {@code (-a [-x | -y])}.
2978 * </p>
2979 * @return the name or names of the option(s) that this option must co-occur with.
2980 * @since 4.0
2981 */
2982 String[] needs() default {};
2983 }
2984
2985 /**
2986 * <p>
2987 * Fields annotated with {@code @ParentCommand} will be initialized with the parent command of the current subcommand.
2988 * If the current command does not have a parent command, this annotation has no effect.
2989 * </p><p>
2990 * Parent commands often define options that apply to all the subcommands.
2991 * This annotation offers a convenient way to inject a reference to the parent command into a subcommand, so the
2992 * subcommand can access its parent options. For example:
2993 * </p><pre>
2994 * @Command(name = "top", subcommands = Sub.class)
2995 * class Top implements Runnable {
2996 *
2997 * @Option(names = {"-d", "--directory"}, description = "this option applies to all subcommands")
2998 * File baseDirectory;
2999 *
3000 * public void run() { System.out.println("Hello from top"); }
3001 * }
3002 *
3003 * @Command(name = "sub")
3004 * class Sub implements Runnable {
3005 *
3006 * @ParentCommand
3007 * private Top parent;
3008 *
3009 * public void run() {
3010 * System.out.println("Subcommand: parent command 'directory' is " + parent.baseDirectory);
3011 * }
3012 * }
3013 * </pre>
3014 * @since 2.2
3015 */
3016 @Retention(RetentionPolicy.RUNTIME)
3017 @Target(ElementType.FIELD)
3018 public @interface ParentCommand { }
3019
3020 /**
3021 * Fields annotated with {@code @Unmatched} will be initialized with the list of unmatched command line arguments, if any.
3022 * If this annotation is found, picocli automatically sets {@linkplain CommandLine#setUnmatchedArgumentsAllowed(boolean) unmatchedArgumentsAllowed} to {@code true}.
3023 * @see CommandLine#isUnmatchedArgumentsAllowed()
3024 * @since 3.0
3025 */
3026 @Retention(RetentionPolicy.RUNTIME)
3027 @Target(ElementType.FIELD)
3028 public @interface Unmatched { }
3029
3030 /**
3031 * <p>
3032 * Fields annotated with {@code @Mixin} are "expanded" into the current command: {@link Option @Option} and
3033 * {@link Parameters @Parameters} in the mixin class are added to the options and positional parameters of this command.
3034 * A {@link DuplicateOptionAnnotationsException} is thrown if any of the options in the mixin has the same name as
3035 * an option in this command.
3036 * </p><p>
3037 * The {@code Mixin} annotation provides a way to reuse common options and parameters without subclassing. For example:
3038 * </p><pre>
3039 * class HelloWorld implements Runnable {
3040 *
3041 * // adds the --help and --version options to this command
3042 * @Mixin
3043 * private HelpOptions = new HelpOptions();
3044 *
3045 * @Option(names = {"-u", "--userName"}, required = true, description = "The user name")
3046 * String userName;
3047 *
3048 * public void run() { System.out.println("Hello, " + userName); }
3049 * }
3050 *
3051 * // Common reusable help options.
3052 * class HelpOptions {
3053 *
3054 * @Option(names = { "-h", "--help"}, usageHelp = true, description = "Display this help and exit")
3055 * private boolean help;
3056 *
3057 * @Option(names = { "-V", "--version"}, versionHelp = true, description = "Display version info and exit")
3058 * private boolean versionHelp;
3059 * }
3060 * </pre>
3061 * @since 3.0
3062 */
3063 @Retention(RetentionPolicy.RUNTIME)
3064 @Target({ElementType.FIELD, ElementType.PARAMETER})
3065 public @interface Mixin {
3066 /** Optionally specify a name that the mixin object can be retrieved with from the {@code CommandSpec}.
3067 * If not specified the name of the annotated field is used.
3068 * @return a String to register the mixin object with, or an empty String if the name of the annotated field should be used */
3069 String name() default "";
3070 }
3071 /**
3072 * Fields annotated with {@code @Spec} will be initialized with the {@code CommandSpec} for the command the field is part of. Example usage:
3073 * <pre>
3074 * class InjectSpecExample implements Runnable {
3075 * @Spec CommandSpec commandSpec;
3076 * //...
3077 * public void run() {
3078 * // do something with the injected objects
3079 * }
3080 * }
3081 * </pre>
3082 * @since 3.2
3083 */
3084 @Retention(RetentionPolicy.RUNTIME)
3085 @Target({ElementType.FIELD, ElementType.METHOD})
3086 public @interface Spec { }
3087 /**
3088 * <p>Annotate your class with {@code @Command} when you want more control over the format of the generated help
3089 * message. From 3.6, methods can also be annotated with {@code @Command}, where the method parameters define the
3090 * command options and positional parameters.
3091 * </p><pre>
3092 * @Command(name = "Encrypt", mixinStandardHelpOptions = true,
3093 * description = "Encrypt FILE(s), or standard input, to standard output or to the output file.",
3094 * version = "Encrypt version 1.0",
3095 * footer = "Copyright (c) 2017")
3096 * public class Encrypt {
3097 * @Parameters(paramLabel = "FILE", description = "Any number of input files")
3098 * private List<File> files = new ArrayList<File>();
3099 *
3100 * @Option(names = { "-o", "--out" }, description = "Output file (default: print to console)")
3101 * private File outputFile;
3102 *
3103 * @Option(names = { "-v", "--verbose"}, description = "Verbose mode. Helpful for troubleshooting. Multiple -v options increase the verbosity.")
3104 * private boolean[] verbose;
3105 * }</pre>
3106 * <p>
3107 * The structure of a help message looks like this:
3108 * </p><ul>
3109 * <li>[header]</li>
3110 * <li>[synopsis]: {@code Usage: <commandName> [OPTIONS] [FILE...]}</li>
3111 * <li>[description]</li>
3112 * <li>[parameter list]: {@code [FILE...] Any number of input files}</li>
3113 * <li>[option list]: {@code -h, --help prints this help message and exits}</li>
3114 * <li>[footer]</li>
3115 * </ul> */
3116 @Retention(RetentionPolicy.RUNTIME)
3117 @Target({ElementType.TYPE, ElementType.LOCAL_VARIABLE, ElementType.FIELD, ElementType.PACKAGE, ElementType.METHOD})
3118 public @interface Command {
3119 /** Program name to show in the synopsis. If omitted, {@code "<main class>"} is used.
3120 * For {@linkplain #subcommands() declaratively added} subcommands, this attribute is also used
3121 * by the parser to recognize subcommands in the command line arguments.
3122 * @return the program name to show in the synopsis
3123 * @see CommandSpec#name()
3124 * @see Help#commandName() */
3125 String name() default "<main class>";
3126
3127 /** Alternative command names by which this subcommand is recognized on the command line.
3128 * @return one or more alternative command names
3129 * @since 3.1 */
3130 String[] aliases() default {};
3131
3132 /** A list of classes to instantiate and register as subcommands. When registering subcommands declaratively
3133 * like this, you don't need to call the {@link CommandLine#addSubcommand(String, Object)} method. For example, this:
3134 * <pre>
3135 * @Command(subcommands = {
3136 * GitStatus.class,
3137 * GitCommit.class,
3138 * GitBranch.class })
3139 * public class Git { ... }
3140 *
3141 * CommandLine commandLine = new CommandLine(new Git());
3142 * </pre> is equivalent to this:
3143 * <pre>
3144 * // alternative: programmatically add subcommands.
3145 * // NOTE: in this case there should be no `subcommands` attribute on the @Command annotation.
3146 * @Command public class Git { ... }
3147 *
3148 * CommandLine commandLine = new CommandLine(new Git())
3149 * .addSubcommand("status", new GitStatus())
3150 * .addSubcommand("commit", new GitCommit())
3151 * .addSubcommand("branch", new GitBranch());
3152 * </pre>
3153 * @return the declaratively registered subcommands of this command, or an empty array if none
3154 * @see CommandLine#addSubcommand(String, Object)
3155 * @see HelpCommand
3156 * @since 0.9.8
3157 */
3158 Class<?>[] subcommands() default {};
3159
3160 /** Specify whether methods annotated with {@code @Command} should be registered as subcommands of their
3161 * enclosing {@code @Command} class.
3162 * The default is {@code true}. For example:
3163 * <pre>
3164 * @Command
3165 * public class Git {
3166 * @Command
3167 * void status() { ... }
3168 * }
3169 *
3170 * CommandLine git = new CommandLine(new Git());
3171 * </pre> is equivalent to this:
3172 * <pre>
3173 * // don't add command methods as subcommands automatically
3174 * @Command(addMethodSubcommands = false)
3175 * public class Git {
3176 * @Command
3177 * void status() { ... }
3178 * }
3179 *
3180 * // add command methods as subcommands programmatically
3181 * CommandLine git = new CommandLine(new Git());
3182 * CommandLine status = new CommandLine(CommandLine.getCommandMethods(Git.class, "status").get(0));
3183 * git.addSubcommand("status", status);
3184 * </pre>
3185 * @return whether methods annotated with {@code @Command} should be registered as subcommands
3186 * @see CommandLine#addSubcommand(String, Object)
3187 * @see CommandLine#getCommandMethods(Class, String)
3188 * @see CommandSpec#addMethodSubcommands()
3189 * @since 3.6.0 */
3190 boolean addMethodSubcommands() default true;
3191
3192 /** String that separates options from option parameters. Default is {@code "="}. Spaces are also accepted.
3193 * @return the string that separates options from option parameters, used both when parsing and when generating usage help
3194 * @see CommandLine#setSeparator(String) */
3195 String separator() default "=";
3196
3197 /** Version information for this command, to print to the console when the user specifies an
3198 * {@linkplain Option#versionHelp() option} to request version help. Each element of the array is rendered on a separate line.
3199 * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
3200 * <p>This is not part of the usage help message.</p>
3201 *
3202 * @return a string or an array of strings with version information about this command (each string in the array is displayed on a separate line).
3203 * @since 0.9.8
3204 * @see CommandLine#printVersionHelp(PrintStream)
3205 */
3206 String[] version() default {};
3207
3208 /** Class that can provide version information dynamically at runtime. An implementation may return version
3209 * information obtained from the JAR manifest, a properties file or some other source.
3210 * @return a Class that can provide version information dynamically at runtime
3211 * @since 2.2 */
3212 Class<? extends IVersionProvider> versionProvider() default NoVersionProvider.class;
3213
3214 /**
3215 * Adds the standard {@code -h} and {@code --help} {@linkplain Option#usageHelp() usageHelp} options and {@code -V}
3216 * and {@code --version} {@linkplain Option#versionHelp() versionHelp} options to the options of this command.
3217 * <p>
3218 * Note that if no {@link #version()} or {@link #versionProvider()} is specified, the {@code --version} option will not print anything.
3219 * </p><p>
3220 * For {@linkplain #resourceBundle() internationalization}: the help option has {@code descriptionKey = "mixinStandardHelpOptions.help"},
3221 * and the version option has {@code descriptionKey = "mixinStandardHelpOptions.version"}.
3222 * </p>
3223 * @return whether the auto-help mixin should be added to this command
3224 * @since 3.0 */
3225 boolean mixinStandardHelpOptions() default false;
3226
3227 /** Set this attribute to {@code true} if this subcommand is a help command, and required options and positional
3228 * parameters of the parent command should not be validated. If a subcommand marked as {@code helpCommand} is
3229 * specified on the command line, picocli will not validate the parent arguments (so no "missing required
3230 * option" errors) and the {@link CommandLine#printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi)} method will return {@code true}.
3231 * @return {@code true} if this subcommand is a help command and picocli should not check for missing required
3232 * options and positional parameters on the parent command
3233 * @since 3.0 */
3234 boolean helpCommand() default false;
3235
3236 /** Set the heading preceding the header section.
3237 * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
3238 * @return the heading preceding the header section
3239 * @see UsageMessageSpec#headerHeading()
3240 * @see Help#headerHeading(Object...) */
3241 String headerHeading() default "";
3242
3243 /** Optional summary description of the command, shown before the synopsis. Each element of the array is rendered on a separate line.
3244 * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
3245 * @return summary description of the command
3246 * @see UsageMessageSpec#header()
3247 * @see Help#header(Object...) */
3248 String[] header() default {};
3249
3250 /** Set the heading preceding the synopsis text. The default heading is {@code "Usage: "} (without a line break between the heading and the synopsis text).
3251 * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
3252 * @return the heading preceding the synopsis text
3253 * @see Help#synopsisHeading(Object...) */
3254 String synopsisHeading() default "Usage: ";
3255
3256 /** Specify {@code true} to generate an abbreviated synopsis like {@code "<main> [OPTIONS] [PARAMETERS...]"}.
3257 * By default, a detailed synopsis with individual option names and parameters is generated.
3258 * @return whether the synopsis should be abbreviated
3259 * @see Help#abbreviatedSynopsis()
3260 * @see Help#detailedSynopsis(Comparator, boolean) */
3261 boolean abbreviateSynopsis() default false;
3262
3263 /** Specify one or more custom synopsis lines to display instead of an auto-generated synopsis. Each element of the array is rendered on a separate line.
3264 * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
3265 * @return custom synopsis text to replace the auto-generated synopsis
3266 * @see Help#customSynopsis(Object...) */
3267 String[] customSynopsis() default {};
3268
3269 /** Set the heading preceding the description section.
3270 * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
3271 * @return the heading preceding the description section
3272 * @see Help#descriptionHeading(Object...) */
3273 String descriptionHeading() default "";
3274
3275 /** Optional text to display between the synopsis line(s) and the list of options. Each element of the array is rendered on a separate line.
3276 * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
3277 * @return description of this command
3278 * @see Help#description(Object...) */
3279 String[] description() default {};
3280
3281 /** Set the heading preceding the parameters list.
3282 * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
3283 * @return the heading preceding the parameters list
3284 * @see Help#parameterListHeading(Object...) */
3285 String parameterListHeading() default "";
3286
3287 /** Set the heading preceding the options list.
3288 * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
3289 * @return the heading preceding the options list
3290 * @see Help#optionListHeading(Object...) */
3291 String optionListHeading() default "";
3292
3293 /** Specify {@code false} to show Options in declaration order. The default is to sort alphabetically.
3294 * @return whether options should be shown in alphabetic order. */
3295 boolean sortOptions() default true;
3296
3297 /** Prefix required options with this character in the options list. The default is no marker: the synopsis
3298 * indicates which options and parameters are required.
3299 * @return the character to show in the options list to mark required options */
3300 char requiredOptionMarker() default ' ';
3301
3302 /** Class that can provide default values dynamically at runtime. An implementation may return default
3303 * value obtained from a configuration file like a properties file or some other source.
3304 * @return a Class that can provide default values dynamically at runtime
3305 * @since 3.6 */
3306 Class<? extends IDefaultValueProvider> defaultValueProvider() default NoDefaultProvider.class;
3307
3308 /** Specify {@code true} to show default values in the description column of the options list (except for
3309 * boolean options). False by default.
3310 * <p>Note that picocli 3.2 allows {@linkplain Option#description() embedding default values} anywhere in the
3311 * option or positional parameter description that ignores this setting.</p>
3312 * @return whether the default values for options and parameters should be shown in the description column */
3313 boolean showDefaultValues() default false;
3314
3315 /** Set the heading preceding the subcommands list. The default heading is {@code "Commands:%n"} (with a line break at the end).
3316 * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
3317 * @return the heading preceding the subcommands list
3318 * @see Help#commandListHeading(Object...) */
3319 String commandListHeading() default "Commands:%n";
3320
3321 /** Set the heading preceding the footer section.
3322 * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
3323 * @return the heading preceding the footer section
3324 * @see Help#footerHeading(Object...) */
3325 String footerHeading() default "";
3326
3327 /** Optional text to display after the list of options. Each element of the array is rendered on a separate line.
3328 * <p>May contain embedded {@linkplain java.util.Formatter format specifiers} like {@code %n} line separators. Literal percent {@code '%'} characters must be escaped with another {@code %}.</p>
3329 * @return text to display after the list of options
3330 * @see Help#footer(Object...) */
3331 String[] footer() default {};
3332
3333 /**
3334 * Set {@code hidden=true} if this command should not be included in the list of commands in the usage help of the parent command.
3335 * @return whether this command should be excluded from the usage message
3336 * @since 3.0
3337 */
3338 boolean hidden() default false;
3339
3340 /** Set the base name of the ResourceBundle to find option and positional parameters descriptions, as well as
3341 * usage help message sections and section headings. <p>See {@link Messages} for more details and an example.</p>
3342 * @return the base name of the ResourceBundle for usage help strings
3343 * @see ArgSpec#messages()
3344 * @see UsageMessageSpec#messages()
3345 * @see CommandSpec#resourceBundle()
3346 * @see CommandLine#setResourceBundle(ResourceBundle)
3347 * @since 3.6
3348 */
3349 String resourceBundle() default "";
3350
3351 /** Set the {@link UsageMessageSpec#width(int) usage help message width}. The default is 80.
3352 * @since 3.7
3353 */
3354 int usageHelpWidth() default 80;
3355 }
3356 /** A {@code Command} may define one or more {@code ArgGroups}: a group of options, positional parameters or a mixture of the two.
3357 * Groups can be used to:
3358 * <ul>
3359 * <li>define <b>mutually exclusive</b> arguments. By default, options and positional parameters
3360 * in a group are mutually exclusive. This can be controlled with the {@link #exclusive() exclusive} attribute.
3361 * Picocli will throw a {@link MutuallyExclusiveArgsException} if the command line contains multiple arguments that are mutually exclusive.</li>
3362 * <li>define a set of arguments that <b>must co-occur</b>. Set {@link #exclusive() exclusive = false}
3363 * to define a group of options and positional parameters that must always be specified together.
3364 * Picocli will throw a {@link MissingParameterException MissingParameterException} if not all the options and positional parameters in a co-occurring group are specified together.</li>
3365 * <li>create an <b>option section</b> in the usage help message.
3366 * To be shown in the usage help message, a group needs to have a {@link #heading() heading} (which may come from a {@linkplain #headingKey() resource bundle}).
3367 * Groups without a heading are only used for validation.
3368 * Set {@link #validate() validate = false} for groups whose purpose is only to customize the usage help message.</li>
3369 * <li>define <b>composite repeating argument groups</b>. Groups may contain other groups to create composite groups.</li>
3370 * </ul>
3371 * <p>Groups may be optional ({@code multiplicity = "0..1"}), required ({@code multiplicity = "1"}), or repeating groups ({@code multiplicity = "0..*"} or {@code multiplicity = "1..*"}).
3372 * For a group of mutually exclusive arguments, making the group required means that one of the arguments in the group must appear on the command line, or a {@link MissingParameterException MissingParameterException} is thrown.
3373 * For a group of co-occurring arguments, all arguments in the group must appear on the command line.
3374 * </p>
3375 * <p>Groups can be composed for validation purposes:</p>
3376 * <ul>
3377 * <li>When the parent group is mutually exclusive, only one of the subgroups may be present.</li>
3378 * <li>When the parent group is a co-occurring group, all subgroups must be present.</li>
3379 * <li>When the parent group is required, at least one subgroup must be present.</li>
3380 * </ul>
3381 * <p>
3382 * Below is an example of an {@code ArgGroup} defining a set of dependent options that must occur together.
3383 * All options are required <em>within the group</em>, while the group itself is optional:</p>
3384 * <pre>
3385 * public class DependentOptions {
3386 * @ArgGroup(exclusive = false, multiplicity = "0..1")
3387 * Dependent group;
3388 *
3389 * static class Dependent {
3390 * @Option(names = "-a", required = true) int a;
3391 * @Option(names = "-b", required = true) int b;
3392 * @Option(names = "-c", required = true) int c;
3393 * }
3394 * }</pre>
3395 * @see ArgGroupSpec
3396 * @since 4.0 */
3397 @Retention(RetentionPolicy.RUNTIME)
3398 @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
3399 public @interface ArgGroup {
3400 /** The heading of this group, used when generating the usage documentation.
3401 * When neither a {@link #heading() heading} nor a {@link #headingKey() headingKey} are specified,
3402 * this group is used for validation only and does not change the usage help message. */
3403 String heading() default "__no_heading__";
3404
3405 /** ResourceBundle key for this group's usage help message section heading.
3406 * When neither a {@link #heading() heading} nor a {@link #headingKey() headingKey} are specified,
3407 * this group is used for validation only and does not change the usage help message. */
3408 String headingKey() default "__no_heading_key__";
3409 /** Determines whether this is a mutually exclusive group; {@code true} by default.
3410 * If {@code false}, this is a co-occurring group. Ignored if {@link #validate()} is {@code false}. */
3411 boolean exclusive() default true;
3412 /** Determines how often this group can be specified on the command line; {@code "0..1"} (optional) by default.
3413 * For a group of mutually exclusive arguments, making the group required {@code multiplicity = "1"} means that
3414 * one of the arguments in the group must appear on the command line, or a MissingParameterException is thrown.
3415 * For a group of co-occurring arguments, making the group required means that all arguments in the group must appear on the command line.
3416 * Ignored if {@link #validate()} is {@code false}. */
3417 String multiplicity() default "0..1";
3418 /** Determines whether picocli should validate the rules of this group ({@code true} by default).
3419 * For a mutually exclusive group validation means verifying that no more than one elements of the group is specified on the command line;
3420 * for a co-ocurring group validation means verifying that all elements of the group are specified on the command line.
3421 * Set {@link #validate() validate = false} for groups whose purpose is only to customize the usage help message.
3422 * @see #multiplicity()
3423 * @see #heading() */
3424 boolean validate() default true;
3425 /** Determines the position in the options list in the usage help message at which this group should be shown.
3426 * Options with a lower number are shown before options with a higher number.
3427 * This attribute is only honored if {@link UsageMessageSpec#sortOptions()} is {@code false} for this command.*/
3428 int order() default -1;
3429 }
3430 /**
3431 * <p>
3432 * When parsing command line arguments and initializing
3433 * fields annotated with {@link Option @Option} or {@link Parameters @Parameters},
3434 * String values can be converted to any type for which a {@code ITypeConverter} is registered.
3435 * </p><p>
3436 * This interface defines the contract for classes that know how to convert a String into some domain object.
3437 * Custom converters can be registered with the {@link #registerConverter(Class, ITypeConverter)} method.
3438 * </p><p>
3439 * Java 8 lambdas make it easy to register custom type converters:
3440 * </p>
3441 * <pre>
3442 * commandLine.registerConverter(java.nio.file.Path.class, s -> java.nio.file.Paths.get(s));
3443 * commandLine.registerConverter(java.time.Duration.class, s -> java.time.Duration.parse(s));</pre>
3444 * <p>
3445 * Built-in type converters are pre-registered for the following java 1.5 types:
3446 * </p>
3447 * <ul>
3448 * <li>all primitive types</li>
3449 * <li>all primitive wrapper types: Boolean, Byte, Character, Double, Float, Integer, Long, Short</li>
3450 * <li>any enum</li>
3451 * <li>java.io.File</li>
3452 * <li>java.math.BigDecimal</li>
3453 * <li>java.math.BigInteger</li>
3454 * <li>java.net.InetAddress</li>
3455 * <li>java.net.URI</li>
3456 * <li>java.net.URL</li>
3457 * <li>java.nio.charset.Charset</li>
3458 * <li>java.sql.Time</li>
3459 * <li>java.util.Date</li>
3460 * <li>java.util.UUID</li>
3461 * <li>java.util.regex.Pattern</li>
3462 * <li>StringBuilder</li>
3463 * <li>CharSequence</li>
3464 * <li>String</li>
3465 * </ul>
3466 * @param <K> the type of the object that is the result of the conversion
3467 */
3468 public interface ITypeConverter<K> {
3469 /**
3470 * Converts the specified command line argument value to some domain object.
3471 * @param value the command line argument String value
3472 * @return the resulting domain object
3473 * @throws Exception an exception detailing what went wrong during the conversion
3474 */
3475 K convert(String value) throws Exception;
3476 }
3477
3478 /**
3479 * Provides version information for a command. Commands may configure a provider with the
3480 * {@link Command#versionProvider()} annotation attribute.
3481 * @since 2.2 */
3482 public interface IVersionProvider {
3483 /**
3484 * Returns version information for a command.
3485 * @return version information (each string in the array is displayed on a separate line)
3486 * @throws Exception an exception detailing what went wrong when obtaining version information
3487 */
3488 String[] getVersion() throws Exception;
3489 }
3490 private static class NoVersionProvider implements IVersionProvider {
3491 public String[] getVersion() throws Exception { throw new UnsupportedOperationException(); }
3492 }
3493
3494 /**
3495 * Provides default value for a command. Commands may configure a provider with the
3496 * {@link Command#defaultValueProvider()} annotation attribute.
3497 * @since 3.6 */
3498 public interface IDefaultValueProvider {
3499
3500 /** Returns the default value for an option or positional parameter or {@code null}.
3501 * The returned value is converted to the type of the option/positional parameter
3502 * via the same type converter used when populating this option/positional
3503 * parameter from a command line argument.
3504 * @param argSpec the option or positional parameter, never {@code null}
3505 * @return the default value for the option or positional parameter, or {@code null} if
3506 * this provider has no default value for the specified option or positional parameter
3507 * @throws Exception when there was a problem obtaining the default value
3508 */
3509 String defaultValue(ArgSpec argSpec) throws Exception;
3510 }
3511 private static class NoDefaultProvider implements IDefaultValueProvider {
3512 public String defaultValue(ArgSpec argSpec) { throw new UnsupportedOperationException(); }
3513 }
3514
3515 /**
3516 * Creates the {@link Help} instance used to render the usage help message.
3517 * @since 3.9
3518 */
3519 public interface IHelpFactory {
3520 /** Returns a {@code Help} instance to assist in rendering the usage help message
3521 * @param commandSpec the command to create usage help for
3522 * @param colorScheme the color scheme to use when rendering usage help
3523 * @return a {@code Help} instance
3524 */
3525 Help create(CommandSpec commandSpec, Help.ColorScheme colorScheme);
3526 }
3527
3528 private static class DefaultHelpFactory implements IHelpFactory {
3529 public Help create(CommandSpec commandSpec, Help.ColorScheme colorScheme) {
3530 return new Help(commandSpec, colorScheme);
3531 }
3532 }
3533
3534 /**
3535 * Factory for instantiating classes that are registered declaratively with annotation attributes, like
3536 * {@link Command#subcommands()}, {@link Option#converter()}, {@link Parameters#converter()} and {@link Command#versionProvider()}.
3537 * <p>The default factory implementation simply creates a new instance of the specified class when {@link #create(Class)} is invoked.
3538 * </p><p>
3539 * You may provide a custom implementation of this interface.
3540 * For example, a custom factory implementation could delegate to a dependency injection container that provides the requested instance.
3541 * </p>
3542 * @see picocli.CommandLine#CommandLine(Object, IFactory)
3543 * @see #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
3544 * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
3545 * @since 2.2 */
3546 public interface IFactory {
3547 /**
3548 * Returns an instance of the specified class.
3549 * @param cls the class of the object to return
3550 * @param <K> the type of the object to return
3551 * @return the instance
3552 * @throws Exception an exception detailing what went wrong when creating or obtaining the instance
3553 */
3554 <K> K create(Class<K> cls) throws Exception;
3555 }
3556 /** Returns a default {@link IFactory} implementation. Package-protected for testing purposes. */
3557 static IFactory defaultFactory() { return new DefaultFactory(); }
3558 private static class DefaultFactory implements IFactory {
3559 public <T> T create(Class<T> cls) throws Exception {
3560 if (cls.isInterface() && Collection.class.isAssignableFrom(cls)) {
3561 if (List.class.isAssignableFrom(cls)) {
3562 return cls.cast(new ArrayList<Object>());
3563 } else if (SortedSet.class.isAssignableFrom(cls)) {
3564 return cls.cast(new TreeSet<Object>());
3565 } else if (Set.class.isAssignableFrom(cls)) {
3566 return cls.cast(new LinkedHashSet<Object>());
3567 } else if (Queue.class.isAssignableFrom(cls)) {
3568 return cls.cast(new LinkedList<Object>()); // ArrayDeque is only available since 1.6
3569 }
3570 return cls.cast(new ArrayList<Object>());
3571 }
3572 if (Map.class.isAssignableFrom(cls)) {
3573 try { // if it is an implementation class, instantiate it
3574 return cls.cast(cls.getDeclaredConstructor().newInstance());
3575 } catch (Exception ignored) { }
3576 return cls.cast(new LinkedHashMap<Object, Object>());
3577 }
3578 try {
3579 return cls.newInstance();
3580 } catch (Exception ex) {
3581 Constructor<T> constructor = cls.getDeclaredConstructor();
3582 constructor.setAccessible(true);
3583 return constructor.newInstance();
3584 }
3585 }
3586 private static ITypeConverter<?>[] createConverter(IFactory factory, Class<? extends ITypeConverter<?>>[] classes) {
3587 ITypeConverter<?>[] result = new ITypeConverter<?>[classes.length];
3588 for (int i = 0; i < classes.length; i++) { result[i] = create(factory, classes[i]); }
3589 return result;
3590 }
3591 static IVersionProvider createVersionProvider(IFactory factory, Class<? extends IVersionProvider> cls) {
3592 return create(factory, cls);
3593 }
3594 static IDefaultValueProvider createDefaultValueProvider(IFactory factory, Class<? extends IDefaultValueProvider> cls) {
3595 return create(factory, cls);
3596 }
3597 static Iterable<String> createCompletionCandidates(IFactory factory, Class<? extends Iterable<String>> cls) {
3598 return create(factory, cls);
3599 }
3600 static <T> T create(IFactory factory, Class<T> cls) {
3601 try { return factory.create(cls); }
3602 catch (Exception ex) { throw new InitializationException("Could not instantiate " + cls + ": " + ex, ex); }
3603 }
3604 }
3605 /** Describes the number of parameters required and accepted by an option or a positional parameter.
3606 * @since 0.9.7
3607 */
3608 public static class Range implements Comparable<Range> {
3609 /** Required number of parameters for an option or positional parameter. */
3610 public final int min;
3611 /** Maximum accepted number of parameters for an option or positional parameter. */
3612 public final int max;
3613 public final boolean isVariable;
3614 private final boolean isUnspecified;
3615 private final String originalValue;
3616
3617 /** Constructs a new Range object with the specified parameters.
3618 * @param min minimum number of required parameters
3619 * @param max maximum number of allowed parameters (or Integer.MAX_VALUE if variable)
3620 * @param variable {@code true} if any number or parameters is allowed, {@code false} otherwise
3621 * @param unspecified {@code true} if no arity was specified on the option/parameter (value is based on type)
3622 * @param originalValue the original value that was specified on the option or parameter
3623 */
3624 public Range(int min, int max, boolean variable, boolean unspecified, String originalValue) {
3625 if (min < 0 || max < 0) { throw new InitializationException("Invalid negative range (min=" + min + ", max=" + max + ")"); }
3626 if (min > max) { throw new InitializationException("Invalid range (min=" + min + ", max=" + max + ")"); }
3627 this.min = min;
3628 this.max = max;
3629 this.isVariable = variable;
3630 this.isUnspecified = unspecified;
3631 this.originalValue = originalValue;
3632 }
3633 /** Returns a new {@code Range} based on the {@link Option#arity()} annotation on the specified field,
3634 * or the field type's default arity if no arity was specified.
3635 * @param field the field whose Option annotation to inspect
3636 * @return a new {@code Range} based on the Option arity annotation on the specified field */
3637 public static Range optionArity(Field field) { return optionArity(new TypedMember(field)); }
3638 private static Range optionArity(IAnnotatedElement member) {
3639 return member.isAnnotationPresent(Option.class)
3640 ? adjustForType(Range.valueOf(member.getAnnotation(Option.class).arity()), member)
3641 : new Range(0, 0, false, true, "0");
3642 }
3643 /** Returns a new {@code Range} based on the {@link Parameters#arity()} annotation on the specified field,
3644 * or the field type's default arity if no arity was specified.
3645 * @param field the field whose Parameters annotation to inspect
3646 * @return a new {@code Range} based on the Parameters arity annotation on the specified field */
3647 public static Range parameterArity(Field field) { return parameterArity(new TypedMember(field)); }
3648 private static Range parameterArity(IAnnotatedElement member) {
3649 if (member.isAnnotationPresent(Parameters.class)) {
3650 return adjustForType(Range.valueOf(member.getAnnotation(Parameters.class).arity()), member);
3651 } else {
3652 return member.isMethodParameter()
3653 ? adjustForType(Range.valueOf(""), member)
3654 : new Range(0, 0, false, true, "0");
3655 }
3656 }
3657 /** Returns a new {@code Range} based on the {@link Parameters#index()} annotation on the specified field.
3658 * @param field the field whose Parameters annotation to inspect
3659 * @return a new {@code Range} based on the Parameters index annotation on the specified field */
3660 public static Range parameterIndex(Field field) { return parameterIndex(new TypedMember(field)); }
3661 private static Range parameterIndex(IAnnotatedElement member) {
3662 if (member.isAnnotationPresent(Parameters.class)) {
3663 Range result = Range.valueOf(member.getAnnotation(Parameters.class).index());
3664 if (!result.isUnspecified) { return result; }
3665 }
3666 if (member.isMethodParameter()) {
3667 int min = member.getMethodParamPosition();
3668 int max = member.isMultiValue() ? Integer.MAX_VALUE : min;
3669 return new Range(min, max, member.isMultiValue(), false, "");
3670 }
3671 return Range.valueOf("*"); // the default
3672 }
3673 static Range adjustForType(Range result, IAnnotatedElement member) {
3674 return result.isUnspecified ? defaultArity(member) : result;
3675 }
3676 /** Returns the default arity {@code Range}: for {@link Option options} this is 0 for booleans and 1 for
3677 * other types, for {@link Parameters parameters} booleans have arity 0, arrays or Collections have
3678 * arity "0..*", and other types have arity 1.
3679 * @param field the field whose default arity to return
3680 * @return a new {@code Range} indicating the default arity of the specified field
3681 * @since 2.0 */
3682 public static Range defaultArity(Field field) { return defaultArity(new TypedMember(field)); }
3683 private static Range defaultArity(IAnnotatedElement member) {
3684 ITypeInfo info = member.getTypeInfo();
3685 if (member.isAnnotationPresent(Option.class)) {
3686 boolean zeroArgs = info.isBoolean() || (info.isMultiValue() && info.getAuxiliaryTypeInfos().get(0).isBoolean());
3687 return zeroArgs ? Range.valueOf("0").unspecified(true)
3688 : Range.valueOf("1").unspecified(true);
3689 }
3690 if (info.isMultiValue()) {
3691 return Range.valueOf("0..1").unspecified(true);
3692 }
3693 return Range.valueOf("1").unspecified(true);// for single-valued fields (incl. boolean positional parameters)
3694 }
3695 /** Returns the default arity {@code Range} for {@link Option options}: booleans have arity 0, other types have arity 1.
3696 * @param type the type whose default arity to return
3697 * @return a new {@code Range} indicating the default arity of the specified type
3698 * @deprecated use {@link #defaultArity(Field)} instead */
3699 @Deprecated public static Range defaultArity(Class<?> type) {
3700 return isBoolean(type) ? Range.valueOf("0").unspecified(true) : Range.valueOf("1").unspecified(true);
3701 }
3702 private int size() { return 1 + max - min; }
3703 static Range parameterCapacity(IAnnotatedElement member) {
3704 Range arity = parameterArity(member);
3705 if (!member.isMultiValue()) { return arity; }
3706 Range index = parameterIndex(member);
3707 return parameterCapacity(arity, index);
3708 }
3709 private static Range parameterCapacity(Range arity, Range index) {
3710 if (arity.max == 0) { return arity; }
3711 if (index.size() == 1) { return arity; }
3712 if (index.isVariable) { return Range.valueOf(arity.min + "..*"); }
3713 if (arity.size() == 1) { return Range.valueOf(arity.min * index.size() + ""); }
3714 if (arity.isVariable) { return Range.valueOf(arity.min * index.size() + "..*"); }
3715 return Range.valueOf(arity.min * index.size() + ".." + arity.max * index.size());
3716 }
3717
3718 /** Leniently parses the specified String as an {@code Range} value and return the result. A range string can
3719 * be a fixed integer value or a range of the form {@code MIN_VALUE + ".." + MAX_VALUE}. If the
3720 * {@code MIN_VALUE} string is not numeric, the minimum is zero. If the {@code MAX_VALUE} is not numeric, the
3721 * range is taken to be variable and the maximum is {@code Integer.MAX_VALUE}.
3722 * @param range the value range string to parse
3723 * @return a new {@code Range} value */
3724 public static Range valueOf(String range) {
3725 range = range.trim();
3726 boolean unspecified = range.length() == 0 || range.startsWith(".."); // || range.endsWith("..");
3727 int min = -1, max = -1;
3728 boolean variable = false;
3729 int dots = -1;
3730 if ((dots = range.indexOf("..")) >= 0) {
3731 min = parseInt(range.substring(0, dots), 0);
3732 max = parseInt(range.substring(dots + 2), Integer.MAX_VALUE);
3733 variable = max == Integer.MAX_VALUE;
3734 } else {
3735 max = parseInt(range, Integer.MAX_VALUE);
3736 variable = max == Integer.MAX_VALUE;
3737 min = variable ? 0 : max;
3738 }
3739 Range result = new Range(min, max, variable, unspecified, range);
3740 return result;
3741 }
3742 private static int parseInt(String str, int defaultValue) {
3743 try {
3744 return Integer.parseInt(str);
3745 } catch (Exception ex) {
3746 return defaultValue;
3747 }
3748 }
3749 /** Returns a new Range object with the {@code min} value replaced by the specified value.
3750 * The {@code max} of the returned Range is guaranteed not to be less than the new {@code min} value.
3751 * @param newMin the {@code min} value of the returned Range object
3752 * @return a new Range object with the specified {@code min} value */
3753 public Range min(int newMin) { return new Range(newMin, Math.max(newMin, max), isVariable, isUnspecified, originalValue); }
3754
3755 /** Returns a new Range object with the {@code max} value replaced by the specified value.
3756 * The {@code min} of the returned Range is guaranteed not to be greater than the new {@code max} value.
3757 * @param newMax the {@code max} value of the returned Range object
3758 * @return a new Range object with the specified {@code max} value */
3759 public Range max(int newMax) { return new Range(Math.min(min, newMax), newMax, isVariable, isUnspecified, originalValue); }
3760
3761 /** Returns a new Range object with the {@code isUnspecified} value replaced by the specified value.
3762 * @param unspecified the {@code unspecified} value of the returned Range object
3763 * @return a new Range object with the specified {@code unspecified} value */
3764 public Range unspecified(boolean unspecified) { return new Range(min, max, isVariable, unspecified, originalValue); }
3765 /** Returns {@code true} if this Range is a default value, {@code false} if the user specified this value.
3766 * @since 4.0 */
3767 public boolean isUnspecified() { return isUnspecified; }
3768
3769 /**
3770 * Returns {@code true} if this Range includes the specified value, {@code false} otherwise.
3771 * @param value the value to check
3772 * @return {@code true} if the specified value is not less than the minimum and not greater than the maximum of this Range
3773 */
3774 public boolean contains(int value) { return min <= value && max >= value; }
3775
3776 public boolean equals(Object object) {
3777 if (!(object instanceof Range)) { return false; }
3778 Range other = (Range) object;
3779 return other.max == this.max && other.min == this.min && other.isVariable == this.isVariable;
3780 }
3781 public int hashCode() {
3782 return ((17 * 37 + max) * 37 + min) * 37 + (isVariable ? 1 : 0);
3783 }
3784 public String toString() {
3785 return min == max ? String.valueOf(min) : min + ".." + (isVariable ? "*" : max);
3786 }
3787 public int compareTo(Range other) {
3788 int result = min - other.min;
3789 return (result == 0) ? max - other.max : result;
3790 }
3791
3792 boolean overlaps(Range index) {
3793 return contains(index.min) || contains(index.max) || index.contains(min) || index.contains(max);
3794 }
3795 }
3796 private static void validatePositionalParameters(List<PositionalParamSpec> positionalParametersFields) {
3797 int min = 0;
3798 for (PositionalParamSpec positional : positionalParametersFields) {
3799 Range index = positional.index();
3800 if (index.min > min) {
3801 throw new ParameterIndexGapException("Command definition should have a positional parameter with index=" + min +
3802 ". Nearest positional parameter '" + positional.paramLabel() + "' has index=" + index.min);
3803 }
3804 min = Math.max(min, index.max);
3805 min = min == Integer.MAX_VALUE ? min : min + 1;
3806 }
3807 }
3808 @SuppressWarnings("unchecked") private static Stack<String> copy(Stack<String> stack) { return (Stack<String>) stack.clone(); }
3809 private static <T> Stack<T> reverse(Stack<T> stack) {
3810 Collections.reverse(stack);
3811 return stack;
3812 }
3813 private static <T> List<T> reverseList(List<T> list) {
3814 Collections.reverse(list);
3815 return list;
3816 }
3817
3818 /** This class provides a namespace for classes and interfaces that model concepts and attributes of command line interfaces in picocli.
3819 * @since 3.0 */
3820 public static final class Model {
3821 private Model() {}
3822
3823 /** The scope of a binding is the context where the current value should be gotten from or set to.
3824 * For a field, the scope is the object whose field value to get/set. For a method binding, it is the
3825 * object on which the method should be invoked.
3826 * <p>The getter and setter of the scope allow you to change the object onto which the option and positional parameter getters and setters should be applied.</p>
3827 * @since 4.0
3828 */
3829 public interface IScope extends IGetter, ISetter {}
3830
3831 /** Customizable getter for obtaining the current value of an option or positional parameter.
3832 * When an option or positional parameter is matched on the command line, its getter or setter is invoked to capture the value.
3833 * For example, an option can be bound to a field or a method, and when the option is matched on the command line, the
3834 * field's value is set or the method is invoked with the option parameter value.
3835 * @since 3.0 */
3836 public static interface IGetter {
3837 /** Returns the current value of the binding. For multi-value options and positional parameters,
3838 * this method returns an array, collection or map to add values to.
3839 * @throws PicocliException if a problem occurred while obtaining the current value
3840 * @throws Exception internally, picocli call sites will catch any exceptions thrown from here and rethrow them wrapped in a PicocliException */
3841 <T> T get() throws Exception;
3842 }
3843 /** Customizable setter for modifying the value of an option or positional parameter.
3844 * When an option or positional parameter is matched on the command line, its setter is invoked to capture the value.
3845 * For example, an option can be bound to a field or a method, and when the option is matched on the command line, the
3846 * field's value is set or the method is invoked with the option parameter value.
3847 * @since 3.0 */
3848 public static interface ISetter {
3849 /** Sets the new value of the option or positional parameter.
3850 *
3851 * @param value the new value of the option or positional parameter
3852 * @param <T> type of the value
3853 * @return the previous value of the binding (if supported by this binding)
3854 * @throws PicocliException if a problem occurred while setting the new value
3855 * @throws Exception internally, picocli call sites will catch any exceptions thrown from here and rethrow them wrapped in a PicocliException */
3856 <T> T set(T value) throws Exception;
3857 }
3858
3859 /** The {@code CommandSpec} class models a command specification, including the options, positional parameters and subcommands
3860 * supported by the command, as well as attributes for the version help message and the usage help message of the command.
3861 * <p>
3862 * Picocli views a command line application as a hierarchy of commands: there is a top-level command (usually the Java
3863 * class with the {@code main} method) with optionally a set of command line options, positional parameters and subcommands.
3864 * Subcommands themselves can have options, positional parameters and nested sub-subcommands to any level of depth.
3865 * </p><p>
3866 * The object model has a corresponding hierarchy of {@code CommandSpec} objects, each with a set of {@link OptionSpec},
3867 * {@link PositionalParamSpec} and {@linkplain CommandLine subcommands} associated with it.
3868 * This object model is used by the picocli command line interpreter and help message generator.
3869 * </p><p>Picocli can construct a {@code CommandSpec} automatically from classes with {@link Command @Command}, {@link Option @Option} and
3870 * {@link Parameters @Parameters} annotations. Alternatively a {@code CommandSpec} can be constructed programmatically.
3871 * </p>
3872 * @since 3.0 */
3873 public static class CommandSpec {
3874 /** Constant String holding the default program name: {@code "<main class>" }. */
3875 static final String DEFAULT_COMMAND_NAME = "<main class>";
3876
3877 /** Constant Boolean holding the default setting for whether this is a help command: <code>{@value}</code>.*/
3878 static final Boolean DEFAULT_IS_HELP_COMMAND = Boolean.FALSE;
3879
3880 /** Constant Boolean holding the default setting for whether method commands should be added as subcommands: <code>{@value}</code>.*/
3881 static final Boolean DEFAULT_IS_ADD_METHOD_SUBCOMMANDS = Boolean.TRUE;
3882
3883 private final Map<String, CommandLine> commands = new LinkedHashMap<String, CommandLine>();
3884 private final Map<String, OptionSpec> optionsByNameMap = new LinkedHashMap<String, OptionSpec>();
3885 private final Map<Character, OptionSpec> posixOptionsByKeyMap = new LinkedHashMap<Character, OptionSpec>();
3886 private final Map<String, CommandSpec> mixins = new LinkedHashMap<String, CommandSpec>();
3887 private final List<ArgSpec> requiredArgs = new ArrayList<ArgSpec>();
3888 private final List<ArgSpec> args = new ArrayList<ArgSpec>();
3889 private final List<OptionSpec> options = new ArrayList<OptionSpec>();
3890 private final List<PositionalParamSpec> positionalParameters = new ArrayList<PositionalParamSpec>();
3891 private final List<UnmatchedArgsBinding> unmatchedArgs = new ArrayList<UnmatchedArgsBinding>();
3892 private final List<ArgGroupSpec> groups = new ArrayList<ArgGroupSpec>();
3893 private final ParserSpec parser = new ParserSpec();
3894 private final UsageMessageSpec usageMessage = new UsageMessageSpec();
3895
3896 private final Object userObject;
3897 private CommandLine commandLine;
3898 private CommandSpec parent;
3899 private Boolean isAddMethodSubcommands;
3900
3901 private String name;
3902 private Set<String> aliases = new LinkedHashSet<String>();
3903 private Boolean isHelpCommand;
3904 private IVersionProvider versionProvider;
3905 private IDefaultValueProvider defaultValueProvider;
3906 private String[] version;
3907 private String toString;
3908
3909 private CommandSpec(Object userObject) { this.userObject = userObject; }
3910
3911 /** Creates and returns a new {@code CommandSpec} without any associated user object. */
3912 public static CommandSpec create() { return wrapWithoutInspection(null); }
3913
3914 /** Creates and returns a new {@code CommandSpec} with the specified associated user object.
3915 * The specified user object is <em>not</em> inspected for annotations.
3916 * @param userObject the associated user object. May be any object, may be {@code null}.
3917 */
3918 public static CommandSpec wrapWithoutInspection(Object userObject) { return new CommandSpec(userObject); }
3919
3920 /** Creates and returns a new {@code CommandSpec} initialized from the specified associated user object. The specified
3921 * user object must have at least one {@link Command}, {@link Option} or {@link Parameters} annotation.
3922 * @param userObject the user object annotated with {@link Command}, {@link Option} and/or {@link Parameters} annotations.
3923 * @throws InitializationException if the specified object has no picocli annotations or has invalid annotations
3924 */
3925 public static CommandSpec forAnnotatedObject(Object userObject) { return forAnnotatedObject(userObject, new DefaultFactory()); }
3926
3927 /** Creates and returns a new {@code CommandSpec} initialized from the specified associated user object. The specified
3928 * user object must have at least one {@link Command}, {@link Option} or {@link Parameters} annotation.
3929 * @param userObject the user object annotated with {@link Command}, {@link Option} and/or {@link Parameters} annotations.
3930 * @param factory the factory used to create instances of {@linkplain Command#subcommands() subcommands}, {@linkplain Option#converter() converters}, etc., that are registered declaratively with annotation attributes
3931 * @throws InitializationException if the specified object has no picocli annotations or has invalid annotations
3932 */
3933 public static CommandSpec forAnnotatedObject(Object userObject, IFactory factory) { return CommandReflection.extractCommandSpec(userObject, factory, true); }
3934
3935 /** Creates and returns a new {@code CommandSpec} initialized from the specified associated user object. If the specified
3936 * user object has no {@link Command}, {@link Option} or {@link Parameters} annotations, an empty {@code CommandSpec} is returned.
3937 * @param userObject the user object annotated with {@link Command}, {@link Option} and/or {@link Parameters} annotations.
3938 * @throws InitializationException if the specified object has invalid annotations
3939 */
3940 public static CommandSpec forAnnotatedObjectLenient(Object userObject) { return forAnnotatedObjectLenient(userObject, new DefaultFactory()); }
3941
3942 /** Creates and returns a new {@code CommandSpec} initialized from the specified associated user object. If the specified
3943 * user object has no {@link Command}, {@link Option} or {@link Parameters} annotations, an empty {@code CommandSpec} is returned.
3944 * @param userObject the user object annotated with {@link Command}, {@link Option} and/or {@link Parameters} annotations.
3945 * @param factory the factory used to create instances of {@linkplain Command#subcommands() subcommands}, {@linkplain Option#converter() converters}, etc., that are registered declaratively with annotation attributes
3946 * @throws InitializationException if the specified object has invalid annotations
3947 */
3948 public static CommandSpec forAnnotatedObjectLenient(Object userObject, IFactory factory) { return CommandReflection.extractCommandSpec(userObject, factory, false); }
3949
3950 /** Ensures all attributes of this {@code CommandSpec} have a valid value; throws an {@link InitializationException} if this cannot be achieved. */
3951 void validate() {
3952 Collections.sort(positionalParameters, new PositionalParametersSorter());
3953 validatePositionalParameters(positionalParameters);
3954 List<String> wrongUsageHelpAttr = new ArrayList<String>();
3955 List<String> wrongVersionHelpAttr = new ArrayList<String>();
3956 List<String> usageHelpAttr = new ArrayList<String>();
3957 List<String> versionHelpAttr = new ArrayList<String>();
3958 for (OptionSpec option : options()) {
3959 if (option.usageHelp()) {
3960 usageHelpAttr.add(option.longestName());
3961 if (!isBoolean(option.type())) { wrongUsageHelpAttr.add(option.longestName()); }
3962 }
3963 if (option.versionHelp()) {
3964 versionHelpAttr.add(option.longestName());
3965 if (!isBoolean(option.type())) { wrongVersionHelpAttr.add(option.longestName()); }
3966 }
3967 }
3968 String wrongType = "Non-boolean options like %s should not be marked as '%s=true'. Usually a command has one %s boolean flag that triggers display of the %s. Alternatively, consider using @Command(mixinStandardHelpOptions = true) on your command instead.";
3969 String multiple = "Multiple options %s are marked as '%s=true'. Usually a command has only one %s option that triggers display of the %s. Alternatively, consider using @Command(mixinStandardHelpOptions = true) on your command instead.%n";
3970 if (!wrongUsageHelpAttr.isEmpty()) {
3971 throw new InitializationException(String.format(wrongType, wrongUsageHelpAttr, "usageHelp", "--help", "usage help message"));
3972 }
3973 if (!wrongVersionHelpAttr.isEmpty()) {
3974 throw new InitializationException(String.format(wrongType, wrongVersionHelpAttr, "versionHelp", "--version", "version information"));
3975 }
3976 if (usageHelpAttr.size() > 1) { new Tracer().warn(multiple, usageHelpAttr, "usageHelp", "--help", "usage help message"); }
3977 if (versionHelpAttr.size() > 1) { new Tracer().warn(multiple, versionHelpAttr, "versionHelp", "--version", "version information"); }
3978 }
3979
3980 /** Returns the user object associated with this command.
3981 * @see CommandLine#getCommand() */
3982 public Object userObject() { return userObject; }
3983
3984 /** Returns the CommandLine constructed with this {@code CommandSpec} model. */
3985 public CommandLine commandLine() { return commandLine;}
3986
3987 /** Sets the CommandLine constructed with this {@code CommandSpec} model. */
3988 protected CommandSpec commandLine(CommandLine commandLine) {
3989 this.commandLine = commandLine;
3990 for (CommandSpec mixedInSpec : mixins.values()) {
3991 mixedInSpec.commandLine(commandLine);
3992 }
3993 for (CommandLine sub : commands.values()) {
3994 sub.getCommandSpec().parent(this);
3995 }
3996 return this;
3997 }
3998
3999 /** Returns the parser specification for this command. */
4000 public ParserSpec parser() { return parser; }
4001 /** Initializes the parser specification for this command from the specified settings and returns this commandSpec.*/
4002 public CommandSpec parser(ParserSpec settings) { parser.initFrom(settings); return this; }
4003
4004 /** Returns the usage help message specification for this command. */
4005 public UsageMessageSpec usageMessage() { return usageMessage; }
4006 /** Initializes the usageMessage specification for this command from the specified settings and returns this commandSpec.*/
4007 public CommandSpec usageMessage(UsageMessageSpec settings) { usageMessage.initFrom(settings, this); return this; }
4008
4009 /** Returns the resource bundle base name for this command.
4010 * @return the resource bundle base name from the {@linkplain UsageMessageSpec#messages()}
4011 * @since 4.0 */
4012 public String resourceBundleBaseName() { return Messages.resourceBundleBaseName(usageMessage.messages()); }
4013 /** Initializes the resource bundle for this command: sets the {@link UsageMessageSpec#messages(Messages) UsageMessageSpec.messages} to
4014 * a {@link Messages Messages} object created from this command spec and the specified bundle, and then sets the
4015 * {@link ArgSpec#messages(Messages) ArgSpec.messages} of all options and positional parameters in this command
4016 * to the same {@code Messages} instance. Subcommands are not modified.
4017 * <p>This method is preferable to {@link #resourceBundle(ResourceBundle)} for pre-Java 8</p>
4018 * @param resourceBundleBaseName the base name of the ResourceBundle to set, may be {@code null}
4019 * @return this commandSpec
4020 * @see #addSubcommand(String, CommandLine)
4021 * @since 4.0 */
4022 public CommandSpec resourceBundleBaseName(String resourceBundleBaseName) {
4023 ResourceBundle bundle = empty(resourceBundleBaseName) ? null : ResourceBundle.getBundle(resourceBundleBaseName);
4024 setBundle(resourceBundleBaseName, bundle);
4025 return this;
4026 }
4027 /** Returns the resource bundle for this command.
4028 * @return the resource bundle from the {@linkplain UsageMessageSpec#messages()}
4029 * @since 3.6 */
4030 public ResourceBundle resourceBundle() { return Messages.resourceBundle(usageMessage.messages()); }
4031 /** Initializes the resource bundle for this command: sets the {@link UsageMessageSpec#messages(Messages) UsageMessageSpec.messages} to
4032 * a {@link Messages Messages} object created from this command spec and the specified bundle, and then sets the
4033 * {@link ArgSpec#messages(Messages) ArgSpec.messages} of all options and positional parameters in this command
4034 * to the same {@code Messages} instance. Subcommands are not modified.
4035 * @param bundle the ResourceBundle to set, may be {@code null}
4036 * @return this commandSpec
4037 * @see #addSubcommand(String, CommandLine)
4038 * @since 3.6 */
4039 public CommandSpec resourceBundle(ResourceBundle bundle) {
4040 setBundle(Messages.extractName(bundle), bundle);
4041 return this;
4042 }
4043 private void setBundle(String bundleBaseName, ResourceBundle bundle) {
4044 usageMessage().messages(new Messages(this, bundleBaseName, bundle));
4045 updateArgSpecMessages();
4046 }
4047 private void updateArgSpecMessages() {
4048 for (OptionSpec opt : options()) { opt.messages(usageMessage().messages()); }
4049 for (PositionalParamSpec pos : positionalParameters()) { pos.messages(usageMessage().messages()); }
4050 for (ArgGroupSpec group : argGroups()) { group.messages(usageMessage().messages()); }
4051 }
4052
4053 /** Returns a read-only view of the subcommand map. */
4054 public Map<String, CommandLine> subcommands() { return Collections.unmodifiableMap(commands); }
4055
4056 /** Adds the specified subcommand with the specified name.
4057 * If the specified subcommand does not have a ResourceBundle set, it is initialized to the ResourceBundle of this command spec.
4058 * @param name subcommand name - when this String is encountered in the command line arguments the subcommand is invoked
4059 * @param subcommand describes the subcommand to envoke when the name is encountered on the command line
4060 * @return this {@code CommandSpec} object for method chaining */
4061 public CommandSpec addSubcommand(String name, CommandSpec subcommand) {
4062 return addSubcommand(name, new CommandLine(subcommand));
4063 }
4064
4065 /** Adds the specified subcommand with the specified name.
4066 * If the specified subcommand does not have a ResourceBundle set, it is initialized to the ResourceBundle of this command spec.
4067 * @param name subcommand name - when this String is encountered in the command line arguments the subcommand is invoked
4068 * @param subCommandLine the subcommand to envoke when the name is encountered on the command line
4069 * @return this {@code CommandSpec} object for method chaining */
4070 public CommandSpec addSubcommand(String name, CommandLine subCommandLine) {
4071 Tracer t = new Tracer();
4072 if (t.isDebug()) {t.debug("Adding subcommand '%s' to '%s'%n", name, this.qualifiedName());}
4073 CommandLine previous = commands.put(name, subCommandLine);
4074 if (previous != null && previous != subCommandLine) { throw new InitializationException("Another subcommand named '" + name + "' already exists for command '" + this.name() + "'"); }
4075 CommandSpec subSpec = subCommandLine.getCommandSpec();
4076 if (subSpec.name == null) { subSpec.name(name); }
4077 subSpec.parent(this);
4078 for (String alias : subSpec.aliases()) {
4079 if (t.isDebug()) {t.debug("Adding alias '%s' for subcommand '%s' to '%s'%n", alias, name, this.qualifiedName());}
4080 previous = commands.put(alias, subCommandLine);
4081 if (previous != null && previous != subCommandLine) { throw new InitializationException("Alias '" + alias + "' for subcommand '" + name + "' is already used by another subcommand of '" + this.name() + "'"); }
4082 }
4083 subSpec.initCommandHierarchyWithResourceBundle(resourceBundleBaseName(), resourceBundle());
4084 return this;
4085 }
4086 private void initCommandHierarchyWithResourceBundle(String bundleBaseName, ResourceBundle rb) {
4087 if (resourceBundle() == null) {
4088 setBundle(bundleBaseName, rb);
4089 }
4090 for (CommandLine sub : commands.values()) { // percolate down the hierarchy
4091 sub.getCommandSpec().initCommandHierarchyWithResourceBundle(bundleBaseName, rb);
4092 }
4093 }
4094
4095 /** Returns whether method commands should be added as subcommands. Used by the annotation processor.
4096 * @since 4.0 */
4097 public boolean isAddMethodSubcommands() { return (isAddMethodSubcommands == null) ? DEFAULT_IS_ADD_METHOD_SUBCOMMANDS : isAddMethodSubcommands; }
4098 /** Sets whether method commands should be added as subcommands. Used by the annotation processor.
4099 * @since 4.0 */
4100 public CommandSpec setAddMethodSubcommands(Boolean addMethodSubcommands) { isAddMethodSubcommands = addMethodSubcommands; return this; }
4101
4102 /** Reflects on the class of the {@linkplain #userObject() user object} and registers any command methods
4103 * (class methods annotated with {@code @Command}) as subcommands.
4104 *
4105 * @return this {@link CommandSpec} object for method chaining
4106 * @see #addMethodSubcommands(IFactory)
4107 * @see #addSubcommand(String, CommandLine)
4108 * @since 3.6.0
4109 */
4110 public CommandSpec addMethodSubcommands() { return addMethodSubcommands(new DefaultFactory()); }
4111
4112 /** Reflects on the class of the {@linkplain #userObject() user object} and registers any command methods
4113 * (class methods annotated with {@code @Command}) as subcommands.
4114 * @param factory the factory used to create instances of subcommands, converters, etc., that are registered declaratively with annotation attributes
4115 * @return this {@link CommandSpec} object for method chaining
4116 * @see #addSubcommand(String, CommandLine)
4117 * @since 3.7.0
4118 */
4119 public CommandSpec addMethodSubcommands(IFactory factory) {
4120 if (userObject() instanceof Method) {
4121 throw new InitializationException("Cannot discover subcommand methods of this Command Method: " + userObject());
4122 }
4123 for (CommandLine sub : createMethodSubcommands(userObject().getClass(), factory)) {
4124 addSubcommand(sub.getCommandName(), sub);
4125 }
4126 isAddMethodSubcommands = true;
4127 return this;
4128 }
4129 static List<CommandLine> createMethodSubcommands(Class<?> cls, IFactory factory) {
4130 List<CommandLine> result = new ArrayList<CommandLine>();
4131 for (Method method : getCommandMethods(cls, null)) {
4132 result.add(new CommandLine(method, factory));
4133 }
4134 return result;
4135 }
4136
4137 /** Returns the parent command of this subcommand, or {@code null} if this is a top-level command. */
4138 public CommandSpec parent() { return parent; }
4139
4140 /** Sets the parent command of this subcommand.
4141 * @return this CommandSpec for method chaining */
4142 public CommandSpec parent(CommandSpec parent) { this.parent = parent; return this; }
4143
4144 /** Adds the specified option spec or positional parameter spec to the list of configured arguments to expect.
4145 * @param arg the option spec or positional parameter spec to add
4146 * @return this CommandSpec for method chaining */
4147 public CommandSpec add(ArgSpec arg) { return arg.isOption() ? addOption((OptionSpec) arg) : addPositional((PositionalParamSpec) arg); }
4148
4149 /** Adds the specified option spec to the list of configured arguments to expect.
4150 * The option's {@linkplain OptionSpec#description()} may now return Strings from this
4151 * CommandSpec's {@linkplain UsageMessageSpec#messages() messages}.
4152 * The option parameter's {@linkplain OptionSpec#defaultValueString()} may
4153 * now return Strings from this CommandSpec's {@link CommandSpec#defaultValueProvider()} IDefaultValueProvider}.
4154 * @param option the option spec to add
4155 * @return this CommandSpec for method chaining
4156 * @throws DuplicateOptionAnnotationsException if any of the names of the specified option is the same as the name of another option */
4157 public CommandSpec addOption(OptionSpec option) {
4158 for (String name : option.names()) { // cannot be null or empty
4159 OptionSpec existing = optionsByNameMap.put(name, option);
4160 if (existing != null) { /* was: && !existing.equals(option)) {*/ // since 4.0 ArgGroups: an option cannot be in multiple groups
4161 throw DuplicateOptionAnnotationsException.create(name, option, existing);
4162 }
4163 if (name.length() == 2 && name.startsWith("-")) { posixOptionsByKeyMap.put(name.charAt(1), option); }
4164 }
4165 options.add(option);
4166 return addArg(option);
4167 }
4168 /** Adds the specified positional parameter spec to the list of configured arguments to expect.
4169 * The positional parameter's {@linkplain PositionalParamSpec#description()} may
4170 * now return Strings from this CommandSpec's {@linkplain UsageMessageSpec#messages() messages}.
4171 * The positional parameter's {@linkplain PositionalParamSpec#defaultValueString()} may
4172 * now return Strings from this CommandSpec's {@link CommandSpec#defaultValueProvider()} IDefaultValueProvider}.
4173 * @param positional the positional parameter spec to add
4174 * @return this CommandSpec for method chaining */
4175 public CommandSpec addPositional(PositionalParamSpec positional) {
4176 positionalParameters.add(positional);
4177 return addArg(positional);
4178 }
4179 private CommandSpec addArg(ArgSpec arg) {
4180 args.add(arg);
4181 if (arg.required() && arg.group() == null) { requiredArgs.add(arg); }
4182 arg.messages(usageMessage().messages());
4183 arg.commandSpec = this;
4184 return this;
4185 }
4186
4187 /** Adds the specified {@linkplain ArgGroupSpec argument group} to the groups in this command.
4188 * @param group the group spec to add
4189 * @return this CommandSpec for method chaining
4190 * @throws InitializationException if the specified group or one of its {@linkplain ArgGroupSpec#parentGroup() ancestors} has already been added
4191 * @since 4.0 */
4192 public CommandSpec addArgGroup(ArgGroupSpec group) {
4193 Assert.notNull(group, "group");
4194 if (group.parentGroup() != null) {
4195 throw new InitializationException("Groups that are part of another group should not be added to a command. Add only the top-level group.");
4196 }
4197 check(group, flatten(groups, new HashSet<ArgGroupSpec>()));
4198 this.groups.add(group);
4199 addGroupArgsToCommand(group, new HashMap<String, ArgGroupSpec>());
4200 return this;
4201 }
4202 private void addGroupArgsToCommand(ArgGroupSpec group, Map<String, ArgGroupSpec> added) {
4203 for (ArgSpec arg : group.args()) {
4204 if (arg.isOption()) {
4205 for (String name : ((OptionSpec) arg).names()) {
4206 if (added.containsKey(name)) {
4207 throw new DuplicateNameException("An option cannot be in multiple groups but " + name + " is in " + group.synopsis() + " and " + added.get(name).synopsis() + ". Refactor to avoid this. For example, (-a | (-a -b)) can be rewritten as (-a [-b]), and (-a -b | -a -c) can be rewritten as (-a (-b | -c)).");
4208 }
4209 }
4210 for (String name : ((OptionSpec) arg).names()) { added.put(name, group); }
4211 }
4212 add(arg);
4213 }
4214 for (ArgGroupSpec sub : group.subgroups()) { addGroupArgsToCommand(sub, added); }
4215 }
4216 private Set<ArgGroupSpec> flatten(Collection<ArgGroupSpec> groups, Set<ArgGroupSpec> result) {
4217 for (ArgGroupSpec group : groups) { flatten(group, result); } return result;
4218 }
4219 private Set<ArgGroupSpec> flatten(ArgGroupSpec group, Set<ArgGroupSpec> result) {
4220 result.add(group);
4221 for (ArgGroupSpec sub : group.subgroups()) { flatten(sub, result); }
4222 return result;
4223 }
4224 private void check(ArgGroupSpec group, Set<ArgGroupSpec> existing) {
4225 if (existing.contains(group)) {
4226 throw new InitializationException("The specified group " + group.synopsis() + " has already been added to the " + qualifiedName() + " command.");
4227 }
4228 for (ArgGroupSpec sub : group.subgroups()) { check(sub, existing); }
4229 }
4230
4231 /** Adds the specified mixin {@code CommandSpec} object to the map of mixins for this command.
4232 * @param name the name that can be used to later retrieve the mixin
4233 * @param mixin the mixin whose options and positional parameters and other attributes to add to this command
4234 * @return this CommandSpec for method chaining */
4235 public CommandSpec addMixin(String name, CommandSpec mixin) {
4236 mixins.put(name, mixin);
4237
4238 parser.initSeparator(mixin.parser.separator());
4239 initName(mixin.name());
4240 initVersion(mixin.version());
4241 initHelpCommand(mixin.helpCommand());
4242 initVersionProvider(mixin.versionProvider());
4243 initDefaultValueProvider(mixin.defaultValueProvider());
4244 usageMessage.initFromMixin(mixin.usageMessage, this);
4245
4246 for (Map.Entry<String, CommandLine> entry : mixin.subcommands().entrySet()) {
4247 addSubcommand(entry.getKey(), entry.getValue());
4248 }
4249 for (OptionSpec optionSpec : mixin.options()) { addOption(optionSpec); }
4250 for (PositionalParamSpec paramSpec : mixin.positionalParameters()) { addPositional(paramSpec); }
4251 return this;
4252 }
4253
4254 /** Adds the specified {@code UnmatchedArgsBinding} to the list of model objects to capture unmatched arguments for this command.
4255 * @param spec the unmatched arguments binding to capture unmatched arguments
4256 * @return this CommandSpec for method chaining */
4257 public CommandSpec addUnmatchedArgsBinding(UnmatchedArgsBinding spec) { unmatchedArgs.add(spec); parser().unmatchedArgumentsAllowed(true); return this; }
4258
4259 /** Returns a map of the mixin names to mixin {@code CommandSpec} objects configured for this command.
4260 * @return an immutable map of mixins added to this command. */
4261 public Map<String, CommandSpec> mixins() { return Collections.unmodifiableMap(mixins); }
4262
4263 /** Returns the list of options configured for this command.
4264 * @return an immutable list of options that this command recognizes. */
4265 public List<OptionSpec> options() { return Collections.unmodifiableList(options); }
4266
4267 /** Returns the list of positional parameters configured for this command.
4268 * @return an immutable list of positional parameters that this command recognizes. */
4269 public List<PositionalParamSpec> positionalParameters() { return Collections.unmodifiableList(positionalParameters); }
4270
4271 /** Returns the {@linkplain ArgGroupSpec argument groups} in this command.
4272 * @return an immutable list of groups of options and positional parameters in this command
4273 * @since 4.0 */
4274 public List<ArgGroupSpec> argGroups() { return Collections.unmodifiableList(groups); }
4275
4276 /** Returns a map of the option names to option spec objects configured for this command.
4277 * @return an immutable map of options that this command recognizes. */
4278 public Map<String, OptionSpec> optionsMap() { return Collections.unmodifiableMap(optionsByNameMap); }
4279
4280 /** Returns a map of the short (single character) option names to option spec objects configured for this command.
4281 * @return an immutable map of options that this command recognizes. */
4282 public Map<Character, OptionSpec> posixOptionsMap() { return Collections.unmodifiableMap(posixOptionsByKeyMap); }
4283
4284 /** Returns the list of required options and positional parameters configured for this command.
4285 * This does not include options and positional parameters that are part of a {@linkplain ArgGroupSpec group}.
4286 * @return an immutable list of the required options and positional parameters for this command. */
4287 public List<ArgSpec> requiredArgs() { return Collections.unmodifiableList(requiredArgs); }
4288
4289 /** Returns the list of {@link UnmatchedArgsBinding UnmatchedArgumentsBindings} configured for this command;
4290 * each {@code UnmatchedArgsBinding} captures the arguments that could not be matched to any options or positional parameters. */
4291 public List<UnmatchedArgsBinding> unmatchedArgsBindings() { return Collections.unmodifiableList(unmatchedArgs); }
4292
4293 /** Returns name of this command. Used in the synopsis line of the help message.
4294 * {@link #DEFAULT_COMMAND_NAME} by default, initialized from {@link Command#name()} if defined.
4295 * @see #qualifiedName() */
4296 public String name() { return (name == null) ? DEFAULT_COMMAND_NAME : name; }
4297
4298 /** Returns the alias command names of this subcommand.
4299 * @since 3.1 */
4300 public String[] aliases() { return aliases.toArray(new String[0]); }
4301
4302 /** Returns all names of this command, including {@link #name()} and {@link #aliases()}.
4303 * @since 3.9 */
4304 public Set<String> names() {
4305 Set<String> result = new LinkedHashSet<String>();
4306 result.add(name());
4307 result.addAll(Arrays.asList(aliases()));
4308 return result;
4309 }
4310
4311 /** Returns the list of all options and positional parameters configured for this command.
4312 * @return an immutable list of all options and positional parameters for this command. */
4313 public List<ArgSpec> args() { return Collections.unmodifiableList(args); }
4314 Object[] argValues() {
4315 Map<Class<?>, CommandSpec> allMixins = null;
4316 int argsLength = args.size();
4317 int shift = 0;
4318 for (Map.Entry<String, CommandSpec> mixinEntry : mixins.entrySet()) {
4319 if (mixinEntry.getKey().equals(AutoHelpMixin.KEY)) {
4320 shift = 2;
4321 argsLength -= shift;
4322 continue;
4323 }
4324 CommandSpec mixin = mixinEntry.getValue();
4325 int mixinArgs = mixin.args.size();
4326 argsLength -= (mixinArgs - 1); // subtract 1 because that's the mixin
4327 if (allMixins == null) {
4328 allMixins = new IdentityHashMap<Class<?>, CommandSpec>(mixins.size());
4329 }
4330 allMixins.put(mixin.userObject.getClass(), mixin);
4331 }
4332
4333 Object[] values = new Object[argsLength];
4334 if (allMixins == null) {
4335 for (int i = 0; i < values.length; i++) { values[i] = args.get(i + shift).getValue(); }
4336 } else {
4337 int argIndex = shift;
4338 Class<?>[] methodParams = ((Method) userObject).getParameterTypes();
4339 for (int i = 0; i < methodParams.length; i++) {
4340 final Class<?> param = methodParams[i];
4341 CommandSpec mixin = allMixins.remove(param);
4342 if (mixin == null) {
4343 values[i] = args.get(argIndex++).getValue();
4344 } else {
4345 values[i] = mixin.userObject;
4346 argIndex += mixin.args.size();
4347 }
4348 }
4349 }
4350 return values;
4351 }
4352
4353 /** Returns the String to use as the program name in the synopsis line of the help message:
4354 * this command's {@link #name() name}, preceded by the qualified name of the parent command, if any, separated by a space.
4355 * @return {@link #DEFAULT_COMMAND_NAME} by default, initialized from {@link Command#name()} and the parent command if defined.
4356 * @since 3.0.1 */
4357 public String qualifiedName() { return qualifiedName(" "); }
4358 /** Returns this command's fully qualified name, which is its {@link #name() name}, preceded by the qualified name of the parent command, if this command has a parent command.
4359 * @return {@link #DEFAULT_COMMAND_NAME} by default, initialized from {@link Command#name()} and the parent command if any.
4360 * @param separator the string to put between the names of the commands in the hierarchy
4361 * @since 3.6 */
4362 public String qualifiedName(String separator) {
4363 String result = name();
4364 if (parent() != null) { result = parent().qualifiedName(separator) + separator + result; }
4365 return result;
4366 }
4367
4368 /** Returns version information for this command, to print to the console when the user specifies an
4369 * {@linkplain OptionSpec#versionHelp() option} to request version help. This is not part of the usage help message.
4370 * @return the version strings generated by the {@link #versionProvider() version provider} if one is set, otherwise the {@linkplain #version(String...) version literals}*/
4371 public String[] version() {
4372 if (versionProvider != null) {
4373 try {
4374 return versionProvider.getVersion();
4375 } catch (Exception ex) {
4376 String msg = "Could not get version info from " + versionProvider + ": " + ex;
4377 throw new ExecutionException(this.commandLine, msg, ex);
4378 }
4379 }
4380 return version == null ? UsageMessageSpec.DEFAULT_MULTI_LINE : version;
4381 }
4382
4383 /** Returns the version provider for this command, to generate the {@link #version()} strings.
4384 * @return the version provider or {@code null} if the version strings should be returned from the {@linkplain #version(String...) version literals}.*/
4385 public IVersionProvider versionProvider() { return versionProvider; }
4386
4387 /** Returns whether this subcommand is a help command, and required options and positional
4388 * parameters of the parent command should not be validated.
4389 * @return {@code true} if this subcommand is a help command and picocli should not check for missing required
4390 * options and positional parameters on the parent command
4391 * @see Command#helpCommand() */
4392 public boolean helpCommand() { return (isHelpCommand == null) ? DEFAULT_IS_HELP_COMMAND : isHelpCommand; }
4393
4394 /** Returns {@code true} if the standard help options have been mixed in with this command, {@code false} otherwise. */
4395 public boolean mixinStandardHelpOptions() { return mixins.containsKey(AutoHelpMixin.KEY); }
4396
4397 /** Returns a string representation of this command, used in error messages and trace messages. */
4398 public String toString() { return toString; }
4399
4400 /** Sets the String to use as the program name in the synopsis line of the help message.
4401 * @return this CommandSpec for method chaining */
4402 public CommandSpec name(String name) { this.name = name; return this; }
4403
4404 /** Sets the alternative names by which this subcommand is recognized on the command line.
4405 * @return this CommandSpec for method chaining
4406 * @since 3.1 */
4407 public CommandSpec aliases(String... aliases) {
4408 this.aliases = new LinkedHashSet<String>(Arrays.asList(aliases == null ? new String[0] : aliases));
4409 return this;
4410 }
4411
4412 /** Returns the default value provider for this command.
4413 * @return the default value provider or {@code null}
4414 * @since 3.6 */
4415 public IDefaultValueProvider defaultValueProvider() { return defaultValueProvider; }
4416
4417 /** Sets default value provider for this command.
4418 * @param defaultValueProvider the default value provider to use, or {@code null}.
4419 * @return this CommandSpec for method chaining
4420 * @since 3.6 */
4421 public CommandSpec defaultValueProvider(IDefaultValueProvider defaultValueProvider) { this.defaultValueProvider = defaultValueProvider; return this; }
4422
4423 /** Sets version information literals for this command, to print to the console when the user specifies an
4424 * {@linkplain OptionSpec#versionHelp() option} to request version help. Only used if no {@link #versionProvider() versionProvider} is set.
4425 * @return this CommandSpec for method chaining */
4426 public CommandSpec version(String... version) { this.version = version; return this; }
4427
4428 /** Sets version provider for this command, to generate the {@link #version()} strings.
4429 * @param versionProvider the version provider to use to generate the version strings, or {@code null} if the {@linkplain #version(String...) version literals} should be used.
4430 * @return this CommandSpec for method chaining */
4431 public CommandSpec versionProvider(IVersionProvider versionProvider) { this.versionProvider = versionProvider; return this; }
4432
4433 /** Sets whether this is a help command and required parameter checking should be suspended.
4434 * @return this CommandSpec for method chaining
4435 * @see Command#helpCommand() */
4436 public CommandSpec helpCommand(boolean newValue) {isHelpCommand = newValue; return this;}
4437
4438 /** Sets whether the standard help options should be mixed in with this command.
4439 * @return this CommandSpec for method chaining
4440 * @see Command#mixinStandardHelpOptions() */
4441 public CommandSpec mixinStandardHelpOptions(boolean newValue) {
4442 if (newValue) {
4443 CommandSpec mixin = CommandSpec.forAnnotatedObject(new AutoHelpMixin(), new DefaultFactory());
4444 addMixin(AutoHelpMixin.KEY, mixin);
4445 } else {
4446 CommandSpec helpMixin = mixins.remove(AutoHelpMixin.KEY);
4447 if (helpMixin != null) {
4448 options.removeAll(helpMixin.options);
4449 for (OptionSpec option : helpMixin.options()) {
4450 for (String name : option.names) {
4451 optionsByNameMap.remove(name);
4452 if (name.length() == 2 && name.startsWith("-")) { posixOptionsByKeyMap.remove(name.charAt(1)); }
4453 }
4454 }
4455 }
4456 }
4457 return this;
4458 }
4459
4460 /** Sets the string representation of this command, used in error messages and trace messages.
4461 * @param newValue the string representation
4462 * @return this CommandSpec for method chaining */
4463 public CommandSpec withToString(String newValue) { this.toString = newValue; return this; }
4464
4465 /**
4466 * Updates the following attributes from the specified {@code @Command} annotation:
4467 * aliases, {@link ParserSpec#separator() parser separator}, command name, version, help command,
4468 * version provider, default provider and {@link UsageMessageSpec usage message spec}.
4469 * @param cmd the {@code @Command} annotation to get attribute values from
4470 * @param factory factory used to instantiate classes
4471 * @since 3.7
4472 */
4473 public void updateCommandAttributes(Command cmd, IFactory factory) {
4474 aliases(cmd.aliases());
4475 parser().updateSeparator(cmd.separator());
4476 updateName(cmd.name());
4477 updateVersion(cmd.version());
4478 updateHelpCommand(cmd.helpCommand());
4479 updateAddMethodSubcommands(cmd.addMethodSubcommands());
4480 usageMessage().updateFromCommand(cmd, this);
4481
4482 if (factory != null) {
4483 updateVersionProvider(cmd.versionProvider(), factory);
4484 initDefaultValueProvider(cmd.defaultValueProvider(), factory);
4485 }
4486 }
4487
4488 void initName(String value) { if (initializable(name, value, DEFAULT_COMMAND_NAME)) {name = value;} }
4489 void initHelpCommand(boolean value) { if (initializable(isHelpCommand, value, DEFAULT_IS_HELP_COMMAND)) {isHelpCommand = value;} }
4490 void initVersion(String[] value) { if (initializable(version, value, UsageMessageSpec.DEFAULT_MULTI_LINE)) {version = value.clone();} }
4491 void initVersionProvider(IVersionProvider value) { if (versionProvider == null) { versionProvider = value; } }
4492 void initDefaultValueProvider(IDefaultValueProvider value) { if (defaultValueProvider == null) { defaultValueProvider = value; } }
4493 void initDefaultValueProvider(Class<? extends IDefaultValueProvider> value, IFactory factory) {
4494 if (initializable(defaultValueProvider, value, NoDefaultProvider.class)) { defaultValueProvider = (DefaultFactory.createDefaultValueProvider(factory, value)); }
4495 }
4496 void updateName(String value) { if (isNonDefault(value, DEFAULT_COMMAND_NAME)) {name = value;} }
4497 void updateHelpCommand(boolean value) { if (isNonDefault(value, DEFAULT_IS_HELP_COMMAND)) {isHelpCommand = value;} }
4498 void updateAddMethodSubcommands(boolean value) { if (isNonDefault(value, DEFAULT_IS_ADD_METHOD_SUBCOMMANDS)) {isAddMethodSubcommands = value;} }
4499 void updateVersion(String[] value) { if (isNonDefault(value, UsageMessageSpec.DEFAULT_MULTI_LINE)) {version = value.clone();} }
4500 void updateVersionProvider(Class<? extends IVersionProvider> value, IFactory factory) {
4501 if (isNonDefault(value, NoVersionProvider.class)) { versionProvider = (DefaultFactory.createVersionProvider(factory, value)); }
4502 }
4503
4504 /** Returns the option with the specified short name, or {@code null} if no option with that name is defined for this command. */
4505 public OptionSpec findOption(char shortName) { return findOption(shortName, options()); }
4506 /** Returns the option with the specified name, or {@code null} if no option with that name is defined for this command.
4507 * @param name used to search the options. May include option name prefix characters or not. */
4508 public OptionSpec findOption(String name) { return findOption(name, options()); }
4509
4510 static OptionSpec findOption(char shortName, Iterable<OptionSpec> options) {
4511 for (OptionSpec option : options) {
4512 for (String name : option.names()) {
4513 if (name.length() == 2 && name.charAt(0) == '-' && name.charAt(1) == shortName) { return option; }
4514 if (name.length() == 1 && name.charAt(0) == shortName) { return option; }
4515 }
4516 }
4517 return null;
4518 }
4519 static OptionSpec findOption(String name, List<OptionSpec> options) {
4520 for (OptionSpec option : options) {
4521 for (String prefixed : option.names()) {
4522 if (prefixed.equals(name) || stripPrefix(prefixed).equals(name)) { return option; }
4523 }
4524 }
4525 return null;
4526 }
4527 static String stripPrefix(String prefixed) {
4528 for (int i = 0; i < prefixed.length(); i++) {
4529 if (Character.isJavaIdentifierPart(prefixed.charAt(i))) { return prefixed.substring(i); }
4530 }
4531 return prefixed;
4532 }
4533 List<String> findOptionNamesWithPrefix(String prefix) {
4534 List<String> result = new ArrayList<String>();
4535 for (OptionSpec option : options()) {
4536 for (String name : option.names()) {
4537 if (stripPrefix(name).startsWith(prefix)) { result.add(name); }
4538 }
4539 }
4540 return result;
4541 }
4542
4543 boolean resemblesOption(String arg, Tracer tracer) {
4544 if (parser().unmatchedOptionsArePositionalParams()) {
4545 if (tracer != null && tracer.isDebug()) {tracer.debug("Parser is configured to treat all unmatched options as positional parameter%n", arg);}
4546 return false;
4547 }
4548 if (arg.length() == 1) {
4549 if (tracer != null && tracer.isDebug()) {tracer.debug("Single-character arguments that don't match known options are considered positional parameters%n", arg);}
4550 return false;
4551 }
4552 if (options().isEmpty()) {
4553 boolean result = arg.startsWith("-");
4554 if (tracer != null && tracer.isDebug()) {tracer.debug("'%s' %s an option%n", arg, (result ? "resembles" : "doesn't resemble"));}
4555 return result;
4556 }
4557 int count = 0;
4558 for (String optionName : optionsMap().keySet()) {
4559 for (int i = 0; i < arg.length(); i++) {
4560 if (optionName.length() > i && arg.charAt(i) == optionName.charAt(i)) { count++; } else { break; }
4561 }
4562 }
4563 boolean result = count > 0 && count * 10 >= optionsMap().size() * 9; // at least one prefix char in common with 9 out of 10 options
4564 if (tracer != null && tracer.isDebug()) {tracer.debug("'%s' %s an option: %d matching prefix chars out of %d option names%n", arg, (result ? "resembles" : "doesn't resemble"), count, optionsMap().size());}
4565 return result;
4566 }
4567 }
4568 private static boolean initializable(Object current, Object candidate, Object defaultValue) {
4569 return current == null && isNonDefault(candidate, defaultValue);
4570 }
4571 private static boolean initializable(Object current, Object[] candidate, Object[] defaultValue) {
4572 return current == null && isNonDefault(candidate, defaultValue);
4573 }
4574 private static boolean isNonDefault(Object candidate, Object defaultValue) {
4575 return !Assert.notNull(defaultValue, "defaultValue").equals(candidate);
4576 }
4577 private static boolean isNonDefault(Object[] candidate, Object[] defaultValue) {
4578 return !Arrays.equals(Assert.notNull(defaultValue, "defaultValue"), candidate);
4579 }
4580 /** Models the usage help message specification and can be used to customize the usage help message.
4581 * <p>
4582 * This class provides two ways to customize the usage help message:
4583 * </p>
4584 * <ul>
4585 * <li>Change the text of the predefined sections (this may also be done declaratively using the annotations)</li>
4586 * <li>Add custom sections, or remove or re-order predefined sections</li>
4587 * </ul>
4588 * <p>
4589 * The pre-defined sections have getters and setters that return a String (or array of Strings). For example:
4590 * {@link #description()} and {@link #description(String...)} or {@link #header()} and {@link #header(String...)}.
4591 * </p><p>
4592 * Changing the section order, or adding custom sections can be accomplished with {@link #sectionKeys(List)} and {@link #sectionMap(Map)}.
4593 * This gives complete freedom on how a usage help message section is rendered, but it also means that the {@linkplain IHelpSectionRenderer section renderer}
4594 * is responsible for all aspects of rendering the section, including layout and emitting ANSI escape codes.
4595 * The {@link Help.TextTable} and {@link Help.Ansi.Text} classes, and the {@link CommandLine.Help.Ansi#string(String)} and {@link CommandLine.Help.Ansi#text(String)} methods may be useful.
4596 * </p><p>
4597 * The usage help message is created more or less like this:
4598 * </p>
4599 * <pre>
4600 * // CommandLine.usage(...) or CommandLine.getUsageMessage(...)
4601 * Help.ColorScheme colorScheme = Help.defaultColorScheme(Help.Ansi.AUTO);
4602 * Help help = getHelpFactory().create(getCommandSpec(), colorScheme)
4603 * StringBuilder result = new StringBuilder();
4604 * for (String key : getHelpSectionKeys()) {
4605 * IHelpSectionRenderer renderer = getHelpSectionMap().get(key);
4606 * if (renderer != null) { result.append(renderer.render(help)); }
4607 * }
4608 * // return or print result
4609 * </pre>
4610 * <p>
4611 * Where the default {@linkplain #sectionMap() help section map} is constructed like this:</p>
4612 * <pre>{@code
4613 * // The default section renderers delegate to methods in Help for their implementation
4614 * // (using Java 8 lambda notation for brevity):
4615 * Map<String, IHelpSectionRenderer> sectionMap = new HashMap<>();
4616 * sectionMap.put(SECTION_KEY_HEADER_HEADING, help -> help.headerHeading());
4617 * sectionMap.put(SECTION_KEY_HEADER, help -> help.header());
4618 * sectionMap.put(SECTION_KEY_SYNOPSIS_HEADING, help -> help.synopsisHeading()); //e.g. Usage:
4619 * sectionMap.put(SECTION_KEY_SYNOPSIS, help -> help.synopsis(help.synopsisHeadingLength())); //e.g. <cmd> [OPTIONS] <subcmd> [COMMAND-OPTIONS] [ARGUMENTS]
4620 * sectionMap.put(SECTION_KEY_DESCRIPTION_HEADING, help -> help.descriptionHeading()); //e.g. %nDescription:%n%n
4621 * sectionMap.put(SECTION_KEY_DESCRIPTION, help -> help.description()); //e.g. {"Converts foos to bars.", "Use options to control conversion mode."}
4622 * sectionMap.put(SECTION_KEY_PARAMETER_LIST_HEADING, help -> help.parameterListHeading()); //e.g. %nPositional parameters:%n%n
4623 * sectionMap.put(SECTION_KEY_PARAMETER_LIST, help -> help.parameterList()); //e.g. [FILE...] the files to convert
4624 * sectionMap.put(SECTION_KEY_OPTION_LIST_HEADING, help -> help.optionListHeading()); //e.g. %nOptions:%n%n
4625 * sectionMap.put(SECTION_KEY_OPTION_LIST, help -> help.optionList()); //e.g. -h, --help displays this help and exits
4626 * sectionMap.put(SECTION_KEY_COMMAND_LIST_HEADING, help -> help.commandListHeading()); //e.g. %nCommands:%n%n
4627 * sectionMap.put(SECTION_KEY_COMMAND_LIST, help -> help.commandList()); //e.g. add adds the frup to the frooble
4628 * sectionMap.put(SECTION_KEY_FOOTER_HEADING, help -> help.footerHeading());
4629 * sectionMap.put(SECTION_KEY_FOOTER, help -> help.footer());
4630 * }</pre>
4631 *
4632 * @since 3.0 */
4633 public static class UsageMessageSpec {
4634
4635 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Header Heading section.
4636 * The default renderer for this section calls {@link Help#headerHeading(Object...)}.
4637 * @since 3.9 */
4638 public static final String SECTION_KEY_HEADER_HEADING = "headerHeading";
4639
4640 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Header section.
4641 * The default renderer for this section calls {@link Help#header(Object...)}.
4642 * @since 3.9 */
4643 public static final String SECTION_KEY_HEADER = "header";
4644
4645 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Synopsis Heading section.
4646 * The default renderer for this section calls {@link Help#synopsisHeading(Object...)}.
4647 * @since 3.9 */
4648 public static final String SECTION_KEY_SYNOPSIS_HEADING = "synopsisHeading";
4649
4650 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Synopsis section.
4651 * The default renderer for this section calls {@link Help#synopsis(int)}.
4652 * @since 3.9 */
4653 public static final String SECTION_KEY_SYNOPSIS = "synopsis";
4654
4655 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Description Heading section.
4656 * The default renderer for this section calls {@link Help#descriptionHeading(Object...)}.
4657 * @since 3.9 */
4658 public static final String SECTION_KEY_DESCRIPTION_HEADING = "descriptionHeading";
4659
4660 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Description section.
4661 * The default renderer for this section calls {@link Help#description(Object...)}.
4662 * @since 3.9 */
4663 public static final String SECTION_KEY_DESCRIPTION = "description";
4664
4665 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Parameter List Heading section.
4666 * The default renderer for this section calls {@link Help#parameterListHeading(Object...)}.
4667 * @since 3.9 */
4668 public static final String SECTION_KEY_PARAMETER_LIST_HEADING = "parameterListHeading";
4669
4670 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Parameter List section.
4671 * The default renderer for this section calls {@link Help#parameterList()}.
4672 * @since 3.9 */
4673 public static final String SECTION_KEY_PARAMETER_LIST = "parameterList";
4674
4675 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Option List Heading section.
4676 * The default renderer for this section calls {@link Help#optionListHeading(Object...)}.
4677 * @since 3.9 */
4678 public static final String SECTION_KEY_OPTION_LIST_HEADING = "optionListHeading";
4679
4680 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Option List section.
4681 * The default renderer for this section calls {@link Help#optionList()}.
4682 * @since 3.9 */
4683 public static final String SECTION_KEY_OPTION_LIST = "optionList";
4684
4685 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Subcommand List Heading section.
4686 * The default renderer for this section calls {@link Help#commandListHeading(Object...)}.
4687 * @since 3.9 */
4688 public static final String SECTION_KEY_COMMAND_LIST_HEADING = "commandListHeading";
4689
4690 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Subcommand List section.
4691 * The default renderer for this section calls {@link Help#commandList()}.
4692 * @since 3.9 */
4693 public static final String SECTION_KEY_COMMAND_LIST = "commandList";
4694
4695 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Footer Heading section.
4696 * The default renderer for this section calls {@link Help#footerHeading(Object...)}.
4697 * @since 3.9 */
4698 public static final String SECTION_KEY_FOOTER_HEADING = "footerHeading";
4699
4700 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Footer section.
4701 * The default renderer for this section calls {@link Help#footer(Object...)}.
4702 * @since 3.9 */
4703 public static final String SECTION_KEY_FOOTER = "footer";
4704
4705 /** Constant holding the default usage message width: <code>{@value}</code>. */
4706 public final static int DEFAULT_USAGE_WIDTH = 80;
4707 private final static int MINIMUM_USAGE_WIDTH = 55;
4708
4709 /** Constant String holding the default synopsis heading: <code>{@value}</code>. */
4710 static final String DEFAULT_SYNOPSIS_HEADING = "Usage: ";
4711
4712 /** Constant String holding the default command list heading: <code>{@value}</code>. */
4713 static final String DEFAULT_COMMAND_LIST_HEADING = "Commands:%n";
4714
4715 /** Constant String holding the default string that separates options from option parameters: {@code ' '} ({@value}). */
4716 static final char DEFAULT_REQUIRED_OPTION_MARKER = ' ';
4717
4718 /** Constant Boolean holding the default setting for whether to abbreviate the synopsis: <code>{@value}</code>.*/
4719 static final Boolean DEFAULT_ABBREVIATE_SYNOPSIS = Boolean.FALSE;
4720
4721 /** Constant Boolean holding the default setting for whether to sort the options alphabetically: <code>{@value}</code>.*/
4722 static final Boolean DEFAULT_SORT_OPTIONS = Boolean.TRUE;
4723
4724 /** Constant Boolean holding the default setting for whether to show default values in the usage help message: <code>{@value}</code>.*/
4725 static final Boolean DEFAULT_SHOW_DEFAULT_VALUES = Boolean.FALSE;
4726
4727 /** Constant Boolean holding the default setting for whether this command should be listed in the usage help of the parent command: <code>{@value}</code>.*/
4728 static final Boolean DEFAULT_HIDDEN = Boolean.FALSE;
4729
4730 static final String DEFAULT_SINGLE_VALUE = "";
4731 static final String[] DEFAULT_MULTI_LINE = {};
4732
4733 private IHelpFactory helpFactory;
4734
4735 private List<String> sectionKeys = Collections.unmodifiableList(Arrays.asList(
4736 SECTION_KEY_HEADER_HEADING,
4737 SECTION_KEY_HEADER,
4738 SECTION_KEY_SYNOPSIS_HEADING,
4739 SECTION_KEY_SYNOPSIS,
4740 SECTION_KEY_DESCRIPTION_HEADING,
4741 SECTION_KEY_DESCRIPTION,
4742 SECTION_KEY_PARAMETER_LIST_HEADING,
4743 SECTION_KEY_PARAMETER_LIST,
4744 SECTION_KEY_OPTION_LIST_HEADING,
4745 SECTION_KEY_OPTION_LIST,
4746 SECTION_KEY_COMMAND_LIST_HEADING,
4747 SECTION_KEY_COMMAND_LIST,
4748 SECTION_KEY_FOOTER_HEADING,
4749 SECTION_KEY_FOOTER));
4750
4751 private Map<String, IHelpSectionRenderer> helpSectionRendererMap = createHelpSectionRendererMap();
4752
4753 private String[] description;
4754 private String[] customSynopsis;
4755 private String[] header;
4756 private String[] footer;
4757 private Boolean abbreviateSynopsis;
4758 private Boolean sortOptions;
4759 private Boolean showDefaultValues;
4760 private Boolean hidden;
4761 private Character requiredOptionMarker;
4762 private String headerHeading;
4763 private String synopsisHeading;
4764 private String descriptionHeading;
4765 private String parameterListHeading;
4766 private String optionListHeading;
4767 private String commandListHeading;
4768 private String footerHeading;
4769 private int width = DEFAULT_USAGE_WIDTH;
4770
4771 private Messages messages;
4772
4773 /**
4774 * Sets the maximum usage help message width to the specified value. Longer values are wrapped.
4775 * @param newValue the new maximum usage help message width. Must be 55 or greater.
4776 * @return this {@code UsageMessageSpec} for method chaining
4777 * @throws IllegalArgumentException if the specified width is less than 55
4778 */
4779 public UsageMessageSpec width(int newValue) {
4780 if (newValue < MINIMUM_USAGE_WIDTH) {
4781 throw new InitializationException("Invalid usage message width " + newValue + ". Minimum value is " + MINIMUM_USAGE_WIDTH);
4782 }
4783 width = newValue; return this;
4784 }
4785
4786 private static int getSysPropertyWidthOrDefault(int defaultWidth) {
4787 String userValue = System.getProperty("picocli.usage.width");
4788 if (userValue == null) { return defaultWidth; }
4789 try {
4790 int width = Integer.parseInt(userValue);
4791 if (width < MINIMUM_USAGE_WIDTH) {
4792 new Tracer().warn("Invalid picocli.usage.width value %d. Using minimum usage width %d.%n", width, MINIMUM_USAGE_WIDTH);
4793 return MINIMUM_USAGE_WIDTH;
4794 }
4795 return width;
4796 } catch (NumberFormatException ex) {
4797 new Tracer().warn("Invalid picocli.usage.width value '%s'. Using usage width %d.%n", userValue, defaultWidth);
4798 return defaultWidth;
4799 }
4800 }
4801
4802 /** Returns the maximum usage help message width. Derived from system property {@code "picocli.usage.width"}
4803 * if set, otherwise returns the value set via the {@link #width(int)} method, or if not set, the {@linkplain #DEFAULT_USAGE_WIDTH default width}.
4804 * @return the maximum usage help message width. Never returns less than 55. */
4805 public int width() { return getSysPropertyWidthOrDefault(width); }
4806
4807 /** Returns the help section renderers for the predefined section keys. see: {@link #sectionKeys()} */
4808 private Map<String, IHelpSectionRenderer> createHelpSectionRendererMap() {
4809 Map<String, IHelpSectionRenderer> result = new HashMap<String, IHelpSectionRenderer>();
4810
4811 result.put(SECTION_KEY_HEADER_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.headerHeading(); } });
4812 result.put(SECTION_KEY_HEADER, new IHelpSectionRenderer() { public String render(Help help) { return help.header(); } });
4813 //e.g. Usage:
4814 result.put(SECTION_KEY_SYNOPSIS_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.synopsisHeading(); } });
4815 //e.g. <main class> [OPTIONS] <command> [COMMAND-OPTIONS] [ARGUMENTS]
4816 result.put(SECTION_KEY_SYNOPSIS, new IHelpSectionRenderer() { public String render(Help help) { return help.synopsis(help.synopsisHeadingLength()); } });
4817 //e.g. %nDescription:%n%n
4818 result.put(SECTION_KEY_DESCRIPTION_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.descriptionHeading(); } });
4819 //e.g. {"Converts foos to bars.", "Use options to control conversion mode."}
4820 result.put(SECTION_KEY_DESCRIPTION, new IHelpSectionRenderer() { public String render(Help help) { return help.description(); } });
4821 //e.g. %nPositional parameters:%n%n
4822 result.put(SECTION_KEY_PARAMETER_LIST_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.parameterListHeading(); } });
4823 //e.g. [FILE...] the files to convert
4824 result.put(SECTION_KEY_PARAMETER_LIST, new IHelpSectionRenderer() { public String render(Help help) { return help.parameterList(); } });
4825 //e.g. %nOptions:%n%n
4826 result.put(SECTION_KEY_OPTION_LIST_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.optionListHeading(); } });
4827 //e.g. -h, --help displays this help and exits
4828 result.put(SECTION_KEY_OPTION_LIST, new IHelpSectionRenderer() { public String render(Help help) { return help.optionList(); } });
4829 //e.g. %nCommands:%n%n
4830 result.put(SECTION_KEY_COMMAND_LIST_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.commandListHeading(); } });
4831 //e.g. add adds the frup to the frooble
4832 result.put(SECTION_KEY_COMMAND_LIST, new IHelpSectionRenderer() { public String render(Help help) { return help.commandList(); } });
4833 result.put(SECTION_KEY_FOOTER_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.footerHeading(); } });
4834 result.put(SECTION_KEY_FOOTER, new IHelpSectionRenderer() { public String render(Help help) { return help.footer(); } });
4835 return result;
4836 }
4837
4838 /**
4839 * Returns the section keys in the order that the usage help message should render the sections.
4840 * This ordering may be modified with the {@link #sectionKeys(List) sectionKeys setter}. The default keys are (in order):
4841 * <ol>
4842 * <li>{@link UsageMessageSpec#SECTION_KEY_HEADER_HEADING SECTION_KEY_HEADER_HEADING}</li>
4843 * <li>{@link UsageMessageSpec#SECTION_KEY_HEADER SECTION_KEY_HEADER}</li>
4844 * <li>{@link UsageMessageSpec#SECTION_KEY_SYNOPSIS_HEADING SECTION_KEY_SYNOPSIS_HEADING}</li>
4845 * <li>{@link UsageMessageSpec#SECTION_KEY_SYNOPSIS SECTION_KEY_SYNOPSIS}</li>
4846 * <li>{@link UsageMessageSpec#SECTION_KEY_DESCRIPTION_HEADING SECTION_KEY_DESCRIPTION_HEADING}</li>
4847 * <li>{@link UsageMessageSpec#SECTION_KEY_DESCRIPTION SECTION_KEY_DESCRIPTION}</li>
4848 * <li>{@link UsageMessageSpec#SECTION_KEY_PARAMETER_LIST_HEADING SECTION_KEY_PARAMETER_LIST_HEADING}</li>
4849 * <li>{@link UsageMessageSpec#SECTION_KEY_PARAMETER_LIST SECTION_KEY_PARAMETER_LIST}</li>
4850 * <li>{@link UsageMessageSpec#SECTION_KEY_OPTION_LIST_HEADING SECTION_KEY_OPTION_LIST_HEADING}</li>
4851 * <li>{@link UsageMessageSpec#SECTION_KEY_OPTION_LIST SECTION_KEY_OPTION_LIST}</li>
4852 * <li>{@link UsageMessageSpec#SECTION_KEY_COMMAND_LIST_HEADING SECTION_KEY_COMMAND_LIST_HEADING}</li>
4853 * <li>{@link UsageMessageSpec#SECTION_KEY_COMMAND_LIST SECTION_KEY_COMMAND_LIST}</li>
4854 * <li>{@link UsageMessageSpec#SECTION_KEY_FOOTER_HEADING SECTION_KEY_FOOTER_HEADING}</li>
4855 * <li>{@link UsageMessageSpec#SECTION_KEY_FOOTER SECTION_KEY_FOOTER}</li>
4856 * </ol>
4857 * @since 3.9
4858 */
4859 public List<String> sectionKeys() { return sectionKeys; }
4860
4861 /**
4862 * Sets the section keys in the order that the usage help message should render the sections.
4863 * @see #sectionKeys
4864 * @since 3.9
4865 */
4866 public UsageMessageSpec sectionKeys(List<String> keys) { sectionKeys = Collections.unmodifiableList(new ArrayList<String>(keys)); return this; }
4867
4868 /**
4869 * Returns the map of section keys and renderers used to construct the usage help message.
4870 * The usage help message can be customized by adding, replacing and removing section renderers from this map.
4871 * Sections can be reordered with the {@link #sectionKeys(List) sectionKeys setter}.
4872 * Sections that are either not in this map or not in the list returned by {@link #sectionKeys() sectionKeys} are omitted.
4873 * @see #sectionKeys
4874 * @since 3.9
4875 */
4876 public Map<String, IHelpSectionRenderer> sectionMap() { return helpSectionRendererMap; }
4877
4878 /**
4879 * Sets the map of section keys and renderers used to construct the usage help message to a copy of the specified map.
4880 * @param map the mapping of section keys to their renderers, must be non-{@code null}.
4881 * @return this UsageMessageSpec for method chaining
4882 * @see #sectionKeys
4883 * @see #setHelpSectionMap(Map)
4884 * @since 3.9
4885 */
4886 public UsageMessageSpec sectionMap(Map<String, IHelpSectionRenderer> map) { this.helpSectionRendererMap = new HashMap<String, IHelpSectionRenderer>(map); return this; }
4887
4888 /** Returns the {@code IHelpFactory} that is used to construct the usage help message.
4889 * @see #setHelpFactory(IHelpFactory)
4890 * @since 3.9
4891 */
4892 public IHelpFactory helpFactory() {
4893 if (helpFactory == null) {
4894 helpFactory = new DefaultHelpFactory();
4895 }
4896 return helpFactory;
4897 }
4898
4899 /** Sets a new {@code IHelpFactory} to customize the usage help message.
4900 * @param helpFactory the new help factory. Must be non-{@code null}.
4901 * @return this {@code UsageMessageSpec} object, to allow method chaining
4902 */
4903 public UsageMessageSpec helpFactory(IHelpFactory helpFactory) {
4904 this.helpFactory = Assert.notNull(helpFactory, "helpFactory");
4905 return this;
4906 }
4907
4908 private String str(String localized, String value, String defaultValue) {
4909 return localized != null ? localized : (value != null ? value : defaultValue);
4910 }
4911 private String[] arr(String[] localized, String[] value, String[] defaultValue) {
4912 return localized != null ? localized : (value != null ? value.clone() : defaultValue);
4913 }
4914 private String resourceStr(String key) { return messages == null ? null : messages.getString(key, null); }
4915 private String[] resourceArr(String key) { return messages == null ? null : messages.getStringArray(key, null); }
4916
4917 /** Returns the optional heading preceding the header section. Initialized from {@link Command#headerHeading()}, or null. */
4918 public String headerHeading() { return str(resourceStr("usage.headerHeading"), headerHeading, DEFAULT_SINGLE_VALUE); }
4919
4920 /** Returns the optional header lines displayed at the top of the help message. For subcommands, the first header line is
4921 * displayed in the list of commands. Values are initialized from {@link Command#header()}
4922 * if the {@code Command} annotation is present, otherwise this is an empty array and the help message has no
4923 * header. Applications may programmatically set this field to create a custom help message. */
4924 public String[] header() { return arr(resourceArr("usage.header"), header, DEFAULT_MULTI_LINE); }
4925
4926 /** Returns the optional heading preceding the synopsis. Initialized from {@link Command#synopsisHeading()}, {@code "Usage: "} by default. */
4927 public String synopsisHeading() { return str(resourceStr("usage.synopsisHeading"), synopsisHeading, DEFAULT_SYNOPSIS_HEADING); }
4928
4929 /** Returns whether the synopsis line(s) should show an abbreviated synopsis without detailed option names. */
4930 public boolean abbreviateSynopsis() { return (abbreviateSynopsis == null) ? DEFAULT_ABBREVIATE_SYNOPSIS : abbreviateSynopsis; }
4931
4932 /** Returns the optional custom synopsis lines to use instead of the auto-generated synopsis.
4933 * Initialized from {@link Command#customSynopsis()} if the {@code Command} annotation is present,
4934 * otherwise this is an empty array and the synopsis is generated.
4935 * Applications may programmatically set this field to create a custom help message. */
4936 public String[] customSynopsis() { return arr(resourceArr("usage.customSynopsis"), customSynopsis, DEFAULT_MULTI_LINE); }
4937
4938 /** Returns the optional heading preceding the description section. Initialized from {@link Command#descriptionHeading()}, or null. */
4939 public String descriptionHeading() { return str(resourceStr("usage.descriptionHeading"), descriptionHeading, DEFAULT_SINGLE_VALUE); }
4940
4941 /** Returns the optional text lines to use as the description of the help message, displayed between the synopsis and the
4942 * options list. Initialized from {@link Command#description()} if the {@code Command} annotation is present,
4943 * otherwise this is an empty array and the help message has no description.
4944 * Applications may programmatically set this field to create a custom help message. */
4945 public String[] description() { return arr(resourceArr("usage.description"), description, DEFAULT_MULTI_LINE); }
4946
4947 /** Returns the optional heading preceding the parameter list. Initialized from {@link Command#parameterListHeading()}, or null. */
4948 public String parameterListHeading() { return str(resourceStr("usage.parameterListHeading"), parameterListHeading, DEFAULT_SINGLE_VALUE); }
4949
4950 /** Returns the optional heading preceding the options list. Initialized from {@link Command#optionListHeading()}, or null. */
4951 public String optionListHeading() { return str(resourceStr("usage.optionListHeading"), optionListHeading, DEFAULT_SINGLE_VALUE); }
4952
4953 /** Returns whether the options list in the usage help message should be sorted alphabetically. */
4954 public boolean sortOptions() { return (sortOptions == null) ? DEFAULT_SORT_OPTIONS : sortOptions; }
4955
4956 /** Returns the character used to prefix required options in the options list. */
4957 public char requiredOptionMarker() { return (requiredOptionMarker == null) ? DEFAULT_REQUIRED_OPTION_MARKER : requiredOptionMarker; }
4958
4959 /** Returns whether the options list in the usage help message should show default values for all non-boolean options. */
4960 public boolean showDefaultValues() { return (showDefaultValues == null) ? DEFAULT_SHOW_DEFAULT_VALUES : showDefaultValues; }
4961
4962 /**
4963 * Returns whether this command should be hidden from the usage help message of the parent command.
4964 * @return {@code true} if this command should not appear in the usage help message of the parent command
4965 */
4966 public boolean hidden() { return (hidden == null) ? DEFAULT_HIDDEN : hidden; }
4967
4968 /** Returns the optional heading preceding the subcommand list. Initialized from {@link Command#commandListHeading()}. {@code "Commands:%n"} by default. */
4969 public String commandListHeading() { return str(resourceStr("usage.commandListHeading"), commandListHeading, DEFAULT_COMMAND_LIST_HEADING); }
4970
4971 /** Returns the optional heading preceding the footer section. Initialized from {@link Command#footerHeading()}, or null. */
4972 public String footerHeading() { return str(resourceStr("usage.footerHeading"), footerHeading, DEFAULT_SINGLE_VALUE); }
4973
4974 /** Returns the optional footer text lines displayed at the bottom of the help message. Initialized from
4975 * {@link Command#footer()} if the {@code Command} annotation is present, otherwise this is an empty array and
4976 * the help message has no footer.
4977 * Applications may programmatically set this field to create a custom help message. */
4978 public String[] footer() { return arr(resourceArr("usage.footer"), footer, DEFAULT_MULTI_LINE); }
4979
4980 /** Sets the heading preceding the header section. Initialized from {@link Command#headerHeading()}, or null.
4981 * @return this UsageMessageSpec for method chaining */
4982 public UsageMessageSpec headerHeading(String headerHeading) { this.headerHeading = headerHeading; return this; }
4983
4984 /** Sets the optional header lines displayed at the top of the help message. For subcommands, the first header line is
4985 * displayed in the list of commands.
4986 * @return this UsageMessageSpec for method chaining */
4987 public UsageMessageSpec header(String... header) { this.header = header; return this; }
4988
4989 /** Sets the optional heading preceding the synopsis.
4990 * @return this UsageMessageSpec for method chaining */
4991 public UsageMessageSpec synopsisHeading(String newValue) {synopsisHeading = newValue; return this;}
4992
4993 /** Sets whether the synopsis line(s) should show an abbreviated synopsis without detailed option names.
4994 * @return this UsageMessageSpec for method chaining */
4995 public UsageMessageSpec abbreviateSynopsis(boolean newValue) {abbreviateSynopsis = newValue; return this;}
4996
4997 /** Sets the optional custom synopsis lines to use instead of the auto-generated synopsis.
4998 * @return this UsageMessageSpec for method chaining */
4999 public UsageMessageSpec customSynopsis(String... customSynopsis) { this.customSynopsis = customSynopsis; return this; }
5000
5001 /** Sets the heading preceding the description section.
5002 * @return this UsageMessageSpec for method chaining */
5003 public UsageMessageSpec descriptionHeading(String newValue) {descriptionHeading = newValue; return this;}
5004
5005 /** Sets the optional text lines to use as the description of the help message, displayed between the synopsis and the
5006 * options list.
5007 * @return this UsageMessageSpec for method chaining */
5008 public UsageMessageSpec description(String... description) { this.description = description; return this; }
5009
5010 /** Sets the optional heading preceding the parameter list.
5011 * @return this UsageMessageSpec for method chaining */
5012 public UsageMessageSpec parameterListHeading(String newValue) {parameterListHeading = newValue; return this;}
5013
5014 /** Sets the heading preceding the options list.
5015 * @return this UsageMessageSpec for method chaining */
5016 public UsageMessageSpec optionListHeading(String newValue) {optionListHeading = newValue; return this;}
5017
5018 /** Sets whether the options list in the usage help message should be sorted alphabetically.
5019 * @return this UsageMessageSpec for method chaining */
5020 public UsageMessageSpec sortOptions(boolean newValue) {sortOptions = newValue; return this;}
5021
5022 /** Sets the character used to prefix required options in the options list.
5023 * @return this UsageMessageSpec for method chaining */
5024 public UsageMessageSpec requiredOptionMarker(char newValue) {requiredOptionMarker = newValue; return this;}
5025
5026 /** Sets whether the options list in the usage help message should show default values for all non-boolean options.
5027 * @return this UsageMessageSpec for method chaining */
5028 public UsageMessageSpec showDefaultValues(boolean newValue) {showDefaultValues = newValue; return this;}
5029
5030 /**
5031 * Set the hidden flag on this command to control whether to show or hide it in the help usage text of the parent command.
5032 * @param value enable or disable the hidden flag
5033 * @return this UsageMessageSpec for method chaining
5034 * @see Command#hidden() */
5035 public UsageMessageSpec hidden(boolean value) { hidden = value; return this; }
5036
5037 /** Sets the optional heading preceding the subcommand list.
5038 * @return this UsageMessageSpec for method chaining */
5039 public UsageMessageSpec commandListHeading(String newValue) {commandListHeading = newValue; return this;}
5040
5041 /** Sets the optional heading preceding the footer section.
5042 * @return this UsageMessageSpec for method chaining */
5043 public UsageMessageSpec footerHeading(String newValue) {footerHeading = newValue; return this;}
5044
5045 /** Sets the optional footer text lines displayed at the bottom of the help message.
5046 * @return this UsageMessageSpec for method chaining */
5047 public UsageMessageSpec footer(String... footer) { this.footer = footer; return this; }
5048 /** Returns the Messages for this usage help message specification, or {@code null}.
5049 * @return the Messages object that encapsulates this {@linkplain CommandSpec#resourceBundle() command's resource bundle}
5050 * @since 3.6 */
5051 public Messages messages() { return messages; }
5052 /** Sets the Messages for this usageMessage specification, and returns this UsageMessageSpec.
5053 * @param msgs the new Messages value that encapsulates this {@linkplain CommandSpec#resourceBundle() command's resource bundle}, may be {@code null}
5054 * @since 3.6 */
5055 public UsageMessageSpec messages(Messages msgs) { messages = msgs; return this; }
5056 void updateFromCommand(Command cmd, CommandSpec commandSpec) {
5057 if (isNonDefault(cmd.synopsisHeading(), DEFAULT_SYNOPSIS_HEADING)) {synopsisHeading = cmd.synopsisHeading();}
5058 if (isNonDefault(cmd.commandListHeading(), DEFAULT_COMMAND_LIST_HEADING)) {commandListHeading = cmd.commandListHeading();}
5059 if (isNonDefault(cmd.requiredOptionMarker(), DEFAULT_REQUIRED_OPTION_MARKER)) {requiredOptionMarker = cmd.requiredOptionMarker();}
5060 if (isNonDefault(cmd.abbreviateSynopsis(), DEFAULT_ABBREVIATE_SYNOPSIS)) {abbreviateSynopsis = cmd.abbreviateSynopsis();}
5061 if (isNonDefault(cmd.sortOptions(), DEFAULT_SORT_OPTIONS)) {sortOptions = cmd.sortOptions();}
5062 if (isNonDefault(cmd.showDefaultValues(), DEFAULT_SHOW_DEFAULT_VALUES)) {showDefaultValues = cmd.showDefaultValues();}
5063 if (isNonDefault(cmd.hidden(), DEFAULT_HIDDEN)) {hidden = cmd.hidden();}
5064 if (isNonDefault(cmd.customSynopsis(), DEFAULT_MULTI_LINE)) {customSynopsis = cmd.customSynopsis().clone();}
5065 if (isNonDefault(cmd.description(), DEFAULT_MULTI_LINE)) {description = cmd.description().clone();}
5066 if (isNonDefault(cmd.descriptionHeading(), DEFAULT_SINGLE_VALUE)) {descriptionHeading = cmd.descriptionHeading();}
5067 if (isNonDefault(cmd.header(), DEFAULT_MULTI_LINE)) {header = cmd.header().clone();}
5068 if (isNonDefault(cmd.headerHeading(), DEFAULT_SINGLE_VALUE)) {headerHeading = cmd.headerHeading();}
5069 if (isNonDefault(cmd.footer(), DEFAULT_MULTI_LINE)) {footer = cmd.footer().clone();}
5070 if (isNonDefault(cmd.footerHeading(), DEFAULT_SINGLE_VALUE)) {footerHeading = cmd.footerHeading();}
5071 if (isNonDefault(cmd.parameterListHeading(), DEFAULT_SINGLE_VALUE)) {parameterListHeading = cmd.parameterListHeading();}
5072 if (isNonDefault(cmd.optionListHeading(), DEFAULT_SINGLE_VALUE)) {optionListHeading = cmd.optionListHeading();}
5073 if (isNonDefault(cmd.usageHelpWidth(), DEFAULT_USAGE_WIDTH)) {width(cmd.usageHelpWidth());} // validate
5074
5075 if (!empty(cmd.resourceBundle())) { // else preserve superclass bundle
5076 messages(new Messages(commandSpec, cmd.resourceBundle()));
5077 }
5078 }
5079 void initFromMixin(UsageMessageSpec mixin, CommandSpec commandSpec) {
5080 if (initializable(synopsisHeading, mixin.synopsisHeading(), DEFAULT_SYNOPSIS_HEADING)) {synopsisHeading = mixin.synopsisHeading();}
5081 if (initializable(commandListHeading, mixin.commandListHeading(), DEFAULT_COMMAND_LIST_HEADING)) {commandListHeading = mixin.commandListHeading();}
5082 if (initializable(requiredOptionMarker, mixin.requiredOptionMarker(), DEFAULT_REQUIRED_OPTION_MARKER)) {requiredOptionMarker = mixin.requiredOptionMarker();}
5083 if (initializable(abbreviateSynopsis, mixin.abbreviateSynopsis(), DEFAULT_ABBREVIATE_SYNOPSIS)) {abbreviateSynopsis = mixin.abbreviateSynopsis();}
5084 if (initializable(sortOptions, mixin.sortOptions(), DEFAULT_SORT_OPTIONS)) {sortOptions = mixin.sortOptions();}
5085 if (initializable(showDefaultValues, mixin.showDefaultValues(), DEFAULT_SHOW_DEFAULT_VALUES)) {showDefaultValues = mixin.showDefaultValues();}
5086 if (initializable(hidden, mixin.hidden(), DEFAULT_HIDDEN)) {hidden = mixin.hidden();}
5087 if (initializable(customSynopsis, mixin.customSynopsis(), DEFAULT_MULTI_LINE)) {customSynopsis = mixin.customSynopsis().clone();}
5088 if (initializable(description, mixin.description(), DEFAULT_MULTI_LINE)) {description = mixin.description().clone();}
5089 if (initializable(descriptionHeading, mixin.descriptionHeading(), DEFAULT_SINGLE_VALUE)) {descriptionHeading = mixin.descriptionHeading();}
5090 if (initializable(header, mixin.header(), DEFAULT_MULTI_LINE)) {header = mixin.header().clone();}
5091 if (initializable(headerHeading, mixin.headerHeading(), DEFAULT_SINGLE_VALUE)) {headerHeading = mixin.headerHeading();}
5092 if (initializable(footer, mixin.footer(), DEFAULT_MULTI_LINE)) {footer = mixin.footer().clone();}
5093 if (initializable(footerHeading, mixin.footerHeading(), DEFAULT_SINGLE_VALUE)) {footerHeading = mixin.footerHeading();}
5094 if (initializable(parameterListHeading, mixin.parameterListHeading(), DEFAULT_SINGLE_VALUE)) {parameterListHeading = mixin.parameterListHeading();}
5095 if (initializable(optionListHeading, mixin.optionListHeading(), DEFAULT_SINGLE_VALUE)) {optionListHeading = mixin.optionListHeading();}
5096 if (Messages.empty(messages)) { messages(Messages.copy(commandSpec, mixin.messages())); }
5097 }
5098 void initFrom(UsageMessageSpec settings, CommandSpec commandSpec) {
5099 description = settings.description;
5100 customSynopsis = settings.customSynopsis;
5101 header = settings.header;
5102 footer = settings.footer;
5103 abbreviateSynopsis = settings.abbreviateSynopsis;
5104 sortOptions = settings.sortOptions;
5105 showDefaultValues = settings.showDefaultValues;
5106 hidden = settings.hidden;
5107 requiredOptionMarker = settings.requiredOptionMarker;
5108 headerHeading = settings.headerHeading;
5109 synopsisHeading = settings.synopsisHeading;
5110 descriptionHeading = settings.descriptionHeading;
5111 parameterListHeading = settings.parameterListHeading;
5112 optionListHeading = settings.optionListHeading;
5113 commandListHeading = settings.commandListHeading;
5114 footerHeading = settings.footerHeading;
5115 width = settings.width;
5116 messages = Messages.copy(commandSpec, settings.messages());
5117 }
5118 }
5119 /** Models parser configuration specification.
5120 * @since 3.0 */
5121 public static class ParserSpec {
5122
5123 /** Constant String holding the default separator between options and option parameters: <code>{@value}</code>.*/
5124 static final String DEFAULT_SEPARATOR = "=";
5125 private String separator;
5126 private boolean stopAtUnmatched = false;
5127 private boolean stopAtPositional = false;
5128 private String endOfOptionsDelimiter = "--";
5129 private boolean toggleBooleanFlags = true;
5130 private boolean overwrittenOptionsAllowed = false;
5131 private boolean unmatchedArgumentsAllowed = false;
5132 private boolean expandAtFiles = true;
5133 private boolean useSimplifiedAtFiles = false;
5134 private Character atFileCommentChar = '#';
5135 private boolean posixClusteredShortOptionsAllowed = true;
5136 private boolean unmatchedOptionsArePositionalParams = false;
5137 private boolean limitSplit = false;
5138 private boolean aritySatisfiedByAttachedOptionParam = false;
5139 private boolean collectErrors = false;
5140 private boolean caseInsensitiveEnumValuesAllowed = false;
5141 private boolean trimQuotes = shouldTrimQuotes();
5142 private boolean splitQuotedStrings = false;
5143
5144 /** Returns the String to use as the separator between options and option parameters. {@code "="} by default,
5145 * initialized from {@link Command#separator()} if defined.*/
5146 public String separator() { return (separator == null) ? DEFAULT_SEPARATOR : separator; }
5147
5148 /** @see CommandLine#isStopAtUnmatched() */
5149 public boolean stopAtUnmatched() { return stopAtUnmatched; }
5150 /** @see CommandLine#isStopAtPositional() */
5151 public boolean stopAtPositional() { return stopAtPositional; }
5152 /** @see CommandLine#getEndOfOptionsDelimiter()
5153 * @since 3.5 */
5154 public String endOfOptionsDelimiter() { return endOfOptionsDelimiter; }
5155 /** @see CommandLine#isToggleBooleanFlags() */
5156 public boolean toggleBooleanFlags() { return toggleBooleanFlags; }
5157 /** @see CommandLine#isOverwrittenOptionsAllowed() */
5158 public boolean overwrittenOptionsAllowed() { return overwrittenOptionsAllowed; }
5159 /** @see CommandLine#isUnmatchedArgumentsAllowed() */
5160 public boolean unmatchedArgumentsAllowed() { return unmatchedArgumentsAllowed; }
5161 /** @see CommandLine#isExpandAtFiles() */
5162 public boolean expandAtFiles() { return expandAtFiles; }
5163 /** @see CommandLine#getAtFileCommentChar()
5164 * @since 3.5 */
5165 public Character atFileCommentChar() { return atFileCommentChar; }
5166 /** @see CommandLine#isUseSimplifiedAtFiles()
5167 * @since 3.9 */
5168 public boolean useSimplifiedAtFiles() {
5169 String value = System.getProperty("picocli.useSimplifiedAtFiles");
5170 if (value != null) {
5171 return "".equals(value) || Boolean.valueOf(value);
5172 }
5173 return useSimplifiedAtFiles;
5174 }
5175 /** @see CommandLine#isPosixClusteredShortOptionsAllowed() */
5176 public boolean posixClusteredShortOptionsAllowed() { return posixClusteredShortOptionsAllowed; }
5177 /** @see CommandLine#isCaseInsensitiveEnumValuesAllowed()
5178 * @since 3.4 */
5179 public boolean caseInsensitiveEnumValuesAllowed() { return caseInsensitiveEnumValuesAllowed; }
5180 /** @see CommandLine#isTrimQuotes()
5181 * @since 3.7 */
5182 public boolean trimQuotes() { return trimQuotes; }
5183 /** @see CommandLine#isSplitQuotedStrings()
5184 * @since 3.7 */
5185 public boolean splitQuotedStrings() { return splitQuotedStrings; }
5186 /** @see CommandLine#isUnmatchedOptionsArePositionalParams() */
5187 public boolean unmatchedOptionsArePositionalParams() { return unmatchedOptionsArePositionalParams; }
5188 private boolean splitFirst() { return limitSplit(); }
5189 /** Returns true if arguments should be split first before any further processing and the number of
5190 * parts resulting from the split is limited to the max arity of the argument. */
5191 public boolean limitSplit() { return limitSplit; }
5192 /** Returns true if options with attached arguments should not consume subsequent arguments and should not validate arity. The default is {@code false}. */
5193 public boolean aritySatisfiedByAttachedOptionParam() { return aritySatisfiedByAttachedOptionParam; }
5194 /** Returns true if exceptions during parsing should be collected instead of thrown.
5195 * Multiple errors may be encountered during parsing. These can be obtained from {@link ParseResult#errors()}.
5196 * @since 3.2 */
5197 public boolean collectErrors() { return collectErrors; }
5198
5199 /** Sets the String to use as the separator between options and option parameters.
5200 * @return this ParserSpec for method chaining */
5201 public ParserSpec separator(String separator) { this.separator = separator; return this; }
5202 /** @see CommandLine#setStopAtUnmatched(boolean) */
5203 public ParserSpec stopAtUnmatched(boolean stopAtUnmatched) { this.stopAtUnmatched = stopAtUnmatched; return this; }
5204 /** @see CommandLine#setStopAtPositional(boolean) */
5205 public ParserSpec stopAtPositional(boolean stopAtPositional) { this.stopAtPositional = stopAtPositional; return this; }
5206 /** @see CommandLine#setEndOfOptionsDelimiter(String)
5207 * @since 3.5 */
5208 public ParserSpec endOfOptionsDelimiter(String delimiter) { this.endOfOptionsDelimiter = Assert.notNull(delimiter, "end-of-options delimiter"); return this; }
5209 /** @see CommandLine#setToggleBooleanFlags(boolean) */
5210 public ParserSpec toggleBooleanFlags(boolean toggleBooleanFlags) { this.toggleBooleanFlags = toggleBooleanFlags; return this; }
5211 /** @see CommandLine#setOverwrittenOptionsAllowed(boolean) */
5212 public ParserSpec overwrittenOptionsAllowed(boolean overwrittenOptionsAllowed) { this.overwrittenOptionsAllowed = overwrittenOptionsAllowed; return this; }
5213 /** @see CommandLine#setUnmatchedArgumentsAllowed(boolean) */
5214 public ParserSpec unmatchedArgumentsAllowed(boolean unmatchedArgumentsAllowed) { this.unmatchedArgumentsAllowed = unmatchedArgumentsAllowed; return this; }
5215 /** @see CommandLine#setExpandAtFiles(boolean) */
5216 public ParserSpec expandAtFiles(boolean expandAtFiles) { this.expandAtFiles = expandAtFiles; return this; }
5217 /** @see CommandLine#setAtFileCommentChar(Character)
5218 * @since 3.5 */
5219 public ParserSpec atFileCommentChar(Character atFileCommentChar) { this.atFileCommentChar = atFileCommentChar; return this; }
5220 /** @see CommandLine#setUseSimplifiedAtFiles(boolean)
5221 * @since 3.9 */
5222 public ParserSpec useSimplifiedAtFiles(boolean useSimplifiedAtFiles) { this.useSimplifiedAtFiles = useSimplifiedAtFiles; return this; }
5223 /** @see CommandLine#setPosixClusteredShortOptionsAllowed(boolean) */
5224 public ParserSpec posixClusteredShortOptionsAllowed(boolean posixClusteredShortOptionsAllowed) { this.posixClusteredShortOptionsAllowed = posixClusteredShortOptionsAllowed; return this; }
5225 /** @see CommandLine#setCaseInsensitiveEnumValuesAllowed(boolean)
5226 * @since 3.4 */
5227 public ParserSpec caseInsensitiveEnumValuesAllowed(boolean caseInsensitiveEnumValuesAllowed) { this.caseInsensitiveEnumValuesAllowed = caseInsensitiveEnumValuesAllowed; return this; }
5228 /** @see CommandLine#setTrimQuotes(boolean)
5229 * @since 3.7 */
5230 public ParserSpec trimQuotes(boolean trimQuotes) { this.trimQuotes = trimQuotes; return this; }
5231 /** @see CommandLine#setSplitQuotedStrings(boolean)
5232 * @since 3.7 */
5233 public ParserSpec splitQuotedStrings(boolean splitQuotedStrings) { this.splitQuotedStrings = splitQuotedStrings; return this; }
5234 /** @see CommandLine#setUnmatchedOptionsArePositionalParams(boolean) */
5235 public ParserSpec unmatchedOptionsArePositionalParams(boolean unmatchedOptionsArePositionalParams) { this.unmatchedOptionsArePositionalParams = unmatchedOptionsArePositionalParams; return this; }
5236 /** Sets whether exceptions during parsing should be collected instead of thrown.
5237 * Multiple errors may be encountered during parsing. These can be obtained from {@link ParseResult#errors()}.
5238 * @since 3.2 */
5239 public ParserSpec collectErrors(boolean collectErrors) { this.collectErrors = collectErrors; return this; }
5240
5241 /** Returns true if options with attached arguments should not consume subsequent arguments and should not validate arity. The default is {@code false}.*/
5242 public ParserSpec aritySatisfiedByAttachedOptionParam(boolean newValue) { aritySatisfiedByAttachedOptionParam = newValue; return this; }
5243
5244 /** Sets whether arguments should be {@linkplain ArgSpec#splitRegex() split} first before any further processing.
5245 * If true, the original argument will only be split into as many parts as allowed by max arity. */
5246 public ParserSpec limitSplit(boolean limitSplit) { this.limitSplit = limitSplit; return this; }
5247
5248 private boolean shouldTrimQuotes() {
5249 String value = System.getProperty("picocli.trimQuotes");
5250 if ("".equals(value)) { value = "true"; }
5251 return Boolean.valueOf(value);
5252 }
5253
5254 void initSeparator(String value) { if (initializable(separator, value, DEFAULT_SEPARATOR)) {separator = value;} }
5255 void updateSeparator(String value) { if (isNonDefault(value, DEFAULT_SEPARATOR)) {separator = value;} }
5256 public String toString() {
5257 return String.format("posixClusteredShortOptionsAllowed=%s, stopAtPositional=%s, stopAtUnmatched=%s, " +
5258 "separator=%s, overwrittenOptionsAllowed=%s, unmatchedArgumentsAllowed=%s, expandAtFiles=%s, " +
5259 "atFileCommentChar=%s, useSimplifiedAtFiles=%s, endOfOptionsDelimiter=%s, limitSplit=%s, aritySatisfiedByAttachedOptionParam=%s, " +
5260 "toggleBooleanFlags=%s, unmatchedOptionsArePositionalParams=%s, collectErrors=%s," +
5261 "caseInsensitiveEnumValuesAllowed=%s, trimQuotes=%s, splitQuotedStrings=%s",
5262 posixClusteredShortOptionsAllowed, stopAtPositional, stopAtUnmatched,
5263 separator, overwrittenOptionsAllowed, unmatchedArgumentsAllowed, expandAtFiles,
5264 atFileCommentChar, useSimplifiedAtFiles, endOfOptionsDelimiter, limitSplit, aritySatisfiedByAttachedOptionParam,
5265 toggleBooleanFlags, unmatchedOptionsArePositionalParams, collectErrors,
5266 caseInsensitiveEnumValuesAllowed, trimQuotes, splitQuotedStrings);
5267 }
5268
5269 void initFrom(ParserSpec settings) {
5270 separator = settings.separator;
5271 stopAtUnmatched = settings.stopAtUnmatched;
5272 stopAtPositional = settings.stopAtPositional;
5273 endOfOptionsDelimiter = settings.endOfOptionsDelimiter;
5274 toggleBooleanFlags = settings.toggleBooleanFlags;
5275 overwrittenOptionsAllowed = settings.overwrittenOptionsAllowed;
5276 unmatchedArgumentsAllowed = settings.unmatchedArgumentsAllowed;
5277 expandAtFiles = settings.expandAtFiles;
5278 atFileCommentChar = settings.atFileCommentChar;
5279 posixClusteredShortOptionsAllowed = settings.posixClusteredShortOptionsAllowed;
5280 unmatchedOptionsArePositionalParams = settings.unmatchedOptionsArePositionalParams;
5281 limitSplit = settings.limitSplit;
5282 aritySatisfiedByAttachedOptionParam = settings.aritySatisfiedByAttachedOptionParam;
5283 collectErrors = settings.collectErrors;
5284 caseInsensitiveEnumValuesAllowed = settings.caseInsensitiveEnumValuesAllowed;
5285 trimQuotes = settings.trimQuotes;
5286 splitQuotedStrings = settings.splitQuotedStrings;
5287 }
5288 }
5289 /** Models the shared attributes of {@link OptionSpec} and {@link PositionalParamSpec}.
5290 * @since 3.0 */
5291 public abstract static class ArgSpec {
5292 static final String DESCRIPTION_VARIABLE_DEFAULT_VALUE = "${DEFAULT-VALUE}";
5293 static final String DESCRIPTION_VARIABLE_COMPLETION_CANDIDATES = "${COMPLETION-CANDIDATES}";
5294 private static final String NO_DEFAULT_VALUE = "__no_default_value__";
5295
5296 // help-related fields
5297 private final boolean hidden;
5298 private final String paramLabel;
5299 private final boolean hideParamSyntax;
5300 private final String[] description;
5301 private final String descriptionKey;
5302 private final Help.Visibility showDefaultValue;
5303 private Messages messages;
5304 CommandSpec commandSpec;
5305 private ArgGroupSpec group;
5306 private final Object userObject;
5307
5308 // parser fields
5309 private final boolean interactive;
5310 private final boolean required;
5311 private final String splitRegex;
5312 private final ITypeInfo typeInfo;
5313 private final ITypeConverter<?>[] converters;
5314 private final Iterable<String> completionCandidates;
5315 private final String defaultValue;
5316 private final Object initialValue;
5317 private final boolean hasInitialValue;
5318 private final IGetter getter;
5319 private final ISetter setter;
5320 private final IScope scope;
5321 private final Range arity;
5322 private List<String> stringValues = new ArrayList<String>();
5323 private List<String> originalStringValues = new ArrayList<String>();
5324 protected String toString;
5325 private List<Object> typedValues = new ArrayList<Object>();
5326 Map<Integer, Object> typedValueAtPosition = new TreeMap<Integer, Object>();
5327
5328 /** Constructs a new {@code ArgSpec}. */
5329 private <T extends Builder<T>> ArgSpec(Builder<T> builder) {
5330 userObject = builder.userObject;
5331 description = builder.description == null ? new String[0] : builder.description;
5332 descriptionKey = builder.descriptionKey;
5333 splitRegex = builder.splitRegex == null ? "" : builder.splitRegex;
5334 paramLabel = empty(builder.paramLabel) ? "PARAM" : builder.paramLabel;
5335 hideParamSyntax = builder.hideParamSyntax;
5336 converters = builder.converters == null ? new ITypeConverter<?>[0] : builder.converters;
5337 showDefaultValue = builder.showDefaultValue == null ? Help.Visibility.ON_DEMAND : builder.showDefaultValue;
5338 hidden = builder.hidden;
5339 interactive = builder.interactive;
5340 initialValue = builder.initialValue;
5341 hasInitialValue = builder.hasInitialValue;
5342 defaultValue = NO_DEFAULT_VALUE.equals(builder.defaultValue) ? null : builder.defaultValue;
5343 required = builder.required && defaultValue == null; //#261 not required if it has a default
5344 toString = builder.toString;
5345 getter = builder.getter;
5346 setter = builder.setter;
5347 scope = builder.scope;
5348
5349 Range tempArity = builder.arity;
5350 if (tempArity == null) {
5351 if (isOption()) {
5352 tempArity = (builder.type == null || isBoolean(builder.type)) ? Range.valueOf("0") : Range.valueOf("1");
5353 } else {
5354 tempArity = Range.valueOf("1");
5355 }
5356 tempArity = tempArity.unspecified(true);
5357 }
5358 arity = tempArity;
5359
5360 if (builder.typeInfo == null) {
5361 this.typeInfo = RuntimeTypeInfo.create(builder.type, builder.auxiliaryTypes,
5362 Collections.<String>emptyList(), arity, (isOption() ? boolean.class : String.class));
5363 } else {
5364 this.typeInfo = builder.typeInfo;
5365 }
5366
5367 if (builder.completionCandidates == null && typeInfo.isEnum()) {
5368 List<String> list = new ArrayList<String>();
5369 for (Object c : typeInfo.getEnumConstantNames()) { list.add(c.toString()); }
5370 completionCandidates = Collections.unmodifiableList(list);
5371 } else {
5372 completionCandidates = builder.completionCandidates;
5373 }
5374 if (interactive && (arity.min != 1 || arity.max != 1)) {
5375 throw new InitializationException("Interactive options and positional parameters are only supported for arity=1, not for arity=" + arity);
5376 }
5377 }
5378 void applyInitialValue(Tracer tracer) {
5379 if (hasInitialValue()) {
5380 try {
5381 setter().set(initialValue());
5382 tracer.debug("Set initial value for %s of type %s to %s.%n", this, type(), String.valueOf(initialValue()));
5383 } catch (Exception ex) {
5384 tracer.warn("Could not set initial value for %s of type %s to %s: %s%n", this, type(), String.valueOf(initialValue()), ex);
5385 }
5386 } else {
5387 tracer.debug("Initial value not available for %s%n", this);
5388 }
5389 }
5390
5391 /** Returns whether this is a required option or positional parameter.
5392 * If this argument is part of a {@linkplain ArgGroup group}, this method returns whether this argument is required <em>within the group</em> (so it is not necessarily a required argument for the command).
5393 * @see Option#required() */
5394 public boolean required() { return required; }
5395
5396 /** Returns whether this option will prompt the user to enter a value on the command line.
5397 * @see Option#interactive() */
5398 public boolean interactive() { return interactive; }
5399
5400 /** Returns the description template of this option, before variables are rendered.
5401 * @see Option#description() */
5402 public String[] description() { return description.clone(); }
5403
5404 /** Returns the description key of this arg spec, used to get the description from a resource bundle.
5405 * @see Option#descriptionKey()
5406 * @see Parameters#descriptionKey()
5407 * @since 3.6 */
5408 public String descriptionKey() { return descriptionKey; }
5409
5410 /** Returns the description of this option, after variables are rendered. Used when generating the usage documentation.
5411 * @see Option#description()
5412 * @since 3.2 */
5413 public String[] renderedDescription() {
5414 String[] desc = description();
5415 if (desc.length == 0) { return desc; }
5416 StringBuilder candidates = new StringBuilder();
5417 if (completionCandidates() != null) {
5418 for (String c : completionCandidates()) {
5419 if (candidates.length() > 0) { candidates.append(", "); }
5420 candidates.append(c);
5421 }
5422 }
5423 String defaultValueString = defaultValueString();
5424 String[] result = new String[desc.length];
5425 for (int i = 0; i < desc.length; i++) {
5426 result[i] = format(desc[i].replace(DESCRIPTION_VARIABLE_DEFAULT_VALUE, defaultValueString.replace("%", "%%"))
5427 .replace(DESCRIPTION_VARIABLE_COMPLETION_CANDIDATES, candidates.toString()));
5428 }
5429 return result;
5430 }
5431
5432 /** Returns how many arguments this option or positional parameter requires.
5433 * @see Option#arity() */
5434 public Range arity() { return arity; }
5435
5436 /** Returns the name of the option or positional parameter used in the usage help message.
5437 * @see Option#paramLabel() {@link Parameters#paramLabel()} */
5438 public String paramLabel() { return paramLabel; }
5439
5440 /** Returns whether usage syntax decorations around the {@linkplain #paramLabel() paramLabel} should be suppressed.
5441 * The default is {@code false}: by default, the paramLabel is surrounded with {@code '['} and {@code ']'} characters
5442 * if the value is optional and followed by ellipses ("...") when multiple values can be specified.
5443 * @since 3.6.0 */
5444 public boolean hideParamSyntax() { return hideParamSyntax; }
5445
5446 /** Returns auxiliary type information used when the {@link #type()} is a generic {@code Collection}, {@code Map} or an abstract class.
5447 * @see Option#type() */
5448 public Class<?>[] auxiliaryTypes() { return typeInfo.getAuxiliaryTypes(); }
5449
5450 /** Returns one or more {@link CommandLine.ITypeConverter type converters} to use to convert the command line
5451 * argument into a strongly typed value (or key-value pair for map fields). This is useful when a particular
5452 * option or positional parameter should use a custom conversion that is different from the normal conversion for the arg spec's type.
5453 * @see Option#converter() */
5454 public ITypeConverter<?>[] converters() { return converters.clone(); }
5455
5456 /** Returns a regular expression to split option parameter values or {@code ""} if the value should not be split.
5457 * @see Option#split() */
5458 public String splitRegex() { return splitRegex; }
5459
5460 /** Returns whether this option should be excluded from the usage message.
5461 * @see Option#hidden() */
5462 public boolean hidden() { return hidden; }
5463
5464 /** Returns the type to convert the option or positional parameter to before {@linkplain #setValue(Object) setting} the value. */
5465 public Class<?> type() { return typeInfo.getType(); }
5466
5467 /** Returns the {@code ITypeInfo} that can be used both at compile time (by annotation processors) and at runtime.
5468 * @since 4.0 */
5469 public ITypeInfo typeInfo() { return typeInfo; }
5470
5471 /** Returns the user object associated with this option or positional parameters.
5472 * @return may return the annotated program element, or some other useful object
5473 * @since 4.0 */
5474 public Object userObject() { return userObject; }
5475
5476 /** Returns the default value of this option or positional parameter, before splitting and type conversion.
5477 * This method returns the programmatically set value; this may differ from the default value that is actually used:
5478 * if this ArgSpec is part of a CommandSpec with a {@link IDefaultValueProvider}, picocli will first try to obtain
5479 * the default value from the default value provider, and this method is only called if the default provider is
5480 * {@code null} or returned a {@code null} value.
5481 * @return the programmatically set default value of this option/positional parameter,
5482 * returning {@code null} means this option or positional parameter does not have a default
5483 * @see CommandSpec#defaultValueProvider()
5484 */
5485 public String defaultValue() { return defaultValue; }
5486 /** Returns the initial value this option or positional parameter. If {@link #hasInitialValue()} is true,
5487 * the option will be reset to the initial value before parsing (regardless of whether a default value exists),
5488 * to clear values that would otherwise remain from parsing previous input. */
5489 public Object initialValue() { return initialValue; }
5490 /** Determines whether the option or positional parameter will be reset to the {@link #initialValue()}
5491 * before parsing new input.*/
5492 public boolean hasInitialValue() { return hasInitialValue; }
5493
5494 /** Returns whether this option or positional parameter's default value should be shown in the usage help. */
5495 public Help.Visibility showDefaultValue() { return showDefaultValue; }
5496
5497 /** Returns the default value String displayed in the description. If this ArgSpec is part of a
5498 * CommandSpec with a {@link IDefaultValueProvider}, this method will first try to obtain
5499 * the default value from the default value provider; if the provider is {@code null} or if it
5500 * returns a {@code null} value, then next any value set to {@link ArgSpec#defaultValue()}
5501 * is returned, and if this is also {@code null}, finally the {@linkplain ArgSpec#initialValue() initial value} is returned.
5502 * @see CommandSpec#defaultValueProvider()
5503 * @see ArgSpec#defaultValue() */
5504 public String defaultValueString() {
5505 String fromProvider = defaultValueFromProvider();
5506 String defaultVal = fromProvider == null ? this.defaultValue() : fromProvider;
5507 Object value = defaultVal == null ? initialValue() : defaultVal;
5508 if (value != null && value.getClass().isArray()) {
5509 StringBuilder sb = new StringBuilder();
5510 for (int i = 0; i < Array.getLength(value); i++) {
5511 sb.append(i > 0 ? ", " : "").append(Array.get(value, i));
5512 }
5513 return sb.insert(0, "[").append("]").toString();
5514 }
5515 return String.valueOf(value);
5516 }
5517
5518 private String defaultValueFromProvider() {
5519 String fromProvider = null;
5520 IDefaultValueProvider defaultValueProvider = null;
5521 try {
5522 defaultValueProvider = commandSpec.defaultValueProvider();
5523 fromProvider = defaultValueProvider == null ? null : defaultValueProvider.defaultValue(this);
5524 } catch (Exception ex) {
5525 new Tracer().info("Error getting default value for %s from %s: %s", this, defaultValueProvider, ex);
5526 }
5527 return fromProvider;
5528 }
5529
5530 /** Returns the explicitly set completion candidates for this option or positional parameter, valid enum
5531 * constant names, or {@code null} if this option or positional parameter does not have any completion
5532 * candidates and its type is not an enum.
5533 * @return the completion candidates for this option or positional parameter, valid enum constant names,
5534 * or {@code null}
5535 * @since 3.2 */
5536 public Iterable<String> completionCandidates() { return completionCandidates; }
5537
5538 /** Returns the {@link IGetter} that is responsible for supplying the value of this argument. */
5539 public IGetter getter() { return getter; }
5540 /** Returns the {@link ISetter} that is responsible for modifying the value of this argument. */
5541 public ISetter setter() { return setter; }
5542 /** Returns the {@link IScope} that determines on which object to set the value (or from which object to get the value) of this argument. */
5543 public IScope scope() { return scope; }
5544
5545 /** Returns the current value of this argument. Delegates to the current {@link #getter()}. */
5546 public <T> T getValue() throws PicocliException {
5547 try {
5548 return getter.get();
5549 } catch (PicocliException ex) { throw ex;
5550 } catch (Exception ex) { throw new PicocliException("Could not get value for " + this + ": " + ex, ex);
5551 }
5552 }
5553 /** Sets the value of this argument to the specified value and returns the previous value. Delegates to the current {@link #setter()}. */
5554 public <T> T setValue(T newValue) throws PicocliException {
5555 try {
5556 return setter.set(newValue);
5557 } catch (PicocliException ex) { throw ex;
5558 } catch (Exception ex) { throw new PicocliException("Could not set value (" + newValue + ") for " + this + ": " + ex, ex);
5559 }
5560 }
5561 /** Sets the value of this argument to the specified value and returns the previous value. Delegates to the current {@link #setter()}.
5562 * @deprecated use {@link #setValue(Object)} instead. This was a design mistake.
5563 * @since 3.5 */
5564 @Deprecated public <T> T setValue(T newValue, CommandLine commandLine) throws PicocliException {
5565 return setValue(newValue);
5566 }
5567
5568 /** Returns {@code true} if this argument's {@link #type()} is an array, a {@code Collection} or a {@code Map}, {@code false} otherwise. */
5569 public boolean isMultiValue() { return typeInfo.isMultiValue(); }
5570 /** Returns {@code true} if this argument is a named option, {@code false} otherwise. */
5571 public abstract boolean isOption();
5572 /** Returns {@code true} if this argument is a positional parameter, {@code false} otherwise. */
5573 public abstract boolean isPositional();
5574
5575 /** Returns the groups this option or positional parameter belongs to, or {@code null} if this option is not part of a group.
5576 * @since 4.0 */
5577 public ArgGroupSpec group() { return group; }
5578
5579 /** Returns the untyped command line arguments matched by this option or positional parameter spec.
5580 * @return the matched arguments after {@linkplain #splitRegex() splitting}, but before type conversion.
5581 * For map properties, {@code "key=value"} values are split into the key and the value part. */
5582 public List<String> stringValues() { return Collections.unmodifiableList(stringValues); }
5583
5584 /** Returns the typed command line arguments matched by this option or positional parameter spec.
5585 * @return the matched arguments after {@linkplain #splitRegex() splitting} and type conversion.
5586 * For map properties, {@code "key=value"} values are split into the key and the value part. */
5587 public List<Object> typedValues() { return Collections.unmodifiableList(typedValues); }
5588
5589 /** Sets the {@code stringValues} to a new list instance. */
5590 protected void resetStringValues() { stringValues = new ArrayList<String>(); }
5591
5592 /** Returns the original command line arguments matched by this option or positional parameter spec.
5593 * @return the matched arguments as found on the command line: empty Strings for options without value, the
5594 * values have not been {@linkplain #splitRegex() split}, and for map properties values may look like {@code "key=value"}*/
5595 public List<String> originalStringValues() { return Collections.unmodifiableList(originalStringValues); }
5596
5597 /** Sets the {@code originalStringValues} to a new list instance. */
5598 protected void resetOriginalStringValues() { originalStringValues = new ArrayList<String>(); }
5599
5600 /** Returns whether the default for this option or positional parameter should be shown, potentially overriding the specified global setting.
5601 * @param usageHelpShowDefaults whether the command's UsageMessageSpec is configured to show default values. */
5602 protected boolean internalShowDefaultValue(boolean usageHelpShowDefaults) {
5603 if (showDefaultValue() == Help.Visibility.ALWAYS) { return true; } // override global usage help setting
5604 if (showDefaultValue() == Help.Visibility.NEVER) { return false; } // override global usage help setting
5605 if (initialValue == null && defaultValue() == null && defaultValueFromProvider() == null) { return false; } // no default value to show
5606 return usageHelpShowDefaults && !isBoolean(type());
5607 }
5608 /** Returns the Messages for this arg specification, or {@code null}.
5609 * @since 3.6 */
5610 public Messages messages() { return messages; }
5611 /** Sets the Messages for this ArgSpec, and returns this ArgSpec.
5612 * @param msgs the new Messages value, may be {@code null}
5613 * @see Command#resourceBundle()
5614 * @see OptionSpec#description()
5615 * @see PositionalParamSpec#description()
5616 * @since 3.6 */
5617 public ArgSpec messages(Messages msgs) { messages = msgs; return this; }
5618
5619 /** Returns a string respresentation of this option or positional parameter. */
5620 public String toString() { return toString; }
5621
5622 String[] splitValue(String value, ParserSpec parser, Range arity, int consumed) {
5623 if (splitRegex().length() == 0) { return new String[] {value}; }
5624 int limit = parser.limitSplit() ? Math.max(arity.max - consumed, 0) : 0;
5625 if (parser.splitQuotedStrings()) {
5626 return debug(value.split(splitRegex(), limit), "Split (ignoring quotes)", value);
5627 }
5628 return debug(splitRespectingQuotedStrings(value, limit, parser, this, splitRegex()), "Split", value);
5629 }
5630 private String[] debug(String[] result, String msg, String value) {
5631 Tracer t = new Tracer();
5632 if (t.isDebug()) {t.debug("%s with regex '%s' resulted in %s parts: %s%n", msg, splitRegex(), result.length, Arrays.asList(result));}
5633 return result;
5634 }
5635 // @since 3.7
5636 private static String[] splitRespectingQuotedStrings(String value, int limit, ParserSpec parser, ArgSpec argSpec, String splitRegex) {
5637 StringBuilder splittable = new StringBuilder();
5638 StringBuilder temp = new StringBuilder();
5639 StringBuilder current = splittable;
5640 Queue<String> quotedValues = new LinkedList<String>();
5641 boolean escaping = false, inQuote = false;
5642 for (int ch = 0, i = 0; i < value.length(); i += Character.charCount(ch)) {
5643 ch = value.codePointAt(i);
5644 switch (ch) {
5645 case '\\': escaping = !escaping; break;
5646 case '\"':
5647 if (!escaping) {
5648 inQuote = !inQuote;
5649 current = inQuote ? temp : splittable;
5650 if (inQuote) {
5651 splittable.appendCodePoint(ch);
5652 continue;
5653 } else {
5654 quotedValues.add(temp.toString());
5655 temp.setLength(0);
5656 }
5657 }
5658 break;
5659 default: escaping = false; break;
5660 }
5661 current.appendCodePoint(ch);
5662 }
5663 if (temp.length() > 0) {
5664 new Tracer().warn("Unbalanced quotes in [%s] for %s (value=%s)%n", temp, argSpec, value);
5665 quotedValues.add(temp.toString());
5666 temp.setLength(0);
5667 }
5668 String[] result = splittable.toString().split(splitRegex, limit);
5669 for (int i = 0; i < result.length; i++) {
5670 result[i] = restoreQuotedValues(result[i], quotedValues, parser);
5671 }
5672 if (!quotedValues.isEmpty()) {
5673 new Tracer().warn("Unable to respect quotes while splitting value %s for %s (unprocessed remainder: %s)%n", value, argSpec, quotedValues);
5674 return value.split(splitRegex, limit);
5675 }
5676 return result;
5677 }
5678
5679 private static String restoreQuotedValues(String part, Queue<String> quotedValues, ParserSpec parser) {
5680 StringBuilder result = new StringBuilder();
5681 boolean escaping = false, inQuote = false, skip = false;
5682 for (int ch = 0, i = 0; i < part.length(); i += Character.charCount(ch)) {
5683 ch = part.codePointAt(i);
5684 switch (ch) {
5685 case '\\': escaping = !escaping; break;
5686 case '\"':
5687 if (!escaping) {
5688 inQuote = !inQuote;
5689 if (!inQuote) { result.append(quotedValues.remove()); }
5690 skip = parser.trimQuotes();
5691 }
5692 break;
5693 default: escaping = false; break;
5694 }
5695 if (!skip) { result.appendCodePoint(ch); }
5696 skip = false;
5697 }
5698 return result.toString();
5699 }
5700
5701 protected boolean equalsImpl(ArgSpec other) {
5702 boolean result = Assert.equals(this.defaultValue, other.defaultValue)
5703 && Assert.equals(this.arity, other.arity)
5704 && Assert.equals(this.hidden, other.hidden)
5705 && Assert.equals(this.paramLabel, other.paramLabel)
5706 && Assert.equals(this.hideParamSyntax, other.hideParamSyntax)
5707 && Assert.equals(this.required, other.required)
5708 && Assert.equals(this.splitRegex, other.splitRegex)
5709 && Arrays.equals(this.description, other.description)
5710 && Assert.equals(this.descriptionKey, other.descriptionKey)
5711 && this.typeInfo.equals(other.typeInfo)
5712 ;
5713 return result;
5714 }
5715 protected int hashCodeImpl() {
5716 return 17
5717 + 37 * Assert.hashCode(defaultValue)
5718 + 37 * Assert.hashCode(arity)
5719 + 37 * Assert.hashCode(hidden)
5720 + 37 * Assert.hashCode(paramLabel)
5721 + 37 * Assert.hashCode(hideParamSyntax)
5722 + 37 * Assert.hashCode(required)
5723 + 37 * Assert.hashCode(splitRegex)
5724 + 37 * Arrays.hashCode(description)
5725 + 37 * Assert.hashCode(descriptionKey)
5726 + 37 * typeInfo.hashCode()
5727 ;
5728 }
5729
5730 private static String describe(Collection<ArgSpec> args) {
5731 StringBuilder sb = new StringBuilder();
5732 for (ArgSpec arg : args) {
5733 if (sb.length() > 0) { sb.append(", "); }
5734 sb.append(describe(arg, "="));
5735 }
5736 return sb.toString();
5737 }
5738 /** Returns a description of the option or positional arg, e.g. {@code -a=<a>}
5739 * @param separator separator between arg and arg parameter label, usually '=' */
5740 private static String describe(ArgSpec argSpec, String separator) {
5741 return describe(argSpec, separator, argSpec.paramLabel());
5742 }
5743 /** Returns a description of the option or positional arg
5744 * @param separator separator between arg and arg parameter value, usually '='
5745 * @param value the value to append after the separator*/
5746 private static String describe(ArgSpec argSpec, String separator, String value) {
5747 String prefix = (argSpec.isOption())
5748 ? ((OptionSpec) argSpec).longestName()
5749 : "params[" + ((PositionalParamSpec) argSpec).index() + "]";
5750 return argSpec.arity().min > 0 ? prefix + separator + value : prefix;
5751 }
5752 abstract static class Builder<T extends Builder<T>> {
5753 private Object userObject;
5754 private Range arity;
5755 private String[] description;
5756 private String descriptionKey;
5757 private boolean required;
5758 private boolean interactive;
5759 private String paramLabel;
5760 private boolean hideParamSyntax;
5761 private String splitRegex;
5762 private boolean hidden;
5763 private Class<?> type;
5764 private Class<?>[] auxiliaryTypes;
5765 private ITypeInfo typeInfo;
5766 private ITypeConverter<?>[] converters;
5767 private String defaultValue;
5768 private Object initialValue;
5769 private boolean hasInitialValue = true;
5770 private Help.Visibility showDefaultValue;
5771 private Iterable<String> completionCandidates;
5772 private String toString;
5773 private IGetter getter = new ObjectBinding();
5774 private ISetter setter = (ISetter) getter;
5775 private IScope scope = new ObjectScope(null);
5776
5777 Builder() {}
5778 Builder(ArgSpec original) {
5779 userObject = original.userObject;
5780 arity = original.arity;
5781 converters = original.converters;
5782 defaultValue = original.defaultValue;
5783 description = original.description;
5784 getter = original.getter;
5785 setter = original.setter;
5786 hidden = original.hidden;
5787 paramLabel = original.paramLabel;
5788 hideParamSyntax = original.hideParamSyntax;
5789 required = original.required;
5790 interactive = original.interactive;
5791 showDefaultValue = original.showDefaultValue;
5792 completionCandidates = original.completionCandidates;
5793 splitRegex = original.splitRegex;
5794 toString = original.toString;
5795 descriptionKey = original.descriptionKey;
5796 setTypeInfo(original.typeInfo);
5797 }
5798 Builder(IAnnotatedElement source) {
5799 userObject = source.userObject();
5800 setTypeInfo(source.getTypeInfo());
5801 toString = source.getToString();
5802 getter = source.getter();
5803 setter = source.setter();
5804 scope = source.scope();
5805 hasInitialValue = source.hasInitialValue();
5806 try { initialValue = source.getter().get(); } catch (Exception ex) { initialValue = null; hasInitialValue = false; }
5807 }
5808 Builder(Option option, IAnnotatedElement source, IFactory factory) {
5809 this(source);
5810 arity = Range.optionArity(source);
5811 required = option.required();
5812
5813 paramLabel = inferLabel(option.paramLabel(), source.getName(), source.getTypeInfo());
5814
5815 hideParamSyntax = option.hideParamSyntax();
5816 interactive = option.interactive();
5817 description = option.description();
5818 descriptionKey = option.descriptionKey();
5819 splitRegex = option.split();
5820 hidden = option.hidden();
5821 defaultValue = option.defaultValue();
5822 showDefaultValue = option.showDefaultValue();
5823 if (factory != null) {
5824 converters = DefaultFactory.createConverter(factory, option.converter());
5825 if (!NoCompletionCandidates.class.equals(option.completionCandidates())) {
5826 completionCandidates = DefaultFactory.createCompletionCandidates(factory, option.completionCandidates());
5827 }
5828 }
5829 }
5830 Builder(Parameters parameters, IAnnotatedElement source, IFactory factory) {
5831 this(source);
5832 arity = Range.parameterArity(source);
5833 required = arity.min > 0;
5834
5835 // method parameters may be positional parameters without @Parameters annotation
5836 if (parameters == null) {
5837 paramLabel = inferLabel(null, source.getName(), source.getTypeInfo());
5838 } else {
5839 paramLabel = inferLabel(parameters.paramLabel(), source.getName(), source.getTypeInfo());
5840
5841 hideParamSyntax = parameters.hideParamSyntax();
5842 interactive = parameters.interactive();
5843 description = parameters.description();
5844 descriptionKey = parameters.descriptionKey();
5845 splitRegex = parameters.split();
5846 hidden = parameters.hidden();
5847 defaultValue = parameters.defaultValue();
5848 showDefaultValue = parameters.showDefaultValue();
5849 if (factory != null) { // annotation processors will pass a null factory
5850 converters = DefaultFactory.createConverter(factory, parameters.converter());
5851 if (!NoCompletionCandidates.class.equals(parameters.completionCandidates())) {
5852 completionCandidates = DefaultFactory.createCompletionCandidates(factory, parameters.completionCandidates());
5853 }
5854 }
5855 }
5856 }
5857 private static String inferLabel(String label, String fieldName, ITypeInfo typeInfo) {
5858 if (!empty(label)) { return label.trim(); }
5859 String name = fieldName;
5860 if (typeInfo.isMap()) { // #195 better param labels for map fields
5861 List<ITypeInfo> aux = typeInfo.getAuxiliaryTypeInfos();
5862 if (aux.size() < 2 || aux.get(0) == null || aux.get(1) == null) {
5863 name = "String=String";
5864 } else { name = aux.get(0).getClassSimpleName() + "=" + aux.get(1).getClassSimpleName(); }
5865 }
5866 return "<" + name + ">";
5867 }
5868
5869 public abstract ArgSpec build();
5870 protected abstract T self(); // subclasses must override to return "this"
5871 /** Returns whether this is a required option or positional parameter.
5872 * @see Option#required() */
5873 public boolean required() { return required; }
5874 /** Returns whether this option prompts the user to enter a value on the command line.
5875 * @see Option#interactive() */
5876 public boolean interactive() { return interactive; }
5877
5878 /** Returns the description of this option, used when generating the usage documentation.
5879 * @see Option#description() */
5880 public String[] description() { return description; }
5881
5882 /** Returns the description key of this arg spec, used to get the description from a resource bundle.
5883 * @see Option#descriptionKey()
5884 * @see Parameters#descriptionKey()
5885 * @since 3.6 */
5886 public String descriptionKey() { return descriptionKey; }
5887
5888 /** Returns how many arguments this option or positional parameter requires.
5889 * @see Option#arity() */
5890 public Range arity() { return arity; }
5891
5892 /** Returns the name of the option or positional parameter used in the usage help message.
5893 * @see Option#paramLabel() {@link Parameters#paramLabel()} */
5894 public String paramLabel() { return paramLabel; }
5895
5896 /** Returns whether usage syntax decorations around the {@linkplain #paramLabel() paramLabel} should be suppressed.
5897 * The default is {@code false}: by default, the paramLabel is surrounded with {@code '['} and {@code ']'} characters
5898 * if the value is optional and followed by ellipses ("...") when multiple values can be specified.
5899 * @since 3.6.0 */
5900 public boolean hideParamSyntax() { return hideParamSyntax; }
5901
5902 /** Returns auxiliary type information used when the {@link #type()} is a generic {@code Collection}, {@code Map} or an abstract class.
5903 * @see Option#type() */
5904 public Class<?>[] auxiliaryTypes() { return auxiliaryTypes; }
5905
5906 /** Returns one or more {@link CommandLine.ITypeConverter type converters} to use to convert the command line
5907 * argument into a strongly typed value (or key-value pair for map fields). This is useful when a particular
5908 * option or positional parameter should use a custom conversion that is different from the normal conversion for the arg spec's type.
5909 * @see Option#converter() */
5910 public ITypeConverter<?>[] converters() { return converters; }
5911
5912 /** Returns a regular expression to split option parameter values or {@code ""} if the value should not be split.
5913 * @see Option#split() */
5914 public String splitRegex() { return splitRegex; }
5915
5916 /** Returns whether this option should be excluded from the usage message.
5917 * @see Option#hidden() */
5918 public boolean hidden() { return hidden; }
5919
5920 /** Returns the type to convert the option or positional parameter to before {@linkplain #setValue(Object) setting} the value. */
5921 public Class<?> type() { return type; }
5922
5923 /** Returns the type info for this option or positional parameter.
5924 * @return type information that does not require {@code Class} objects and be constructed both at runtime and compile time
5925 * @since 4.0
5926 */
5927 public ITypeInfo typeInfo() { return typeInfo; }
5928
5929 /** Returns the user object associated with this option or positional parameters.
5930 * @return may return the annotated program element, or some other useful object
5931 * @since 4.0 */
5932 public Object userObject() { return userObject; }
5933
5934 /** Returns the default value of this option or positional parameter, before splitting and type conversion.
5935 * A value of {@code null} means this option or positional parameter does not have a default. */
5936 public String defaultValue() { return defaultValue; }
5937 /** Returns the initial value this option or positional parameter. If {@link #hasInitialValue()} is true,
5938 * the option will be reset to the initial value before parsing (regardless of whether a default value exists),
5939 * to clear values that would otherwise remain from parsing previous input. */
5940 public Object initialValue() { return initialValue; }
5941 /** Determines whether the option or positional parameter will be reset to the {@link #initialValue()}
5942 * before parsing new input.*/
5943 public boolean hasInitialValue() { return hasInitialValue; }
5944
5945 /** Returns whether this option or positional parameter's default value should be shown in the usage help. */
5946 public Help.Visibility showDefaultValue() { return showDefaultValue; }
5947
5948 /** Returns the completion candidates for this option or positional parameter, or {@code null}.
5949 * @since 3.2 */
5950 public Iterable<String> completionCandidates() { return completionCandidates; }
5951
5952 /** Returns the {@link IGetter} that is responsible for supplying the value of this argument. */
5953 public IGetter getter() { return getter; }
5954 /** Returns the {@link ISetter} that is responsible for modifying the value of this argument. */
5955 public ISetter setter() { return setter; }
5956 /** Returns the {@link IScope} that determines where the setter sets the value (or the getter gets the value) of this argument. */
5957 public IScope scope() { return scope; }
5958
5959 public String toString() { return toString; }
5960
5961 /** Sets whether this is a required option or positional parameter, and returns this builder. */
5962 public T required(boolean required) { this.required = required; return self(); }
5963
5964 /** Sets whether this option prompts the user to enter a value on the command line, and returns this builder. */
5965 public T interactive(boolean interactive) { this.interactive = interactive; return self(); }
5966
5967 /** Sets the description of this option, used when generating the usage documentation, and returns this builder.
5968 * @see Option#description() */
5969 public T description(String... description) { this.description = Assert.notNull(description, "description").clone(); return self(); }
5970
5971 /** Sets the description key that is used to look up the description in a resource bundle, and returns this builder.
5972 * @see Option#descriptionKey()
5973 * @see Parameters#descriptionKey()
5974 * @since 3.6 */
5975 public T descriptionKey(String descriptionKey) { this.descriptionKey = descriptionKey; return self(); }
5976
5977 /** Sets how many arguments this option or positional parameter requires, and returns this builder. */
5978 public T arity(String range) { return arity(Range.valueOf(range)); }
5979
5980 /** Sets how many arguments this option or positional parameter requires, and returns this builder. */
5981 public T arity(Range arity) { this.arity = Assert.notNull(arity, "arity"); return self(); }
5982
5983 /** Sets the name of the option or positional parameter used in the usage help message, and returns this builder. */
5984 public T paramLabel(String paramLabel) { this.paramLabel = Assert.notNull(paramLabel, "paramLabel"); return self(); }
5985
5986 /** Sets whether usage syntax decorations around the {@linkplain #paramLabel() paramLabel} should be suppressed.
5987 * The default is {@code false}: by default, the paramLabel is surrounded with {@code '['} and {@code ']'} characters
5988 * if the value is optional and followed by ellipses ("...") when multiple values can be specified.
5989 * @since 3.6.0 */
5990 public T hideParamSyntax(boolean hideParamSyntax) { this.hideParamSyntax = hideParamSyntax; return self(); }
5991
5992 /** Sets auxiliary type information, and returns this builder.
5993 * @param types the element type(s) when the {@link #type()} is a generic {@code Collection} or a {@code Map};
5994 * or the concrete type when the {@link #type()} is an abstract class. */
5995 public T auxiliaryTypes(Class<?>... types) { this.auxiliaryTypes = Assert.notNull(types, "types").clone(); return self(); }
5996
5997 /** Sets option/positional param-specific converter (or converters for Maps), and returns this builder. */
5998 public T converters(ITypeConverter<?>... cs) { this.converters = Assert.notNull(cs, "type converters").clone(); return self(); }
5999
6000 /** Sets a regular expression to split option parameter values or {@code ""} if the value should not be split, and returns this builder. */
6001 public T splitRegex(String splitRegex) { this.splitRegex = Assert.notNull(splitRegex, "splitRegex"); return self(); }
6002
6003 /** Sets whether this option or positional parameter's default value should be shown in the usage help, and returns this builder. */
6004 public T showDefaultValue(Help.Visibility visibility) { showDefaultValue = Assert.notNull(visibility, "visibility"); return self(); }
6005
6006 /** Sets the completion candidates for this option or positional parameter, and returns this builder.
6007 * @since 3.2 */
6008 public T completionCandidates(Iterable<String> completionCandidates) { this.completionCandidates = completionCandidates; return self(); }
6009
6010 /** Sets whether this option should be excluded from the usage message, and returns this builder. */
6011 public T hidden(boolean hidden) { this.hidden = hidden; return self(); }
6012
6013 /** Sets the type to convert the option or positional parameter to before {@linkplain #setValue(Object) setting} the value, and returns this builder.
6014 * @param propertyType the type of this option or parameter. For multi-value options and positional parameters this can be an array, or a (sub-type of) Collection or Map. */
6015 public T type(Class<?> propertyType) { this.type = Assert.notNull(propertyType, "type"); return self(); }
6016
6017 /** Sets the type info for this option or positional parameter, and returns this builder.
6018 * @param typeInfo type information that does not require {@code Class} objects and be constructed both at runtime and compile time
6019 * @since 4.0 */
6020 public T typeInfo(ITypeInfo typeInfo) {
6021 setTypeInfo(Assert.notNull(typeInfo, "typeInfo"));
6022 return self();
6023 }
6024 private void setTypeInfo(ITypeInfo newValue) {
6025 this.typeInfo = newValue;
6026 if (typeInfo != null) {
6027 type = typeInfo.getType();
6028 auxiliaryTypes = typeInfo.getAuxiliaryTypes();
6029 }
6030 }
6031
6032 /** Sets the user object associated with this option or positional parameters, and returns this builder.
6033 * @param userObject may be the annotated program element, or some other useful object
6034 * @since 4.0 */
6035 public T userObject(Object userObject) { this.userObject = Assert.notNull(userObject, "userObject"); return self(); }
6036
6037 /** Sets the default value of this option or positional parameter to the specified value, and returns this builder.
6038 * Before parsing the command line, the result of {@linkplain #splitRegex() splitting} and {@linkplain #converters() type converting}
6039 * this default value is applied to the option or positional parameter. A value of {@code null} or {@code "__no_default_value__"} means no default. */
6040 public T defaultValue(String defaultValue) { this.defaultValue = defaultValue; return self(); }
6041
6042 /** Sets the initial value of this option or positional parameter to the specified value, and returns this builder.
6043 * If {@link #hasInitialValue()} is true, the option will be reset to the initial value before parsing (regardless
6044 * of whether a default value exists), to clear values that would otherwise remain from parsing previous input. */
6045 public T initialValue(Object initialValue) { this.initialValue = initialValue; return self(); }
6046
6047 /** Determines whether the option or positional parameter will be reset to the {@link #initialValue()}
6048 * before parsing new input.*/
6049 public T hasInitialValue(boolean hasInitialValue) { this.hasInitialValue = hasInitialValue; return self(); }
6050
6051 /** Sets the {@link IGetter} that is responsible for getting the value of this argument, and returns this builder. */
6052 public T getter(IGetter getter) { this.getter = getter; return self(); }
6053 /** Sets the {@link ISetter} that is responsible for modifying the value of this argument, and returns this builder. */
6054 public T setter(ISetter setter) { this.setter = setter; return self(); }
6055 /** Sets the {@link IScope} that targets where the setter sets the value, and returns this builder. */
6056 public T scope(IScope scope) { this.scope = scope; return self(); }
6057
6058 /** Sets the string respresentation of this option or positional parameter to the specified value, and returns this builder. */
6059 public T withToString(String toString) { this.toString = toString; return self(); }
6060 }
6061 }
6062 /** The {@code OptionSpec} class models aspects of a <em>named option</em> of a {@linkplain CommandSpec command}, including whether
6063 * it is required or optional, the option parameters supported (or required) by the option,
6064 * and attributes for the usage help message describing the option.
6065 * <p>
6066 * An option has one or more names. The option is matched when the parser encounters one of the option names in the command line arguments.
6067 * Depending on the option's {@link #arity() arity},
6068 * the parser may expect it to have option parameters. The parser will call {@link #setValue(Object) setValue} on
6069 * the matched option for each of the option parameters encountered.
6070 * </p><p>
6071 * For multi-value options, the {@code type} may be an array, a {@code Collection} or a {@code Map}. In this case
6072 * the parser will get the data structure by calling {@link #getValue() getValue} and modify the contents of this data structure.
6073 * (In the case of arrays, the array is replaced with a new instance with additional elements.)
6074 * </p><p>
6075 * Before calling the setter, picocli converts the option parameter value from a String to the option parameter's type.
6076 * </p>
6077 * <ul>
6078 * <li>If a option-specific {@link #converters() converter} is configured, this will be used for type conversion.
6079 * If the option's type is a {@code Map}, the map may have different types for its keys and its values, so
6080 * {@link #converters() converters} should provide two converters: one for the map keys and one for the map values.</li>
6081 * <li>Otherwise, the option's {@link #type() type} is used to look up a converter in the list of
6082 * {@linkplain CommandLine#registerConverter(Class, ITypeConverter) registered converters}.
6083 * For multi-value options,
6084 * the {@code type} may be an array, or a {@code Collection} or a {@code Map}. In that case the elements are converted
6085 * based on the option's {@link #auxiliaryTypes() auxiliaryTypes}. The auxiliaryType is used to look up
6086 * the converter(s) to use to convert the individual parameter values.
6087 * Maps may have different types for its keys and its values, so {@link #auxiliaryTypes() auxiliaryTypes}
6088 * should provide two types: one for the map keys and one for the map values.</li>
6089 * </ul>
6090 * <p>
6091 * {@code OptionSpec} objects are used by the picocli command line interpreter and help message generator.
6092 * Picocli can construct an {@code OptionSpec} automatically from fields and methods with {@link Option @Option}
6093 * annotations. Alternatively an {@code OptionSpec} can be constructed programmatically.
6094 * </p><p>
6095 * When an {@code OptionSpec} is created from an {@link Option @Option} -annotated field or method, it is "bound"
6096 * to that field or method: this field is set (or the method is invoked) when the option is matched and
6097 * {@link #setValue(Object) setValue} is called.
6098 * Programmatically constructed {@code OptionSpec} instances will remember the value passed to the
6099 * {@link #setValue(Object) setValue} method so it can be retrieved with the {@link #getValue() getValue} method.
6100 * This behaviour can be customized by installing a custom {@link IGetter} and {@link ISetter} on the {@code OptionSpec}.
6101 * </p>
6102 * @since 3.0 */
6103 public static class OptionSpec extends ArgSpec implements IOrdered {
6104 static final int DEFAULT_ORDER = -1;
6105 private String[] names;
6106 private boolean help;
6107 private boolean usageHelp;
6108 private boolean versionHelp;
6109 private int order;
6110
6111 public static OptionSpec.Builder builder(String name, String... names) {
6112 String[] copy = new String[Assert.notNull(names, "names").length + 1];
6113 copy[0] = Assert.notNull(name, "name");
6114 System.arraycopy(names, 0, copy, 1, names.length);
6115 return new Builder(copy);
6116 }
6117 public static OptionSpec.Builder builder(String[] names) { return new Builder(names); }
6118 public static OptionSpec.Builder builder(IAnnotatedElement source, IFactory factory) { return new Builder(source, factory); }
6119
6120 /** Ensures all attributes of this {@code OptionSpec} have a valid value; throws an {@link InitializationException} if this cannot be achieved. */
6121 private OptionSpec(Builder builder) {
6122 super(builder);
6123 if (builder.names == null) {
6124 throw new InitializationException("OptionSpec names cannot be null. Specify at least one option name.");
6125 }
6126 names = builder.names.clone();
6127 help = builder.help;
6128 usageHelp = builder.usageHelp;
6129 versionHelp = builder.versionHelp;
6130 order = builder.order;
6131
6132 if (names.length == 0 || Arrays.asList(names).contains("")) {
6133 throw new InitializationException("Invalid names: " + Arrays.toString(names));
6134 }
6135 if (toString() == null) { toString = "option " + longestName(); }
6136
6137// if (arity().max == 0 && !(isBoolean(type()) || (isMultiValue() && isBoolean(auxiliaryTypes()[0])))) {
6138// throw new InitializationException("Option " + longestName() + " is not a boolean so should not be defined with arity=" + arity());
6139// }
6140 }
6141
6142 /** Returns a new Builder initialized with the attributes from this {@code OptionSpec}. Calling {@code build} immediately will return a copy of this {@code OptionSpec}.
6143 * @return a builder that can create a copy of this spec
6144 */
6145 public Builder toBuilder() { return new Builder(this); }
6146 @Override public boolean isOption() { return true; }
6147 @Override public boolean isPositional() { return false; }
6148
6149 protected boolean internalShowDefaultValue(boolean usageMessageShowDefaults) {
6150 return super.internalShowDefaultValue(usageMessageShowDefaults) && !help() && !versionHelp() && !usageHelp();
6151 }
6152
6153 /** Returns the description template of this option, before variables are {@linkplain Option#description() rendered}.
6154 * If a resource bundle has been {@linkplain ArgSpec#messages(Messages) set}, this method will first try to find a value in the resource bundle:
6155 * If the resource bundle has no entry for the {@code fully qualified commandName + "." + descriptionKey} or for the unqualified {@code descriptionKey},
6156 * an attempt is made to find the option description using any of the option names (without leading hyphens) as key,
6157 * first with the {@code fully qualified commandName + "."} prefix, then without.
6158 * @see CommandSpec#qualifiedName(String)
6159 * @see Option#description() */
6160 @Override public String[] description() {
6161 if (messages() == null) { return super.description(); }
6162 String[] newValue = messages().getStringArray(descriptionKey(), null);
6163 if (newValue != null) { return newValue; }
6164 for (String name : names()) {
6165 newValue = messages().getStringArray(CommandSpec.stripPrefix(name), null);
6166 if (newValue != null) { return newValue; }
6167 }
6168 return super.description();
6169 }
6170
6171 /** Returns one or more option names. The returned array will contain at least one option name.
6172 * @see Option#names() */
6173 public String[] names() { return names.clone(); }
6174
6175 /** Returns the longest {@linkplain #names() option name}. */
6176 public String longestName() { return Help.ShortestFirst.longestFirst(names.clone())[0]; }
6177
6178 /** Returns the shortest {@linkplain #names() option name}.
6179 * @since 3.8 */
6180 public String shortestName() { return Help.ShortestFirst.sort(names.clone())[0]; }
6181
6182 /** Returns the position in the options list in the usage help message at which this option should be shown.
6183 * Options with a lower number are shown before options with a higher number.
6184 * This attribute is only honored if {@link UsageMessageSpec#sortOptions()} is {@code false} for this command.
6185 * @see Option#order()
6186 * @since 3.9 */
6187 public int order() { return this.order; }
6188
6189 /** Returns whether this option disables validation of the other arguments.
6190 * @see Option#help()
6191 * @deprecated Use {@link #usageHelp()} and {@link #versionHelp()} instead. */
6192 @Deprecated public boolean help() { return help; }
6193
6194 /** Returns whether this option allows the user to request usage help.
6195 * @see Option#usageHelp() */
6196 public boolean usageHelp() { return usageHelp; }
6197
6198 /** Returns whether this option allows the user to request version information.
6199 * @see Option#versionHelp() */
6200 public boolean versionHelp() { return versionHelp; }
6201 public boolean equals(Object obj) {
6202 if (obj == this) { return true; }
6203 if (!(obj instanceof OptionSpec)) { return false; }
6204 OptionSpec other = (OptionSpec) obj;
6205 boolean result = super.equalsImpl(other)
6206 && help == other.help
6207 && usageHelp == other.usageHelp
6208 && versionHelp == other.versionHelp
6209 && order == other.order
6210 && new HashSet<String>(Arrays.asList(names)).equals(new HashSet<String>(Arrays.asList(other.names)));
6211 return result;
6212 }
6213 public int hashCode() {
6214 return super.hashCodeImpl()
6215 + 37 * Assert.hashCode(help)
6216 + 37 * Assert.hashCode(usageHelp)
6217 + 37 * Assert.hashCode(versionHelp)
6218 + 37 * Arrays.hashCode(names)
6219 + 37 * order;
6220 }
6221
6222 /** Builder responsible for creating valid {@code OptionSpec} objects.
6223 * @since 3.0
6224 */
6225 public static class Builder extends ArgSpec.Builder<Builder> {
6226 private String[] names;
6227 private boolean help;
6228 private boolean usageHelp;
6229 private boolean versionHelp;
6230 private int order = DEFAULT_ORDER;
6231
6232 private Builder(String[] names) { this.names = names; }
6233 private Builder(OptionSpec original) {
6234 super(original);
6235 names = original.names;
6236 help = original.help;
6237 usageHelp = original.usageHelp;
6238 versionHelp = original.versionHelp;
6239 order = original.order;
6240 }
6241 private Builder(IAnnotatedElement member, IFactory factory) {
6242 super(member.getAnnotation(Option.class), member, factory);
6243 Option option = member.getAnnotation(Option.class);
6244 names = option.names();
6245 help = option.help();
6246 usageHelp = option.usageHelp();
6247 versionHelp = option.versionHelp();
6248 order = option.order();
6249 }
6250
6251 /** Returns a valid {@code OptionSpec} instance. */
6252 @Override public OptionSpec build() { return new OptionSpec(this); }
6253 /** Returns this builder. */
6254 @Override protected Builder self() { return this; }
6255
6256 /** Returns one or more option names. At least one option name is required.
6257 * @see Option#names() */
6258 public String[] names() { return names; }
6259
6260 /** Returns whether this option disables validation of the other arguments.
6261 * @see Option#help()
6262 * @deprecated Use {@link #usageHelp()} and {@link #versionHelp()} instead. */
6263 @Deprecated public boolean help() { return help; }
6264
6265 /** Returns whether this option allows the user to request usage help.
6266 * @see Option#usageHelp() */
6267 public boolean usageHelp() { return usageHelp; }
6268
6269 /** Returns whether this option allows the user to request version information.
6270 * @see Option#versionHelp() */
6271 public boolean versionHelp() { return versionHelp; }
6272
6273 /** Returns the position in the options list in the usage help message at which this option should be shown.
6274 * Options with a lower number are shown before options with a higher number.
6275 * This attribute is only honored if {@link UsageMessageSpec#sortOptions()} is {@code false} for this command.
6276 * @see Option#order()
6277 * @since 3.9 */
6278 public int order() { return order; }
6279
6280 /** Replaces the option names with the specified values. At least one option name is required, and returns this builder.
6281 * @return this builder instance to provide a fluent interface */
6282 public Builder names(String... names) { this.names = Assert.notNull(names, "names").clone(); return self(); }
6283
6284 /** Sets whether this option disables validation of the other arguments, and returns this builder. */
6285 public Builder help(boolean help) { this.help = help; return self(); }
6286
6287 /** Sets whether this option allows the user to request usage help, and returns this builder. */
6288 public Builder usageHelp(boolean usageHelp) { this.usageHelp = usageHelp; return self(); }
6289
6290 /** Sets whether this option allows the user to request version information, and returns this builder.*/
6291 public Builder versionHelp(boolean versionHelp) { this.versionHelp = versionHelp; return self(); }
6292
6293 /** Sets the position in the options list in the usage help message at which this option should be shown, and returns this builder.
6294 * @since 3.9 */
6295 public Builder order(int order) { this.order = order; return self(); }
6296 }
6297 }
6298 /** The {@code PositionalParamSpec} class models aspects of a <em>positional parameter</em> of a {@linkplain CommandSpec command}, including whether
6299 * it is required or optional, and attributes for the usage help message describing the positional parameter.
6300 * <p>
6301 * Positional parameters have an {@link #index() index} (or a range of indices). A positional parameter is matched when the parser
6302 * encounters a command line argument at that index. Named options and their parameters do not change the index counter,
6303 * so the command line can contain a mixture of positional parameters and named options.
6304 * </p><p>
6305 * Depending on the positional parameter's {@link #arity() arity}, the parser may consume multiple command line
6306 * arguments starting from the current index. The parser will call {@link #setValue(Object) setValue} on
6307 * the {@code PositionalParamSpec} for each of the parameters encountered.
6308 * For multi-value positional parameters, the {@code type} may be an array, a {@code Collection} or a {@code Map}. In this case
6309 * the parser will get the data structure by calling {@link #getValue() getValue} and modify the contents of this data structure.
6310 * (In the case of arrays, the array is replaced with a new instance with additional elements.)
6311 * </p><p>
6312 * Before calling the setter, picocli converts the positional parameter value from a String to the parameter's type.
6313 * </p>
6314 * <ul>
6315 * <li>If a positional parameter-specific {@link #converters() converter} is configured, this will be used for type conversion.
6316 * If the positional parameter's type is a {@code Map}, the map may have different types for its keys and its values, so
6317 * {@link #converters() converters} should provide two converters: one for the map keys and one for the map values.</li>
6318 * <li>Otherwise, the positional parameter's {@link #type() type} is used to look up a converter in the list of
6319 * {@linkplain CommandLine#registerConverter(Class, ITypeConverter) registered converters}. For multi-value positional parameters,
6320 * the {@code type} may be an array, or a {@code Collection} or a {@code Map}. In that case the elements are converted
6321 * based on the positional parameter's {@link #auxiliaryTypes() auxiliaryTypes}. The auxiliaryType is used to look up
6322 * the converter(s) to use to convert the individual parameter values.
6323 * Maps may have different types for its keys and its values, so {@link #auxiliaryTypes() auxiliaryTypes}
6324 * should provide two types: one for the map keys and one for the map values.</li>
6325 * </ul>
6326 * <p>
6327 * {@code PositionalParamSpec} objects are used by the picocli command line interpreter and help message generator.
6328 * Picocli can construct a {@code PositionalParamSpec} automatically from fields and methods with {@link Parameters @Parameters}
6329 * annotations. Alternatively a {@code PositionalParamSpec} can be constructed programmatically.
6330 * </p><p>
6331 * When a {@code PositionalParamSpec} is created from a {@link Parameters @Parameters} -annotated field or method,
6332 * it is "bound" to that field or method: this field is set (or the method is invoked) when the position is matched
6333 * and {@link #setValue(Object) setValue} is called.
6334 * Programmatically constructed {@code PositionalParamSpec} instances will remember the value passed to the
6335 * {@link #setValue(Object) setValue} method so it can be retrieved with the {@link #getValue() getValue} method.
6336 * This behaviour can be customized by installing a custom {@link IGetter} and {@link ISetter} on the {@code PositionalParamSpec}.
6337 * </p>
6338 * @since 3.0 */
6339 public static class PositionalParamSpec extends ArgSpec {
6340 private Range index;
6341 private Range capacity;
6342
6343 /** Ensures all attributes of this {@code PositionalParamSpec} have a valid value; throws an {@link InitializationException} if this cannot be achieved. */
6344 private PositionalParamSpec(Builder builder) {
6345 super(builder);
6346 index = builder.index == null ? Range.valueOf("*") : builder.index;
6347 capacity = builder.capacity == null ? Range.parameterCapacity(arity(), index) : builder.capacity;
6348 if (toString == null) { toString = "positional parameter[" + index() + "]"; }
6349 }
6350 public static Builder builder() { return new Builder(); }
6351 public static Builder builder(IAnnotatedElement source, IFactory factory) { return new Builder(source, factory); }
6352 /** Returns a new Builder initialized with the attributes from this {@code PositionalParamSpec}. Calling {@code build} immediately will return a copy of this {@code PositionalParamSpec}.
6353 * @return a builder that can create a copy of this spec
6354 */
6355 public Builder toBuilder() { return new Builder(this); }
6356 @Override public boolean isOption() { return false; }
6357 @Override public boolean isPositional() { return true; }
6358
6359 /** Returns the description template of this positional parameter, before variables are {@linkplain Parameters#description() rendered}.
6360 * If a resource bundle has been {@linkplain ArgSpec#messages(Messages) set}, this method will first try to find a value in the resource bundle:
6361 * If the resource bundle has no entry for the {@code fully qualified commandName + "." + descriptionKey} or for the unqualified {@code descriptionKey},
6362 * an attempt is made to find the positional parameter description using {@code paramLabel() + "[" + index() + "]"} as key,
6363 * first with the {@code fully qualified commandName + "."} prefix, then without.
6364 * @see Parameters#description()
6365 * @see CommandSpec#qualifiedName(String)
6366 * @since 3.6 */
6367 @Override public String[] description() {
6368 if (messages() == null) { return super.description(); }
6369 String[] newValue = messages().getStringArray(descriptionKey(), null);
6370 if (newValue != null) { return newValue; }
6371 newValue = messages().getStringArray(paramLabel() + "[" + index() + "]", null);
6372 if (newValue != null) { return newValue; }
6373 return super.description();
6374 }
6375
6376 /** Returns an index or range specifying which of the command line arguments should be assigned to this positional parameter.
6377 * @see Parameters#index() */
6378 public Range index() { return index; }
6379 private Range capacity() { return capacity; }
6380
6381 public int hashCode() {
6382 return super.hashCodeImpl()
6383 + 37 * Assert.hashCode(capacity)
6384 + 37 * Assert.hashCode(index);
6385 }
6386 public boolean equals(Object obj) {
6387 if (obj == this) {
6388 return true;
6389 }
6390 if (!(obj instanceof PositionalParamSpec)) {
6391 return false;
6392 }
6393 PositionalParamSpec other = (PositionalParamSpec) obj;
6394 return super.equalsImpl(other)
6395 && Assert.equals(this.capacity, other.capacity)
6396 && Assert.equals(this.index, other.index);
6397 }
6398
6399 /** Builder responsible for creating valid {@code PositionalParamSpec} objects.
6400 * @since 3.0
6401 */
6402 public static class Builder extends ArgSpec.Builder<Builder> {
6403 private Range capacity;
6404 private Range index;
6405 private Builder() {}
6406 private Builder(PositionalParamSpec original) {
6407 super(original);
6408 index = original.index;
6409 capacity = original.capacity;
6410 }
6411 private Builder(IAnnotatedElement member, IFactory factory) {
6412 super(member.getAnnotation(Parameters.class), member, factory);
6413 index = Range.parameterIndex(member);
6414 capacity = Range.parameterCapacity(member);
6415 }
6416 /** Returns a valid {@code PositionalParamSpec} instance. */
6417 @Override public PositionalParamSpec build() { return new PositionalParamSpec(this); }
6418 /** Returns this builder. */
6419 @Override protected Builder self() { return this; }
6420
6421 /** Returns an index or range specifying which of the command line arguments should be assigned to this positional parameter.
6422 * @see Parameters#index() */
6423 public Range index() { return index; }
6424
6425 /** Sets the index or range specifying which of the command line arguments should be assigned to this positional parameter, and returns this builder. */
6426 public Builder index(String range) { return index(Range.valueOf(range)); }
6427
6428 /** Sets the index or range specifying which of the command line arguments should be assigned to this positional parameter, and returns this builder. */
6429 public Builder index(Range index) { this.index = index; return self(); }
6430
6431 Range capacity() { return capacity; }
6432 Builder capacity(Range capacity) { this.capacity = capacity; return self(); }
6433 }
6434 }
6435
6436 /** Interface for sorting {@link OptionSpec options} and {@link ArgGroupSpec groups} together.
6437 * @since 4.0 */
6438 public interface IOrdered {
6439 /** Returns the position in the options list in the usage help message at which this element should be shown.
6440 * Elements with a lower number are shown before elements with a higher number.
6441 * This attribute is only honored if {@link UsageMessageSpec#sortOptions()} is {@code false} for this command. */
6442 int order();
6443 }
6444
6445 /** The {@code ArgGroupSpec} class models a {@link ArgGroup group} of arguments (options, positional parameters or a mixture of the two).
6446 * @see ArgGroup
6447 * @since 4.0 */
6448 public static class ArgGroupSpec implements IOrdered {
6449 static final int DEFAULT_ORDER = -1;
6450 private static final String NO_HEADING = "__no_heading__";
6451 private static final String NO_HEADING_KEY = "__no_heading_key__";
6452 private final String heading;
6453 private final String headingKey;
6454 private final boolean exclusive;
6455 private final Range multiplicity;
6456 private final boolean validate;
6457 private final int order;
6458 private final IGetter getter;
6459 private final ISetter setter;
6460 private final IScope scope;
6461 private final ITypeInfo typeInfo;
6462 private final List<ArgGroupSpec> subgroups;
6463 private final Set<ArgSpec> args;
6464 private Messages messages;
6465 private ArgGroupSpec parentGroup;
6466 private String id = "1";
6467
6468 ArgGroupSpec(ArgGroupSpec.Builder builder) {
6469 heading = NO_HEADING .equals(builder.heading) ? null : builder.heading;
6470 headingKey = NO_HEADING_KEY.equals(builder.headingKey) ? null : builder.headingKey;
6471 exclusive = builder.exclusive;
6472 multiplicity = builder.multiplicity;
6473 validate = builder.validate;
6474 order = builder.order;
6475 typeInfo = builder.typeInfo;
6476 getter = builder.getter;
6477 setter = builder.setter;
6478 scope = builder.scope;
6479
6480 args = Collections.unmodifiableSet(new LinkedHashSet<ArgSpec>(builder.args()));
6481 subgroups = Collections.unmodifiableList(new ArrayList<ArgGroupSpec>(builder.subgroups()));
6482 if (args.isEmpty() && subgroups.isEmpty()) { throw new InitializationException("ArgGroup has no options or positional parameters, and no subgroups"); }
6483
6484 int i = 1;
6485 for (ArgGroupSpec sub : subgroups) { sub.parentGroup = this; sub.id = id + "." + i++; }
6486 for (ArgSpec arg : args) { arg.group = this; }
6487 }
6488
6489 /** Returns a new {@link Builder}.
6490 * @return a new ArgGroupSpec.Builder instance */
6491 public static Builder builder() { return new Builder(); }
6492
6493 /** Returns a new {@link Builder} associated with the specified annotated element.
6494 * @param annotatedElement the annotated element containing {@code @Option} and {@code @Parameters}
6495 * @return a new ArgGroupSpec.Builder instance */
6496 public static Builder builder(IAnnotatedElement annotatedElement) { return new Builder(Assert.notNull(annotatedElement, "annotatedElement")); }
6497
6498 /** Returns whether this is a mutually exclusive group; {@code true} by default.
6499 * If {@code false}, this is a co-occurring group. Ignored if {@link #validate()} is {@code false}.
6500 * @see ArgGroup#exclusive() */
6501 public boolean exclusive() { return exclusive; }
6502
6503 /** Returns the multiplicity of this group: how many occurrences it may have on the command line; {@code "0..1"} (optional) by default.
6504 * A group can be made required by specifying a multiplicity of {@code "1"}. For a group of mutually exclusive arguments,
6505 * being required means that one of the arguments in the group must appear on the command line, or a MissingParameterException is thrown.
6506 * For a group of co-occurring arguments, being required means that all arguments in the group must appear on the command line.
6507 * Ignored if {@link #validate()} is {@code false}.
6508 * @see ArgGroup#multiplicity() */
6509 public Range multiplicity() { return multiplicity; }
6510
6511 /** Returns whether picocli should validate the rules of this group:
6512 * for a mutually exclusive group this means that no more than one arguments in the group is specified on the command line;
6513 * for a co-ocurring group this means that all arguments in the group are specified on the command line.
6514 * {@code true} by default.
6515 * @see ArgGroup#validate() */
6516 public boolean validate() { return validate; }
6517
6518 /** Returns the position in the options list in the usage help message at which this group should be shown.
6519 * Options with a lower number are shown before options with a higher number.
6520 * This attribute is only honored if {@link UsageMessageSpec#sortOptions()} is {@code false} for this command. */
6521 public int order() { return this.order; }
6522
6523 /** Returns the heading of this group (may be {@code null}), used when generating the usage documentation.
6524 * @see ArgGroup#heading() */
6525 public String heading() {
6526 if (messages() == null) { return heading; }
6527 String newValue = messages().getString(headingKey(), null);
6528 if (newValue != null) { return newValue; }
6529 return heading;
6530 }
6531
6532 /** Returns the heading key of this group (may be {@code null}), used to get the heading from a resource bundle.
6533 * @see ArgGroup#headingKey() */
6534 public String headingKey() { return headingKey; }
6535
6536 /**
6537 * Returns the parent group that this group is part of, or {@code null} if this group is not part of a composite.
6538 */
6539 public ArgGroupSpec parentGroup() { return parentGroup; }
6540
6541 /** Return the subgroups that this group is composed of; may be empty but not {@code null}.
6542 * @return immutable list of subgroups that this group is composed of. */
6543 public List<ArgGroupSpec> subgroups() { return subgroups; }
6544
6545 /**
6546 * Returns {@code true} if this group is a subgroup (or a nested sub-subgroup, to any level of depth)
6547 * of the specified group, {@code false} otherwise.
6548 * @param group the group to check if it contains this group
6549 * @return {@code true} if this group is a subgroup or a nested sub-subgroup of the specified group
6550 */
6551 public boolean isSubgroupOf(ArgGroupSpec group) {
6552 for (ArgGroupSpec sub : group.subgroups) {
6553 if (this == sub) { return true; }
6554 if (isSubgroupOf(sub)) { return true; }
6555 }
6556 return false;
6557 }
6558 /** Returns the type info for the annotated program element associated with this group.
6559 * @return type information that does not require {@code Class} objects and be constructed both at runtime and compile time
6560 */
6561 public ITypeInfo typeInfo() { return typeInfo; }
6562
6563 /** Returns the {@link IGetter} that is responsible for supplying the value of the annotated program element associated with this group. */
6564 public IGetter getter() { return getter; }
6565 /** Returns the {@link ISetter} that is responsible for modifying the value of the annotated program element associated with this group. */
6566 public ISetter setter() { return setter; }
6567 /** Returns the {@link IScope} that determines where the setter sets the value (or the getter gets the value) of the annotated program element associated with this group. */
6568 public IScope scope() { return scope; }
6569
6570 Object userObject() { try { return getter.get(); } catch (Exception ex) { return ex.toString(); } }
6571 String id() { return id; }
6572
6573 /** Returns the options and positional parameters in this group; may be empty but not {@code null}. */
6574 public Set<ArgSpec> args() { return args; }
6575 /** Returns the required options and positional parameters in this group; may be empty but not {@code null}. */
6576 public Set<ArgSpec> requiredArgs() {
6577 Set<ArgSpec> result = new LinkedHashSet<ArgSpec>(args);
6578 for (Iterator<ArgSpec> iter = result.iterator(); iter.hasNext(); ) {
6579 if (!iter.next().required()) { iter.remove(); }
6580 }
6581 return Collections.unmodifiableSet(result);
6582 }
6583
6584 /** Returns the list of positional parameters configured for this group.
6585 * @return an immutable list of positional parameters in this group. */
6586 public List<PositionalParamSpec> positionalParameters() {
6587 List<PositionalParamSpec> result = new ArrayList<PositionalParamSpec>();
6588 for (ArgSpec arg : args()) { if (arg instanceof PositionalParamSpec) { result.add((PositionalParamSpec) arg); } }
6589 return Collections.unmodifiableList(result);
6590 }
6591 /** Returns the list of options configured for this group.
6592 * @return an immutable list of options in this group. */
6593 public List<OptionSpec> options() {
6594 List<OptionSpec> result = new ArrayList<OptionSpec>();
6595 for (ArgSpec arg : args()) { if (arg instanceof OptionSpec) { result.add((OptionSpec) arg); } }
6596 return Collections.unmodifiableList(result);
6597 }
6598
6599 public String synopsis() {
6600 return synopsisText(new Help.ColorScheme(Help.Ansi.OFF)).toString();
6601 }
6602
6603 public Text synopsisText(Help.ColorScheme colorScheme) {
6604 String infix = exclusive() ? " | " : " ";
6605 Text synopsis = colorScheme.ansi().new Text(0);
6606 for (ArgSpec arg : args()) {
6607 if (synopsis.length > 0) { synopsis = synopsis.concat(infix); }
6608 if (arg instanceof OptionSpec) {
6609 synopsis = concatOptionText(synopsis, colorScheme, (OptionSpec) arg);
6610 } else {
6611 synopsis = concatPositionalText(synopsis, colorScheme, (PositionalParamSpec) arg);
6612 }
6613 }
6614 for (ArgGroupSpec subgroup : subgroups()) {
6615 if (synopsis.length > 0) { synopsis = synopsis.concat(infix); }
6616 synopsis = synopsis.concat(subgroup.synopsisText(colorScheme));
6617 }
6618 String prefix = multiplicity().min > 0 ? "(" : "[";
6619 String postfix = multiplicity().min > 0 ? ")" : "]";
6620 Text result = colorScheme.ansi().text(prefix).concat(synopsis).concat(postfix);
6621 int i = 1;
6622 for (; i < multiplicity.min; i++) {
6623 result = result.concat(" (").concat(synopsis).concat(")");
6624 }
6625 if (multiplicity().isVariable) {
6626 result = result.concat("...");
6627 } else {
6628 for (; i < multiplicity.max; i++) {
6629 result = result.concat(" [").concat(synopsis).concat("]");
6630 }
6631 }
6632 return result;
6633 }
6634
6635 private Text concatOptionText(Text text, Help.ColorScheme colorScheme, OptionSpec option) {
6636 if (!option.hidden()) {
6637 Text name = colorScheme.optionText(option.shortestName());
6638 Text param = createLabelRenderer(option.commandSpec).renderParameterLabel(option, colorScheme.ansi(), colorScheme.optionParamStyles);
6639 text = text.concat(open(option)).concat(name).concat(param).concat(close(option));
6640 if (option.isMultiValue()) { // e.g., -x=VAL [-x=VAL]...
6641 text = text.concat(" [").concat(name).concat(param).concat("]...");
6642 }
6643 }
6644 return text;
6645 }
6646
6647 private Text concatPositionalText(Text text, Help.ColorScheme colorScheme, PositionalParamSpec positionalParam) {
6648 if (!positionalParam.hidden()) {
6649 Text label = createLabelRenderer(positionalParam.commandSpec).renderParameterLabel(positionalParam, colorScheme.ansi(), colorScheme.parameterStyles);
6650 text = text.concat(open(positionalParam)).concat(label).concat(close(positionalParam));
6651 }
6652 return text;
6653 }
6654 private String open(ArgSpec argSpec) { return argSpec.required() ? "" : "["; }
6655 private String close(ArgSpec argSpec) { return argSpec.required() ? "" : "]"; }
6656
6657 public Help.IParamLabelRenderer createLabelRenderer(CommandSpec commandSpec) {
6658 return new Help.DefaultParamLabelRenderer(commandSpec == null ? CommandSpec.create() : commandSpec);
6659 }
6660 /** Returns the Messages for this argument group specification, or {@code null}. */
6661 public Messages messages() { return messages; }
6662 /** Sets the Messages for this ArgGroupSpec, and returns this ArgGroupSpec.
6663 * @param msgs the new Messages value, may be {@code null}
6664 * @see Command#resourceBundle()
6665 * @see #headingKey()
6666 */
6667 public ArgGroupSpec messages(Messages msgs) {
6668 messages = msgs;
6669 for (ArgGroupSpec sub : subgroups()) { sub.messages(msgs); }
6670 return this;
6671 }
6672
6673 @Override public boolean equals(Object obj) {
6674 if (obj == this) { return true; }
6675 if (!(obj instanceof ArgGroupSpec)) { return false; }
6676 ArgGroupSpec other = (ArgGroupSpec) obj;
6677 return exclusive == other.exclusive
6678 && Assert.equals(multiplicity, other.multiplicity)
6679 && validate == other.validate
6680 && order == other.order
6681 && Assert.equals(heading, other.heading)
6682 && Assert.equals(headingKey, other.headingKey)
6683 && Assert.equals(subgroups, other.subgroups)
6684 && Assert.equals(args, other.args);
6685 }
6686
6687 @Override public int hashCode() {
6688 int result = 17;
6689 result += 37 * result + Assert.hashCode(exclusive);
6690 result += 37 * result + Assert.hashCode(multiplicity);
6691 result += 37 * result + Assert.hashCode(validate);
6692 result += 37 * result + order;
6693 result += 37 * result + Assert.hashCode(heading);
6694 result += 37 * result + Assert.hashCode(headingKey);
6695 result += 37 * result + Assert.hashCode(subgroups);
6696 result += 37 * result + Assert.hashCode(args);
6697 return result;
6698 }
6699
6700 @Override public String toString() {
6701 List<String> argNames = new ArrayList<String>();
6702 for (ArgSpec arg : args()) {
6703 if (arg instanceof OptionSpec) {
6704 argNames.add(((OptionSpec) arg).shortestName());
6705 } else {
6706 PositionalParamSpec p = (PositionalParamSpec) arg;
6707 argNames.add(p.index() + " (" + p.paramLabel() + ")");
6708 }
6709 }
6710 return "ArgGroup[exclusive=" + exclusive + ", multiplicity=" + multiplicity +
6711 ", validate=" + validate + ", order=" + order + ", args=[" + ArgSpec.describe(args()) +
6712 "], headingKey=" + quote(headingKey) + ", heading=" + quote(heading) +
6713 ", subgroups=" + subgroups + "]";
6714 }
6715 private static String quote(String s) { return s == null ? "null" : "'" + s + "'"; }
6716
6717 void initUserObject(CommandLine commandLine) {
6718 if (commandLine == null) { new Tracer().debug("Could not create user object for %s with null CommandLine%n.", this); }
6719 try {
6720 tryInitUserObject(commandLine);
6721 } catch (PicocliException ex) {
6722 throw ex;
6723 } catch (Exception ex) {
6724 throw new InitializationException("Could not create user object for " + this, ex);
6725 }
6726 }
6727 void tryInitUserObject(CommandLine commandLine) throws Exception {
6728 Tracer tracer = commandLine.tracer;
6729 if (typeInfo() != null) {
6730 tracer.debug("Creating new user object of type %s for group %s%n", typeInfo().getAuxiliaryTypes()[0], synopsis());
6731 Object userObject = DefaultFactory.create(commandLine.factory, typeInfo().getAuxiliaryTypes()[0]);
6732 tracer.debug("Created %s, invoking setter %s with scope %s%n", userObject, setter(), scope());
6733 setUserObject(userObject, commandLine.factory);
6734 for (ArgSpec arg : args()) {
6735 tracer.debug("Initializing %s in group %s: setting scope to user object %s and initializing initial and default values%n", ArgSpec.describe(arg, "="), synopsis(), userObject);
6736 arg.scope().set(userObject); // flip the actual user object for the arg (and all other args in this group; they share the same IScope instance)
6737 commandLine.interpreter.parseResultBuilder.isInitializingDefaultValues = true;
6738 arg.applyInitialValue(tracer);
6739 commandLine.interpreter.applyDefault(commandLine.getCommandSpec().defaultValueProvider(), arg);
6740 commandLine.interpreter.parseResultBuilder.isInitializingDefaultValues = false;
6741 }
6742 for (ArgGroupSpec subgroup : subgroups()) {
6743 tracer.debug("Setting scope for subgroup %s with setter=%s in group %s to user object %s%n", subgroup.synopsis(), subgroup.setter(), synopsis(), userObject);
6744 subgroup.scope().set(userObject); // flip the actual user object for the arg (and all other args in this group; they share the same IScope instance)
6745 }
6746 } else {
6747 tracer.debug("No type information available for group %s: cannot create new user object. Scope for arg setters is not changed.%n", synopsis());
6748 }
6749 tracer.debug("Initialization complete for group %s%n", synopsis());
6750 }
6751
6752 void setUserObject(Object userObject, IFactory factory) throws Exception {
6753 if (typeInfo().isCollection()) {
6754 @SuppressWarnings("unchecked") Collection<Object> c = (Collection<Object>) getter().get();
6755 if (c == null) {
6756 @SuppressWarnings("unchecked")
6757 Collection<Object> c2 = (Collection<Object>) DefaultFactory.create(factory, typeInfo.getType());
6758 setter().set(c = c2);
6759 }
6760 (c).add(userObject);
6761 } else if (typeInfo().isArray()) {
6762 Object old = getter().get();
6763 int oldSize = old == null ? 0 : Array.getLength(old);
6764 Object array = Array.newInstance(typeInfo().getAuxiliaryTypes()[0], oldSize + 1);
6765 for (int i = 0; i < oldSize; i++) {
6766 Array.set(array, i, Array.get(old, i));
6767 }
6768 Array.set(array, oldSize, userObject);
6769 setter().set(array);
6770 } else {
6771 setter().set(userObject);
6772 }
6773 }
6774
6775 enum GroupValidationResult {
6776 SUCCESS_PRESENT, SUCCESS_ABSENT,
6777 FAILURE_PRESENT, FAILURE_ABSENT, FAILURE_PARTIAL;
6778 static boolean containsBlockingFailure(EnumSet<GroupValidationResult> set) {
6779 return set.contains(FAILURE_PRESENT) || set.contains(FAILURE_PARTIAL);
6780 }
6781 /** FAILURE_PRESENT or FAILURE_PARTIAL */
6782 boolean blockingFailure() { return this == FAILURE_PRESENT || this == FAILURE_PARTIAL; }
6783 boolean present() { return this == SUCCESS_PRESENT /*|| this == FAILURE_PRESENT*/; }
6784 boolean success() { return this == SUCCESS_ABSENT || this == SUCCESS_PRESENT; }
6785 }
6786
6787 private ParameterException validationException;
6788 private GroupValidationResult validationResult;
6789
6790 /** Clears temporary validation state for this group and its subgroups. */
6791 void clearValidationResult() {
6792 validationException = null;
6793 validationResult = null;
6794 for (ArgGroupSpec sub : subgroups()) { sub.clearValidationResult(); }
6795 }
6796
6797 /** Throws an exception if the constraints in this group are not met by the specified match. */
6798 void validateConstraints(ParseResult parseResult) {
6799 if (!validate()) { return; }
6800 CommandLine commandLine = parseResult.commandSpec().commandLine();
6801
6802 // first validate args in this group
6803 validationResult = validateArgs(commandLine, parseResult);
6804 if (validationResult.blockingFailure()) {
6805 commandLine.interpreter.maybeThrow(validationException); // composite parent validations cannot succeed anyway
6806 }
6807 // then validate sub groups
6808 EnumSet<GroupValidationResult> validationResults = validateSubgroups(parseResult);
6809 if (GroupValidationResult.containsBlockingFailure(validationResults)) {
6810 commandLine.interpreter.maybeThrow(validationException); // composite parent validations cannot succeed anyway
6811 }
6812 List<MatchedGroup> matchedGroups = parseResult.findMatchedGroup(this);
6813 if (matchedGroups.isEmpty()) {
6814 // TODO can/should we verify minimum multiplicity here?
6815 if (multiplicity().min > 0) {
6816 if (validationResult.success()) {
6817 validationResult = GroupValidationResult.FAILURE_ABSENT;
6818 validationException = new MissingParameterException(commandLine, args(),
6819 "Error: Group: " + synopsis() + " must be specified " + multiplicity().min + " times but was missing");
6820 }
6821 }
6822 }
6823 for (MatchedGroup matchedGroup : matchedGroups) {
6824 int matchCount = matchedGroup.multiples().size();
6825 // note: matchCount == 0 if only subgroup(s) are matched for a group without args (subgroups-only)
6826 boolean checkMinimum = matchCount > 0 || !args().isEmpty();
6827 if (checkMinimum && matchCount < multiplicity().min) {
6828 if (validationResult.success()) {
6829 validationResult = matchCount == 0 ? GroupValidationResult.FAILURE_ABSENT: GroupValidationResult.FAILURE_PARTIAL;
6830 validationException = new MissingParameterException(commandLine, args(),
6831 "Error: Group: " + synopsis() + " must be specified " + multiplicity().min + " times but was matched " + matchCount + " times");
6832 }
6833 } else if (matchCount > multiplicity().max) {
6834 if (!validationResult.blockingFailure()) {
6835 validationResult = GroupValidationResult.FAILURE_PRESENT;
6836 validationException = new MaxValuesExceededException(commandLine,
6837 "Error: Group: " + synopsis() + " can only be specified " + multiplicity().max + " times but was matched " + matchCount + " times.");
6838 }
6839 }
6840 if (validationResult.blockingFailure()) {
6841 commandLine.interpreter.maybeThrow(validationException);
6842 }
6843 }
6844 if (validationException != null && parentGroup == null) {
6845 commandLine.interpreter.maybeThrow(validationException);
6846 }
6847 }
6848
6849 private EnumSet<GroupValidationResult> validateSubgroups(ParseResult parseResult) {
6850 EnumSet<GroupValidationResult> validationResults = EnumSet.of(validationResult);
6851 if (subgroups().isEmpty()) { return validationResults; }
6852 for (ArgGroupSpec subgroup : subgroups()) {
6853 subgroup.validateConstraints(parseResult);
6854 validationResults.add(Assert.notNull(subgroup.validationResult, "subgroup validation result"));
6855 if (subgroup.validationResult.blockingFailure()) { this.validationException = subgroup.validationException; break; }
6856 }
6857 // now do some coarse-grained checking for exclusive subgroups
6858 int elementCount = args().size() + subgroups().size();
6859 int presentCount = validationResult.present() ? 1 : 0;
6860 String exclusiveElements = "";
6861 for (ArgGroupSpec subgroup : subgroups()) {
6862 if (!parseResult.findMatchedGroup(subgroup).isEmpty()) { presentCount++; }
6863 //presentCount += parseResult.findMatchedGroup(subgroup).size(); // this would give incorrect error message if A and B are exclusive and A is matched 2x and B is not matched
6864 if (exclusiveElements.length() > 0) { exclusiveElements += " and "; }
6865 exclusiveElements += subgroup.synopsis();
6866 }
6867 validationResult = validate(parseResult.commandSpec().commandLine(), presentCount, presentCount < elementCount,
6868 presentCount > 0 && presentCount < elementCount, exclusiveElements, synopsis(), synopsis());
6869 validationResults.add(validationResult);
6870 return validationResults;
6871 }
6872
6873 private GroupValidationResult validateArgs(CommandLine commandLine, ParseResult parseResult) {
6874 if (args().isEmpty()) { return GroupValidationResult.SUCCESS_ABSENT; }
6875 return validateArgs(commandLine, parseResult.findMatchedGroup(this));
6876 }
6877
6878 private GroupValidationResult validateArgs(CommandLine commandLine, List<MatchedGroup> matchedGroups) {
6879 if (matchedGroups.isEmpty()) {
6880 int presentCount = 0;
6881 boolean haveMissing = true;
6882 boolean someButNotAllSpecified = false;
6883 String exclusiveElements = "";
6884 String missingElements = ArgSpec.describe(requiredArgs());
6885 return validate(commandLine, presentCount, haveMissing, someButNotAllSpecified, exclusiveElements, missingElements, missingElements);
6886 }
6887 GroupValidationResult result = GroupValidationResult.SUCCESS_ABSENT;
6888 Map<MatchedGroup, List<MatchedGroup>> byParent = groupByParent(matchedGroups);
6889 for (Map.Entry<MatchedGroup, List<MatchedGroup>> entry : byParent.entrySet()) {
6890 List<MatchedGroup> allForOneParent = entry.getValue();
6891 for (MatchedGroup oneForOneParent : allForOneParent) {
6892 result = validateMultiples(commandLine, oneForOneParent.multiples());
6893 if (result.blockingFailure()) { return result; }
6894 }
6895 }
6896 return result;
6897 }
6898
6899 private Map<MatchedGroup, List<MatchedGroup>> groupByParent(List<MatchedGroup> matchedGroups) {
6900 Map<MatchedGroup, List<MatchedGroup>> result = new HashMap<MatchedGroup, List<MatchedGroup>>();
6901 for (MatchedGroup mg : matchedGroups) {
6902 addValueToListInMap(result, mg.parentMatchedGroup(), mg);
6903 }
6904 return result;
6905 }
6906
6907 private List<ParseResult.MatchedGroupMultiple> flatListMultiples(Collection<MatchedGroup> matchedGroups) {
6908 List<ParseResult.MatchedGroupMultiple> all = new ArrayList<ParseResult.MatchedGroupMultiple>();
6909 for (MatchedGroup matchedGroup : matchedGroups) {
6910 all.addAll(matchedGroup.multiples());
6911 }
6912 return all;
6913 }
6914
6915 private GroupValidationResult validateMultiples(CommandLine commandLine, List<ParseResult.MatchedGroupMultiple> multiples) {
6916 Set<ArgSpec> intersection = new LinkedHashSet<ArgSpec>(this.args());
6917 Set<ArgSpec> missing = new LinkedHashSet<ArgSpec>(this.requiredArgs());
6918 Set<ArgSpec> found = new LinkedHashSet<ArgSpec>();
6919 for (ParseResult.MatchedGroupMultiple multiple : multiples) {
6920 found.addAll(multiple.matchedValues.keySet());
6921 missing.removeAll(multiple.matchedValues.keySet());
6922 }
6923 intersection.retainAll(found);
6924 int presentCount = intersection.size();
6925 boolean haveMissing = !missing.isEmpty();
6926 boolean someButNotAllSpecified = haveMissing && !intersection.isEmpty();
6927 String exclusiveElements = ArgSpec.describe(intersection);
6928 String requiredElements = ArgSpec.describe(requiredArgs());
6929 String missingElements = ArgSpec.describe(missing);
6930
6931 return validate(commandLine, presentCount, haveMissing, someButNotAllSpecified, exclusiveElements, requiredElements, missingElements);
6932 }
6933
6934 private GroupValidationResult validate(CommandLine commandLine, int presentCount, boolean haveMissing, boolean someButNotAllSpecified, String exclusiveElements, String requiredElements, String missingElements) {
6935 if (exclusive()) {
6936 if (presentCount > 1) {
6937 validationException = new MutuallyExclusiveArgsException(commandLine,
6938 "Error: " + exclusiveElements + " are mutually exclusive (specify only one)");
6939 return GroupValidationResult.FAILURE_PRESENT;
6940 }
6941 // check that exactly one member was matched
6942 if (multiplicity().min > 0 && presentCount < 1) {
6943 validationException = new MissingParameterException(commandLine, args(),
6944 "Error: Missing required argument (specify one of these): " + requiredElements);
6945 return GroupValidationResult.FAILURE_ABSENT;
6946 }
6947 return GroupValidationResult.SUCCESS_PRESENT;
6948 } else { // co-occurring group
6949 if (someButNotAllSpecified) {
6950 validationException = new MissingParameterException(commandLine, args(),
6951 "Error: Missing required argument(s): " + missingElements);
6952 return GroupValidationResult.FAILURE_PARTIAL;
6953 }
6954 if ((multiplicity().min > 0 && haveMissing)) {
6955 validationException = new MissingParameterException(commandLine, args(),
6956 "Error: Missing required argument(s): " + missingElements);
6957 return GroupValidationResult.FAILURE_ABSENT;
6958 }
6959 return haveMissing ? GroupValidationResult.SUCCESS_ABSENT : GroupValidationResult.SUCCESS_PRESENT;
6960 }
6961 }
6962
6963 /** Builder responsible for creating valid {@code ArgGroupSpec} objects.
6964 * @since 4.0 */
6965 public static class Builder {
6966 private IGetter getter;
6967 private ISetter setter;
6968 private IScope scope;
6969 private ITypeInfo typeInfo;
6970 private String heading;
6971 private String headingKey;
6972 private boolean exclusive = true;
6973 private Range multiplicity = Range.valueOf("0..1");
6974 private boolean validate = true;
6975 private int order = DEFAULT_ORDER;
6976 private List<ArgSpec> args = new ArrayList<ArgSpec>();
6977 private List<ArgGroupSpec> subgroups = new ArrayList<ArgGroupSpec>();
6978
6979 // for topological sorting; private only
6980 private Boolean topologicalSortDone;
6981 private List<Builder> compositesReferencingMe = new ArrayList<Builder>();
6982
6983 Builder() { }
6984 Builder(IAnnotatedElement source) {
6985 typeInfo = source.getTypeInfo();
6986 getter = source.getter();
6987 setter = source.setter();
6988 scope = source.scope();
6989 }
6990
6991 /** Updates this builder from the specified annotation values.
6992 * @param group annotation values
6993 * @return this builder for method chaining */
6994 public Builder updateArgGroupAttributes(ArgGroup group) {
6995 return this
6996 .heading(group.heading())
6997 .headingKey(group.headingKey())
6998 .exclusive(group.exclusive())
6999 .multiplicity(group.multiplicity())
7000 .validate(group.validate())
7001 .order(group.order());
7002 }
7003
7004 /** Returns a valid {@code ArgGroupSpec} instance. */
7005 public ArgGroupSpec build() { return new ArgGroupSpec(this); }
7006
7007 /** Returns whether this is a mutually exclusive group; {@code true} by default.
7008 * If {@code false}, this is a co-occurring group. Ignored if {@link #validate()} is {@code false}.
7009 * @see ArgGroup#exclusive() */
7010 public boolean exclusive() { return exclusive; }
7011 /** Sets whether this is a mutually exclusive group; {@code true} by default.
7012 * If {@code false}, this is a co-occurring group. Ignored if {@link #validate()} is {@code false}.
7013 * @see ArgGroup#exclusive() */
7014 public Builder exclusive(boolean newValue) { exclusive = newValue; return this; }
7015
7016 /** Returns the multiplicity of this group: how many occurrences it may have on the command line; {@code "0..1"} (optional) by default.
7017 * A group can be made required by specifying a multiplicity of {@code "1"}. For a group of mutually exclusive arguments,
7018 * being required means that one of the arguments in the group must appear on the command line, or a MissingParameterException is thrown.
7019 * For a group of co-occurring arguments, being required means that all arguments in the group must appear on the command line.
7020 * Ignored if {@link #validate()} is {@code false}.
7021 * @see ArgGroup#multiplicity() */
7022 public Range multiplicity() { return multiplicity; }
7023 /** Sets the multiplicity of this group: how many occurrences it may have on the command line; {@code "0..1"} (optional) by default.
7024 * A group can be made required by specifying a multiplicity of {@code "1"}. For a group of mutually exclusive arguments,
7025 * being required means that one of the arguments in the group must appear on the command line, or a MissingParameterException is thrown.
7026 * For a group of co-occurring arguments, being required means that all arguments in the group must appear on the command line.
7027 * Ignored if {@link #validate()} is {@code false}.
7028 * @see ArgGroup#multiplicity() */
7029 public Builder multiplicity(String newValue) { return multiplicity(Range.valueOf(newValue)); }
7030 /** Sets the multiplicity of this group: how many occurrences it may have on the command line; {@code "0..1"} (optional) by default.
7031 * A group can be made required by specifying a multiplicity of {@code "1"}. For a group of mutually exclusive arguments,
7032 * being required means that one of the arguments in the group must appear on the command line, or a MissingParameterException is thrown.
7033 * For a group of co-occurring arguments, being required means that all arguments in the group must appear on the command line.
7034 * Ignored if {@link #validate()} is {@code false}.
7035 * @see ArgGroup#multiplicity() */
7036 public Builder multiplicity(Range newValue) { multiplicity = newValue; return this; }
7037
7038 /** Returns whether picocli should validate the rules of this group:
7039 * for a mutually exclusive group this means that no more than one arguments in the group is specified on the command line;
7040 * for a co-ocurring group this means that all arguments in the group are specified on the command line.
7041 * {@code true} by default.
7042 * @see ArgGroup#validate() */
7043 public boolean validate() { return validate; }
7044 /** Sets whether picocli should validate the rules of this group:
7045 * for a mutually exclusive group this means that no more than one arguments in the group is specified on the command line;
7046 * for a co-ocurring group this means that all arguments in the group are specified on the command line.
7047 * {@code true} by default.
7048 * @see ArgGroup#validate() */
7049 public Builder validate(boolean newValue) { validate = newValue; return this; }
7050
7051 /** Returns the position in the options list in the usage help message at which this group should be shown.
7052 * Options with a lower number are shown before options with a higher number.
7053 * This attribute is only honored if {@link UsageMessageSpec#sortOptions()} is {@code false} for this command.*/
7054 public int order() { return order; }
7055
7056 /** Sets the position in the options list in the usage help message at which this group should be shown, and returns this builder. */
7057 public Builder order(int order) { this.order = order; return this; }
7058
7059 /** Returns the heading of this group, used when generating the usage documentation.
7060 * @see ArgGroup#heading() */
7061 public String heading() { return heading; }
7062
7063 /** Sets the heading of this group (may be {@code null}), used when generating the usage documentation.
7064 * @see ArgGroup#heading() */
7065 public Builder heading(String newValue) { this.heading = newValue; return this; }
7066
7067 /** Returns the heading key of this group, used to get the heading from a resource bundle.
7068 * @see ArgGroup#headingKey() */
7069 public String headingKey() { return headingKey; }
7070 /** Sets the heading key of this group, used to get the heading from a resource bundle.
7071 * @see ArgGroup#headingKey() */
7072 public Builder headingKey(String newValue) { this.headingKey = newValue; return this; }
7073
7074 /** Returns the type info for the annotated program element associated with this group.
7075 * @return type information that does not require {@code Class} objects and be constructed both at runtime and compile time
7076 */
7077 public ITypeInfo typeInfo() { return typeInfo; }
7078 /** Sets the type info for the annotated program element associated with this group, and returns this builder.
7079 * @param newValue type information that does not require {@code Class} objects and be constructed both at runtime and compile time
7080 */
7081 public Builder typeInfo(ITypeInfo newValue) { this.typeInfo = newValue; return this; }
7082
7083 /** Returns the {@link IGetter} that is responsible for supplying the value of the annotated program element associated with this group. */
7084 public IGetter getter() { return getter; }
7085 /** Sets the {@link IGetter} that is responsible for getting the value of the annotated program element associated with this group, and returns this builder. */
7086 public Builder getter(IGetter getter) { this.getter = getter; return this; }
7087
7088 /** Returns the {@link ISetter} that is responsible for modifying the value of the annotated program element associated with this group. */
7089 public ISetter setter() { return setter; }
7090 /** Sets the {@link ISetter} that is responsible for modifying the value of the annotated program element associated with this group, and returns this builder. */
7091 public Builder setter(ISetter setter) { this.setter = setter; return this; }
7092
7093 /** Returns the {@link IScope} that determines where the setter sets the value (or the getter gets the value) of the annotated program element associated with this group. */
7094 public IScope scope() { return scope; }
7095 /** Sets the {@link IScope} that targets where the setter sets the value of the annotated program element associated with this group, and returns this builder. */
7096 public Builder scope(IScope scope) { this.scope = scope; return this; }
7097
7098 /** Adds the specified argument to the list of options and positional parameters that depend on this group. */
7099 public Builder addArg(ArgSpec arg) { args.add(arg); return this; }
7100
7101 /** Returns the list of options and positional parameters that depend on this group.*/
7102 public List<ArgSpec> args() { return args; }
7103
7104 /** Adds the specified group to the list of subgroups that this group is composed of. */
7105 public Builder addSubgroup(ArgGroupSpec group) { subgroups.add(group); return this; }
7106
7107 /** Returns the list of subgroups that this group is composed of.*/
7108 public List<ArgGroupSpec> subgroups() { return subgroups; }
7109 }
7110 }
7111
7112 /** This class allows applications to specify a custom binding that will be invoked for unmatched arguments.
7113 * A binding can be created with a {@code ISetter} that consumes the unmatched arguments {@code String[]}, or with a
7114 * {@code IGetter} that produces a {@code Collection<String>} that the unmatched arguments can be added to.
7115 * @since 3.0 */
7116 public static class UnmatchedArgsBinding {
7117 private final IGetter getter;
7118 private final ISetter setter;
7119
7120 /** Creates a {@code UnmatchedArgsBinding} for a setter that consumes {@code String[]} objects.
7121 * @param setter consumes the String[] array with unmatched arguments. */
7122 public static UnmatchedArgsBinding forStringArrayConsumer(ISetter setter) { return new UnmatchedArgsBinding(null, setter); }
7123
7124 /** Creates a {@code UnmatchedArgsBinding} for a getter that produces a {@code Collection<String>} that the unmatched arguments can be added to.
7125 * @param getter supplies a {@code Collection<String>} that the unmatched arguments can be added to. */
7126 public static UnmatchedArgsBinding forStringCollectionSupplier(IGetter getter) { return new UnmatchedArgsBinding(getter, null); }
7127
7128 private UnmatchedArgsBinding(IGetter getter, ISetter setter) {
7129 if (getter == null && setter == null) { throw new IllegalArgumentException("Getter and setter cannot both be null"); }
7130 this.setter = setter;
7131 this.getter = getter;
7132 }
7133 /** Returns the getter responsible for producing a {@code Collection} that the unmatched arguments can be added to. */
7134 public IGetter getter() { return getter; }
7135 /** Returns the setter responsible for consuming the unmatched arguments. */
7136 public ISetter setter() { return setter; }
7137 void addAll(String[] unmatched) {
7138 if (setter != null) {
7139 try {
7140 setter.set(unmatched);
7141 } catch (Exception ex) {
7142 throw new PicocliException(String.format("Could not invoke setter (%s) with unmatched argument array '%s': %s", setter, Arrays.toString(unmatched), ex), ex);
7143 }
7144 }
7145 if (getter != null) {
7146 try {
7147 Collection<String> collection = getter.get();
7148 Assert.notNull(collection, "getter returned null Collection");
7149 collection.addAll(Arrays.asList(unmatched));
7150 } catch (Exception ex) {
7151 throw new PicocliException(String.format("Could not add unmatched argument array '%s' to collection returned by getter (%s): %s",
7152 Arrays.toString(unmatched), getter, ex), ex);
7153 }
7154 }
7155 }
7156 }
7157 /** Command method parameter, similar to java.lang.reflect.Parameter (not available before Java 8).
7158 * @since 4.0 */
7159 public static class MethodParam extends AccessibleObject {
7160 final Method method;
7161 final int paramIndex;
7162 final String name;
7163 int position;
7164
7165 public MethodParam(Method method, int paramIndex) {
7166 this.method = method;
7167 this.paramIndex = paramIndex;
7168 String tmp = "arg" + paramIndex;
7169 try {
7170 Method getParameters = Method.class.getMethod("getParameters");
7171 Object parameters = getParameters.invoke(method);
7172 Object parameter = Array.get(parameters, paramIndex);
7173 tmp = (String) Class.forName("java.lang.reflect.Parameter").getDeclaredMethod("getName").invoke(parameter);
7174 } catch (Exception ignored) {}
7175 this.name = tmp;
7176 }
7177 public Type getParameterizedType() { return method.getGenericParameterTypes()[paramIndex]; }
7178 public String getName() { return name; }
7179 public Class<?> getType() { return method.getParameterTypes()[paramIndex]; }
7180 public Method getDeclaringExecutable() { return method; }
7181 @Override public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
7182 for (Annotation annotation : getDeclaredAnnotations()) {
7183 if (annotationClass.isAssignableFrom(annotation.getClass())) { return annotationClass.cast(annotation); }
7184 }
7185 return null;
7186 }
7187 @Override public Annotation[] getDeclaredAnnotations() { return method.getParameterAnnotations()[paramIndex]; }
7188 @Override public void setAccessible(boolean flag) throws SecurityException { method.setAccessible(flag); }
7189 @Override public boolean isAccessible() throws SecurityException { return method.isAccessible(); }
7190 @Override public String toString() { return method.toString() + ":" + getName(); }
7191 }
7192
7193 /** Encapculates type information for an option or parameter to make this information available both at runtime
7194 * and at compile time (when {@code Class} values are not available).
7195 * Most of the methods in this interface (but not all!) are safe to use by annotation processors.
7196 * @since 4.0
7197 */
7198 public interface ITypeInfo {
7199 /** Returns {@code true} if {@link #getType()} is {@code boolean} or {@code java.lang.Boolean}. */
7200 boolean isBoolean();
7201 /** Returns {@code true} if {@link #getType()} is an array, map or collection. */
7202 boolean isMultiValue();
7203 boolean isArray();
7204 boolean isCollection();
7205 boolean isMap();
7206 /** Returns {@code true} if {@link #getType()} is an enum. */
7207 boolean isEnum();
7208 List<String> getEnumConstantNames();
7209 String getClassName();
7210 String getClassSimpleName();
7211 /** Returns type information of components or elements of a {@link #isMultiValue() multivalue} type. */
7212 List<ITypeInfo> getAuxiliaryTypeInfos();
7213 /** Returns the names of the type arguments if this is a generic type. For example, returns {@code ["java.lang.String"]} if this type is {@code List<String>}. */
7214 List<String> getActualGenericTypeArguments();
7215
7216 /** Returns the class that the option or parameter value should be converted to when matched on the command
7217 * line. This method is <em>not</em> safe for annotation processors to use.
7218 * @return the class that the option or parameter value should be converted to
7219 */
7220 Class<?> getType();
7221 /** Returns the component class of an array, or the parameter type of a generic Collection, or the parameter
7222 * types of the key and the value of a generic Map.
7223 * This method is <em>not</em> safe for annotation processors to use.
7224 * @return the component type or types of an array, Collection or Map type
7225 */
7226 Class<?>[] getAuxiliaryTypes();
7227 }
7228 static class RuntimeTypeInfo implements ITypeInfo {
7229 private final Class<?> type;
7230 private final Class<?>[] auxiliaryTypes;
7231 private final List<String> actualGenericTypeArguments;
7232
7233 RuntimeTypeInfo(Class<?> type, Class<?>[] auxiliaryTypes, List<String> actualGenericTypeArguments) {
7234 this.type = Assert.notNull(type, "type");
7235 this.auxiliaryTypes = Assert.notNull(auxiliaryTypes, "auxiliaryTypes").clone();
7236 this.actualGenericTypeArguments = actualGenericTypeArguments == null ? Collections.<String>emptyList() : Collections.unmodifiableList(new ArrayList<String>(actualGenericTypeArguments));
7237 }
7238
7239 static ITypeInfo createForAuxType(Class<?> type) {
7240 return create(type, new Class[0], (Type) null, Range.valueOf("1"), String.class);
7241 }
7242 public static ITypeInfo create(Class<?> type,
7243 Class<?>[] annotationTypes,
7244 Type genericType,
7245 Range arity,
7246 Class<?> defaultType) {
7247 Class<?>[] auxiliaryTypes = RuntimeTypeInfo.inferTypes(type, annotationTypes, genericType);
7248 List<String> actualGenericTypeArguments = new ArrayList<String>();
7249 if (genericType instanceof ParameterizedType) {
7250 Class[] declaredTypeParameters = extractTypeParameters((ParameterizedType) genericType);
7251 for (Class<?> c : declaredTypeParameters) { actualGenericTypeArguments.add(c.getName()); }
7252 }
7253 return create(type, auxiliaryTypes, actualGenericTypeArguments, arity, defaultType);
7254 }
7255
7256 public static ITypeInfo create(Class<?> type, Class<?>[] auxiliaryTypes, List<String> actualGenericTypeArguments, Range arity, Class<?> defaultType) {
7257 if (type == null) {
7258 if (auxiliaryTypes == null || auxiliaryTypes.length == 0) {
7259 if (arity.isVariable || arity.max > 1) {
7260 type = String[].class;
7261 } else if (arity.max == 1) {
7262 type = String.class;
7263 } else {
7264 type = defaultType;
7265 }
7266 } else {
7267 type = auxiliaryTypes[0];
7268 }
7269 }
7270 if (auxiliaryTypes == null || auxiliaryTypes.length == 0) {
7271 if (type.isArray()) {
7272 auxiliaryTypes = new Class<?>[] {type.getComponentType()};
7273 } else if (Collection.class.isAssignableFrom(type)) { // type is a collection but element type is unspecified
7274 auxiliaryTypes = new Class<?>[] {String.class}; // use String elements
7275 } else if (Map.class.isAssignableFrom(type)) { // type is a map but element type is unspecified
7276 auxiliaryTypes = new Class<?>[] {String.class, String.class}; // use String keys and String values
7277 } else {
7278 auxiliaryTypes = new Class<?>[] {type};
7279 }
7280 }
7281 return new RuntimeTypeInfo(type, auxiliaryTypes, actualGenericTypeArguments);
7282 }
7283 static Class<?>[] inferTypes(Class<?> propertyType, Class<?>[] annotationTypes, Type genericType) {
7284 if (annotationTypes != null && annotationTypes.length > 0) { return annotationTypes; }
7285 if (propertyType.isArray()) { return new Class<?>[] { propertyType.getComponentType() }; }
7286 if (CommandLine.isMultiValue(propertyType)) {
7287 if (genericType instanceof ParameterizedType) {// e.g. Map<Long, ? extends Number>
7288 return extractTypeParameters((ParameterizedType) genericType);
7289 }
7290 return new Class<?>[] {String.class, String.class}; // field is multi-value but not ParameterizedType
7291 }
7292 return new Class<?>[] {propertyType}; // not a multi-value field
7293 }
7294
7295 static Class<?>[] extractTypeParameters(ParameterizedType genericType) {
7296 ParameterizedType parameterizedType = genericType;
7297 Type[] paramTypes = parameterizedType.getActualTypeArguments(); // e.g. ? extends Number
7298 Class<?>[] result = new Class<?>[paramTypes.length];
7299 for (int i = 0; i < paramTypes.length; i++) {
7300 if (paramTypes[i] instanceof Class) { result[i] = (Class<?>) paramTypes[i]; continue; } // e.g. Long
7301 if (paramTypes[i] instanceof WildcardType) { // e.g. ? extends Number
7302 WildcardType wildcardType = (WildcardType) paramTypes[i];
7303 Type[] lower = wildcardType.getLowerBounds(); // e.g. []
7304 if (lower.length > 0 && lower[0] instanceof Class) { result[i] = (Class<?>) lower[0]; continue; }
7305 Type[] upper = wildcardType.getUpperBounds(); // e.g. Number
7306 if (upper.length > 0 && upper[0] instanceof Class) { result[i] = (Class<?>) upper[0]; continue; }
7307 }
7308 Arrays.fill(result, String.class); return result; // too convoluted generic type, giving up
7309 }
7310 return result; // we inferred all types from ParameterizedType
7311 }
7312
7313 public boolean isBoolean() { return auxiliaryTypes[0] == boolean.class || auxiliaryTypes[0] == Boolean.class; }
7314 public boolean isMultiValue() { return CommandLine.isMultiValue(type); }
7315 public boolean isArray() { return type.isArray(); }
7316 public boolean isCollection() { return Collection.class.isAssignableFrom(type); }
7317 public boolean isMap() { return Map.class.isAssignableFrom(type); }
7318 public boolean isEnum() { return auxiliaryTypes[0].isEnum(); }
7319 public String getClassName() { return type.getName(); }
7320 public String getClassSimpleName() { return type.getSimpleName(); }
7321 public Class<?> getType() { return type; }
7322 public Class<?>[] getAuxiliaryTypes() { return auxiliaryTypes; }
7323 public List<String> getActualGenericTypeArguments() { return actualGenericTypeArguments; }
7324
7325 public List<ITypeInfo> getAuxiliaryTypeInfos() {
7326 List<ITypeInfo> result = new ArrayList<ITypeInfo>();
7327 for (Class<?> c : auxiliaryTypes) { result.add(createForAuxType(c)); }
7328 return result;
7329 }
7330 public List<String> getEnumConstantNames() {
7331 if (!isEnum()) { return Collections.emptyList(); }
7332 List<String> result = new ArrayList<String>();
7333 for (Object c : auxiliaryTypes[0].getEnumConstants()) { result.add(c.toString()); }
7334 return result;
7335 }
7336
7337 public boolean equals(Object obj) {
7338 if (obj == this) { return true; }
7339 if (!(obj instanceof RuntimeTypeInfo)) { return false; }
7340 RuntimeTypeInfo other = (RuntimeTypeInfo) obj;
7341 return Arrays.equals(other.auxiliaryTypes, auxiliaryTypes) && type.equals(other.type);
7342 }
7343 public int hashCode() {
7344 return Arrays.hashCode(auxiliaryTypes) + 37 * Assert.hashCode(type);
7345 }
7346 public String toString() {
7347 return String.format("RuntimeTypeInfo(%s, aux=%s, collection=%s, map=%s)",
7348 type.getCanonicalName(), Arrays.toString(auxiliaryTypes), isCollection(), isMap());
7349 }
7350 }
7351 /** Internal interface to allow annotation processors to construct a command model at compile time.
7352 * @since 4.0 */
7353 public interface IAnnotatedElement {
7354 Object userObject();
7355 boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
7356 <T extends Annotation> T getAnnotation(Class<T> annotationClass);
7357 String getName();
7358 String getMixinName();
7359 boolean isArgSpec();
7360 boolean isOption();
7361 boolean isParameter();
7362 boolean isArgGroup();
7363 boolean isMixin();
7364 boolean isUnmatched();
7365 boolean isInjectSpec();
7366 boolean isMultiValue();
7367 boolean hasInitialValue();
7368 boolean isMethodParameter();
7369 int getMethodParamPosition();
7370 CommandLine.Model.IScope scope();
7371 CommandLine.Model.IGetter getter();
7372 CommandLine.Model.ISetter setter();
7373 ITypeInfo getTypeInfo();
7374 String getToString();
7375 }
7376
7377 static class TypedMember implements IAnnotatedElement {
7378 final AccessibleObject accessible;
7379 final String name;
7380 final ITypeInfo typeInfo;
7381 boolean hasInitialValue;
7382 private IScope scope;
7383 private IGetter getter;
7384 private ISetter setter;
7385 static TypedMember createIfAnnotated(Field field, IScope scope) {
7386 return isAnnotated(field) ? new TypedMember(field, scope) : null;
7387 }
7388 static boolean isAnnotated(AnnotatedElement e) {
7389 return false
7390 || e.isAnnotationPresent(Option.class)
7391 || e.isAnnotationPresent(Parameters.class)
7392 || e.isAnnotationPresent(ArgGroup.class)
7393 || e.isAnnotationPresent(Unmatched.class)
7394 || e.isAnnotationPresent(Mixin.class)
7395 || e.isAnnotationPresent(Spec.class)
7396 || e.isAnnotationPresent(ParentCommand.class);
7397 }
7398 TypedMember(Field field) {
7399 accessible = Assert.notNull(field, "field");
7400 accessible.setAccessible(true);
7401 name = field.getName();
7402 typeInfo = createTypeInfo(field.getType(), field.getGenericType());
7403 hasInitialValue = true;
7404 }
7405 private TypedMember(Field field, IScope scope) {
7406 this(field);
7407 Object obj = ObjectScope.tryGet(scope);
7408 if (obj != null && Proxy.isProxyClass(obj.getClass())) {
7409 throw new InitializationException("Invalid picocli annotation on interface field");
7410 }
7411 FieldBinding binding = new FieldBinding(scope, field);
7412 getter = binding; setter = binding;
7413 this.scope = scope;
7414 hasInitialValue &= obj != null ;
7415 }
7416 static TypedMember createIfAnnotated(Method method, IScope scope, CommandSpec spec) {
7417 return isAnnotated(method) ? new TypedMember(method, scope, spec) : null;
7418 }
7419 private TypedMember(Method method, IScope scope, CommandSpec spec) {
7420 accessible = Assert.notNull(method, "method");
7421 accessible.setAccessible(true);
7422 name = propertyName(method.getName());
7423 Class<?>[] parameterTypes = method.getParameterTypes();
7424 boolean isGetter = parameterTypes.length == 0 && method.getReturnType() != Void.TYPE && method.getReturnType() != Void.class;
7425 boolean isSetter = parameterTypes.length > 0;
7426 if (isSetter == isGetter) { throw new InitializationException("Invalid method, must be either getter or setter: " + method); }
7427 if (isGetter) {
7428 hasInitialValue = true;
7429 typeInfo = createTypeInfo(method.getReturnType(), method.getGenericReturnType());
7430 Object proxy = ObjectScope.tryGet(scope);
7431 if (Proxy.isProxyClass(proxy.getClass())) {
7432 PicocliInvocationHandler handler = (PicocliInvocationHandler) Proxy.getInvocationHandler(proxy);
7433 PicocliInvocationHandler.ProxyBinding binding = handler.new ProxyBinding(method);
7434 getter = binding; setter = binding;
7435 initializeInitialValue(method);
7436 } else {
7437 //throw new IllegalArgumentException("Getter method but not a proxy: " + scope + ": " + method);
7438 MethodBinding binding = new MethodBinding(scope, method, spec);
7439 getter = binding; setter = binding;
7440 }
7441 } else {
7442 hasInitialValue = false;
7443 typeInfo = createTypeInfo(parameterTypes[0], method.getGenericParameterTypes()[0]);
7444 MethodBinding binding = new MethodBinding(scope, method, spec);
7445 getter = binding; setter = binding;
7446 }
7447 }
7448 TypedMember(MethodParam param, IScope scope) {
7449 accessible = Assert.notNull(param, "command method parameter");
7450 accessible.setAccessible(true);
7451 name = param.getName();
7452 typeInfo = createTypeInfo(param.getType(), param.getParameterizedType());
7453 // bind parameter
7454 ObjectBinding binding = new ObjectBinding();
7455 getter = binding; setter = binding;
7456 initializeInitialValue(param);
7457 hasInitialValue = true;
7458 }
7459
7460 private ITypeInfo createTypeInfo(Class<?> type, Type genericType) {
7461 Range arity = null;
7462 if (isOption()) { arity = Range.valueOf(getAnnotation(Option.class).arity()); }
7463 if (isParameter()) { arity = Range.valueOf(getAnnotation(Parameters.class).arity()); }
7464 if (arity == null || arity.isUnspecified) {
7465 if (isOption()) {
7466 arity = (type == null || isBoolean(type)) ? Range.valueOf("0") : Range.valueOf("1");
7467 } else {
7468 arity = Range.valueOf("1");
7469 }
7470 arity = arity.unspecified(true);
7471 }
7472 return RuntimeTypeInfo.create(type, annotationTypes(), genericType, arity, (isOption() ? boolean.class : String.class));
7473 }
7474
7475 private void initializeInitialValue(Object arg) {
7476 Class<?> type = typeInfo.getType();
7477 try {
7478 if (type == Boolean.TYPE ) { setter.set(false); }
7479 else if (type == Byte.TYPE ) { setter.set(Byte.valueOf((byte) 0)); }
7480 else if (type == Character.TYPE) { setter.set(Character.valueOf((char) 0)); }
7481 else if (type == Short.TYPE ) { setter.set(Short.valueOf((short) 0)); }
7482 else if (type == Integer.TYPE ) { setter.set(Integer.valueOf(0)); }
7483 else if (type == Long.TYPE ) { setter.set(Long.valueOf(0L)); }
7484 else if (type == Float.TYPE ) { setter.set(Float.valueOf(0f)); }
7485 else if (type == Double.TYPE ) { setter.set(Double.valueOf(0d)); }
7486 else { setter.set(null); }
7487 } catch (Exception ex) {
7488 throw new InitializationException("Could not set initial value for " + arg + ": " + ex.toString(), ex);
7489 }
7490 }
7491 public Object userObject() { return accessible; }
7492 public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) { return accessible.isAnnotationPresent(annotationClass); }
7493 public <T extends Annotation> T getAnnotation(Class<T> annotationClass) { return accessible.getAnnotation(annotationClass); }
7494 public String getName() { return name; }
7495 public boolean isArgSpec() { return isOption() || isParameter() || (isMethodParameter() && !isMixin()); }
7496 public boolean isOption() { return isAnnotationPresent(Option.class); }
7497 public boolean isParameter() { return isAnnotationPresent(Parameters.class); }
7498 public boolean isArgGroup() { return isAnnotationPresent(ArgGroup.class); }
7499 public boolean isMixin() { return isAnnotationPresent(Mixin.class); }
7500 public boolean isUnmatched() { return isAnnotationPresent(Unmatched.class); }
7501 public boolean isInjectSpec() { return isAnnotationPresent(Spec.class); }
7502 public boolean isMultiValue() { return CommandLine.isMultiValue(getType()); }
7503 public IScope scope() { return scope; }
7504 public IGetter getter() { return getter; }
7505 public ISetter setter() { return setter; }
7506 public ITypeInfo getTypeInfo() { return typeInfo; }
7507 public Class<?> getType() { return typeInfo.getType(); }
7508 public Class<?>[] getAuxiliaryTypes() { return typeInfo.getAuxiliaryTypes(); }
7509 private Class<?>[] annotationTypes() {
7510 if (isOption()) { return getAnnotation(Option.class).type(); }
7511 if (isParameter()) { return getAnnotation(Parameters.class).type(); }
7512 return new Class[0];
7513 }
7514 public String toString() { return accessible.toString(); }
7515 public String getToString() {
7516 if (isMixin()) { return abbreviate("mixin from member " + toGenericString()); }
7517 return (accessible instanceof Field ? "field " : accessible instanceof Method ? "method " : accessible.getClass().getSimpleName() + " ") + abbreviate(toGenericString());
7518 }
7519 public String toGenericString() { return accessible instanceof Field ? ((Field) accessible).toGenericString() : accessible instanceof Method ? ((Method) accessible).toGenericString() : ((MethodParam)accessible).toString(); }
7520 public boolean hasInitialValue() { return hasInitialValue; }
7521 public boolean isMethodParameter() { return accessible instanceof MethodParam; }
7522 public int getMethodParamPosition() { return isMethodParameter() ? ((MethodParam) accessible).position : -1; }
7523 public String getMixinName() {
7524 String annotationName = getAnnotation(Mixin.class).name();
7525 return empty(annotationName) ? getName() : annotationName;
7526 }
7527 static String propertyName(String methodName) {
7528 if (methodName.length() > 3 && (methodName.startsWith("get") || methodName.startsWith("set"))) { return decapitalize(methodName.substring(3)); }
7529 return decapitalize(methodName);
7530 }
7531 private static String decapitalize(String name) {
7532 if (name == null || name.length() == 0) { return name; }
7533 char[] chars = name.toCharArray();
7534 chars[0] = Character.toLowerCase(chars[0]);
7535 return new String(chars);
7536 }
7537 static String abbreviate(String text) {
7538 return text.replace("private ", "")
7539 .replace("protected ", "")
7540 .replace("public ", "")
7541 .replace("java.lang.", "");
7542 }
7543 }
7544
7545 /** Utility class for getting resource bundle strings.
7546 * Enhances the standard <a href="https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
7547 * with support for String arrays and qualified keys: keys that may or may not be prefixed with the fully qualified command name.
7548 * <p>Example properties resource bundle:</p><pre>
7549 * # Usage Help Message Sections
7550 * # ---------------------------
7551 * # Numbered resource keys can be used to create multi-line sections.
7552 * usage.headerHeading = This is my app. There are other apps like it but this one is mine.%n
7553 * usage.header = header first line
7554 * usage.header.0 = header second line
7555 * usage.descriptionHeading = Description:%n
7556 * usage.description.0 = first line
7557 * usage.description.1 = second line
7558 * usage.description.2 = third line
7559 * usage.synopsisHeading = Usage:\u0020
7560 * # Leading whitespace is removed by default. Start with \u0020 to keep the leading whitespace.
7561 * usage.customSynopsis.0 = Usage: ln [OPTION]... [-T] TARGET LINK_NAME (1st form)
7562 * usage.customSynopsis.1 = \u0020 or: ln [OPTION]... TARGET (2nd form)
7563 * usage.customSynopsis.2 = \u0020 or: ln [OPTION]... TARGET... DIRECTORY (3rd form)
7564 * # Headings can contain the %n character to create multi-line values.
7565 * usage.parameterListHeading = %nPositional parameters:%n
7566 * usage.optionListHeading = %nOptions:%n
7567 * usage.commandListHeading = %nCommands:%n
7568 * usage.footerHeading = Powered by picocli%n
7569 * usage.footer = footer
7570 *
7571 * # Option Descriptions
7572 * # -------------------
7573 * # Use numbered keys to create multi-line descriptions.
7574 * help = Show this help message and exit.
7575 * version = Print version information and exit.
7576 * </pre>
7577 * <p>Resources for multiple commands can be specified in a single ResourceBundle. Keys and their value can be
7578 * shared by multiple commands (so you don't need to repeat them for every command), but keys can be prefixed with
7579 * {@code fully qualified command name + "."} to specify different values for different commands.
7580 * The most specific key wins. For example: </p>
7581 * <pre>
7582 * jfrog.rt.usage.header = Artifactory commands
7583 * jfrog.rt.config.usage.header = Configure Artifactory details.
7584 * jfrog.rt.upload.usage.header = Upload files.
7585 *
7586 * jfrog.bt.usage.header = Bintray commands
7587 * jfrog.bt.config.usage.header = Configure Bintray details.
7588 * jfrog.bt.upload.usage.header = Upload files.
7589 *
7590 * # shared between all commands
7591 * usage.footerHeading = Environment Variables:
7592 * usage.footer.0 = footer line 0
7593 * usage.footer.1 = footer line 1
7594 * </pre>
7595 * @see Command#resourceBundle()
7596 * @see Option#descriptionKey()
7597 * @see OptionSpec#description()
7598 * @see PositionalParamSpec#description()
7599 * @see CommandSpec#qualifiedName(String)
7600 * @since 3.6 */
7601 public static class Messages {
7602 private final CommandSpec spec;
7603 private final String bundleBaseName;
7604 private final ResourceBundle rb;
7605 private final Set<String> keys;
7606 public Messages(CommandSpec spec, String baseName) {
7607 this(spec, baseName, createBundle(baseName));
7608 }
7609 public Messages(CommandSpec spec, ResourceBundle rb) {
7610 this(spec, extractName(rb), rb);
7611 }
7612 public Messages(CommandSpec spec, String baseName, ResourceBundle rb) {
7613 this.spec = Assert.notNull(spec, "CommandSpec");
7614 this.bundleBaseName = baseName;
7615 this.rb = rb;
7616 this.keys = keys(rb);
7617 }
7618 private static ResourceBundle createBundle(String baseName) {
7619 return ResourceBundle.getBundle(baseName);
7620 }
7621 private static String extractName(ResourceBundle rb) {
7622 try { // ResourceBundle.getBaseBundleName was introduced in Java 8
7623 return (String) ResourceBundle.class.getDeclaredMethod("getBaseBundleName").invoke(rb);
7624 } catch (Exception ignored) { return ""; }
7625 }
7626 private static Set<String> keys(ResourceBundle rb) {
7627 if (rb == null) { return Collections.emptySet(); }
7628 Set<String> keys = new LinkedHashSet<String>();
7629 for (Enumeration<String> k = rb.getKeys(); k.hasMoreElements(); keys.add(k.nextElement()));
7630 return keys;
7631 }
7632
7633 /** Returns a copy of the specified Messages object with the CommandSpec replaced by the specified one.
7634 * @param spec the CommandSpec of the returned Messages
7635 * @param original the Messages object whose ResourceBundle to reference
7636 * @return a Messages object with the specified CommandSpec and the ResourceBundle of the specified Messages object
7637 */
7638 public static Messages copy(CommandSpec spec, Messages original) {
7639 return original == null ? null : new Messages(spec, original.bundleBaseName, original.rb);
7640 }
7641 /** Returns {@code true} if the specified {@code Messages} is {@code null} or has a {@code null ResourceBundle}. */
7642 public static boolean empty(Messages messages) { return messages == null || messages.rb == null; }
7643
7644 /** Returns the String value found in the resource bundle for the specified key, or the specified default value if not found.
7645 * @param key unqualified resource bundle key. This method will first try to find a value by qualifying the key with the command's fully qualified name,
7646 * and if not found, it will try with the unqualified key.
7647 * @param defaultValue value to return if the resource bundle is null or empty, or if no value was found by the qualified or unqualified key
7648 * @return the String value found in the resource bundle for the specified key, or the specified default value
7649 */
7650 public String getString(String key, String defaultValue) {
7651 if (isEmpty()) { return defaultValue; }
7652 String cmd = spec.qualifiedName(".");
7653 if (keys.contains(cmd + "." + key)) { return rb.getString(cmd + "." + key); }
7654 if (keys.contains(key)) { return rb.getString(key); }
7655 return defaultValue;
7656 }
7657
7658 boolean isEmpty() { return rb == null || keys.isEmpty(); }
7659
7660 /** Returns the String array value found in the resource bundle for the specified key, or the specified default value if not found.
7661 * Multi-line strings can be specified in the resource bundle with {@code key.0}, {@code key.1}, {@code key.2}, etc.
7662 * @param key unqualified resource bundle key. This method will first try to find a value by qualifying the key with the command's fully qualified name,
7663 * and if not found, it will try with the unqualified key.
7664 * @param defaultValues value to return if the resource bundle is null or empty, or if no value was found by the qualified or unqualified key
7665 * @return the String array value found in the resource bundle for the specified key, or the specified default value
7666 */
7667 public String[] getStringArray(String key, String[] defaultValues) {
7668 if (isEmpty()) { return defaultValues; }
7669 String cmd = spec.qualifiedName(".");
7670 List<String> result = addAllWithPrefix(rb, cmd + "." + key, keys, new ArrayList<String>());
7671 if (!result.isEmpty()) { return result.toArray(new String[0]); }
7672 addAllWithPrefix(rb, key, keys, result);
7673 return result.isEmpty() ? defaultValues : result.toArray(new String[0]);
7674 }
7675 private static List<String> addAllWithPrefix(ResourceBundle rb, String key, Set<String> keys, List<String> result) {
7676 if (keys.contains(key)) { result.add(rb.getString(key)); }
7677 for (int i = 0; true; i++) {
7678 String elementKey = key + "." + i;
7679 if (keys.contains(elementKey)) {
7680 result.add(rb.getString(elementKey));
7681 } else {
7682 return result;
7683 }
7684 }
7685 }
7686 /** Returns the ResourceBundle of the specified Messages object or {@code null} if the specified Messages object is {@code null}.
7687 * @since 4.0 */
7688 public static String resourceBundleBaseName(Messages messages) { return messages == null ? null : messages.resourceBundleBaseName(); }
7689 /** Returns the ResourceBundle of the specified Messages object or {@code null} if the specified Messages object is {@code null}. */
7690 public static ResourceBundle resourceBundle(Messages messages) { return messages == null ? null : messages.resourceBundle(); }
7691 /** Returns the base name of the ResourceBundle of this object or {@code null}.
7692 * @since 4.0 */
7693 public String resourceBundleBaseName() { return bundleBaseName; }
7694 /** Returns the ResourceBundle of this object or {@code null}. */
7695 public ResourceBundle resourceBundle() { return rb; }
7696 /** Returns the CommandSpec of this object, never {@code null}. */
7697 public CommandSpec commandSpec() { return spec; }
7698 }
7699 private static class CommandReflection {
7700 static ArgGroupSpec extractArgGroupSpec(IAnnotatedElement member, IFactory factory, CommandSpec commandSpec, boolean annotationsAreMandatory) throws Exception {
7701 Object instance = null;
7702 try { instance = member.getter().get(); } catch (Exception ignored) {}
7703 Class<?> cls = instance == null ? member.getTypeInfo().getType() : instance.getClass();
7704 Tracer t = new Tracer();
7705
7706 if (member.isMultiValue()) {
7707 cls = member.getTypeInfo().getAuxiliaryTypes()[0];
7708 }
7709 IScope scope = new ObjectScope(instance);
7710 ArgGroupSpec.Builder builder = ArgGroupSpec.builder(member);
7711 builder.updateArgGroupAttributes(member.getAnnotation(ArgGroup.class));
7712 if (member.isOption() || member.isParameter()) {
7713 if (member instanceof TypedMember) { validateArgSpecMember((TypedMember) member); }
7714 builder.addArg(buildArgForMember(member, factory));
7715 }
7716
7717 Stack<Class<?>> hierarchy = new Stack<Class<?>>();
7718 while (cls != null) { hierarchy.add(cls); cls = cls.getSuperclass(); }
7719 boolean hasArgAnnotation = false;
7720 while (!hierarchy.isEmpty()) {
7721 cls = hierarchy.pop();
7722 hasArgAnnotation |= initFromAnnotatedFields(scope, cls, commandSpec, builder, factory);
7723 }
7724 ArgGroupSpec result = builder.build();
7725 if (annotationsAreMandatory) {validateArgGroupSpec(result, hasArgAnnotation, cls.getName()); }
7726 return result;
7727 }
7728 static CommandSpec extractCommandSpec(Object command, IFactory factory, boolean annotationsAreMandatory) {
7729 Class<?> cls = command.getClass();
7730 Tracer t = new Tracer();
7731 t.debug("Creating CommandSpec for object of class %s with factory %s%n", cls.getName(), factory.getClass().getName());
7732 if (command instanceof CommandSpec) { return (CommandSpec) command; }
7733
7734 Object[] tmp = getOrCreateInstance(cls, command, factory, t);
7735 cls = (Class<?>) tmp[0];
7736 Object instance = tmp[1];
7737 String commandClassName = (String) tmp[2];
7738
7739 CommandSpec result = CommandSpec.wrapWithoutInspection(Assert.notNull(instance, "command"));
7740 ObjectScope scope = new ObjectScope(instance);
7741
7742 Stack<Class<?>> hierarchy = new Stack<Class<?>>();
7743 while (cls != null) { hierarchy.add(cls); cls = cls.getSuperclass(); }
7744 boolean hasCommandAnnotation = false;
7745 boolean mixinStandardHelpOptions = false;
7746 while (!hierarchy.isEmpty()) {
7747 cls = hierarchy.pop();
7748 Command cmd = cls.getAnnotation(Command.class);
7749 if (cmd != null) {
7750 result.updateCommandAttributes(cmd, factory);
7751 initSubcommands(cmd, cls, result, factory);
7752 // addGroups(cmd, groupBuilders); // TODO delete
7753 hasCommandAnnotation = true;
7754 }
7755 hasCommandAnnotation |= initFromAnnotatedFields(scope, cls, result, null, factory);
7756 if (cls.isAnnotationPresent(Command.class)) {
7757 mixinStandardHelpOptions |= cls.getAnnotation(Command.class).mixinStandardHelpOptions();
7758 }
7759 }
7760 result.mixinStandardHelpOptions(mixinStandardHelpOptions); //#377 Standard help options should be added last
7761 if (command instanceof Method) {
7762 Method method = (Method) command;
7763 t.debug("Using method %s as command %n", method);
7764 commandClassName = method.toString();
7765 Command cmd = method.getAnnotation(Command.class);
7766 result.updateCommandAttributes(cmd, factory);
7767 result.setAddMethodSubcommands(false); // method commands don't have method subcommands
7768 initSubcommands(cmd, null, result, factory);
7769 hasCommandAnnotation = true;
7770 result.mixinStandardHelpOptions(method.getAnnotation(Command.class).mixinStandardHelpOptions());
7771 initFromMethodParameters(scope, method, result, null, factory);
7772 // set command name to method name, unless @Command#name is set
7773 result.initName(((Method)command).getName());
7774 }
7775 result.updateArgSpecMessages();
7776
7777 if (annotationsAreMandatory) {validateCommandSpec(result, hasCommandAnnotation, commandClassName); }
7778 result.withToString(commandClassName).validate();
7779 return result;
7780 }
7781
7782 private static Object[] getOrCreateInstance(Class<?> cls, Object command, IFactory factory, Tracer t) {
7783 Object instance = command;
7784 String commandClassName = cls.getName();
7785 if (command instanceof Class) {
7786 cls = (Class) command;
7787 commandClassName = cls.getName();
7788 try {
7789 t.debug("Getting a %s instance from the factory%n", cls.getName());
7790 instance = DefaultFactory.create(factory, cls);
7791 cls = instance.getClass();
7792 commandClassName = cls.getName();
7793 t.debug("Factory returned a %s instance%n", commandClassName);
7794 } catch (InitializationException ex) {
7795 if (cls.isInterface()) {
7796 t.debug("%s. Creating Proxy for interface %s%n", ex.getCause(), cls.getName());
7797 instance = Proxy.newProxyInstance(cls.getClassLoader(), new Class<?>[]{cls}, new PicocliInvocationHandler());
7798 } else {
7799 throw ex;
7800 }
7801 }
7802 } else if (command instanceof Method) {
7803 cls = null; // don't mix in options/positional params from outer class @Command
7804 } else if (instance == null) {
7805 t.debug("Getting a %s instance from the factory%n", cls.getName());
7806 instance = DefaultFactory.create(factory, cls);
7807 t.debug("Factory returned a %s instance%n", instance.getClass().getName());
7808 }
7809 return new Object[] { cls, instance, commandClassName };
7810 }
7811 private static void initSubcommands(Command cmd, Class<?> cls, CommandSpec parent, IFactory factory) {
7812 for (Class<?> sub : cmd.subcommands()) {
7813 try {
7814 if (Help.class == sub) { throw new InitializationException(Help.class.getName() + " is not a valid subcommand. Did you mean " + HelpCommand.class.getName() + "?"); }
7815 CommandLine subcommandLine = toCommandLine(factory.create(sub), factory);
7816 parent.addSubcommand(subcommandName(sub), subcommandLine);
7817 initParentCommand(subcommandLine.getCommandSpec().userObject(), parent.userObject());
7818 }
7819 catch (InitializationException ex) { throw ex; }
7820 catch (NoSuchMethodException ex) { throw new InitializationException("Cannot instantiate subcommand " +
7821 sub.getName() + ": the class has no constructor", ex); }
7822 catch (Exception ex) {
7823 throw new InitializationException("Could not instantiate and add subcommand " +
7824 sub.getName() + ": " + ex, ex);
7825 }
7826 }
7827 if (cmd.addMethodSubcommands() && cls != null) {
7828 for (CommandLine sub : CommandSpec.createMethodSubcommands(cls, factory)) {
7829 parent.addSubcommand(sub.getCommandName(), sub);
7830 }
7831 }
7832 }
7833 static void initParentCommand(Object subcommand, Object parent) {
7834 if (subcommand == null) { return; }
7835 try {
7836 Class<?> cls = subcommand.getClass();
7837 while (cls != null) {
7838 for (Field f : cls.getDeclaredFields()) {
7839 if (f.isAnnotationPresent(ParentCommand.class)) {
7840 f.setAccessible(true);
7841 f.set(subcommand, parent);
7842 }
7843 }
7844 cls = cls.getSuperclass();
7845 }
7846 } catch (Exception ex) {
7847 throw new InitializationException("Unable to initialize @ParentCommand field: " + ex, ex);
7848 }
7849 }
7850 private static String subcommandName(Class<?> sub) {
7851 Command subCommand = sub.getAnnotation(Command.class);
7852 if (subCommand == null || Help.DEFAULT_COMMAND_NAME.equals(subCommand.name())) {
7853 throw new InitializationException("Subcommand " + sub.getName() +
7854 " is missing the mandatory @Command annotation with a 'name' attribute");
7855 }
7856 return subCommand.name();
7857 }
7858 private static boolean initFromAnnotatedFields(IScope scope, Class<?> cls, CommandSpec receiver, ArgGroupSpec.Builder groupBuilder, IFactory factory) {
7859 boolean result = false;
7860 for (Field field : cls.getDeclaredFields()) {
7861 result |= initFromAnnotatedTypedMembers(TypedMember.createIfAnnotated(field, scope), receiver, groupBuilder, factory);
7862 }
7863 for (Method method : cls.getDeclaredMethods()) {
7864 result |= initFromAnnotatedTypedMembers(TypedMember.createIfAnnotated(method, scope, receiver), receiver, groupBuilder, factory);
7865 }
7866 return result;
7867 }
7868 @SuppressWarnings("unchecked")
7869 private static boolean initFromAnnotatedTypedMembers(TypedMember member,
7870 CommandSpec commandSpec,
7871 ArgGroupSpec.Builder groupBuilder,
7872 IFactory factory) {
7873 boolean result = false;
7874 if (member == null) { return result; }
7875 if (member.isMixin()) {
7876 assertNoDuplicateAnnotations(member, Mixin.class, Option.class, Parameters.class, Unmatched.class, Spec.class, ArgGroup.class);
7877 if (groupBuilder != null) {
7878 throw new InitializationException("@Mixins are not supported on @ArgGroups");
7879 // TODO groupBuilder.addMixin(member.getMixinName(), buildMixinForMember(member, factory));
7880 } else {
7881 commandSpec.addMixin(member.getMixinName(), buildMixinForMember(member, factory));
7882 }
7883 result = true;
7884 }
7885 if (member.isArgGroup()) {
7886 assertNoDuplicateAnnotations(member, ArgGroup.class, Spec.class, Parameters.class, Option.class, Unmatched.class, Mixin.class);
7887 if (groupBuilder != null) {
7888 groupBuilder.addSubgroup(buildArgGroupForMember(member, factory, commandSpec));
7889 } else {
7890 commandSpec.addArgGroup(buildArgGroupForMember(member, factory, commandSpec));
7891 }
7892 return true;
7893 }
7894 if (member.isUnmatched()) {
7895 assertNoDuplicateAnnotations(member, Unmatched.class, Mixin.class, Option.class, Parameters.class, Spec.class, ArgGroup.class);
7896 if (groupBuilder != null) {
7897 // we don't support @Unmatched on @ArgGroup class members...
7898 throw new InitializationException("@Unmatched are not supported on @ArgGroups");
7899 } else {
7900 commandSpec.addUnmatchedArgsBinding(buildUnmatchedForMember(member));
7901 }
7902 }
7903 if (member.isArgSpec()) {
7904 validateArgSpecMember(member);
7905 if (groupBuilder != null) {
7906 groupBuilder.addArg(buildArgForMember(member, factory));
7907 } else {
7908 commandSpec.add(buildArgForMember(member, factory));
7909 }
7910 result = true;
7911 }
7912 if (member.isInjectSpec()) {
7913 validateInjectSpec(member);
7914 try { member.setter().set(commandSpec); } catch (Exception ex) { throw new InitializationException("Could not inject spec", ex); }
7915 }
7916 return result;
7917 }
7918 private static boolean initFromMethodParameters(IScope scope, Method method, CommandSpec receiver, ArgGroupSpec.Builder groupBuilder, IFactory factory) {
7919 boolean result = false;
7920 int optionCount = 0;
7921 for (int i = 0, count = method.getParameterTypes().length; i < count; i++) {
7922 MethodParam param = new MethodParam(method, i);
7923 if (param.isAnnotationPresent(Option.class) || param.isAnnotationPresent(Mixin.class)) {
7924 optionCount++;
7925 } else {
7926 param.position = i - optionCount;
7927 }
7928 result |= initFromAnnotatedTypedMembers(new TypedMember(param, scope), receiver, groupBuilder, factory);
7929 }
7930 return result;
7931 }
7932 @SuppressWarnings("unchecked")
7933 private static void validateArgSpecMember(TypedMember member) {
7934 if (!member.isArgSpec()) { throw new IllegalStateException("Bug: validateArgSpecMember() should only be called with an @Option or @Parameters member"); }
7935 if (member.isOption()) {
7936 assertNoDuplicateAnnotations(member, Option.class, Unmatched.class, Mixin.class, Parameters.class, Spec.class, ArgGroup.class);
7937 } else {
7938 assertNoDuplicateAnnotations(member, Parameters.class, Option.class, Unmatched.class, Mixin.class, Spec.class, ArgGroup.class);
7939 }
7940 if (!(member.accessible instanceof Field)) { return; }
7941 Field field = (Field) member.accessible;
7942 if (Modifier.isFinal(field.getModifiers()) && (field.getType().isPrimitive() || String.class.isAssignableFrom(field.getType()))) {
7943 throw new InitializationException("Constant (final) primitive and String fields like " + field + " cannot be used as " +
7944 (member.isOption() ? "an @Option" : "a @Parameter") + ": compile-time constant inlining may hide new values written to it.");
7945 }
7946 }
7947 private static void validateCommandSpec(CommandSpec result, boolean hasCommandAnnotation, String commandClassName) {
7948 if (!hasCommandAnnotation && result.positionalParameters.isEmpty() && result.optionsByNameMap.isEmpty() && result.unmatchedArgs.isEmpty()) {
7949 throw new InitializationException(commandClassName + " is not a command: it has no @Command, @Option, @Parameters or @Unmatched annotations");
7950 }
7951 }
7952 private static void validateArgGroupSpec(ArgGroupSpec result, boolean hasArgAnnotation, String className) {
7953 if (!hasArgAnnotation && result.args().isEmpty()) {
7954 throw new InitializationException(className + " is not a group: it has no @Option or @Parameters annotations");
7955 }
7956 }
7957 @SuppressWarnings("unchecked")
7958 private static void validateInjectSpec(TypedMember member) {
7959 if (!member.isInjectSpec()) { throw new IllegalStateException("Bug: validateInjectSpec() should only be called with @Spec members"); }
7960 assertNoDuplicateAnnotations(member, Spec.class, Parameters.class, Option.class, Unmatched.class, Mixin.class, ArgGroup.class);
7961 if (!CommandSpec.class.getName().equals(member.getTypeInfo().getClassName())) {
7962 throw new InitializationException("@picocli.CommandLine.Spec annotation is only supported on fields of type " + CommandSpec.class.getName());
7963 }
7964 }
7965 private static void assertNoDuplicateAnnotations(TypedMember member, Class<? extends Annotation> myAnnotation, Class<? extends Annotation>... forbidden) {
7966 for (Class<? extends Annotation> annotation : forbidden) {
7967 if (member.isAnnotationPresent(annotation)) {
7968 throw new DuplicateOptionAnnotationsException("A member cannot have both @" + myAnnotation.getSimpleName() + " and @" + annotation.getSimpleName() + " annotations, but '" + member + "' has both.");
7969 }
7970 }
7971 }
7972 private static CommandSpec buildMixinForMember(IAnnotatedElement member, IFactory factory) {
7973 try {
7974 Object userObject = member.getter().get();
7975 if (userObject == null) {
7976 userObject = factory.create(member.getTypeInfo().getType());
7977 member.setter().set(userObject);
7978 }
7979 CommandSpec result = CommandSpec.forAnnotatedObject(userObject, factory);
7980 return result.withToString(member.getToString());
7981 } catch (InitializationException ex) {
7982 throw ex;
7983 } catch (Exception ex) {
7984 throw new InitializationException("Could not access or modify mixin member " + member + ": " + ex, ex);
7985 }
7986 }
7987 private static ArgSpec buildArgForMember(IAnnotatedElement member, IFactory factory) {
7988 if (member.isOption()) { return OptionSpec.builder(member, factory).build(); }
7989 else if (member.isParameter()) { return PositionalParamSpec.builder(member, factory).build(); }
7990 else { return PositionalParamSpec.builder(member, factory).build(); }
7991 }
7992 private static ArgGroupSpec buildArgGroupForMember(IAnnotatedElement member, IFactory factory, CommandSpec commandSpec) {
7993 try {
7994 return extractArgGroupSpec(member, factory, commandSpec, true);
7995 } catch (InitializationException ex) {
7996 throw ex;
7997 } catch (Exception ex) {
7998 throw new InitializationException("Could not access or modify ArgGroup member " + member + ": " + ex, ex);
7999 }
8000 }
8001 private static UnmatchedArgsBinding buildUnmatchedForMember(final IAnnotatedElement member) {
8002 ITypeInfo info = member.getTypeInfo();
8003 if (!(info.getClassName().equals(String[].class.getName()) ||
8004 (info.isCollection() && info.getActualGenericTypeArguments().equals(Arrays.asList(String.class.getName()))))) {
8005 throw new InitializationException("Invalid type for " + member + ": must be either String[] or List<String>");
8006 }
8007 if (info.getClassName().equals(String[].class.getName())) {
8008 return UnmatchedArgsBinding.forStringArrayConsumer(member.setter());
8009 } else {
8010 return UnmatchedArgsBinding.forStringCollectionSupplier(new IGetter() {
8011 @SuppressWarnings("unchecked") public <T> T get() throws Exception {
8012 List<String> result = (List<String>) member.getter().get();
8013 if (result == null) {
8014 result = new ArrayList<String>();
8015 member.setter().set(result);
8016 }
8017 return (T) result;
8018 }
8019 });
8020 }
8021 }
8022 }
8023
8024 static class FieldBinding implements IGetter, ISetter {
8025 private final IScope scope;
8026 private final Field field;
8027 private static IScope asScope(Object scope) { return scope instanceof IScope ? ((IScope) scope) : new ObjectScope(scope); }
8028 FieldBinding(Object scope, Field field) { this(asScope(scope), field); }
8029 FieldBinding(IScope scope, Field field) { this.scope = scope; this.field = field; }
8030 public <T> T get() throws PicocliException {
8031 Object obj = null;
8032 try { obj = scope.get(); }
8033 catch (Exception ex) { throw new PicocliException("Could not get scope for field " + field, ex); }
8034 try {
8035 @SuppressWarnings("unchecked") T result = (T) field.get(obj);
8036 return result;
8037 } catch (Exception ex) {
8038 throw new PicocliException("Could not get value for field " + field, ex);
8039 }
8040 }
8041 public <T> T set(T value) throws PicocliException {
8042 Object obj = null;
8043 try { obj = scope.get(); }
8044 catch (Exception ex) { throw new PicocliException("Could not get scope for field " + field, ex); }
8045 try {
8046 @SuppressWarnings("unchecked") T result = (T) field.get(obj);
8047 field.set(obj, value);
8048 return result;
8049 } catch (Exception ex) {
8050 throw new PicocliException("Could not set value for field " + field + " to " + value, ex);
8051 }
8052 }
8053 public String toString() {
8054 return String.format("%s(%s %s.%s)", getClass().getSimpleName(), field.getType().getName(),
8055 field.getDeclaringClass().getName(), field.getName());
8056 }
8057 }
8058 static class MethodBinding implements IGetter, ISetter {
8059 private final IScope scope;
8060 private final Method method;
8061 private final CommandSpec spec;
8062 private Object currentValue;
8063 MethodBinding(IScope scope, Method method, CommandSpec spec) {
8064 this.scope = scope;
8065 this.method = method;
8066 this.spec = spec;
8067 }
8068 @SuppressWarnings("unchecked") public <T> T get() { return (T) currentValue; }
8069 public <T> T set(T value) throws PicocliException {
8070 Object obj = null;
8071 try { obj = scope.get(); }
8072 catch (Exception ex) { throw new PicocliException("Could not get scope for method " + method, ex); }
8073 try {
8074 @SuppressWarnings("unchecked") T result = (T) currentValue;
8075 method.invoke(obj, value);
8076 currentValue = value;
8077 return result;
8078 } catch (InvocationTargetException ex) {
8079 if (ex.getCause() instanceof PicocliException) { throw (PicocliException) ex.getCause(); }
8080 throw createParameterException(value, ex.getCause());
8081 } catch (Exception ex) {
8082 throw createParameterException(value, ex);
8083 }
8084 }
8085 private ParameterException createParameterException(Object value, Throwable t) {
8086 CommandLine cmd = spec.commandLine() == null ? new CommandLine(spec) : spec.commandLine();
8087 return new ParameterException(cmd, "Could not invoke " + method + " with " + value, t);
8088 }
8089 public String toString() {
8090 return String.format("%s(%s)", getClass().getSimpleName(), method);
8091 }
8092 }
8093 private static class PicocliInvocationHandler implements InvocationHandler {
8094 final Map<String, Object> map = new HashMap<String, Object>();
8095 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
8096 return map.get(method.getName());
8097 }
8098 class ProxyBinding implements IGetter, ISetter {
8099 private final Method method;
8100 ProxyBinding(Method method) { this.method = Assert.notNull(method, "method"); }
8101 @SuppressWarnings("unchecked") public <T> T get() { return (T) map.get(method.getName()); }
8102 public <T> T set(T value) {
8103 T result = get();
8104 map.put(method.getName(), value);
8105 return result;
8106 }
8107 }
8108 }
8109 private static class ObjectBinding implements IGetter, ISetter {
8110 private Object value;
8111 @SuppressWarnings("unchecked") public <T> T get() { return (T) value; }
8112 public <T> T set(T value) {
8113 @SuppressWarnings("unchecked") T result = value;
8114 this.value = value;
8115 return result;
8116 }
8117 public String toString() {
8118 return String.format("%s(value=%s)", getClass().getSimpleName(), value);
8119 }
8120 }
8121 static class ObjectScope implements IScope {
8122 private Object value;
8123 public ObjectScope(Object value) { this.value = value; }
8124 @SuppressWarnings("unchecked") public <T> T get() { return (T) value; }
8125 @SuppressWarnings("unchecked") public <T> T set(T value) { T old = (T) this.value; this.value = value; return old; }
8126 public static Object tryGet(IScope scope) {
8127 try {
8128 return scope.get();
8129 } catch (Exception e) {
8130 throw new InitializationException("Could not get scope value", e);
8131 }
8132 }
8133 public String toString() { return String.format("Scope(value=%s)", value); }
8134 }
8135 }
8136
8137 /** Encapsulates the result of parsing an array of command line arguments.
8138 * @since 3.0 */
8139 public static class ParseResult {
8140 private final CommandSpec commandSpec;
8141 private final List<OptionSpec> matchedOptions;
8142 private final List<PositionalParamSpec> matchedUniquePositionals;
8143 private final List<String> originalArgs;
8144 private final List<String> unmatched;
8145 private final List<List<PositionalParamSpec>> matchedPositionalParams;
8146 private final List<Exception> errors;
8147 private final MatchedGroup matchedGroup;
8148 final List<Object> tentativeMatch;
8149
8150 private final ParseResult subcommand;
8151 private final boolean usageHelpRequested;
8152 private final boolean versionHelpRequested;
8153
8154 private ParseResult(ParseResult.Builder builder) {
8155 commandSpec = builder.commandSpec;
8156 subcommand = builder.subcommand;
8157 matchedOptions = new ArrayList<OptionSpec>(builder.options);
8158 unmatched = new ArrayList<String>(builder.unmatched);
8159 originalArgs = new ArrayList<String>(builder.originalArgList);
8160 matchedUniquePositionals = new ArrayList<PositionalParamSpec>(builder.positionals);
8161 matchedPositionalParams = new ArrayList<List<PositionalParamSpec>>(builder.positionalParams);
8162 errors = new ArrayList<Exception>(builder.errors);
8163 usageHelpRequested = builder.usageHelpRequested;
8164 versionHelpRequested = builder.versionHelpRequested;
8165 tentativeMatch = builder.nowProcessing;
8166 matchedGroup = builder.matchedGroup.trim();
8167 }
8168 /** Creates and returns a new {@code ParseResult.Builder} for the specified command spec. */
8169 public static Builder builder(CommandSpec commandSpec) { return new Builder(commandSpec); }
8170
8171 /**
8172 * Returns the matches for the specified argument group.
8173 * @since 4.0 */
8174 public List<MatchedGroup> findMatchedGroup(ArgGroupSpec group) {
8175 return matchedGroup.findMatchedGroup(group, new ArrayList<MatchedGroup>());
8176 }
8177
8178 /**
8179 * Returns the top-level container for the {@link ArgGroupSpec ArgGroupSpec} match or matches found.
8180 * <p>
8181 * If the user input was a valid combination of group arguments, the returned list should contain a single
8182 * {@linkplain MatchedGroupMultiple multiple}. Details of the {@linkplain MatchedGroup matched groups} encountered
8183 * on the command line can be obtained via its {@link MatchedGroupMultiple#matchedSubgroups() matchedSubgroups()} method.
8184 * The top-level multiple returned by this method contains no {@linkplain MatchedGroupMultiple#matchedValues(ArgSpec) matched arguments}.
8185 * </p><p>
8186 * If the returned list contains more than one {@linkplain MatchedGroupMultiple multiple}, the user input was invalid:
8187 * the maximum {@linkplain ArgGroup#multiplicity() multiplicity} of a group was exceeded, and the parser created an extra
8188 * {@code multiple} to capture the values. Usually this results in a {@link ParameterException ParameterException}
8189 * being thrown by the {@code parse} method, unless the parser is configured to {@linkplain ParserSpec#collectErrors() collect errors}.
8190 * </p>
8191 * @since 4.0 */
8192 public List<MatchedGroupMultiple> getMatchedGroupMultiples() {
8193 return matchedGroup.multiples();
8194 }
8195 /** Returns the option with the specified short name, or {@code null} if no option with that name was matched
8196 * on the command line.
8197 * <p>Use {@link OptionSpec#getValue() getValue} on the returned {@code OptionSpec} to get the matched value (or values),
8198 * converted to the type of the option. Alternatively, use {@link OptionSpec#stringValues() stringValues}
8199 * to get the matched String values after they were {@linkplain OptionSpec#splitRegex() split} into parts, or
8200 * {@link OptionSpec#originalStringValues() originalStringValues} to get the original String values that were
8201 * matched on the command line, before any processing.
8202 * </p><p>To get the {@linkplain OptionSpec#defaultValue() default value} of an option that was
8203 * {@linkplain #hasMatchedOption(char) <em>not</em> matched} on the command line, use
8204 * {@code parseResult.commandSpec().findOption(shortName).getValue()}. </p>
8205 * @see CommandSpec#findOption(char) */
8206 public OptionSpec matchedOption(char shortName) { return CommandSpec.findOption(shortName, matchedOptions); }
8207
8208 /** Returns the option with the specified name, or {@code null} if no option with that name was matched on the command line.
8209 * <p>Use {@link OptionSpec#getValue() getValue} on the returned {@code OptionSpec} to get the matched value (or values),
8210 * converted to the type of the option. Alternatively, use {@link OptionSpec#stringValues() stringValues}
8211 * to get the matched String values after they were {@linkplain OptionSpec#splitRegex() split} into parts, or
8212 * {@link OptionSpec#originalStringValues() originalStringValues} to get the original String values that were
8213 * matched on the command line, before any processing.
8214 * </p><p>To get the {@linkplain OptionSpec#defaultValue() default value} of an option that was
8215 * {@linkplain #hasMatchedOption(String) <em>not</em> matched} on the command line, use
8216 * {@code parseResult.commandSpec().findOption(String).getValue()}. </p>
8217 * @see CommandSpec#findOption(String)
8218 * @param name used to search the matched options. May be an alias of the option name that was actually specified on the command line.
8219 * The specified name may include option name prefix characters or not. */
8220 public OptionSpec matchedOption(String name) { return CommandSpec.findOption(name, matchedOptions); }
8221
8222 /** Returns the first {@code PositionalParamSpec} that matched an argument at the specified position, or {@code null} if no positional parameters were matched at that position. */
8223 public PositionalParamSpec matchedPositional(int position) {
8224 if (matchedPositionalParams.size() <= position || matchedPositionalParams.get(position).isEmpty()) { return null; }
8225 return matchedPositionalParams.get(position).get(0);
8226 }
8227
8228 /** Returns all {@code PositionalParamSpec} objects that matched an argument at the specified position, or an empty list if no positional parameters were matched at that position. */
8229 public List<PositionalParamSpec> matchedPositionals(int position) {
8230 if (matchedPositionalParams.size() <= position) { return Collections.emptyList(); }
8231 return matchedPositionalParams.get(position) == null ? Collections.<PositionalParamSpec>emptyList() : matchedPositionalParams.get(position);
8232 }
8233 /** Returns the {@code CommandSpec} for the matched command. */
8234 public CommandSpec commandSpec() { return commandSpec; }
8235
8236 /** Returns whether an option whose aliases include the specified short name was matched on the command line.
8237 * @param shortName used to search the matched options. May be an alias of the option name that was actually specified on the command line. */
8238 public boolean hasMatchedOption(char shortName) { return matchedOption(shortName) != null; }
8239 /** Returns whether an option whose aliases include the specified name was matched on the command line.
8240 * @param name used to search the matched options. May be an alias of the option name that was actually specified on the command line.
8241 * The specified name may include option name prefix characters or not. */
8242 public boolean hasMatchedOption(String name) { return matchedOption(name) != null; }
8243 /** Returns whether the specified option was matched on the command line. */
8244 public boolean hasMatchedOption(OptionSpec option) { return matchedOptions.contains(option); }
8245
8246 /** Returns whether a positional parameter was matched at the specified position. */
8247 public boolean hasMatchedPositional(int position) { return matchedPositional(position) != null; }
8248 /** Returns whether the specified positional parameter was matched on the command line. */
8249 public boolean hasMatchedPositional(PositionalParamSpec positional) { return matchedUniquePositionals.contains(positional); }
8250
8251 /** Returns a list of matched options, in the order they were found on the command line. */
8252 public List<OptionSpec> matchedOptions() { return Collections.unmodifiableList(matchedOptions); }
8253
8254 /** Returns a list of matched positional parameters. */
8255 public List<PositionalParamSpec> matchedPositionals() { return Collections.unmodifiableList(matchedUniquePositionals); }
8256
8257 /** Returns a list of command line arguments that did not match any options or positional parameters. */
8258 public List<String> unmatched() { return Collections.unmodifiableList(unmatched); }
8259
8260 /** Returns the command line arguments that were parsed. */
8261 public List<String> originalArgs() { return Collections.unmodifiableList(originalArgs); }
8262
8263 /** If {@link ParserSpec#collectErrors} is {@code true}, returns the list of exceptions that were encountered during parsing, otherwise, returns an empty list.
8264 * @since 3.2 */
8265 public List<Exception> errors() { return Collections.unmodifiableList(errors); }
8266
8267 /** Returns the command line argument value of the option with the specified name, converted to the {@linkplain OptionSpec#type() type} of the option, or the specified default value if no option with the specified name was matched. */
8268 public <T> T matchedOptionValue(char shortName, T defaultValue) { return matchedOptionValue(matchedOption(shortName), defaultValue); }
8269 /** Returns the command line argument value of the option with the specified name, converted to the {@linkplain OptionSpec#type() type} of the option, or the specified default value if no option with the specified name was matched. */
8270 public <T> T matchedOptionValue(String name, T defaultValue) { return matchedOptionValue(matchedOption(name), defaultValue); }
8271 /** Returns the command line argument value of the specified option, converted to the {@linkplain OptionSpec#type() type} of the option, or the specified default value if the specified option is {@code null}. */
8272 @SuppressWarnings("unchecked")
8273 private <T> T matchedOptionValue(OptionSpec option, T defaultValue) { return option == null ? defaultValue : (T) option.getValue(); }
8274
8275 /** Returns the command line argument value of the positional parameter at the specified position, converted to the {@linkplain PositionalParamSpec#type() type} of the positional parameter, or the specified default value if no positional parameter was matched at that position. */
8276 public <T> T matchedPositionalValue(int position, T defaultValue) { return matchedPositionalValue(matchedPositional(position), defaultValue); }
8277 /** Returns the command line argument value of the specified positional parameter, converted to the {@linkplain PositionalParamSpec#type() type} of the positional parameter, or the specified default value if the specified positional parameter is {@code null}. */
8278 @SuppressWarnings("unchecked")
8279 private <T> T matchedPositionalValue(PositionalParamSpec positional, T defaultValue) { return positional == null ? defaultValue : (T) positional.getValue(); }
8280
8281 /** Returns {@code true} if a subcommand was matched on the command line, {@code false} otherwise. */
8282 public boolean hasSubcommand() { return subcommand != null; }
8283
8284 /** Returns the {@code ParseResult} for the subcommand of this command that was matched on the command line, or {@code null} if no subcommand was matched. */
8285 public ParseResult subcommand() { return subcommand; }
8286
8287 /** Returns {@code true} if one of the options that was matched on the command line is a {@link OptionSpec#usageHelp() usageHelp} option. */
8288 public boolean isUsageHelpRequested() { return usageHelpRequested; }
8289
8290 /** Returns {@code true} if one of the options that was matched on the command line is a {@link OptionSpec#versionHelp() versionHelp} option. */
8291 public boolean isVersionHelpRequested() { return versionHelpRequested; }
8292
8293 /** Returns this {@code ParseResult} as a list of {@code CommandLine} objects, one for each matched command/subcommand.
8294 * For backwards compatibility with pre-3.0 methods. */
8295 public List<CommandLine> asCommandLineList() {
8296 List<CommandLine> result = new ArrayList<CommandLine>();
8297 ParseResult pr = this;
8298 while (pr != null) { result.add(pr.commandSpec().commandLine()); pr = pr.hasSubcommand() ? pr.subcommand() : null; }
8299 return result;
8300 }
8301
8302 /** Builds immutable {@code ParseResult} instances. */
8303 public static class Builder {
8304 private final CommandSpec commandSpec;
8305 private final Set<OptionSpec> options = new LinkedHashSet<OptionSpec>();
8306 private final Set<PositionalParamSpec> positionals = new LinkedHashSet<PositionalParamSpec>();
8307 private final List<String> unmatched = new ArrayList<String>();
8308 private final List<String> originalArgList = new ArrayList<String>();
8309 private final List<List<PositionalParamSpec>> positionalParams = new ArrayList<List<PositionalParamSpec>>();
8310 private ParseResult subcommand;
8311 private boolean usageHelpRequested;
8312 private boolean versionHelpRequested;
8313 boolean isInitializingDefaultValues;
8314 private List<Exception> errors = new ArrayList<Exception>(1);
8315 private List<Object> nowProcessing;
8316 private MatchedGroup matchedGroup = new MatchedGroup(null, null);
8317
8318 private Builder(CommandSpec spec) { commandSpec = Assert.notNull(spec, "commandSpec"); }
8319 /** Creates and returns a new {@code ParseResult} instance for this builder's configuration. */
8320 public ParseResult build() {
8321 return new ParseResult(this);
8322 }
8323
8324 private void nowProcessing(ArgSpec spec, Object value) {
8325 if (nowProcessing != null && !isInitializingDefaultValues) {
8326 nowProcessing.add(spec.isPositional() ? spec : value);
8327 }
8328 }
8329
8330 /** Adds the specified {@code OptionSpec} or {@code PositionalParamSpec} to the list of options and parameters
8331 * that were matched on the command line.
8332 * @param arg the matched {@code OptionSpec} or {@code PositionalParamSpec}
8333 * @param position the command line position at which the {@code PositionalParamSpec} was matched. Ignored for {@code OptionSpec}s.
8334 * @return this builder for method chaining */
8335 public Builder add(ArgSpec arg, int position) {
8336 if (arg.isOption()) {
8337 addOption((OptionSpec) arg);
8338 } else {
8339 addPositionalParam((PositionalParamSpec) arg, position);
8340 }
8341 afterMatchingGroupElement(arg, position);
8342 return this;
8343 }
8344
8345 /** Adds the specified {@code OptionSpec} to the list of options that were matched on the command line. */
8346 public Builder addOption(OptionSpec option) { if (!isInitializingDefaultValues) {options.add(option);} return this; }
8347 /** Adds the specified {@code PositionalParamSpec} to the list of parameters that were matched on the command line.
8348 * @param positionalParam the matched {@code PositionalParamSpec}
8349 * @param position the command line position at which the {@code PositionalParamSpec} was matched.
8350 * @return this builder for method chaining */
8351 public Builder addPositionalParam(PositionalParamSpec positionalParam, int position) {
8352 if (isInitializingDefaultValues) { return this; }
8353 positionals.add(positionalParam);
8354 while (positionalParams.size() <= position) { positionalParams.add(new ArrayList<PositionalParamSpec>()); }
8355 positionalParams.get(position).add(positionalParam);
8356 return this;
8357 }
8358 /** Adds the specified command line argument to the list of unmatched command line arguments. */
8359 public Builder addUnmatched(String arg) { unmatched.add(arg); return this; }
8360 /** Adds all elements of the specified command line arguments stack to the list of unmatched command line arguments. */
8361 public Builder addUnmatched(Stack<String> args) { while (!args.isEmpty()) { addUnmatched(args.pop()); } return this; }
8362 /** Sets the specified {@code ParseResult} for a subcommand that was matched on the command line. */
8363 public Builder subcommand(ParseResult subcommand) { this.subcommand = subcommand; return this; }
8364 /** Sets the specified command line arguments that were parsed. */
8365 public Builder originalArgs(String[] originalArgs) { originalArgList.addAll(Arrays.asList(originalArgs)); return this;}
8366
8367 void addStringValue (ArgSpec argSpec, String value) { if (!isInitializingDefaultValues) { argSpec.stringValues.add(value);} }
8368 void addOriginalStringValue(ArgSpec argSpec, String value) {
8369 if (!isInitializingDefaultValues) {
8370 argSpec.originalStringValues.add(value);
8371 if (argSpec.group() != null) {
8372 MatchedGroup matchedGroup = this.matchedGroup.findOrCreateMatchingGroup(argSpec, commandSpec.commandLine);
8373 matchedGroup.multiple().addOriginalStringValue(argSpec, value);
8374 }
8375 }
8376 }
8377
8378 void addTypedValues(ArgSpec argSpec, int position, Object typedValue) {
8379 if (!isInitializingDefaultValues) {
8380 argSpec.typedValues.add(typedValue);
8381 if (argSpec.group() == null) {
8382 argSpec.typedValueAtPosition.put(position, typedValue);
8383 } else {
8384 MatchedGroup matchedGroup = this.matchedGroup.findOrCreateMatchingGroup(argSpec, commandSpec.commandLine);
8385 matchedGroup.multiple().addMatchedValue(argSpec, position, typedValue, commandSpec.commandLine.tracer);
8386 }
8387 }
8388 }
8389
8390 public void addError(PicocliException ex) {
8391 errors.add(Assert.notNull(ex, "exception"));
8392 }
8393
8394 void beforeMatchingGroupElement(ArgSpec argSpec) throws Exception {
8395 ArgGroupSpec group = argSpec.group();
8396 if (group == null || isInitializingDefaultValues) { return; }
8397 MatchedGroup foundMatchedGroup = this.matchedGroup.findOrCreateMatchingGroup(argSpec, commandSpec.commandLine);
8398 if (foundMatchedGroup.multiple().matchedMinElements() && argSpec.required()) {
8399 // we need to create a new multiple; if maxMultiplicity has been reached, we need to add a new MatchedGroup.
8400 String elementDescription = ArgSpec.describe(argSpec, "=");
8401 Tracer tracer = commandSpec.commandLine.tracer;
8402 tracer.info("MatchedGroupMultiple %s is complete: its mandatory elements are all matched. (User object: %s.) %s is required in the group, so it starts a new MatchedGroupMultiple.%n", foundMatchedGroup.multiple(), foundMatchedGroup.group.userObject(), elementDescription);
8403 foundMatchedGroup.addMultiple(commandSpec.commandLine);
8404 }
8405 }
8406
8407 private void afterMatchingGroupElement(ArgSpec argSpec, int position) {
8408// ArgGroupSpec group = argSpec.group();
8409// if (group == null || isInitializingDefaultValues) { return; }
8410// MatchedGroup matchedGroup = this.matchedGroup.findOrCreateMatchingGroup(argSpec, commandSpec.commandLine);
8411// promotePartiallyMatchedGroupToMatched(group, matchedGroup, true);
8412 }
8413
8414 private void promotePartiallyMatchedGroupToMatched(ArgGroupSpec group, MatchedGroup matchedGroup, boolean allRequired) {
8415 if (!matchedGroup.matchedFully(allRequired)) { return; }
8416
8417 // FIXME: before promoting the child group, check to see if the parent is matched, given the child group
8418
8419 Tracer tracer = commandSpec.commandLine.tracer;
8420 if (matchedGroup.matchedMaxElements()) {
8421 tracer.info("Marking matched group %s as complete: max elements reached. User object: %s%n", matchedGroup, matchedGroup.group.userObject());
8422 matchedGroup.complete(commandSpec.commandLine());
8423 }
8424 }
8425 }
8426
8427 /** Provides information about an {@link ArgGroup} that was matched on the command line.
8428 * <p>
8429 * The {@code ParseResult} may have more than one {@code MatchedGroup} for an {@code ArgGroupSpec}, when the
8430 * group was matched more often than its maximum {@linkplain ArgGroup#multiplicity() multiplicity}.
8431 * This is not necessarily a problem: the parser will add a multiple to the {@linkplain MatchedGroup#parentMatchedGroup() parent matched group}
8432 * until the maximum multiplicity of the parent group is exceeded, in which case parser will add a multiple to the parent's parent group, etc.
8433 * </p><p>
8434 * Ultimately, as long as the {@link ParseResult#getMatchedGroupMultiples()} method does not return more than one multiple, the maximum number of elements is not exceeded.
8435 * </p>
8436 * @since 4.0 */
8437 public static class MatchedGroup {
8438 private final ArgGroupSpec group;
8439 private MatchedGroup parentMatchedGroup;
8440 private List<MatchedGroupMultiple> multiples = new ArrayList<MatchedGroupMultiple>();
8441
8442 MatchedGroup(ArgGroupSpec group, CommandLine cmd) { this.group = group; addMultiple(cmd);}
8443
8444 /** Returns the {@code ArgGroupSpec} whose matches are captured in this {@code MatchedGroup}. */
8445 public ArgGroupSpec group() { return group; }
8446 /** Returns the {@code MatchedGroup} of the parent {@code ArgGroupSpec}, or {@code null} if this group has no parent. */
8447 public MatchedGroup parentMatchedGroup() { return parentMatchedGroup; }
8448 /** Returns the list of {@code MatchedGroupMultiple} instances: {@code ArgGroupSpec}s with a multiplicity greater than one may be matched multiple times. */
8449 public List<MatchedGroupMultiple> multiples() { return Collections.unmodifiableList(multiples); }
8450
8451 void addMultiple(CommandLine commandLine) {
8452 Tracer tracer = commandLine == null ? new Tracer() : commandLine.tracer;
8453 if (group != null && isMaxMultiplicityReached()) {
8454 tracer.info("Completing MatchedGroup %s: max multiplicity is reached.%n", this);
8455 complete(commandLine);
8456 } else {
8457 if (group != null) {
8458 tracer.info("Adding multiple to MatchedGroup %s (group=%s %s).%n", this, group == null ? "?" : group.id(), group == null ? "ROOT" : group.synopsis());
8459 }
8460 multiples.add(new MatchedGroupMultiple(this));
8461 if (group == null) { return; }
8462 }
8463 group.initUserObject(commandLine);
8464 }
8465 void complete(CommandLine commandLine) {
8466 if (parentMatchedGroup == null) {
8467 addMultiple(commandLine); // we have no choice but to potentially exceed the max multiplicity of this group...
8468 } else {
8469 parentMatchedGroup.addMultiple(commandLine);
8470 }
8471 }
8472 /** Returns the "active" multiple of this MatchedGroup. */
8473 MatchedGroupMultiple multiple() { return multiples.get(multiples.size() - 1); }
8474 /** Returns {@code true} if no more {@code MatchedGroupMultiples} can be added to this {@code MatchedGroup}. Each multiple may be a complete or an incomplete match.*/
8475 boolean isMaxMultiplicityReached() { return multiples.size() >= group.multiplicity.max; }
8476 /** Returns {@code true} if this {@code MatchedGroup} has at least the minimum number of {@code MatchedGroupMultiples}. Each multiple may be a complete or an incomplete match. */
8477 boolean isMinMultiplicityReached() { return multiples.size() >= group.multiplicity.min; }
8478
8479 /** Returns {@code true} if the minimum number of multiples has been matched for the multiplicity of this group,
8480 * and each multiple has matched at least the {@linkplain MatchedGroupMultiple#matchedMinElements() minimum number of elements}.*/
8481 boolean matchedMinElements() { return matchedFully(false); }
8482 /** Returns {@code true} if the maximum number of multiples has been matched for the multiplicity of this group,
8483 * and the last multiple has {@linkplain MatchedGroupMultiple#matchedMaxElements() matched the maximum number of elements},
8484 * while all other multiples have matched at least the {@linkplain MatchedGroupMultiple#matchedMinElements() minimum number of elements}.*/
8485 boolean matchedMaxElements() { return matchedFully(true); }
8486 private boolean matchedFully(boolean allRequired) {
8487 for (MatchedGroupMultiple multiple : multiples) {
8488 boolean actuallyAllRequired = allRequired && multiple == multiple();
8489 if (!multiple.matchedFully(actuallyAllRequired)) { return false; }
8490 }
8491 return allRequired ? isMaxMultiplicityReached() : isMinMultiplicityReached();
8492 }
8493
8494 private MatchedGroup findOrCreateMatchingGroup(ArgSpec argSpec, CommandLine commandLine) {
8495 ArgGroupSpec searchGroup = Assert.notNull(argSpec.group(), "group for " + argSpec);
8496 MatchedGroup match = this;
8497 if (searchGroup == match.group()) { return match; }
8498 List<ArgGroupSpec> keys = new ArrayList<ArgGroupSpec>();
8499 while (searchGroup != null) {
8500 keys.add(searchGroup);
8501 searchGroup = searchGroup.parentGroup();
8502 }
8503 Collections.reverse(keys);
8504 for (ArgGroupSpec key : keys) {
8505 MatchedGroup sub = match.multiple().matchedSubgroups().get(key);
8506 if (sub == null) {
8507 sub = createMatchedGroup(key, match, commandLine);
8508 }
8509 match = sub;
8510 }
8511 return match;
8512 }
8513 private MatchedGroup createMatchedGroup(ArgGroupSpec group, MatchedGroup parent, CommandLine commandLine) {
8514 MatchedGroup result = new MatchedGroup(group, commandLine);
8515 result.parentMatchedGroup = parent;
8516 parent.multiple().matchedSubgroups.put(group, result);
8517 return result;
8518 }
8519 MatchedGroup trim() {
8520 for (Iterator<MatchedGroupMultiple> iter = multiples.iterator(); iter.hasNext(); ) {
8521 MatchedGroupMultiple multiple = iter.next();
8522 if (multiple.isEmpty()) { iter.remove(); }
8523 for (MatchedGroup sub : multiple.matchedSubgroups.values()) { sub.trim(); }
8524 }
8525 return this;
8526 }
8527
8528 List<MatchedGroup> findMatchedGroup(ArgGroupSpec group, List<MatchedGroup> result) {
8529 if (this.group == group) { result.add(this); return result; }
8530 for (MatchedGroupMultiple multiple : multiples()) {
8531 for (MatchedGroup mg : multiple.matchedSubgroups.values()) {
8532 mg.findMatchedGroup(group, result);
8533 }
8534 }
8535 return result;
8536 }
8537
8538 @Override public String toString() {
8539 return toString(new StringBuilder()).toString();
8540 }
8541
8542 private StringBuilder toString(StringBuilder result) {
8543 String prefix = result.length() == 0 ? "={" : "";
8544 String suffix = result.length() == 0 ? "}" : "";
8545 if (group != null && result.length() == 0) {
8546 result.append(group.synopsis());
8547 }
8548 result.append(prefix);
8549 String infix = "";
8550 for (MatchedGroupMultiple occurrence : multiples) {
8551 result.append(infix);
8552 occurrence.toString(result);
8553 infix = " ";
8554 }
8555 return result.append(suffix);
8556
8557 }
8558 }
8559
8560 /** A group's {@linkplain ArgGroup#multiplicity() multiplicity} specifies how many multiples of a group can/must
8561 * appear on the command line before a group is fully matched. This class models a single "multiple".
8562 * For example, this group: {@code (-a -b) (-a -b)} requires two multiples of its arguments to fully match.
8563 * @since 4.0
8564 */
8565 public static class MatchedGroupMultiple {
8566 int position;
8567 final MatchedGroup container;
8568
8569 Map<ArgGroupSpec, MatchedGroup> matchedSubgroups = new LinkedHashMap<ArgGroupSpec, MatchedGroup>(2); // preserve order: used in toString()
8570 Map<ArgSpec, List<Object>> matchedValues = new IdentityHashMap<ArgSpec, List<Object>>(); // identity map for performance
8571 Map<ArgSpec, List<String>> originalStringValues = new LinkedHashMap<ArgSpec, List<String>>(); // preserve order: used in toString()
8572 Map<ArgSpec, Map<Integer, List<Object>>> matchedValuesAtPosition = new IdentityHashMap<ArgSpec, Map<Integer, List<Object>>>();
8573
8574 MatchedGroupMultiple(MatchedGroup container) { this.container = container; }
8575
8576 /** Returns {@code true} if this multiple has no matched arguments and no matched subgroups. */
8577 public boolean isEmpty() { return originalStringValues.isEmpty() && matchedSubgroups.isEmpty(); }
8578 /** Returns the {@code ArgGroupSpec} of the container {@code MatchedGroup} of this multiple. */
8579 public ArgGroupSpec group() { return container.group; }
8580 /** Returns the container {@code MatchedGroup} of this multiple. */
8581 public MatchedGroup container() { return container; }
8582 /** Returns matches for the subgroups, if any. */
8583 public Map<ArgGroupSpec, MatchedGroup> matchedSubgroups() { return Collections.unmodifiableMap(matchedSubgroups); }
8584 int matchCount(ArgSpec argSpec) { return matchedValues.get(argSpec) == null ? 0 : matchedValues.get(argSpec).size(); }
8585 /** Returns the values matched for the specified argument, converted to the type of the argument. */
8586 public List<Object> matchedValues(ArgSpec argSpec) { return matchedValues.get(argSpec) == null ? Collections.emptyList() : Collections.unmodifiableList(matchedValues.get(argSpec)); }
8587 void addOriginalStringValue(ArgSpec argSpec, String value) {
8588 addValueToListInMap(originalStringValues, argSpec, value);
8589 }
8590 void addMatchedValue(ArgSpec argSpec, int matchPosition, Object stronglyTypedValue, Tracer tracer) {
8591 addValueToListInMap(matchedValues, argSpec, stronglyTypedValue);
8592
8593 Map<Integer, List<Object>> positionalValues = matchedValuesAtPosition.get(argSpec);
8594 if (positionalValues == null) {
8595 positionalValues = new TreeMap<Integer, List<Object>>();
8596 matchedValuesAtPosition.put(argSpec, positionalValues);
8597 }
8598 addValueToListInMap(positionalValues, matchPosition, stronglyTypedValue);
8599 }
8600 boolean hasMatchedValueAtPosition(ArgSpec arg, int position) { Map<Integer, List<Object>> atPos = matchedValuesAtPosition.get(arg); return atPos != null && atPos.containsKey(position); }
8601
8602 /** Returns {@code true} if the minimum number of elements have been matched for this multiple:
8603 * all required arguments have been matched, and for each subgroup,
8604 * the {@linkplain MatchedGroup#matchedMinElements() minimum number of elements have been matched}.*/
8605 boolean matchedMinElements() { return matchedFully(false); }
8606 /** Returns {@code true} if the maximum number of multiples has been matched for this multiple:
8607 * all arguments (required or not) have been matched, and for each subgroup,
8608 * the {@linkplain MatchedGroup#matchedMaxElements() maximum number of elements have been matched}.*/
8609 boolean matchedMaxElements() { return matchedFully(true); }
8610 private boolean matchedFully(boolean allRequired) {
8611 if (group().exclusive()) { return !matchedValues.isEmpty() || hasFullyMatchedSubgroup(allRequired); }
8612 for (ArgSpec arg : group().args()) {
8613 if (matchedValues.get(arg) == null && (arg.required() || allRequired)) { return false; }
8614 }
8615 for (ArgGroupSpec subgroup : group().subgroups()) {
8616 MatchedGroup matchedGroup = matchedSubgroups.get(subgroup);
8617 if (matchedGroup != null) {
8618 if (!matchedGroup.matchedFully(allRequired)) { return false; }
8619 } else {
8620 if (allRequired || subgroup.multiplicity().min > 0) { return false; }
8621 }
8622 }
8623 return true;
8624 }
8625 private boolean hasFullyMatchedSubgroup(boolean allRequired) {
8626 for (MatchedGroup sub : matchedSubgroups.values()) { if (sub.matchedFully(allRequired)) { return true; } }
8627 return false;
8628 }
8629 @Override public String toString() {
8630 return toString(new StringBuilder()).toString();
8631 }
8632
8633 private StringBuilder toString(StringBuilder result) {
8634 int originalLength = result.length();
8635 for (ArgSpec arg : originalStringValues.keySet()) {
8636 List<String> values = originalStringValues.get(arg);
8637 for (String value : values) {
8638 if (result.length() != originalLength) { result.append(" "); }
8639 result.append(ArgSpec.describe(arg, "=", value));
8640 }
8641 }
8642 for (MatchedGroup sub : matchedSubgroups.values()) {
8643 if (result.length() != originalLength) { result.append(" "); }
8644 if (originalLength == 0) {
8645 result.append(sub.toString()); // include synopsis
8646 } else {
8647 sub.toString(result); // without synopsis
8648 }
8649 }
8650 return result;
8651 }
8652 }
8653 }
8654 static <K, T> void addValueToListInMap(Map<K, List<T>> map, K key, T value) {
8655 List<T> values = map.get(key);
8656 if (values == null) { values = new ArrayList<T>(); map.put(key, values); }
8657 values.add(value);
8658 }
8659 static <T> List<T> flatList(Collection<? extends Collection<T>> collection) {
8660 List<T> result = new ArrayList<T>();
8661 for (Collection<T> sub : collection) { result.addAll(sub); }
8662 return result;
8663 }
8664 private enum LookBehind { SEPARATE, ATTACHED, ATTACHED_WITH_SEPARATOR;
8665 public boolean isAttached() { return this != LookBehind.SEPARATE; }
8666 }
8667 /**
8668 * Helper class responsible for processing command line arguments.
8669 */
8670 private class Interpreter {
8671 private final Map<Class<?>, ITypeConverter<?>> converterRegistry = new HashMap<Class<?>, ITypeConverter<?>>();
8672 private boolean isHelpRequested;
8673 private int position;
8674 private boolean endOfOptions;
8675 private ParseResult.Builder parseResultBuilder;
8676
8677 Interpreter() { registerBuiltInConverters(); }
8678
8679 private void registerBuiltInConverters() {
8680 converterRegistry.put(Object.class, new BuiltIn.StringConverter());
8681 converterRegistry.put(String.class, new BuiltIn.StringConverter());
8682 converterRegistry.put(StringBuilder.class, new BuiltIn.StringBuilderConverter());
8683 converterRegistry.put(CharSequence.class, new BuiltIn.CharSequenceConverter());
8684 converterRegistry.put(Byte.class, new BuiltIn.ByteConverter());
8685 converterRegistry.put(Byte.TYPE, new BuiltIn.ByteConverter());
8686 converterRegistry.put(Boolean.class, new BuiltIn.BooleanConverter());
8687 converterRegistry.put(Boolean.TYPE, new BuiltIn.BooleanConverter());
8688 converterRegistry.put(Character.class, new BuiltIn.CharacterConverter());
8689 converterRegistry.put(Character.TYPE, new BuiltIn.CharacterConverter());
8690 converterRegistry.put(Short.class, new BuiltIn.ShortConverter());
8691 converterRegistry.put(Short.TYPE, new BuiltIn.ShortConverter());
8692 converterRegistry.put(Integer.class, new BuiltIn.IntegerConverter());
8693 converterRegistry.put(Integer.TYPE, new BuiltIn.IntegerConverter());
8694 converterRegistry.put(Long.class, new BuiltIn.LongConverter());
8695 converterRegistry.put(Long.TYPE, new BuiltIn.LongConverter());
8696 converterRegistry.put(Float.class, new BuiltIn.FloatConverter());
8697 converterRegistry.put(Float.TYPE, new BuiltIn.FloatConverter());
8698 converterRegistry.put(Double.class, new BuiltIn.DoubleConverter());
8699 converterRegistry.put(Double.TYPE, new BuiltIn.DoubleConverter());
8700 converterRegistry.put(File.class, new BuiltIn.FileConverter());
8701 converterRegistry.put(URI.class, new BuiltIn.URIConverter());
8702 converterRegistry.put(URL.class, new BuiltIn.URLConverter());
8703 converterRegistry.put(Date.class, new BuiltIn.ISO8601DateConverter());
8704 converterRegistry.put(BigDecimal.class, new BuiltIn.BigDecimalConverter());
8705 converterRegistry.put(BigInteger.class, new BuiltIn.BigIntegerConverter());
8706 converterRegistry.put(Charset.class, new BuiltIn.CharsetConverter());
8707 converterRegistry.put(InetAddress.class, new BuiltIn.InetAddressConverter());
8708 converterRegistry.put(Pattern.class, new BuiltIn.PatternConverter());
8709 converterRegistry.put(UUID.class, new BuiltIn.UUIDConverter());
8710 converterRegistry.put(Currency.class, new BuiltIn.CurrencyConverter());
8711 converterRegistry.put(TimeZone.class, new BuiltIn.TimeZoneConverter());
8712 converterRegistry.put(ByteOrder.class, new BuiltIn.ByteOrderConverter());
8713 converterRegistry.put(Class.class, new BuiltIn.ClassConverter());
8714 converterRegistry.put(NetworkInterface.class, new BuiltIn.NetworkInterfaceConverter());
8715
8716 BuiltIn.ISO8601TimeConverter.registerIfAvailable(converterRegistry, tracer);
8717 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.sql.Connection", "java.sql.DriverManager","getConnection", String.class);
8718 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.sql.Driver", "java.sql.DriverManager","getDriver", String.class);
8719 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.sql.Timestamp", "java.sql.Timestamp","valueOf", String.class);
8720
8721 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.Duration", "parse", CharSequence.class);
8722 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.Instant", "parse", CharSequence.class);
8723 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.LocalDate", "parse", CharSequence.class);
8724 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.LocalDateTime", "parse", CharSequence.class);
8725 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.LocalTime", "parse", CharSequence.class);
8726 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.MonthDay", "parse", CharSequence.class);
8727 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.OffsetDateTime", "parse", CharSequence.class);
8728 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.OffsetTime", "parse", CharSequence.class);
8729 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.Period", "parse", CharSequence.class);
8730 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.Year", "parse", CharSequence.class);
8731 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.YearMonth", "parse", CharSequence.class);
8732 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.ZonedDateTime", "parse", CharSequence.class);
8733 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.ZoneId", "of", String.class);
8734 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.ZoneOffset", "of", String.class);
8735
8736 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.nio.file.Path", "java.nio.file.Paths", "get", String.class, String[].class);
8737 }
8738 private ParserSpec config() { return commandSpec.parser(); }
8739 /**
8740 * Entry point into parsing command line arguments.
8741 * @param args the command line arguments
8742 * @return a list with all commands and subcommands initialized by this method
8743 * @throws ParameterException if the specified command line arguments are invalid
8744 */
8745 List<CommandLine> parse(String... args) {
8746 Assert.notNull(args, "argument array");
8747 if (tracer.isInfo()) {tracer.info("Picocli version: %s%n", versionString());}
8748 if (tracer.isInfo()) {tracer.info("Parsing %d command line args %s%n", args.length, Arrays.toString(args));}
8749 if (tracer.isDebug()){tracer.debug("Parser configuration: %s%n", config());}
8750 if (tracer.isDebug()){tracer.debug("(ANSI is %s by default: isatty=%s, XTERM=%s, OSTYPE=%s, isWindows=%s, JansiConsoleInstalled=%s, ANSICON=%s, ConEmuANSI=%s, NO_COLOR=%s, CLICOLOR=%s, CLICOLOR_FORCE=%s)%n",
8751 Help.Ansi.ansiPossible() ? "enabled" : "disabled", Help.Ansi.isTTY(), System.getenv("XTERM"), System.getenv("OSTYPE"), Help.Ansi.isWindows(), Help.Ansi.isJansiConsoleInstalled(), System.getenv("ANSICON"), System.getenv("ConEmuANSI"), System.getenv("NO_COLOR"), System.getenv("CLICOLOR"), System.getenv("CLICOLOR_FORCE"));}
8752 List<String> expanded = new ArrayList<String>();
8753 for (String arg : args) { addOrExpand(arg, expanded, new LinkedHashSet<String>()); }
8754 Stack<String> arguments = new Stack<String>();
8755 arguments.addAll(reverseList(expanded));
8756 List<CommandLine> result = new ArrayList<CommandLine>();
8757 parse(result, arguments, args, new ArrayList<Object>());
8758 return result;
8759 }
8760
8761 private void addOrExpand(String arg, List<String> arguments, Set<String> visited) {
8762 if (config().expandAtFiles() && !arg.equals("@") && arg.startsWith("@")) {
8763 arg = arg.substring(1);
8764 if (arg.startsWith("@")) {
8765 if (tracer.isInfo()) { tracer.info("Not expanding @-escaped argument %s (trimmed leading '@' char)%n", arg); }
8766 } else {
8767 if (tracer.isInfo()) { tracer.info("Expanding argument file @%s%n", arg); }
8768 expandArgumentFile(arg, arguments, visited);
8769 return;
8770 }
8771 }
8772 arguments.add(arg);
8773 }
8774 private void expandArgumentFile(String fileName, List<String> arguments, Set<String> visited) {
8775 File file = new File(fileName);
8776 if (!file.canRead()) {
8777 if (tracer.isInfo()) {tracer.info("File %s does not exist or cannot be read; treating argument literally%n", fileName);}
8778 arguments.add("@" + fileName);
8779 } else if (visited.contains(file.getAbsolutePath())) {
8780 if (tracer.isInfo()) {tracer.info("Already visited file %s; ignoring...%n", file.getAbsolutePath());}
8781 } else {
8782 expandValidArgumentFile(fileName, file, arguments, visited);
8783 }
8784 }
8785 private void expandValidArgumentFile(String fileName, File file, List<String> arguments, Set<String> visited) {
8786 List<String> result = new ArrayList<String>();
8787 LineNumberReader reader = null;
8788 try {
8789 visited.add(file.getAbsolutePath());
8790 reader = new LineNumberReader(new FileReader(file));
8791 if (commandSpec.parser().useSimplifiedAtFiles()) {
8792 String token;
8793 while ((token = reader.readLine()) != null) {
8794 if (token.length() > 0 && !token.trim().startsWith(String.valueOf(commandSpec.parser().atFileCommentChar()))) {
8795 addOrExpand(token, result, visited);
8796 }
8797 }
8798 } else {
8799 StreamTokenizer tok = new StreamTokenizer(reader);
8800 tok.resetSyntax();
8801 tok.wordChars(' ', 255);
8802 tok.whitespaceChars(0, ' ');
8803 tok.quoteChar('"');
8804 tok.quoteChar('\'');
8805 if (commandSpec.parser().atFileCommentChar() != null) {
8806 tok.commentChar(commandSpec.parser().atFileCommentChar());
8807 }
8808 while (tok.nextToken() != StreamTokenizer.TT_EOF) {
8809 addOrExpand(tok.sval, result, visited);
8810 }
8811 }
8812 } catch (Exception ex) {
8813 throw new InitializationException("Could not read argument file @" + fileName, ex);
8814 } finally {
8815 if (reader != null) { try {reader.close();} catch (Exception ignored) {} }
8816 }
8817 if (tracer.isInfo()) {tracer.info("Expanded file @%s to arguments %s%n", fileName, result);}
8818 arguments.addAll(result);
8819 }
8820 private void clear() {
8821 position = 0;
8822 endOfOptions = false;
8823 isHelpRequested = false;
8824 parseResultBuilder = ParseResult.builder(getCommandSpec());
8825 for (OptionSpec option : getCommandSpec().options()) { clear(option); }
8826 for (PositionalParamSpec positional : getCommandSpec().positionalParameters()) { clear(positional); }
8827 }
8828 private void clear(ArgSpec argSpec) {
8829 argSpec.resetStringValues();
8830 argSpec.resetOriginalStringValues();
8831 argSpec.typedValues.clear();
8832 argSpec.typedValueAtPosition.clear();
8833 if (argSpec.group() == null) { argSpec.applyInitialValue(tracer); } // groups do their own initialization
8834 }
8835
8836 private void maybeThrow(PicocliException ex) throws PicocliException {
8837 if (commandSpec.parser().collectErrors) {
8838 parseResultBuilder.addError(ex);
8839 } else {
8840 throw ex;
8841 }
8842 }
8843
8844 private void parse(List<CommandLine> parsedCommands, Stack<String> argumentStack, String[] originalArgs, List<Object> nowProcessing) {
8845 clear(); // first reset any state in case this CommandLine instance is being reused
8846 if (tracer.isDebug()) {
8847 tracer.debug("Initializing %s: %d options, %d positional parameters, %d required, %d groups, %d subcommands.%n",
8848 commandSpec.toString(), new HashSet<ArgSpec>(commandSpec.optionsMap().values()).size(),
8849 commandSpec.positionalParameters().size(), commandSpec.requiredArgs().size(),
8850 commandSpec.argGroups().size(), commandSpec.subcommands().size());
8851 }
8852 parsedCommands.add(CommandLine.this);
8853 List<ArgSpec> required = new ArrayList<ArgSpec>(commandSpec.requiredArgs());
8854 Set<ArgSpec> initialized = new LinkedHashSet<ArgSpec>();
8855 Collections.sort(required, new PositionalParametersSorter());
8856 boolean continueOnError = commandSpec.parser().collectErrors();
8857 do {
8858 int stackSize = argumentStack.size();
8859 try {
8860 applyDefaultValues(required);
8861 processArguments(parsedCommands, argumentStack, required, initialized, originalArgs, nowProcessing);
8862 } catch (ParameterException ex) {
8863 maybeThrow(ex);
8864 } catch (Exception ex) {
8865 int offendingArgIndex = originalArgs.length - argumentStack.size() - 1;
8866 String arg = offendingArgIndex >= 0 && offendingArgIndex < originalArgs.length ? originalArgs[offendingArgIndex] : "?";
8867 maybeThrow(ParameterException.create(CommandLine.this, ex, arg, offendingArgIndex, originalArgs));
8868 }
8869 if (continueOnError && stackSize == argumentStack.size() && stackSize > 0) {
8870 parseResultBuilder.unmatched.add(argumentStack.pop());
8871 }
8872 } while (!argumentStack.isEmpty() && continueOnError);
8873
8874 if (!isAnyHelpRequested()) {
8875 validateConstraints(argumentStack, required, initialized);
8876 }
8877 }
8878
8879 private void validateConstraints(Stack<String> argumentStack, List<ArgSpec> required, Set<ArgSpec> matched) {
8880 if (!required.isEmpty()) {
8881 for (ArgSpec missing : required) {
8882 Assert.assertTrue(missing.group() == null, "Arguments in a group are not necessarily required for the command");
8883 if (missing.isOption()) {
8884 maybeThrow(MissingParameterException.create(CommandLine.this, required, config().separator()));
8885 } else {
8886 assertNoMissingParameters(missing, missing.arity(), argumentStack);
8887 }
8888 }
8889 }
8890 if (!parseResultBuilder.unmatched.isEmpty()) {
8891 String[] unmatched = parseResultBuilder.unmatched.toArray(new String[0]);
8892 for (UnmatchedArgsBinding unmatchedArgsBinding : getCommandSpec().unmatchedArgsBindings()) {
8893 unmatchedArgsBinding.addAll(unmatched.clone());
8894 }
8895 if (!isUnmatchedArgumentsAllowed()) { maybeThrow(new UnmatchedArgumentException(CommandLine.this, Collections.unmodifiableList(parseResultBuilder.unmatched))); }
8896 if (tracer.isInfo()) { tracer.info("Unmatched arguments: %s%n", parseResultBuilder.unmatched); }
8897 }
8898 for (ArgGroupSpec group : commandSpec.argGroups()) {
8899 group.clearValidationResult();
8900 }
8901 ParseResult pr = parseResultBuilder.build();
8902 for (ArgGroupSpec group : commandSpec.argGroups()) {
8903 group.validateConstraints(pr);
8904 }
8905 List<ParseResult.MatchedGroupMultiple> matchedGroupMultiples = pr.getMatchedGroupMultiples();
8906 if (matchedGroupMultiples.size() > 1) {
8907 failGroupMultiplicityExceeded(matchedGroupMultiples);
8908 }
8909 }
8910
8911 private void failGroupMultiplicityExceeded(List<ParseResult.MatchedGroupMultiple> matchedGroupMultiples) {
8912 Map<ArgGroupSpec, List<List<ParseResult.MatchedGroupMultiple>>> multiplesPerGroup = new IdentityHashMap<ArgGroupSpec, List<List<ParseResult.MatchedGroupMultiple>>>();
8913 String msg = "";
8914 for (ParseResult.MatchedGroupMultiple multiple : matchedGroupMultiples) {
8915 if (msg.length() > 0) { msg += " and "; }
8916 msg += multiple.toString();
8917 Map<ArgGroupSpec, MatchedGroup> subgroups = multiple.matchedSubgroups();
8918 for (ArgGroupSpec group : subgroups.keySet()) {
8919 addValueToListInMap(multiplesPerGroup, group, subgroups.get(group).multiples());
8920 }
8921 }
8922 if (!simplifyErrorMessageForSingleGroup(multiplesPerGroup)) {
8923 maybeThrow(new MaxValuesExceededException(CommandLine.this, "Error: expected only one match but got " + msg));
8924 }
8925 }
8926
8927 private boolean simplifyErrorMessageForSingleGroup(Map<ArgGroupSpec, List<List<ParseResult.MatchedGroupMultiple>>> multiplesPerGroup) {
8928 if (multiplesPerGroup.size() == 1) { // all multiples were matches for a single group
8929 ArgGroupSpec group = multiplesPerGroup.keySet().iterator().next();
8930 List<ParseResult.MatchedGroupMultiple> flat = flatList(multiplesPerGroup.get(group));
8931 for (ParseResult.MatchedGroupMultiple multiple : flat) {
8932 if (!multiple.matchedSubgroups().isEmpty()) { return false; }
8933 }
8934 group.validationException = null;
8935 group.validateMultiples(CommandLine.this, flat);
8936 if (group.validationException != null) {
8937 maybeThrow(group.validationException);
8938 return true;
8939 }
8940 }
8941 return false;
8942 }
8943
8944 private void applyDefaultValues(List<ArgSpec> required) throws Exception {
8945 parseResultBuilder.isInitializingDefaultValues = true;
8946 for (ArgSpec arg : commandSpec.args()) {
8947 if (arg.group() == null) {
8948 if (applyDefault(commandSpec.defaultValueProvider(), arg)) { required.remove(arg); }
8949 }
8950 }
8951 parseResultBuilder.isInitializingDefaultValues = false;
8952 }
8953
8954 private boolean applyDefault(IDefaultValueProvider defaultValueProvider, ArgSpec arg) throws Exception {
8955
8956 // Default value provider return value is only used if provider exists and if value
8957 // is not null otherwise the original default or initial value are used
8958 String fromProvider = defaultValueProvider == null ? null : defaultValueProvider.defaultValue(arg);
8959 String defaultValue = fromProvider == null ? arg.defaultValue() : fromProvider;
8960
8961 if (defaultValue != null) {
8962 if (tracer.isDebug()) {tracer.debug("Applying defaultValue (%s) to %s%n", defaultValue, arg);}
8963 Range arity = arg.arity().min(Math.max(1, arg.arity().min));
8964 applyOption(arg, LookBehind.SEPARATE, arity, stack(defaultValue), new HashSet<ArgSpec>(), arg.toString);
8965 }
8966 return defaultValue != null;
8967 }
8968
8969 private Stack<String> stack(String value) {Stack<String> result = new Stack<String>(); result.push(value); return result;}
8970
8971 private void processArguments(List<CommandLine> parsedCommands,
8972 Stack<String> args,
8973 Collection<ArgSpec> required,
8974 Set<ArgSpec> initialized,
8975 String[] originalArgs,
8976 List<Object> nowProcessing) throws Exception {
8977 // arg must be one of:
8978 // 1. the "--" double dash separating options from positional arguments
8979 // 1. a stand-alone flag, like "-v" or "--verbose": no value required, must map to boolean or Boolean field
8980 // 2. a short option followed by an argument, like "-f file" or "-ffile": may map to any type of field
8981 // 3. a long option followed by an argument, like "-file out.txt" or "-file=out.txt"
8982 // 3. one or more remaining arguments without any associated options. Must be the last in the list.
8983 // 4. a combination of stand-alone options, like "-vxr". Equivalent to "-v -x -r", "-v true -x true -r true"
8984 // 5. a combination of stand-alone options and one option with an argument, like "-vxrffile"
8985
8986 parseResultBuilder.originalArgs(originalArgs);
8987 parseResultBuilder.nowProcessing = nowProcessing;
8988 String separator = config().separator();
8989 while (!args.isEmpty()) {
8990 if (endOfOptions) {
8991 processRemainderAsPositionalParameters(required, initialized, args);
8992 return;
8993 }
8994 String arg = args.pop();
8995 if (tracer.isDebug()) {tracer.debug("Processing argument '%s'. Remainder=%s%n", arg, reverse(copy(args)));}
8996
8997 // Double-dash separates options from positional arguments.
8998 // If found, then interpret the remaining args as positional parameters.
8999 if (commandSpec.parser.endOfOptionsDelimiter().equals(arg)) {
9000 tracer.info("Found end-of-options delimiter '--'. Treating remainder as positional parameters.%n");
9001 endOfOptions = true;
9002 processRemainderAsPositionalParameters(required, initialized, args);
9003 return; // we are done
9004 }
9005
9006 // if we find another command, we are done with the current command
9007 if (commandSpec.subcommands().containsKey(arg)) {
9008 CommandLine subcommand = commandSpec.subcommands().get(arg);
9009 nowProcessing.add(subcommand.commandSpec);
9010 updateHelpRequested(subcommand.commandSpec);
9011 if (!isAnyHelpRequested() && !required.isEmpty()) { // ensure current command portion is valid
9012 throw MissingParameterException.create(CommandLine.this, required, separator);
9013 }
9014 if (tracer.isDebug()) {tracer.debug("Found subcommand '%s' (%s)%n", arg, subcommand.commandSpec.toString());}
9015 subcommand.interpreter.parse(parsedCommands, args, originalArgs, nowProcessing);
9016 parseResultBuilder.subcommand(subcommand.interpreter.parseResultBuilder.build());
9017 return; // remainder done by the command
9018 }
9019
9020 // First try to interpret the argument as a single option (as opposed to a compact group of options).
9021 // A single option may be without option parameters, like "-v" or "--verbose" (a boolean value),
9022 // or an option may have one or more option parameters.
9023 // A parameter may be attached to the option.
9024 boolean paramAttachedToOption = false;
9025 int separatorIndex = arg.indexOf(separator);
9026 if (separatorIndex > 0) {
9027 String key = arg.substring(0, separatorIndex);
9028 // be greedy. Consume the whole arg as an option if possible.
9029 if (commandSpec.optionsMap().containsKey(key) && commandSpec.optionsMap().containsKey(arg)) {
9030 tracer.warn("Both '%s' and '%s' are valid option names in %s. Using '%s'...%n", arg, key, getCommandName(), arg);
9031 } else if (commandSpec.optionsMap().containsKey(key)) {
9032 paramAttachedToOption = true;
9033 String optionParam = arg.substring(separatorIndex + separator.length());
9034 args.push(optionParam);
9035 arg = key;
9036 if (tracer.isDebug()) {tracer.debug("Separated '%s' option from '%s' option parameter%n", key, optionParam);}
9037 } else {
9038 if (tracer.isDebug()) {tracer.debug("'%s' contains separator '%s' but '%s' is not a known option%n", arg, separator, key);}
9039 }
9040 } else {
9041 if (tracer.isDebug()) {tracer.debug("'%s' cannot be separated into <option>%s<option-parameter>%n", arg, separator);}
9042 }
9043 if (isStandaloneOption(arg)) {
9044 processStandaloneOption(required, initialized, arg, args, paramAttachedToOption);
9045 }
9046 // Compact (single-letter) options can be grouped with other options or with an argument.
9047 // only single-letter options can be combined with other options or with an argument
9048 else if (config().posixClusteredShortOptionsAllowed() && arg.length() > 2 && arg.startsWith("-")) {
9049 if (tracer.isDebug()) {tracer.debug("Trying to process '%s' as clustered short options%n", arg, args);}
9050 processClusteredShortOptions(required, initialized, arg, args);
9051 }
9052 // The argument could not be interpreted as an option: process it as a positional argument
9053 else {
9054 args.push(arg);
9055 if (tracer.isDebug()) {tracer.debug("Could not find option '%s', deciding whether to treat as unmatched option or positional parameter...%n", arg);}
9056 if (commandSpec.resemblesOption(arg, tracer)) { handleUnmatchedArgument(args); continue; } // #149
9057 if (tracer.isDebug()) {tracer.debug("No option named '%s' found. Processing as positional parameter%n", arg);}
9058 processPositionalParameter(required, initialized, args);
9059 }
9060 }
9061 }
9062
9063 private boolean isStandaloneOption(String arg) {
9064 return commandSpec.optionsMap().containsKey(arg);
9065 }
9066 private void handleUnmatchedArgument(Stack<String> args) throws Exception {
9067 if (!args.isEmpty()) { handleUnmatchedArgument(args.pop()); }
9068 if (config().stopAtUnmatched()) {
9069 // addAll would give args in reverse order
9070 while (!args.isEmpty()) { handleUnmatchedArgument(args.pop()); }
9071 }
9072 }
9073 private void handleUnmatchedArgument(String arg) {
9074 parseResultBuilder.unmatched.add(arg);
9075 }
9076
9077 private void processRemainderAsPositionalParameters(Collection<ArgSpec> required, Set<ArgSpec> initialized, Stack<String> args) throws Exception {
9078 while (!args.empty()) {
9079 processPositionalParameter(required, initialized, args);
9080 }
9081 }
9082 private void processPositionalParameter(Collection<ArgSpec> required, Set<ArgSpec> initialized, Stack<String> args) throws Exception {
9083 if (tracer.isDebug()) {tracer.debug("Processing next arg as a positional parameter. Command-local position=%d. Remainder=%s%n", position, reverse(copy(args)));}
9084 if (config().stopAtPositional()) {
9085 if (!endOfOptions && tracer.isDebug()) {tracer.debug("Parser was configured with stopAtPositional=true, treating remaining arguments as positional parameters.%n");}
9086 endOfOptions = true;
9087 }
9088 int consumedByGroup = 0;
9089 int argsConsumed = 0;
9090 int interactiveConsumed = 0;
9091 int originalNowProcessingSize = parseResultBuilder.nowProcessing.size();
9092 Map<PositionalParamSpec, Integer> newPositions = new IdentityHashMap<PositionalParamSpec, Integer>();
9093 for (PositionalParamSpec positionalParam : commandSpec.positionalParameters()) {
9094 Range indexRange = positionalParam.index();
9095 int localPosition = getPosition(positionalParam);
9096 if (positionalParam.group() != null) { // does the positionalParam's index range contain the current position in the currently matching group
9097 MatchedGroup matchedGroup = parseResultBuilder.matchedGroup.findOrCreateMatchingGroup(positionalParam, commandSpec.commandLine());
9098 if (!indexRange.contains(localPosition) || (matchedGroup != null && matchedGroup.multiple().hasMatchedValueAtPosition(positionalParam, localPosition))) {
9099 continue;
9100 }
9101 } else {
9102 if (!indexRange.contains(localPosition) || positionalParam.typedValueAtPosition.get(localPosition) != null) {
9103 continue;
9104 }
9105 }
9106 Stack<String> argsCopy = copy(args);
9107 Range arity = positionalParam.arity();
9108 if (tracer.isDebug()) {tracer.debug("Position %s is in index range %s. Trying to assign args to %s, arity=%s%n", positionDesc(positionalParam), indexRange, positionalParam, arity);}
9109 if (!assertNoMissingParameters(positionalParam, arity, argsCopy)) { break; } // #389 collectErrors parsing
9110 int originalSize = argsCopy.size();
9111 int actuallyConsumed = applyOption(positionalParam, LookBehind.SEPARATE, arity, argsCopy, initialized, "args[" + indexRange + "] at position " + localPosition);
9112 int count = originalSize - argsCopy.size();
9113 if (count > 0 || actuallyConsumed > 0) {
9114 required.remove(positionalParam);
9115 if (positionalParam.interactive()) { interactiveConsumed++; }
9116 }
9117 if (positionalParam.group() == null) { // don't update the command-level position for group args
9118 argsConsumed = Math.max(argsConsumed, count);
9119 } else {
9120 newPositions.put(positionalParam, localPosition + count);
9121 consumedByGroup = Math.max(consumedByGroup, count);
9122 }
9123 while (parseResultBuilder.nowProcessing.size() > originalNowProcessingSize + count) {
9124 parseResultBuilder.nowProcessing.remove(parseResultBuilder.nowProcessing.size() - 1);
9125 }
9126 }
9127 // remove processed args from the stack
9128 int maxConsumed = Math.max(consumedByGroup, argsConsumed);
9129 for (int i = 0; i < maxConsumed; i++) { args.pop(); }
9130 position += argsConsumed + interactiveConsumed;
9131 if (tracer.isDebug()) {tracer.debug("Consumed %d arguments and %d interactive values, moving command-local position to index %d.%n", argsConsumed, interactiveConsumed, position);}
9132 for (PositionalParamSpec positional : newPositions.keySet()) {
9133 MatchedGroup inProgress = parseResultBuilder.matchedGroup.findOrCreateMatchingGroup(positional, commandSpec.commandLine());
9134 if (inProgress != null) {
9135 inProgress.multiple().position = newPositions.get(positional);
9136 if (tracer.isDebug()) {tracer.debug("Updated group position to %s for group %s.%n", inProgress.multiple().position, inProgress);}
9137 }
9138 }
9139 if (consumedByGroup == 0 && argsConsumed == 0 && interactiveConsumed == 0 && !args.isEmpty()) {
9140 handleUnmatchedArgument(args);
9141 }
9142 }
9143
9144 private void processStandaloneOption(Collection<ArgSpec> required,
9145 Set<ArgSpec> initialized,
9146 String arg,
9147 Stack<String> args,
9148 boolean paramAttachedToKey) throws Exception {
9149 ArgSpec argSpec = commandSpec.optionsMap().get(arg);
9150 required.remove(argSpec);
9151 Range arity = argSpec.arity();
9152 if (paramAttachedToKey) {
9153 arity = arity.min(Math.max(1, arity.min)); // if key=value, minimum arity is at least 1
9154 }
9155 LookBehind lookBehind = paramAttachedToKey ? LookBehind.ATTACHED_WITH_SEPARATOR : LookBehind.SEPARATE;
9156 if (tracer.isDebug()) {tracer.debug("Found option named '%s': %s, arity=%s%n", arg, argSpec, arity);}
9157 parseResultBuilder.nowProcessing.add(argSpec);
9158 applyOption(argSpec, lookBehind, arity, args, initialized, "option " + arg);
9159 }
9160
9161 private void processClusteredShortOptions(Collection<ArgSpec> required,
9162 Set<ArgSpec> initialized,
9163 String arg,
9164 Stack<String> args) throws Exception {
9165 String prefix = arg.substring(0, 1);
9166 String cluster = arg.substring(1);
9167 boolean paramAttachedToOption = true;
9168 boolean first = true;
9169 do {
9170 if (cluster.length() > 0 && commandSpec.posixOptionsMap().containsKey(cluster.charAt(0))) {
9171 ArgSpec argSpec = commandSpec.posixOptionsMap().get(cluster.charAt(0));
9172 Range arity = argSpec.arity();
9173 String argDescription = "option " + prefix + cluster.charAt(0);
9174 if (tracer.isDebug()) {tracer.debug("Found option '%s%s' in %s: %s, arity=%s%n", prefix, cluster.charAt(0), arg,
9175 argSpec, arity);}
9176 required.remove(argSpec);
9177 cluster = cluster.substring(1);
9178 paramAttachedToOption = cluster.length() > 0;
9179 LookBehind lookBehind = paramAttachedToOption ? LookBehind.ATTACHED : LookBehind.SEPARATE;
9180 if (cluster.startsWith(config().separator())) {// attached with separator, like -f=FILE or -v=true
9181 lookBehind = LookBehind.ATTACHED_WITH_SEPARATOR;
9182 cluster = cluster.substring(config().separator().length());
9183 arity = arity.min(Math.max(1, arity.min)); // if key=value, minimum arity is at least 1
9184 }
9185 if (arity.min > 0 && !empty(cluster)) {
9186 if (tracer.isDebug()) {tracer.debug("Trying to process '%s' as option parameter%n", cluster);}
9187 }
9188 // arity may be >= 1, or
9189 // arity <= 0 && !cluster.startsWith(separator)
9190 // e.g., boolean @Option("-v", arity=0, varargs=true); arg "-rvTRUE", remainder cluster="TRUE"
9191 if (!empty(cluster)) {
9192 args.push(cluster); // interpret remainder as option parameter (CAUTION: may be empty string!)
9193 }
9194 if (first) {
9195 parseResultBuilder.nowProcessing.add(argSpec);
9196 first = false;
9197 } else {
9198 parseResultBuilder.nowProcessing.set(parseResultBuilder.nowProcessing.size() - 1, argSpec); // replace
9199 }
9200 int argCount = args.size();
9201 int consumed = applyOption(argSpec, lookBehind, arity, args, initialized, argDescription);
9202 // if cluster was consumed as a parameter or if this field was the last in the cluster we're done; otherwise continue do-while loop
9203 if (empty(cluster) || args.isEmpty() || args.size() < argCount) {
9204 return;
9205 }
9206 cluster = args.pop();
9207 } else { // cluster is empty || cluster.charAt(0) is not a short option key
9208 if (cluster.length() == 0) { // we finished parsing a group of short options like -rxv
9209 return; // return normally and parse the next arg
9210 }
9211 // We get here when the remainder of the cluster group is neither an option,
9212 // nor a parameter that the last option could consume.
9213 if (arg.endsWith(cluster)) {
9214 args.push(paramAttachedToOption ? prefix + cluster : cluster);
9215 if (args.peek().equals(arg)) { // #149 be consistent between unmatched short and long options
9216 if (tracer.isDebug()) {tracer.debug("Could not match any short options in %s, deciding whether to treat as unmatched option or positional parameter...%n", arg);}
9217 if (commandSpec.resemblesOption(arg, tracer)) { handleUnmatchedArgument(args); return; } // #149
9218 processPositionalParameter(required, initialized, args);
9219 return;
9220 }
9221 // remainder was part of a clustered group that could not be completely parsed
9222 if (tracer.isDebug()) {tracer.debug("No option found for %s in %s%n", cluster, arg);}
9223 String tmp = args.pop();
9224 tmp = tmp + " (while processing option: '" + arg + "')";
9225 args.push(tmp);
9226 handleUnmatchedArgument(args);
9227 } else {
9228 args.push(cluster);
9229 if (tracer.isDebug()) {tracer.debug("%s is not an option parameter for %s%n", cluster, arg);}
9230 processPositionalParameter(required, initialized, args);
9231 }
9232 return;
9233 }
9234 } while (true);
9235 }
9236
9237 private int applyOption(ArgSpec argSpec,
9238 LookBehind lookBehind,
9239 Range arity,
9240 Stack<String> args,
9241 Set<ArgSpec> initialized,
9242 String argDescription) throws Exception {
9243 updateHelpRequested(argSpec);
9244 boolean consumeOnlyOne = commandSpec.parser().aritySatisfiedByAttachedOptionParam() && lookBehind.isAttached();
9245 Stack<String> workingStack = args;
9246 if (consumeOnlyOne) {
9247 workingStack = args.isEmpty() ? args : stack(args.pop());
9248 } else {
9249 if (!assertNoMissingParameters(argSpec, arity, args)) { return 0; } // #389 collectErrors parsing
9250 }
9251
9252 if (argSpec.interactive()) {
9253 String name = argSpec.isOption() ? ((OptionSpec) argSpec).longestName() : "position " + position;
9254 String prompt = String.format("Enter value for %s (%s): ", name, str(argSpec.renderedDescription(), 0));
9255 if (tracer.isDebug()) {tracer.debug("Reading value for %s from console...%n", name);}
9256 char[] value = readPassword(prompt);
9257 if (tracer.isDebug()) {tracer.debug("User entered '%s' for %s.%n", value, name);}
9258 workingStack.push(new String(value));
9259 }
9260
9261 parseResultBuilder.beforeMatchingGroupElement(argSpec);
9262
9263 int result;
9264 if (argSpec.type().isArray()) {
9265 result = applyValuesToArrayField(argSpec, lookBehind, arity, workingStack, initialized, argDescription);
9266 } else if (Collection.class.isAssignableFrom(argSpec.type())) {
9267 result = applyValuesToCollectionField(argSpec, lookBehind, arity, workingStack, initialized, argDescription);
9268 } else if (Map.class.isAssignableFrom(argSpec.type())) {
9269 result = applyValuesToMapField(argSpec, lookBehind, arity, workingStack, initialized, argDescription);
9270 } else {
9271 result = applyValueToSingleValuedField(argSpec, lookBehind, arity, workingStack, initialized, argDescription);
9272 }
9273 if (workingStack != args && !workingStack.isEmpty()) {
9274 args.push(workingStack.pop());
9275 Assert.assertTrue(workingStack.isEmpty(), "Working stack should be empty but was " + new ArrayList<String>(workingStack));
9276 }
9277 return result;
9278 }
9279
9280 private int applyValueToSingleValuedField(ArgSpec argSpec,
9281 LookBehind lookBehind,
9282 Range derivedArity,
9283 Stack<String> args,
9284 Set<ArgSpec> initialized,
9285 String argDescription) throws Exception {
9286 boolean noMoreValues = args.isEmpty();
9287 String value = args.isEmpty() ? null : trim(args.pop()); // unquote the value
9288 Range arity = argSpec.arity().isUnspecified ? derivedArity : argSpec.arity(); // #509
9289 if (arity.max == 0 && !arity.isUnspecified && lookBehind == LookBehind.ATTACHED_WITH_SEPARATOR) { // #509
9290 throw new MaxValuesExceededException(CommandLine.this, optionDescription("", argSpec, 0) +
9291 " should be specified without '" + value + "' parameter");
9292 }
9293 int result = arity.min; // the number or args we need to consume
9294
9295 Class<?> cls = argSpec.auxiliaryTypes()[0]; // field may be interface/abstract type, use annotation to get concrete type
9296 if (arity.min <= 0) { // value may be optional
9297
9298 // special logic for booleans: BooleanConverter accepts only "true" or "false".
9299 if (cls == Boolean.class || cls == Boolean.TYPE) {
9300
9301 // boolean option with arity = 0..1 or 0..*: value MAY be a param
9302 if (arity.max > 0 && ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value))) {
9303 result = 1; // if it is a varargs we only consume 1 argument if it is a boolean value
9304 if (!lookBehind.isAttached()) { parseResultBuilder.nowProcessing(argSpec, value); }
9305 } else if (lookBehind != LookBehind.ATTACHED_WITH_SEPARATOR) { // if attached, try converting the value to boolean (and fail if invalid value)
9306 // it's okay to ignore value if not attached to option
9307 if (value != null) {
9308 args.push(value); // we don't consume the value
9309 }
9310 if (commandSpec.parser().toggleBooleanFlags()) {
9311 Boolean currentValue = (Boolean) argSpec.getValue();
9312 value = String.valueOf(currentValue == null || !currentValue); // #147 toggle existing boolean value
9313 } else {
9314 value = "true";
9315 }
9316 }
9317 } else { // non-boolean option with optional value #325, #279
9318 if (isOption(value)) {
9319 args.push(value); // we don't consume the value
9320 value = "";
9321 } else if (value == null) {
9322 value = "";
9323 } else {
9324 if (!lookBehind.isAttached()) { parseResultBuilder.nowProcessing(argSpec, value); }
9325 }
9326 }
9327 } else {
9328 if (!lookBehind.isAttached()) { parseResultBuilder.nowProcessing(argSpec, value); }
9329 }
9330 if (noMoreValues && value == null) {
9331 return 0;
9332 }
9333 ITypeConverter<?> converter = getTypeConverter(cls, argSpec, 0);
9334 Object newValue = tryConvert(argSpec, -1, converter, value, cls);
9335 Object oldValue = argSpec.getValue();
9336 String traceMessage = "Setting %s to '%3$s' (was '%2$s') for %4$s%n";
9337 if (argSpec.group() == null && initialized.contains(argSpec)) {
9338 if (!isOverwrittenOptionsAllowed()) {
9339 throw new OverwrittenOptionException(CommandLine.this, argSpec, optionDescription("", argSpec, 0) + " should be specified only once");
9340 }
9341 traceMessage = "Overwriting %s value '%s' with '%s' for %s%n";
9342 }
9343 initialized.add(argSpec);
9344
9345 if (tracer.isInfo()) { tracer.info(traceMessage, argSpec.toString(), String.valueOf(oldValue), String.valueOf(newValue), argDescription); }
9346 int pos = getPosition(argSpec);
9347 argSpec.setValue(newValue);
9348 parseResultBuilder.addOriginalStringValue(argSpec, value);// #279 track empty string value if no command line argument was consumed
9349 parseResultBuilder.addStringValue(argSpec, value);
9350 parseResultBuilder.addTypedValues(argSpec, pos, newValue);
9351 parseResultBuilder.add(argSpec, pos);
9352 return result;
9353 }
9354 private int applyValuesToMapField(ArgSpec argSpec,
9355 LookBehind lookBehind,
9356 Range arity,
9357 Stack<String> args,
9358 Set<ArgSpec> initialized,
9359 String argDescription) throws Exception {
9360 Class<?>[] classes = argSpec.auxiliaryTypes();
9361 if (classes.length < 2) { throw new ParameterException(CommandLine.this, argSpec.toString() + " needs two types (one for the map key, one for the value) but only has " + classes.length + " types configured.",argSpec, null); }
9362 ITypeConverter<?> keyConverter = getTypeConverter(classes[0], argSpec, 0);
9363 ITypeConverter<?> valueConverter = getTypeConverter(classes[1], argSpec, 1);
9364 @SuppressWarnings("unchecked") Map<Object, Object> map = (Map<Object, Object>) argSpec.getValue();
9365 if (map == null || (!map.isEmpty() && !initialized.contains(argSpec))) {
9366 tracer.debug("Initializing binding for %s with empty %s%n", optionDescription("", argSpec, 0), argSpec.type().getSimpleName());
9367 map = createMap(argSpec.type()); // map class
9368 argSpec.setValue(map);
9369 }
9370 initialized.add(argSpec);
9371 int originalSize = map.size();
9372 int pos = getPosition(argSpec);
9373 consumeMapArguments(argSpec, lookBehind, arity, args, classes, keyConverter, valueConverter, map, argDescription);
9374 parseResultBuilder.add(argSpec, pos);
9375 argSpec.setValue(map);
9376 return map.size() - originalSize;
9377 }
9378
9379 private void consumeMapArguments(ArgSpec argSpec,
9380 LookBehind lookBehind,
9381 Range arity,
9382 Stack<String> args,
9383 Class<?>[] classes,
9384 ITypeConverter<?> keyConverter,
9385 ITypeConverter<?> valueConverter,
9386 Map<Object, Object> result,
9387 String argDescription) throws Exception {
9388
9389 // don't modify Interpreter.position: same position may be consumed by multiple ArgSpec objects
9390 int currentPosition = getPosition(argSpec);
9391
9392 // first do the arity.min mandatory parameters
9393 int initialSize = argSpec.stringValues().size();
9394 int consumed = consumedCountMap(0, initialSize, argSpec);
9395 for (int i = 0; consumed < arity.min && !args.isEmpty(); i++) {
9396 Map<Object, Object> typedValuesAtPosition = new LinkedHashMap<Object, Object>();
9397 parseResultBuilder.addTypedValues(argSpec, currentPosition++, typedValuesAtPosition);
9398 assertNoMissingMandatoryParameter(argSpec, args, i, arity);
9399 consumeOneMapArgument(argSpec, lookBehind, arity, consumed, args.pop(), classes, keyConverter, valueConverter, typedValuesAtPosition, i, argDescription);
9400 result.putAll(typedValuesAtPosition);
9401 consumed = consumedCountMap(i + 1, initialSize, argSpec);
9402 lookBehind = LookBehind.SEPARATE;
9403 }
9404 // now process the varargs if any
9405 for (int i = consumed; consumed < arity.max && !args.isEmpty(); i++) {
9406 if (!varargCanConsumeNextValue(argSpec, args.peek())) { break; }
9407
9408 Map<Object, Object> typedValuesAtPosition = new LinkedHashMap<Object, Object>();
9409 parseResultBuilder.addTypedValues(argSpec, currentPosition++, typedValuesAtPosition);
9410 if (!canConsumeOneMapArgument(argSpec, arity, consumed, args.peek(), classes, keyConverter, valueConverter, argDescription)) {
9411 break; // leave empty map at argSpec.typedValueAtPosition[currentPosition] so we won't try to consume that position again
9412 }
9413 consumeOneMapArgument(argSpec, lookBehind, arity, consumed, args.pop(), classes, keyConverter, valueConverter, typedValuesAtPosition, i, argDescription);
9414 result.putAll(typedValuesAtPosition);
9415 consumed = consumedCountMap(i + 1, initialSize, argSpec);
9416 lookBehind = LookBehind.SEPARATE;
9417 }
9418 }
9419
9420 private void consumeOneMapArgument(ArgSpec argSpec,
9421 LookBehind lookBehind,
9422 Range arity, int consumed,
9423 String arg,
9424 Class<?>[] classes,
9425 ITypeConverter<?> keyConverter, ITypeConverter<?> valueConverter,
9426 Map<Object, Object> result,
9427 int index,
9428 String argDescription) throws Exception {
9429 if (!lookBehind.isAttached()) { parseResultBuilder.nowProcessing(argSpec, arg); }
9430 String raw = trim(arg);
9431 String[] values = argSpec.splitValue(raw, commandSpec.parser(), arity, consumed);
9432 for (String value : values) {
9433 String[] keyValue = splitKeyValue(argSpec, value);
9434 Object mapKey = tryConvert(argSpec, index, keyConverter, keyValue[0], classes[0]);
9435 Object mapValue = tryConvert(argSpec, index, valueConverter, keyValue[1], classes[1]);
9436 result.put(mapKey, mapValue);
9437 if (tracer.isInfo()) { tracer.info("Putting [%s : %s] in %s<%s, %s> %s for %s%n", String.valueOf(mapKey), String.valueOf(mapValue),
9438 result.getClass().getSimpleName(), classes[0].getSimpleName(), classes[1].getSimpleName(), argSpec.toString(), argDescription); }
9439 parseResultBuilder.addStringValue(argSpec, keyValue[0]);
9440 parseResultBuilder.addStringValue(argSpec, keyValue[1]);
9441 }
9442 parseResultBuilder.addOriginalStringValue(argSpec, raw);
9443 }
9444
9445 private boolean canConsumeOneMapArgument(ArgSpec argSpec, Range arity, int consumed,
9446 String raw, Class<?>[] classes,
9447 ITypeConverter<?> keyConverter, ITypeConverter<?> valueConverter,
9448 String argDescription) {
9449 String[] values = argSpec.splitValue(raw, commandSpec.parser(), arity, consumed);
9450 try {
9451 for (String value : values) {
9452 String[] keyValue = splitKeyValue(argSpec, value);
9453 tryConvert(argSpec, -1, keyConverter, keyValue[0], classes[0]);
9454 tryConvert(argSpec, -1, valueConverter, keyValue[1], classes[1]);
9455 }
9456 return true;
9457 } catch (PicocliException ex) {
9458 tracer.debug("$s cannot be assigned to %s: type conversion fails: %s.%n", raw, argDescription, ex.getMessage());
9459 return false;
9460 }
9461 }
9462
9463 private String[] splitKeyValue(ArgSpec argSpec, String value) {
9464 String[] keyValue = ArgSpec.splitRespectingQuotedStrings(value, 2, config(), argSpec, "=");
9465
9466 if (keyValue.length < 2) {
9467 String splitRegex = argSpec.splitRegex();
9468 if (splitRegex.length() == 0) {
9469 throw new ParameterException(CommandLine.this, "Value for option " + optionDescription("",
9470 argSpec, 0) + " should be in KEY=VALUE format but was " + value, argSpec, value);
9471 } else {
9472 throw new ParameterException(CommandLine.this, "Value for option " + optionDescription("",
9473 argSpec, 0) + " should be in KEY=VALUE[" + splitRegex + "KEY=VALUE]... format but was " + value, argSpec, value);
9474 }
9475 }
9476 return keyValue;
9477 }
9478
9479 private void assertNoMissingMandatoryParameter(ArgSpec argSpec, Stack<String> args, int i, Range arity) {
9480 if (!varargCanConsumeNextValue(argSpec, args.peek())) {
9481 String desc = arity.min > 1 ? (i + 1) + " (of " + arity.min + " mandatory parameters) " : "";
9482 throw new MissingParameterException(CommandLine.this, argSpec, "Expected parameter " + desc + "for " + optionDescription("", argSpec, -1) + " but found '" + args.peek() + "'");
9483 }
9484 }
9485 private int applyValuesToArrayField(ArgSpec argSpec,
9486 LookBehind lookBehind,
9487 Range arity,
9488 Stack<String> args,
9489 Set<ArgSpec> initialized,
9490 String argDescription) throws Exception {
9491 Object existing = argSpec.getValue();
9492 int length = existing == null ? 0 : Array.getLength(existing);
9493 Class<?> type = argSpec.auxiliaryTypes()[0];
9494 int pos = getPosition(argSpec);
9495 List<Object> converted = consumeArguments(argSpec, lookBehind, arity, args, type, argDescription);
9496 List<Object> newValues = new ArrayList<Object>();
9497 if (initialized.contains(argSpec)) { // existing values are default values if initialized does NOT contain argsSpec
9498 for (int i = 0; i < length; i++) {
9499 newValues.add(Array.get(existing, i)); // keep non-default values
9500 }
9501 }
9502 initialized.add(argSpec);
9503 for (Object obj : converted) {
9504 if (obj instanceof Collection<?>) {
9505 newValues.addAll((Collection<?>) obj);
9506 } else {
9507 newValues.add(obj);
9508 }
9509 }
9510 Object array = Array.newInstance(type, newValues.size());
9511 for (int i = 0; i < newValues.size(); i++) {
9512 Array.set(array, i, newValues.get(i));
9513 }
9514 argSpec.setValue(array);
9515 parseResultBuilder.add(argSpec, pos);
9516 return converted.size(); // return how many args were consumed
9517 }
9518
9519 @SuppressWarnings("unchecked")
9520 private int applyValuesToCollectionField(ArgSpec argSpec,
9521 LookBehind lookBehind,
9522 Range arity,
9523 Stack<String> args,
9524 Set<ArgSpec> initialized,
9525 String argDescription) throws Exception {
9526 Collection<Object> collection = (Collection<Object>) argSpec.getValue();
9527 Class<?> type = argSpec.auxiliaryTypes()[0];
9528 int pos = getPosition(argSpec);
9529 List<Object> converted = consumeArguments(argSpec, lookBehind, arity, args, type, argDescription);
9530 if (collection == null || (!collection.isEmpty() && !initialized.contains(argSpec))) {
9531 tracer.debug("Initializing binding for %s with empty %s%n", optionDescription("", argSpec, 0), argSpec.type().getSimpleName());
9532 collection = createCollection(argSpec.type(), type); // collection type, element type
9533 argSpec.setValue(collection);
9534 }
9535 initialized.add(argSpec);
9536 for (Object element : converted) {
9537 if (element instanceof Collection<?>) {
9538 collection.addAll((Collection<?>) element);
9539 } else {
9540 collection.add(element);
9541 }
9542 }
9543 parseResultBuilder.add(argSpec, pos);
9544 argSpec.setValue(collection);
9545 return converted.size();
9546 }
9547
9548 private List<Object> consumeArguments(ArgSpec argSpec,
9549 LookBehind lookBehind,
9550 Range arity,
9551 Stack<String> args,
9552 Class<?> type,
9553 String argDescription) throws Exception {
9554 List<Object> result = new ArrayList<Object>();
9555
9556 // don't modify Interpreter.position: same position may be consumed by multiple ArgSpec objects
9557 int currentPosition = getPosition(argSpec);
9558
9559 // first do the arity.min mandatory parameters
9560 int initialSize = argSpec.stringValues().size();
9561 int consumed = consumedCount(0, initialSize, argSpec);
9562 for (int i = 0; consumed < arity.min && !args.isEmpty(); i++) {
9563 List<Object> typedValuesAtPosition = new ArrayList<Object>();
9564 parseResultBuilder.addTypedValues(argSpec, currentPosition++, typedValuesAtPosition);
9565 assertNoMissingMandatoryParameter(argSpec, args, i, arity);
9566 consumeOneArgument(argSpec, lookBehind, arity, consumed, args.pop(), type, typedValuesAtPosition, i, argDescription);
9567 result.addAll(typedValuesAtPosition);
9568 consumed = consumedCount(i + 1, initialSize, argSpec);
9569 lookBehind = LookBehind.SEPARATE;
9570 }
9571 // now process the varargs if any
9572 for (int i = consumed; consumed < arity.max && !args.isEmpty(); i++) {
9573 if (!varargCanConsumeNextValue(argSpec, args.peek())) { break; }
9574
9575 List<Object> typedValuesAtPosition = new ArrayList<Object>();
9576 parseResultBuilder.addTypedValues(argSpec, currentPosition++, typedValuesAtPosition);
9577 if (!canConsumeOneArgument(argSpec, arity, consumed, args.peek(), type, argDescription)) {
9578 break; // leave empty list at argSpec.typedValueAtPosition[currentPosition] so we won't try to consume that position again
9579 }
9580 consumeOneArgument(argSpec, lookBehind, arity, consumed, args.pop(), type, typedValuesAtPosition, i, argDescription);
9581 result.addAll(typedValuesAtPosition);
9582 consumed = consumedCount(i + 1, initialSize, argSpec);
9583 lookBehind = LookBehind.SEPARATE;
9584 }
9585 if (result.isEmpty() && arity.min == 0 && arity.max <= 1 && isBoolean(type)) {
9586 return Arrays.asList((Object) Boolean.TRUE);
9587 }
9588 return result;
9589 }
9590
9591 private int consumedCount(int i, int initialSize, ArgSpec arg) {
9592 return commandSpec.parser().splitFirst() ? arg.stringValues().size() - initialSize : i;
9593 }
9594
9595 private int consumedCountMap(int i, int initialSize, ArgSpec arg) {
9596 return commandSpec.parser().splitFirst() ? (arg.stringValues().size() - initialSize) / 2 : i;
9597 }
9598
9599 private int consumeOneArgument(ArgSpec argSpec,
9600 LookBehind lookBehind,
9601 Range arity,
9602 int consumed,
9603 String arg,
9604 Class<?> type,
9605 List<Object> result,
9606 int index,
9607 String argDescription) {
9608 if (!lookBehind.isAttached()) { parseResultBuilder.nowProcessing(argSpec, arg); }
9609 String raw = trim(arg);
9610 String[] values = argSpec.splitValue(raw, commandSpec.parser(), arity, consumed);
9611 ITypeConverter<?> converter = getTypeConverter(type, argSpec, 0);
9612 for (int j = 0; j < values.length; j++) {
9613 Object stronglyTypedValue = tryConvert(argSpec, index, converter, values[j], type);
9614 result.add(stronglyTypedValue);
9615 if (tracer.isInfo()) {
9616 tracer.info("Adding [%s] to %s for %s%n", String.valueOf(result.get(result.size() - 1)), argSpec.toString(), argDescription);
9617 }
9618 parseResultBuilder.addStringValue(argSpec, values[j]);
9619 }
9620 parseResultBuilder.addOriginalStringValue(argSpec, raw);
9621 return ++index;
9622 }
9623 private boolean canConsumeOneArgument(ArgSpec argSpec, Range arity, int consumed, String arg, Class<?> type, String argDescription) {
9624 ITypeConverter<?> converter = getTypeConverter(type, argSpec, 0);
9625 try {
9626 String[] values = argSpec.splitValue(trim(arg), commandSpec.parser(), arity, consumed);
9627// if (!argSpec.acceptsValues(values.length, commandSpec.parser())) {
9628// tracer.debug("$s would split into %s values but %s cannot accept that many values.%n", arg, values.length, argDescription);
9629// return false;
9630// }
9631 for (String value : values) {
9632 tryConvert(argSpec, -1, converter, value, type);
9633 }
9634 return true;
9635 } catch (PicocliException ex) {
9636 tracer.debug("$s cannot be assigned to %s: type conversion fails: %s.%n", arg, argDescription, ex.getMessage());
9637 return false;
9638 }
9639 }
9640
9641 /** Returns whether the next argument can be assigned to a vararg option/positional parameter.
9642 * <p>
9643 * Usually, we stop if we encounter '--', a command, or another option.
9644 * However, if end-of-options has been reached, positional parameters may consume all remaining arguments. </p>*/
9645 private boolean varargCanConsumeNextValue(ArgSpec argSpec, String nextValue) {
9646 if (endOfOptions && argSpec.isPositional()) { return true; }
9647 boolean isCommand = commandSpec.subcommands().containsKey(nextValue);
9648 return !isCommand && !isOption(nextValue);
9649 }
9650
9651 /**
9652 * Called when parsing varargs parameters for a multi-value option.
9653 * When an option is encountered, the remainder should not be interpreted as vararg elements.
9654 * @param arg the string to determine whether it is an option or not
9655 * @return true if it is an option, false otherwise
9656 */
9657 private boolean isOption(String arg) {
9658 if (arg == null) { return false; }
9659 if ("--".equals(arg)) { return true; }
9660
9661 // not just arg prefix: we may be in the middle of parsing -xrvfFILE
9662 if (commandSpec.optionsMap().containsKey(arg)) { // -v or -f or --file (not attached to param or other option)
9663 return true;
9664 }
9665 int separatorIndex = arg.indexOf(config().separator());
9666 if (separatorIndex > 0) { // -f=FILE or --file==FILE (attached to param via separator)
9667 if (commandSpec.optionsMap().containsKey(arg.substring(0, separatorIndex))) {
9668 return true;
9669 }
9670 }
9671 return (arg.length() > 2 && arg.startsWith("-") && commandSpec.posixOptionsMap().containsKey(arg.charAt(1)));
9672 }
9673 private Object tryConvert(ArgSpec argSpec, int index, ITypeConverter<?> converter, String value, Class<?> type)
9674 throws ParameterException {
9675 try {
9676 return converter.convert(value);
9677 } catch (TypeConversionException ex) {
9678 String msg = String.format("Invalid value for %s: %s", optionDescription("", argSpec, index), ex.getMessage());
9679 throw new ParameterException(CommandLine.this, msg, argSpec, value);
9680 } catch (Exception other) {
9681 String desc = optionDescription("", argSpec, index);
9682 String msg = String.format("Invalid value for %s: cannot convert '%s' to %s (%s)", desc, value, type.getSimpleName(), other);
9683 throw new ParameterException(CommandLine.this, msg, other, argSpec, value);
9684 }
9685 }
9686
9687 private String optionDescription(String prefix, ArgSpec argSpec, int index) {
9688 String desc = "";
9689 if (argSpec.isOption()) {
9690 desc = prefix + "option '" + ((OptionSpec) argSpec).longestName() + "'";
9691 if (index >= 0) {
9692 if (argSpec.arity().max > 1) {
9693 desc += " at index " + index;
9694 }
9695 desc += " (" + argSpec.paramLabel() + ")";
9696 }
9697 } else {
9698 desc = prefix + "positional parameter at index " + ((PositionalParamSpec) argSpec).index() + " (" + argSpec.paramLabel() + ")";
9699 }
9700 return desc;
9701 }
9702
9703 private boolean isAnyHelpRequested() { return isHelpRequested || parseResultBuilder.versionHelpRequested || parseResultBuilder.usageHelpRequested; }
9704
9705 private void updateHelpRequested(CommandSpec command) {
9706 isHelpRequested |= command.helpCommand();
9707 }
9708 private void updateHelpRequested(ArgSpec argSpec) {
9709 if (!parseResultBuilder.isInitializingDefaultValues && argSpec.isOption()) {
9710 OptionSpec option = (OptionSpec) argSpec;
9711 isHelpRequested |= is(argSpec, "help", option.help());
9712 parseResultBuilder.versionHelpRequested |= is(argSpec, "versionHelp", option.versionHelp());
9713 parseResultBuilder.usageHelpRequested |= is(argSpec, "usageHelp", option.usageHelp());
9714 }
9715 }
9716 private boolean is(ArgSpec p, String attribute, boolean value) {
9717 if (value) { if (tracer.isInfo()) {tracer.info("%s has '%s' annotation: not validating required fields%n", p.toString(), attribute); }}
9718 return value;
9719 }
9720 @SuppressWarnings("unchecked")
9721 private Collection<Object> createCollection(Class<?> collectionClass, Class<?> elementType) throws Exception {
9722 if (EnumSet.class.isAssignableFrom(collectionClass) && Enum.class.isAssignableFrom(elementType)) {
9723 Object enumSet = EnumSet.noneOf((Class<Enum>) elementType);
9724 return (Collection<Object>) enumSet;
9725 }
9726 // custom Collection implementation class must have default constructor
9727 return (Collection<Object>) factory.create(collectionClass);
9728 }
9729 @SuppressWarnings("unchecked") private Map<Object, Object> createMap(Class<?> mapClass) throws Exception {
9730 return (Map<Object, Object>) factory.create(mapClass);
9731 }
9732 private ITypeConverter<?> getTypeConverter(final Class<?> type, ArgSpec argSpec, int index) {
9733 if (argSpec.converters().length > index) { return argSpec.converters()[index]; }
9734 if (converterRegistry.containsKey(type)) { return converterRegistry.get(type); }
9735 if (type.isEnum()) {
9736 return new ITypeConverter<Object>() {
9737 @SuppressWarnings("unchecked")
9738 public Object convert(String value) throws Exception {
9739 String sensitivity = "case-sensitive";
9740 if (commandSpec.parser().caseInsensitiveEnumValuesAllowed()) {
9741 String upper = value.toUpperCase();
9742 for (Object enumConstant : type.getEnumConstants()) {
9743 if (upper.equals(String.valueOf(enumConstant).toUpperCase())) { return enumConstant; }
9744 }
9745 sensitivity = "case-insensitive";
9746 }
9747 try { return Enum.valueOf((Class<Enum>) type, value); }
9748 catch (Exception ex) {
9749 Enum<?>[] constants = ((Class<Enum<?>>) type).getEnumConstants();
9750 String[] names = new String[constants.length];
9751 for (int i = 0; i < names.length; i++) { names[i] = constants[i].name(); }
9752 throw new TypeConversionException(
9753 String.format("expected one of %s (%s) but was '%s'", Arrays.asList(names), sensitivity, value)); }
9754 }
9755 };
9756 }
9757 throw new MissingTypeConverterException(CommandLine.this, "No TypeConverter registered for " + type.getName() + " of " + argSpec);
9758 }
9759
9760 private boolean assertNoMissingParameters(ArgSpec argSpec, Range arity, Stack<String> args) {
9761 if (argSpec.interactive()) { return true; }
9762 int available = args.size();
9763 if (available > 0 && commandSpec.parser().splitFirst() && argSpec.splitRegex().length() > 0) {
9764 available += argSpec.splitValue(args.peek(), commandSpec.parser(), arity, 0).length - 1;
9765 }
9766 if (arity.min > available) {
9767 if (arity.min == 1) {
9768 if (argSpec.isOption()) {
9769 maybeThrow(new MissingParameterException(CommandLine.this, argSpec, "Missing required parameter for " +
9770 optionDescription("", argSpec, 0)));
9771 return false;
9772 }
9773 Range indexRange = ((PositionalParamSpec) argSpec).index();
9774 String sep = "";
9775 String names = ": ";
9776 int count = 0;
9777 List<PositionalParamSpec> positionalParameters = commandSpec.positionalParameters();
9778 for (int i = indexRange.min; i < positionalParameters.size(); i++) {
9779 if (positionalParameters.get(i).arity().min > 0) {
9780 names += sep + positionalParameters.get(i).paramLabel();
9781 sep = ", ";
9782 count++;
9783 }
9784 }
9785 String msg = "Missing required parameter";
9786 Range paramArity = argSpec.arity();
9787 if (count > 1 || arity.min - available > 1) {
9788 msg += "s";
9789 }
9790 maybeThrow(new MissingParameterException(CommandLine.this, argSpec, msg + names));
9791 } else if (args.isEmpty()) {
9792 maybeThrow(new MissingParameterException(CommandLine.this, argSpec, optionDescription("", argSpec, 0) +
9793 " requires at least " + arity.min + " values, but none were specified."));
9794 } else {
9795 maybeThrow(new MissingParameterException(CommandLine.this, argSpec, optionDescription("", argSpec, 0) +
9796 " requires at least " + arity.min + " values, but only " + available + " were specified: " + reverse(args)));
9797 }
9798 return false;
9799 }
9800 return true;
9801 }
9802 private String trim(String value) {
9803 return unquote(value);
9804 }
9805
9806 private String unquote(String value) {
9807 if (!commandSpec.parser().trimQuotes()) { return value; }
9808 return value == null
9809 ? null
9810 : (value.length() > 1 && value.startsWith("\"") && value.endsWith("\""))
9811 ? value.substring(1, value.length() - 1)
9812 : value;
9813 }
9814
9815 char[] readPassword(String prompt) {
9816 try {
9817 Object console = System.class.getDeclaredMethod("console").invoke(null);
9818 Method method = Class.forName("java.io.Console").getDeclaredMethod("readPassword", String.class, Object[].class);
9819 return (char[]) method.invoke(console, prompt, new Object[0]);
9820 } catch (Exception e) {
9821 System.out.print(prompt);
9822 InputStreamReader isr = new InputStreamReader(System.in);
9823 BufferedReader in = new BufferedReader(isr);
9824 try {
9825 return in.readLine().toCharArray();
9826 } catch (IOException ex2) {
9827 throw new IllegalStateException(ex2);
9828 }
9829 }
9830 }
9831 int getPosition(ArgSpec arg) {
9832 if (arg.group() == null) { return position; }
9833 MatchedGroup matchedGroup = parseResultBuilder.matchedGroup.findOrCreateMatchingGroup(arg, commandSpec.commandLine());
9834 return matchedGroup == null ? 0 : matchedGroup.multiple().position;
9835 }
9836 String positionDesc(ArgSpec arg) {
9837 int pos = getPosition(arg);
9838 return (arg.group() == null) ? pos + " (command-local)" : pos + " (in group " + arg.group().synopsis() + ")";
9839 }
9840 }
9841 private static class PositionalParametersSorter implements Comparator<ArgSpec> {
9842 private static final Range OPTION_INDEX = new Range(0, 0, false, true, "0");
9843 public int compare(ArgSpec p1, ArgSpec p2) {
9844 int result = index(p1).compareTo(index(p2));
9845 return (result == 0) ? p1.arity().compareTo(p2.arity()) : result;
9846 }
9847 private Range index(ArgSpec arg) { return arg.isOption() ? OPTION_INDEX : ((PositionalParamSpec) arg).index(); }
9848 }
9849 /**
9850 * Inner class to group the built-in {@link ITypeConverter} implementations.
9851 */
9852 private static class BuiltIn {
9853 static class StringConverter implements ITypeConverter<String> {
9854 public String convert(String value) { return value; }
9855 }
9856 static class StringBuilderConverter implements ITypeConverter<StringBuilder> {
9857 public StringBuilder convert(String value) { return new StringBuilder(value); }
9858 }
9859 static class CharSequenceConverter implements ITypeConverter<CharSequence> {
9860 public String convert(String value) { return value; }
9861 }
9862 /** Converts {@code "true"} or {@code "false"} to a {@code Boolean}. Other values result in a ParameterException.*/
9863 static class BooleanConverter implements ITypeConverter<Boolean> {
9864 public Boolean convert(String value) {
9865 if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) {
9866 return Boolean.parseBoolean(value);
9867 } else {
9868 throw new TypeConversionException("'" + value + "' is not a boolean");
9869 }
9870 }
9871 }
9872 static class CharacterConverter implements ITypeConverter<Character> {
9873 public Character convert(String value) {
9874 if (value.length() > 1) {
9875 throw new TypeConversionException("'" + value + "' is not a single character");
9876 }
9877 return value.charAt(0);
9878 }
9879 }
9880 private static TypeConversionException fail(String value, Class<?> c) { return fail(value, c, "'%s' is not a %s"); }
9881 private static TypeConversionException fail(String value, Class<?> c, String template) {
9882 return new TypeConversionException(String.format(template, value, c.getSimpleName()));
9883 }
9884 /** Converts text to a {@code Byte} by delegating to {@link Byte#valueOf(String)}.*/
9885 static class ByteConverter implements ITypeConverter<Byte> {
9886 public Byte convert(String value) { try {return Byte.valueOf(value);} catch (Exception ex) {throw fail(value, Byte.TYPE);} }
9887 }
9888 /** Converts text to a {@code Short} by delegating to {@link Short#valueOf(String)}.*/
9889 static class ShortConverter implements ITypeConverter<Short> {
9890 public Short convert(String value) { try {return Short.valueOf(value);} catch (Exception ex) {throw fail(value, Short.TYPE);} }
9891 }
9892 /** Converts text to an {@code Integer} by delegating to {@link Integer#valueOf(String)}.*/
9893 static class IntegerConverter implements ITypeConverter<Integer> {
9894 public Integer convert(String value) { try {return Integer.valueOf(value);} catch (Exception ex) {throw fail(value, Integer.TYPE, "'%s' is not an %s");} }
9895 }
9896 /** Converts text to a {@code Long} by delegating to {@link Long#valueOf(String)}.*/
9897 static class LongConverter implements ITypeConverter<Long> {
9898 public Long convert(String value) { try {return Long.valueOf(value);} catch (Exception ex) {throw fail(value, Long.TYPE);} }
9899 }
9900 static class FloatConverter implements ITypeConverter<Float> {
9901 public Float convert(String value) { try {return Float.valueOf(value);} catch (Exception ex) {throw fail(value, Float.TYPE);} }
9902 }
9903 static class DoubleConverter implements ITypeConverter<Double> {
9904 public Double convert(String value) { try {return Double.valueOf(value);} catch (Exception ex) {throw fail(value, Double.TYPE);} }
9905 }
9906 static class FileConverter implements ITypeConverter<File> {
9907 public File convert(String value) { return new File(value); }
9908 }
9909 static class URLConverter implements ITypeConverter<URL> {
9910 public URL convert(String value) throws MalformedURLException { return new URL(value); }
9911 }
9912 static class URIConverter implements ITypeConverter<URI> {
9913 public URI convert(String value) throws URISyntaxException { return new URI(value); }
9914 }
9915 /** Converts text in {@code yyyy-mm-dd} format to a {@code java.util.Date}. ParameterException on failure. */
9916 static class ISO8601DateConverter implements ITypeConverter<Date> {
9917 public Date convert(String value) {
9918 try {
9919 return new SimpleDateFormat("yyyy-MM-dd").parse(value);
9920 } catch (ParseException e) {
9921 throw new TypeConversionException("'" + value + "' is not a yyyy-MM-dd date");
9922 }
9923 }
9924 }
9925 /** Converts text in any of the following formats to a {@code java.sql.Time}: {@code HH:mm}, {@code HH:mm:ss},
9926 * {@code HH:mm:ss.SSS}, {@code HH:mm:ss,SSS}. Other formats result in a ParameterException. */
9927 static class ISO8601TimeConverter implements ITypeConverter<Object> {
9928 // Implementation note: use reflection so that picocli only requires the java.base module in Java 9.
9929 private static /*final*/ String FQCN = "java.sql.Time"; // non-final for testing
9930 public Object convert(String value) {
9931 try {
9932 if (value.length() <= 5) {
9933 return createTime(new SimpleDateFormat("HH:mm").parse(value).getTime());
9934 } else if (value.length() <= 8) {
9935 return createTime(new SimpleDateFormat("HH:mm:ss").parse(value).getTime());
9936 } else if (value.length() <= 12) {
9937 try {
9938 return createTime(new SimpleDateFormat("HH:mm:ss.SSS").parse(value).getTime());
9939 } catch (ParseException e2) {
9940 return createTime(new SimpleDateFormat("HH:mm:ss,SSS").parse(value).getTime());
9941 }
9942 }
9943 } catch (ParseException ignored) {
9944 // ignored because we throw a ParameterException below
9945 }
9946 throw new TypeConversionException("'" + value + "' is not a HH:mm[:ss[.SSS]] time");
9947 }
9948
9949 private Object createTime(long epochMillis) {
9950 try {
9951 Class<?> timeClass = Class.forName(FQCN);
9952 Constructor<?> constructor = timeClass.getDeclaredConstructor(long.class);
9953 return constructor.newInstance(epochMillis);
9954 } catch (Exception e) {
9955 throw new TypeConversionException("Unable to create new java.sql.Time with long value " + epochMillis + ": " + e.getMessage());
9956 }
9957 }
9958
9959 public static void registerIfAvailable(Map<Class<?>, ITypeConverter<?>> registry, Tracer tracer) {
9960 if (excluded(FQCN, tracer)) { return; }
9961 try {
9962 registry.put(Class.forName(FQCN), new ISO8601TimeConverter());
9963 } catch (Exception e) {
9964 if (!traced.contains(FQCN)) {
9965 tracer.debug("Could not register converter for %s: %s%n", FQCN, e.toString());
9966 }
9967 traced.add(FQCN);
9968 }
9969 }
9970 }
9971 static class BigDecimalConverter implements ITypeConverter<BigDecimal> {
9972 public BigDecimal convert(String value) { return new BigDecimal(value); }
9973 }
9974 static class BigIntegerConverter implements ITypeConverter<BigInteger> {
9975 public BigInteger convert(String value) { return new BigInteger(value); }
9976 }
9977 static class CharsetConverter implements ITypeConverter<Charset> {
9978 public Charset convert(String s) { return Charset.forName(s); }
9979 }
9980 /** Converts text to a {@code InetAddress} by delegating to {@link InetAddress#getByName(String)}. */
9981 static class InetAddressConverter implements ITypeConverter<InetAddress> {
9982 public InetAddress convert(String s) throws Exception { return InetAddress.getByName(s); }
9983 }
9984 static class PatternConverter implements ITypeConverter<Pattern> {
9985 public Pattern convert(String s) { return Pattern.compile(s); }
9986 }
9987 static class UUIDConverter implements ITypeConverter<UUID> {
9988 public UUID convert(String s) throws Exception { return UUID.fromString(s); }
9989 }
9990 static class CurrencyConverter implements ITypeConverter<Currency> {
9991 public Currency convert(String s) throws Exception { return Currency.getInstance(s); }
9992 }
9993 static class TimeZoneConverter implements ITypeConverter<TimeZone> {
9994 public TimeZone convert(String s) throws Exception { return TimeZone.getTimeZone(s); }
9995 }
9996 static class ByteOrderConverter implements ITypeConverter<ByteOrder> {
9997 public ByteOrder convert(String s) throws Exception {
9998 if (s.equalsIgnoreCase(ByteOrder.BIG_ENDIAN.toString())) { return ByteOrder.BIG_ENDIAN; }
9999 if (s.equalsIgnoreCase(ByteOrder.LITTLE_ENDIAN.toString())) { return ByteOrder.LITTLE_ENDIAN; }
10000 throw new TypeConversionException("'" + s + "' is not a valid ByteOrder");
10001 }
10002 }
10003 static class ClassConverter implements ITypeConverter<Class<?>> {
10004 public Class<?> convert(String s) throws Exception { return Class.forName(s); }
10005 }
10006 static class NetworkInterfaceConverter implements ITypeConverter<NetworkInterface> {
10007 public NetworkInterface convert(String s) throws Exception {
10008 try {
10009 InetAddress addr = new InetAddressConverter().convert(s);
10010 return NetworkInterface.getByInetAddress(addr);
10011 } catch (Exception ex) {
10012 try { return NetworkInterface.getByName(s);
10013 } catch (Exception ex2) {
10014 throw new TypeConversionException("'" + s + "' is not an InetAddress or NetworkInterface name");
10015 }
10016 }
10017 }
10018 }
10019 static void registerIfAvailable(Map<Class<?>, ITypeConverter<?>> registry, Tracer tracer, String fqcn, String factoryMethodName, Class<?>... paramTypes) {
10020 registerIfAvailable(registry, tracer, fqcn, fqcn, factoryMethodName, paramTypes);
10021 }
10022 static void registerIfAvailable(Map<Class<?>, ITypeConverter<?>> registry, Tracer tracer, String fqcn, String factoryClass, String factoryMethodName, Class<?>... paramTypes) {
10023 if (excluded(fqcn, tracer)) { return; }
10024 try {
10025 Class<?> cls = Class.forName(fqcn);
10026 Class<?> factory = Class.forName(factoryClass);
10027 Method method = factory.getDeclaredMethod(factoryMethodName, paramTypes);
10028 registry.put(cls, new ReflectionConverter(method, paramTypes));
10029 } catch (Exception e) {
10030 if (!traced.contains(fqcn)) {
10031 tracer.debug("Could not register converter for %s: %s%n", fqcn, e.toString());
10032 }
10033 traced.add(fqcn);
10034 }
10035 }
10036 static boolean excluded(String fqcn, Tracer tracer) {
10037 String[] excludes = System.getProperty("picocli.converters.excludes", "").split(",");
10038 for (String regex : excludes) {
10039 if (fqcn.matches(regex)) {
10040 tracer.debug("BuiltIn type converter for %s is not loaded: (picocli.converters.excludes=%s)%n", fqcn, System.getProperty("picocli.converters.excludes"));
10041 return true;
10042 }
10043 }
10044 return false;
10045 }
10046 static Set<String> traced = new HashSet<String>();
10047 static class ReflectionConverter implements ITypeConverter<Object> {
10048 private final Method method;
10049 private Class<?>[] paramTypes;
10050
10051 public ReflectionConverter(Method method, Class<?>... paramTypes) {
10052 this.method = Assert.notNull(method, "method");
10053 this.paramTypes = Assert.notNull(paramTypes, "paramTypes");
10054 }
10055
10056 public Object convert(String s) {
10057 try {
10058 if (paramTypes.length > 1) {
10059 return method.invoke(null, s, new String[0]);
10060 } else {
10061 return method.invoke(null, s);
10062 }
10063 } catch (InvocationTargetException e) {
10064 throw new TypeConversionException(String.format("cannot convert '%s' to %s (%s)", s, method.getReturnType(), e.getTargetException()));
10065 } catch (Exception e) {
10066 throw new TypeConversionException(String.format("Internal error converting '%s' to %s (%s)", s, method.getReturnType(), e));
10067 }
10068 }
10069 }
10070 private BuiltIn() {} // private constructor: never instantiate
10071 }
10072
10073 static class AutoHelpMixin {
10074 private static final String KEY = "mixinStandardHelpOptions";
10075
10076 @Option(names = {"-h", "--help"}, usageHelp = true, descriptionKey = "mixinStandardHelpOptions.help",
10077 description = "Show this help message and exit.")
10078 private boolean helpRequested;
10079
10080 @Option(names = {"-V", "--version"}, versionHelp = true, descriptionKey = "mixinStandardHelpOptions.version",
10081 description = "Print version information and exit.")
10082 private boolean versionRequested;
10083 }
10084
10085 /** Help command that can be installed as a subcommand on all application commands. When invoked with a subcommand
10086 * argument, it prints usage help for the specified subcommand. For example:<pre>
10087 *
10088 * // print help for subcommand
10089 * command help subcommand
10090 * </pre><p>
10091 * When invoked without additional parameters, it prints usage help for the parent command. For example:
10092 * </p><pre>
10093 *
10094 * // print help for command
10095 * command help
10096 * </pre>
10097 * For {@linkplain Messages internationalization}: this command has a {@code --help} option with {@code descriptionKey = "helpCommand.help"},
10098 * and a {@code COMMAND} positional parameter with {@code descriptionKey = "helpCommand.command"}.
10099 * @since 3.0
10100 */
10101 @Command(name = "help", header = "Displays help information about the specified command",
10102 synopsisHeading = "%nUsage: ", helpCommand = true,
10103 description = {"%nWhen no COMMAND is given, the usage help for the main command is displayed.",
10104 "If a COMMAND is specified, the help for that command is shown.%n"})
10105 public static final class HelpCommand implements IHelpCommandInitializable, Runnable {
10106
10107 @Option(names = {"-h", "--help"}, usageHelp = true, descriptionKey = "helpCommand.help",
10108 description = "Show usage help for the help command and exit.")
10109 private boolean helpRequested;
10110
10111 @Parameters(paramLabel = "COMMAND", descriptionKey = "helpCommand.command",
10112 description = "The COMMAND to display the usage help message for.")
10113 private String[] commands = new String[0];
10114
10115 private CommandLine self;
10116 private PrintStream out;
10117 private PrintStream err;
10118 private Help.Ansi ansi;
10119
10120 /** Invokes {@link #usage(PrintStream, Help.Ansi) usage} for the specified command, or for the parent command. */
10121 public void run() {
10122 CommandLine parent = self == null ? null : self.getParent();
10123 if (parent == null) { return; }
10124 if (commands.length > 0) {
10125 CommandLine subcommand = parent.getSubcommands().get(commands[0]);
10126 if (subcommand != null) {
10127 subcommand.usage(out, ansi);
10128 } else {
10129 throw new ParameterException(parent, "Unknown subcommand '" + commands[0] + "'.", null, commands[0]);
10130 }
10131 } else {
10132 parent.usage(out, ansi);
10133 }
10134 }
10135 /** {@inheritDoc} */
10136 public void init(CommandLine helpCommandLine, Help.Ansi ansi, PrintStream out, PrintStream err) {
10137 this.self = Assert.notNull(helpCommandLine, "helpCommandLine");
10138 this.ansi = Assert.notNull(ansi, "ansi");
10139 this.out = Assert.notNull(out, "out");
10140 this.err = Assert.notNull(err, "err");
10141 }
10142 }
10143
10144 /** Help commands that provide usage help for other commands can implement this interface to be initialized with the information they need.
10145 * <p>The {@link #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi) CommandLine::printHelpIfRequested} method calls the
10146 * {@link #init(CommandLine, picocli.CommandLine.Help.Ansi, PrintStream, PrintStream) init} method on commands marked as {@link Command#helpCommand() helpCommand}
10147 * before the help command's {@code run} or {@code call} method is called.</p>
10148 * <p><b>Implementation note:</b></p><p>
10149 * If an error occurs in the {@code run} or {@code call} method while processing the help request, it is recommended custom Help
10150 * commands throw a {@link ParameterException ParameterException} with a reference to the parent command. The {@link DefaultExceptionHandler DefaultExceptionHandler} will print
10151 * the error message and the usage for the parent command, and will terminate with the exit code of the exception handler if one was set.
10152 * </p>
10153 * @since 3.0 */
10154 public static interface IHelpCommandInitializable {
10155 /** Initializes this object with the information needed to implement a help command that provides usage help for other commands.
10156 * @param helpCommandLine the {@code CommandLine} object associated with this help command. Implementors can use
10157 * this to walk the command hierarchy and get access to the help command's parent and sibling commands.
10158 * @param ansi whether to use Ansi colors or not
10159 * @param out the stream to print the usage help message to
10160 * @param err the error stream to print any diagnostic messages to, in addition to the output from the exception handler
10161 */
10162 void init(CommandLine helpCommandLine, Help.Ansi ansi, PrintStream out, PrintStream err);
10163 }
10164
10165 /**
10166 * Renders a section of the usage help message. The usage help message can be customized:
10167 * use the {@link #setHelpSectionKeys(List)} and {@link #setHelpSectionMap(Map)} to change the order of sections,
10168 * delete standard sections, add custom sections or replace the renderer of a standard sections with a custom one.
10169 * <p>
10170 * This gives complete freedom on how a usage help message section is rendered, but it also means that the section renderer
10171 * is responsible for all aspects of rendering the section, including layout and emitting ANSI escape codes.
10172 * The {@link Help.TextTable} and {@link Help.Ansi.Text} classes, and the {@link CommandLine.Help.Ansi#string(String)} and {@link CommandLine.Help.Ansi#text(String)} methods may be useful.
10173 * </p>
10174 * @see UsageMessageSpec
10175 * @since 3.9
10176 */
10177 public interface IHelpSectionRenderer {
10178 /**
10179 * Renders a section of the usage help, like header heading, header, synopsis heading,
10180 * synopsis, description heading, description, etc.
10181 * @param help the {@code Help} instance for which to render a section
10182 * @return the text for this section; may contain {@linkplain Help.Ansi ANSI} escape codes
10183 * @since 3.9
10184 */
10185 String render(Help help);
10186 }
10187
10188 /**
10189 * A collection of methods and inner classes that provide fine-grained control over the contents and layout of
10190 * the usage help message to display to end users when help is requested or invalid input values were specified.
10191 * <h2>Class Diagram of the CommandLine.Help API</h2>
10192 * <p>
10193 * <img src="doc-files/class-diagram-help-api.png" alt="Class Diagram of the CommandLine.Help API">
10194 * </p>
10195 * <h2>Layered API</h2>
10196 * <p>The {@link Command} annotation and the {@link UsageMessageSpec} programmatic API equivalent
10197 * provide the easiest way to configure the usage help message. See
10198 * the <a href="https://remkop.github.io/picocli/index.html#_usage_help">Manual</a> for details.</p>
10199 * <p>This Help class provides high-level functions to create sections of the usage help message and headings
10200 * for these sections. Instead of calling the {@link CommandLine#usage(PrintStream, CommandLine.Help.ColorScheme)}
10201 * method, application authors may want to create a custom usage help message by reorganizing sections in a
10202 * different order and/or adding custom sections.</p>
10203 * <p>Finally, the Help class contains inner classes and interfaces that can be used to create custom help messages.</p>
10204 * <h3>IOptionRenderer and IParameterRenderer</h3>
10205 * <p>Renders a field annotated with {@link Option} or {@link Parameters} to an array of {@link Text} values.
10206 * By default, these values are</p><ul>
10207 * <li>mandatory marker character (if the option/parameter is {@link Option#required() required})</li>
10208 * <li>short option name (empty for parameters)</li>
10209 * <li>comma or empty (empty for parameters)</li>
10210 * <li>long option names (the parameter {@link IParamLabelRenderer label} for parameters)</li>
10211 * <li>description</li>
10212 * </ul>
10213 * <p>Other components rely on this ordering.</p>
10214 * <h3>Layout</h3>
10215 * <p>Delegates to the renderers to create {@link Text} values for the annotated fields, and uses a
10216 * {@link TextTable} to display these values in tabular format. Layout is responsible for deciding which values
10217 * to display where in the table. By default, Layout shows one option or parameter per table row.</p>
10218 * <h3>TextTable</h3>
10219 * <p>Responsible for spacing out {@link Text} values according to the {@link Column} definitions the table was
10220 * created with. Columns have a width, indentation, and an overflow policy that decides what to do if a value is
10221 * longer than the column's width.</p>
10222 * <h3>Text</h3>
10223 * <p>Encapsulates rich text with styles and colors in a way that other components like {@link TextTable} are
10224 * unaware of the embedded ANSI escape codes.</p>
10225 */
10226 public static class Help {
10227
10228 /** Constant String holding the default program name, value defined in {@link CommandSpec#DEFAULT_COMMAND_NAME}. */
10229 protected static final String DEFAULT_COMMAND_NAME = CommandSpec.DEFAULT_COMMAND_NAME;
10230
10231 /** Constant String holding the default string that separates options from option parameters, value defined in {@link ParserSpec#DEFAULT_SEPARATOR}. */
10232 protected static final String DEFAULT_SEPARATOR = ParserSpec.DEFAULT_SEPARATOR;
10233
10234 private final static int defaultOptionsColumnWidth = 24;
10235 private final CommandSpec commandSpec;
10236 private final ColorScheme colorScheme;
10237 private final Map<String, Help> commands = new LinkedHashMap<String, Help>();
10238 private List<String> aliases = Collections.emptyList();
10239
10240 private IParamLabelRenderer parameterLabelRenderer;
10241
10242 /** Constructs a new {@code Help} instance with a default color scheme, initialized from annotatations
10243 * on the specified class and superclasses.
10244 * @param command the annotated object to create usage help for */
10245 public Help(Object command) {
10246 this(command, Ansi.AUTO);
10247 }
10248
10249 /** Constructs a new {@code Help} instance with a default color scheme, initialized from annotatations
10250 * on the specified class and superclasses.
10251 * @param command the annotated object to create usage help for
10252 * @param ansi whether to emit ANSI escape codes or not */
10253 public Help(Object command, Ansi ansi) {
10254 this(command, defaultColorScheme(ansi));
10255 }
10256 /** Constructs a new {@code Help} instance with the specified color scheme, initialized from annotatations
10257 * on the specified class and superclasses.
10258 * @param command the annotated object to create usage help for
10259 * @param colorScheme the color scheme to use
10260 * @deprecated use {@link picocli.CommandLine.Help#Help(picocli.CommandLine.Model.CommandSpec, picocli.CommandLine.Help.ColorScheme)} */
10261 @Deprecated public Help(Object command, ColorScheme colorScheme) {
10262 this(CommandSpec.forAnnotatedObject(command, new DefaultFactory()), colorScheme);
10263 }
10264 /** Constructs a new {@code Help} instance with the specified color scheme, initialized from annotatations
10265 * on the specified class and superclasses.
10266 * @param commandSpec the command model to create usage help for
10267 * @param colorScheme the color scheme to use */
10268 public Help(CommandSpec commandSpec, ColorScheme colorScheme) {
10269 this.commandSpec = Assert.notNull(commandSpec, "commandSpec");
10270 this.aliases = new ArrayList<String>(Arrays.asList(commandSpec.aliases()));
10271 this.aliases.add(0, commandSpec.name());
10272 this.colorScheme = Assert.notNull(colorScheme, "colorScheme").applySystemProperties();
10273 parameterLabelRenderer = createDefaultParamLabelRenderer(); // uses help separator
10274
10275 this.addAllSubcommands(commandSpec.subcommands());
10276 }
10277
10278 Help withCommandNames(List<String> aliases) { this.aliases = aliases; return this; }
10279
10280 /** Returns the {@code CommandSpec} model that this Help was constructed with.
10281 * @since 3.9 */
10282 public CommandSpec commandSpec() { return commandSpec; }
10283
10284 /** Returns the {@code ColorScheme} model that this Help was constructed with.
10285 * @since 3.0 */
10286 public ColorScheme colorScheme() { return colorScheme; }
10287
10288 /** Returns the {@code IHelpFactory} that this Help was constructed with.
10289 * @since 3.9 */
10290 private IHelpFactory getHelpFactory() { return commandSpec.usageMessage().helpFactory(); }
10291
10292 /** Returns the map of subcommand {@code Help} instances for this command Help.
10293 * @since 3.9 */
10294 protected Map<String, Help> subcommands() { return Collections.unmodifiableMap(commands); }
10295
10296 /** Returns the list of aliases for the command in this Help.
10297 * @since 3.9 */
10298 protected List<String> aliases() { return Collections.unmodifiableList(aliases); }
10299
10300 /** Option and positional parameter value label renderer used for the synopsis line(s) and the option list.
10301 * By default initialized to the result of {@link #createDefaultParamLabelRenderer()}, which takes a snapshot
10302 * of the {@link ParserSpec#separator()} at construction time. If the separator is modified after Help construction, you
10303 * may need to re-initialize this field by calling {@link #createDefaultParamLabelRenderer()} again. */
10304 public IParamLabelRenderer parameterLabelRenderer() {return parameterLabelRenderer;}
10305
10306 /** Registers all specified subcommands with this Help.
10307 * @param commands maps the command names to the associated CommandLine object
10308 * @return this Help instance (for method chaining)
10309 * @see CommandLine#getSubcommands()
10310 */
10311 public Help addAllSubcommands(Map<String, CommandLine> commands) {
10312 if (commands != null) {
10313 // first collect aliases
10314 Map<CommandLine, List<String>> done = new IdentityHashMap<CommandLine, List<String>>();
10315 for (CommandLine cmd : commands.values()) {
10316 if (!done.containsKey(cmd)) {
10317 done.put(cmd, new ArrayList<String>(Arrays.asList(cmd.commandSpec.aliases())));
10318 }
10319 }
10320 // then loop over all names that the command was registered with and add this name to the front of the list (if it isn't already in the list)
10321 for (Map.Entry<String, CommandLine> entry : commands.entrySet()) {
10322 List<String> aliases = done.get(entry.getValue());
10323 if (!aliases.contains(entry.getKey())) { aliases.add(0, entry.getKey()); }
10324 }
10325 // The aliases list for each command now has at least one entry, with the main name at the front.
10326 // Now we loop over the commands in the order that they were registered on their parent command.
10327 for (Map.Entry<String, CommandLine> entry : commands.entrySet()) {
10328 // not registering hidden commands is easier than suppressing display in Help.commandList():
10329 // if all subcommands are hidden, help should not show command list header
10330 if (!entry.getValue().getCommandSpec().usageMessage().hidden()) {
10331 List<String> aliases = done.remove(entry.getValue());
10332 if (aliases != null) { // otherwise we already processed this command by another alias
10333 addSubcommand(aliases, entry.getValue());
10334 }
10335 }
10336 }
10337 }
10338 return this;
10339 }
10340
10341 /** Registers the specified subcommand with this Help.
10342 * @param commandNames the name and aliases of the subcommand to display in the usage message
10343 * @param commandLine the {@code CommandLine} object to get more information from
10344 * @return this Help instance (for method chaining) */
10345 Help addSubcommand(List<String> commandNames, CommandLine commandLine) {
10346 String all = commandNames.toString();
10347 commands.put(all.substring(1, all.length() - 1), getHelpFactory().create(commandLine.commandSpec, colorScheme).withCommandNames(commandNames));
10348 return this;
10349 }
10350
10351 /** Registers the specified subcommand with this Help.
10352 * @param commandName the name of the subcommand to display in the usage message
10353 * @param command the {@code CommandSpec} or {@code @Command} annotated object to get more information from
10354 * @return this Help instance (for method chaining)
10355 * @deprecated
10356 */
10357 @Deprecated public Help addSubcommand(String commandName, Object command) {
10358 commands.put(commandName,
10359 getHelpFactory().create(CommandSpec.forAnnotatedObject(command, commandSpec.commandLine().factory), defaultColorScheme(Ansi.AUTO)));
10360 return this;
10361 }
10362
10363 List<OptionSpec> options() { return commandSpec.options(); }
10364 List<PositionalParamSpec> positionalParameters() { return commandSpec.positionalParameters(); }
10365 String commandName() { return commandSpec.name(); }
10366
10367 /** Returns a synopsis for the command without reserving space for the synopsis heading.
10368 * @return a synopsis
10369 * @see #abbreviatedSynopsis()
10370 * @see #detailedSynopsis(Comparator, boolean)
10371 * @deprecated use {@link #synopsis(int)} instead
10372 */
10373 @Deprecated public String synopsis() { return synopsis(0); }
10374
10375 /**
10376 * Returns a synopsis for the command, reserving the specified space for the synopsis heading.
10377 * @param synopsisHeadingLength the length of the synopsis heading that will be displayed on the same line
10378 * @return a synopsis
10379 * @see #abbreviatedSynopsis()
10380 * @see #detailedSynopsis(Comparator, boolean)
10381 * @see #synopsisHeading
10382 */
10383 public String synopsis(int synopsisHeadingLength) {
10384 if (!empty(commandSpec.usageMessage().customSynopsis())) { return customSynopsis(); }
10385 return commandSpec.usageMessage().abbreviateSynopsis() ? abbreviatedSynopsis()
10386 : detailedSynopsis(synopsisHeadingLength, createShortOptionArityAndNameComparator(), true);
10387 }
10388
10389 /** Generates a generic synopsis like {@code <command name> [OPTIONS] [PARAM1 [PARAM2]...]}, omitting parts
10390 * that don't apply to the command (e.g., does not show [OPTIONS] if the command has no options).
10391 * @return a generic synopsis */
10392 public String abbreviatedSynopsis() {
10393 StringBuilder sb = new StringBuilder();
10394 if (!commandSpec.optionsMap().isEmpty()) { // only show if annotated object actually has options
10395 sb.append(" [OPTIONS]");
10396 }
10397 // sb.append(" [--] "); // implied
10398 for (PositionalParamSpec positionalParam : commandSpec.positionalParameters()) {
10399 if (!positionalParam.hidden()) {
10400 sb.append(' ').append(parameterLabelRenderer().renderParameterLabel(positionalParam, ansi(), colorScheme.parameterStyles));
10401 }
10402 }
10403
10404 // only show if object has subcommands
10405 if (!commandSpec.subcommands().isEmpty()) {
10406 sb.append(" [COMMAND]");
10407 }
10408
10409 return colorScheme.commandText(commandSpec.qualifiedName()).toString()
10410 + (sb.toString()) + System.getProperty("line.separator");
10411 }
10412 /** Generates a detailed synopsis message showing all options and parameters. Follows the unix convention of
10413 * showing optional options and parameters in square brackets ({@code [ ]}).
10414 * @param optionSort comparator to sort options or {@code null} if options should not be sorted
10415 * @param clusterBooleanOptions {@code true} if boolean short options should be clustered into a single string
10416 * @return a detailed synopsis
10417 * @deprecated use {@link #detailedSynopsis(int, Comparator, boolean)} instead. */
10418 @Deprecated public String detailedSynopsis(Comparator<OptionSpec> optionSort, boolean clusterBooleanOptions) {
10419 return detailedSynopsis(0, optionSort, clusterBooleanOptions);
10420 }
10421
10422 /** Generates a detailed synopsis message showing all options and parameters. Follows the unix convention of
10423 * showing optional options and parameters in square brackets ({@code [ ]}).
10424 * @param synopsisHeadingLength the length of the synopsis heading that will be displayed on the same line
10425 * @param optionSort comparator to sort options or {@code null} if options should not be sorted
10426 * @param clusterBooleanOptions {@code true} if boolean short options should be clustered into a single string
10427 * @return a detailed synopsis
10428 * @since 3.0 */
10429 public String detailedSynopsis(int synopsisHeadingLength, Comparator<OptionSpec> optionSort, boolean clusterBooleanOptions) {
10430 Set<ArgSpec> argsInGroups = new HashSet<ArgSpec>();
10431 Text groupsText = createDetailedSynopsisGroupsText(argsInGroups);
10432 Text optionText = createDetailedSynopsisOptionsText(argsInGroups, optionSort, clusterBooleanOptions);
10433 Text positionalParamText = createDetailedSynopsisPositionalsText(argsInGroups);
10434 Text commandText = createDetailedSynopsisCommandText();
10435
10436 Text text = groupsText.concat(optionText).concat(positionalParamText).concat(commandText);
10437
10438 return insertSynopsisCommandName(synopsisHeadingLength, text);
10439 }
10440
10441 /** Returns a Text object containing a partial detailed synopsis showing only the options and positional parameters in
10442 * the specified {@linkplain ArgGroup#validate() validating} {@linkplain ArgGroup groups}, starting with a {@code " "} space.
10443 * @param outparam_groupArgs all options and positional parameters in the groups this method generates a synopsis for;
10444 * these options and positional parameters should be excluded from appearing elsewhere in the synopsis
10445 * @return the formatted groups synopsis elements, starting with a {@code " "} space, or an empty Text if this command has no validating groups
10446 * @since 4.0 */
10447 protected Text createDetailedSynopsisGroupsText(Set<ArgSpec> outparam_groupArgs) {
10448 Set<ArgGroupSpec> remove = new HashSet<ArgGroupSpec>();
10449 List<ArgGroupSpec> groups = new ArrayList<ArgGroupSpec>(commandSpec().argGroups());
10450 for (ArgGroupSpec group : groups) {
10451 if (group.validate()) {
10452 // remove subgroups
10453 remove.addAll(group.subgroups());
10454
10455 // exclude options and positional parameters in this group
10456 outparam_groupArgs.addAll(group.args());
10457
10458 // exclude options and positional parameters in the subgroups
10459 for (ArgGroupSpec subgroup : group.subgroups()) {
10460 outparam_groupArgs.addAll(subgroup.args());
10461 }
10462 } else {
10463 remove.add(group); // non-validating groups should not impact synopsis
10464 }
10465 }
10466 groups.removeAll(remove);
10467 Text groupText = ansi().new Text(0);
10468 for (ArgGroupSpec group : groups) {
10469 groupText = groupText.concat(" ").concat(group.synopsisText(colorScheme()));
10470 }
10471 return groupText;
10472 }
10473 /** Returns a Text object containing a partial detailed synopsis showing only the options, starting with a {@code " "} space.
10474 * Follows the unix convention of showing optional options and parameters in square brackets ({@code [ ]}).
10475 * @param done the list of options and positional parameters for which a synopsis was already generated. Options in this set should be excluded.
10476 * @param optionSort comparator to sort options or {@code null} if options should not be sorted
10477 * @param clusterBooleanOptions {@code true} if boolean short options should be clustered into a single string
10478 * @return the formatted options, starting with a {@code " "} space, or an empty Text if this command has no named options
10479 * @since 3.9 */
10480 protected Text createDetailedSynopsisOptionsText(Collection<ArgSpec> done, Comparator<OptionSpec> optionSort, boolean clusterBooleanOptions) {
10481 Text optionText = ansi().new Text(0);
10482 List<OptionSpec> options = new ArrayList<OptionSpec>(commandSpec.options()); // iterate in declaration order
10483 if (optionSort != null) {
10484 Collections.sort(options, optionSort);// iterate in specified sort order
10485 }
10486 options.removeAll(done);
10487 if (clusterBooleanOptions) { // cluster all short boolean options into a single string
10488 List<OptionSpec> booleanOptions = new ArrayList<OptionSpec>();
10489 StringBuilder clusteredRequired = new StringBuilder("-");
10490 StringBuilder clusteredOptional = new StringBuilder("-");
10491 for (OptionSpec option : options) {
10492 if (option.hidden()) { continue; }
10493 boolean isFlagOption = option.typeInfo().isBoolean();
10494 if (isFlagOption && option.arity().max <= 0) { // #612 consider arity: boolean options may require a parameter
10495 String shortestName = option.shortestName();
10496 if (shortestName.length() == 2 && shortestName.startsWith("-")) {
10497 booleanOptions.add(option);
10498 if (option.required()) {
10499 clusteredRequired.append(shortestName.substring(1));
10500 } else {
10501 clusteredOptional.append(shortestName.substring(1));
10502 }
10503 }
10504 }
10505 }
10506 options.removeAll(booleanOptions);
10507 if (clusteredRequired.length() > 1) { // initial length was 1
10508 optionText = optionText.concat(" ").concat(colorScheme.optionText(clusteredRequired.toString()));
10509 }
10510 if (clusteredOptional.length() > 1) { // initial length was 1
10511 optionText = optionText.concat(" [").concat(colorScheme.optionText(clusteredOptional.toString())).concat("]");
10512 }
10513 }
10514 for (OptionSpec option : options) {
10515 if (!option.hidden()) {
10516 Text name = colorScheme.optionText(option.shortestName());
10517 Text param = parameterLabelRenderer().renderParameterLabel(option, colorScheme.ansi(), colorScheme.optionParamStyles);
10518 if (option.required()) { // e.g., -x=VAL
10519 optionText = optionText.concat(" ").concat(name).concat(param).concat("");
10520 if (option.isMultiValue()) { // e.g., -x=VAL [-x=VAL]...
10521 optionText = optionText.concat(" [").concat(name).concat(param).concat("]...");
10522 }
10523 } else {
10524 optionText = optionText.concat(" [").concat(name).concat(param).concat("]");
10525 if (option.isMultiValue()) { // add ellipsis to show option is repeatable
10526 optionText = optionText.concat("...");
10527 }
10528 }
10529 }
10530 }
10531 return optionText;
10532 }
10533
10534 /** Returns a Text object containing a partial detailed synopsis showing only the positional parameters, starting with a {@code " "} space.
10535 * Follows the unix convention of showing optional options and parameters in square brackets ({@code [ ]}).
10536 * @param done the list of options and positional parameters for which a synopsis was already generated. Positional parameters in this set should be excluded.
10537 * @return the formatted positional parameters, starting with a {@code " "} space, or an empty Text if this command has no positional parameters
10538 * @since 3.9 */
10539 protected Text createDetailedSynopsisPositionalsText(Collection<ArgSpec> done) {
10540 Text positionalParamText = ansi().new Text(0);
10541 List<PositionalParamSpec> positionals = new ArrayList<PositionalParamSpec>(commandSpec.positionalParameters()); // iterate in declaration order
10542 positionals.removeAll(done);
10543 for (PositionalParamSpec positionalParam : positionals) {
10544 if (!positionalParam.hidden()) {
10545 positionalParamText = positionalParamText.concat(" ");
10546 Text label = parameterLabelRenderer().renderParameterLabel(positionalParam, colorScheme.ansi(), colorScheme.parameterStyles);
10547 positionalParamText = positionalParamText.concat(label);
10548 }
10549 }
10550 return positionalParamText;
10551 }
10552
10553 /** Returns a Text object containing a partial detailed synopsis showing only the subcommands, starting with a {@code " "} space.
10554 * Follows the unix convention of showing optional elements in square brackets ({@code [ ]}).
10555 * @return this implementation returns a hard-coded string {@code " [COMMAND]"} if this command has subcommands, an empty Text otherwise
10556 * @since 3.9 */
10557 protected Text createDetailedSynopsisCommandText() {
10558 Text commandText = ansi().new Text(0);
10559 if (!commandSpec.subcommands().isEmpty()){
10560 commandText = commandText.concat(" [")
10561 .concat("COMMAND")
10562 .concat("]");
10563 }
10564 return commandText;
10565 }
10566
10567 /**
10568 * Returns the detailed synopsis text by inserting the command name before the specified text with options and positional parameters details.
10569 * @param synopsisHeadingLength length of the synopsis heading string to be displayed on the same line as the first synopsis line.
10570 * For example, if the synopsis heading is {@code "Usage: "}, this value is 7.
10571 * @param optionsAndPositionalsAndCommandsDetails formatted string with options, positional parameters and subcommands.
10572 * Follows the unix convention of showing optional options and parameters in square brackets ({@code [ ]}).
10573 * @return the detailed synopsis text, in multiple lines if the length exceeds the usage width
10574 */
10575 protected String insertSynopsisCommandName(int synopsisHeadingLength, Text optionsAndPositionalsAndCommandsDetails) {
10576 // Fix for #142: first line of synopsis overshoots max. characters
10577 String commandName = commandSpec.qualifiedName();
10578 int firstColumnLength = commandName.length() + synopsisHeadingLength;
10579
10580 // synopsis heading ("Usage: ") may be on the same line, so adjust column width
10581 TextTable textTable = TextTable.forColumnWidths(ansi(), firstColumnLength, width() - firstColumnLength);
10582 textTable.indentWrappedLines = 1; // don't worry about first line: options (2nd column) always start with a space
10583
10584 // right-adjust the command name by length of synopsis heading
10585 Text PADDING = Ansi.OFF.new Text(stringOf('X', synopsisHeadingLength));
10586 textTable.addRowValues(PADDING.concat(colorScheme.commandText(commandName)), optionsAndPositionalsAndCommandsDetails);
10587 return textTable.toString().substring(synopsisHeadingLength); // cut off leading synopsis heading spaces
10588 }
10589
10590 /** Returns the number of characters the synopsis heading will take on the same line as the synopsis.
10591 * @return the number of characters the synopsis heading will take on the same line as the synopsis.
10592 * @see #detailedSynopsis(int, Comparator, boolean)
10593 */
10594 public int synopsisHeadingLength() {
10595 String[] lines = Ansi.OFF.new Text(commandSpec.usageMessage().synopsisHeading()).toString().split("\\r?\\n|\\r|%n", -1);
10596 return lines[lines.length - 1].length();
10597 }
10598 /**
10599 * <p>Returns a description of the {@linkplain Option options} supported by the application.
10600 * This implementation {@linkplain #createShortOptionNameComparator() sorts options alphabetically}, and shows
10601 * only the {@linkplain Option#hidden() non-hidden} options in a {@linkplain TextTable tabular format}
10602 * using the {@linkplain #createDefaultOptionRenderer() default renderer} and {@linkplain Layout default layout}.</p>
10603 * @return the fully formatted option list
10604 * @see #optionList(Layout, Comparator, IParamLabelRenderer)
10605 */
10606 public String optionList() {
10607 Comparator<OptionSpec> sortOrder = commandSpec.usageMessage().sortOptions()
10608 ? createShortOptionNameComparator()
10609 : createOrderComparatorIfNecessary(commandSpec.options());
10610
10611 return optionList(createLayout(calcLongOptionColumnWidth()), sortOrder, parameterLabelRenderer());
10612 }
10613
10614 private static Comparator<OptionSpec> createOrderComparatorIfNecessary(List<OptionSpec> options) {
10615 for (OptionSpec option : options) { if (option.order() != OptionSpec.DEFAULT_ORDER) { return createOrderComparator(); } }
10616 return null;
10617 }
10618
10619 private int calcLongOptionColumnWidth() {
10620 int max = 0;
10621 IOptionRenderer optionRenderer = new DefaultOptionRenderer(false, " ");
10622 for (OptionSpec option : commandSpec.options()) {
10623 Text[][] values = optionRenderer.render(option, parameterLabelRenderer(), colorScheme);
10624 int len = values[0][3].length;
10625 if (len < Help.defaultOptionsColumnWidth - 3) { max = Math.max(max, len); }
10626 }
10627 IParameterRenderer paramRenderer = new DefaultParameterRenderer(false, " ");
10628 for (PositionalParamSpec positional : commandSpec.positionalParameters()) {
10629 Text[][] values = paramRenderer.render(positional, parameterLabelRenderer(), colorScheme);
10630 int len = values[0][3].length;
10631 if (len < Help.defaultOptionsColumnWidth - 3) { max = Math.max(max, len); }
10632 }
10633 return max + 3;
10634 }
10635
10636 /** Sorts all {@code Options} with the specified {@code comparator} (if the comparator is non-{@code null}),
10637 * then {@linkplain Layout#addOption(CommandLine.Model.OptionSpec, CommandLine.Help.IParamLabelRenderer) adds} all non-hidden options to the
10638 * specified TextTable and returns the result of TextTable.toString().
10639 * @param layout responsible for rendering the option list
10640 * @param valueLabelRenderer used for options with a parameter
10641 * @return the fully formatted option list
10642 * @since 3.0 */
10643 public String optionList(Layout layout, Comparator<OptionSpec> optionSort, IParamLabelRenderer valueLabelRenderer) {
10644 List<OptionSpec> options = new ArrayList<OptionSpec>(commandSpec.options()); // options are stored in order of declaration
10645 if (optionSort != null) {
10646 Collections.sort(options, optionSort); // default: sort options ABC
10647 }
10648 List<ArgGroupSpec> groups = optionListGroups();
10649 for (ArgGroupSpec group : groups) { options.removeAll(group.options()); }
10650
10651 StringBuilder sb = new StringBuilder();
10652 layout.addOptions(options, valueLabelRenderer);
10653 sb.append(layout.toString());
10654
10655 int longOptionColumnWidth = calcLongOptionColumnWidth();
10656 Collections.sort(groups, new SortByOrder<ArgGroupSpec>());
10657 for (ArgGroupSpec group : groups) {
10658 sb.append(heading(ansi(), width(), group.heading()));
10659
10660 Layout groupLayout = createLayout(longOptionColumnWidth);
10661 groupLayout.addPositionalParameters(group.positionalParameters(), valueLabelRenderer);
10662 List<OptionSpec> groupOptions = new ArrayList<OptionSpec>(group.options());
10663 if (optionSort != null) {
10664 Collections.sort(groupOptions, optionSort);
10665 }
10666 groupLayout.addOptions(groupOptions, valueLabelRenderer);
10667 sb.append(groupLayout);
10668 }
10669 return sb.toString();
10670 }
10671
10672 /** Returns the list of {@code ArgGroupSpec}s with a non-{@code null} heading. */
10673 private List<ArgGroupSpec> optionListGroups() {
10674 List<ArgGroupSpec> result = new ArrayList<ArgGroupSpec>();
10675 optionListGroups(commandSpec.argGroups(), result);
10676 return result;
10677 }
10678 private static void optionListGroups(List<ArgGroupSpec> groups, List<ArgGroupSpec> result) {
10679 for (ArgGroupSpec group : groups) {
10680 optionListGroups(group.subgroups(), result);
10681 if (group.heading() != null) { result.add(group); }
10682 }
10683 }
10684
10685 /**
10686 * Returns the section of the usage help message that lists the parameters with their descriptions.
10687 * @return the section of the usage help message that lists the parameters
10688 */
10689 public String parameterList() {
10690 return parameterList(createLayout(calcLongOptionColumnWidth()), parameterLabelRenderer());
10691 }
10692 /**
10693 * Returns the section of the usage help message that lists the parameters with their descriptions.
10694 * @param layout the layout to use
10695 * @param paramLabelRenderer for rendering parameter names
10696 * @return the section of the usage help message that lists the parameters
10697 */
10698 public String parameterList(Layout layout, IParamLabelRenderer paramLabelRenderer) {
10699 List<PositionalParamSpec> positionals = new ArrayList<PositionalParamSpec>(commandSpec.positionalParameters());
10700 List<ArgGroupSpec> groups = optionListGroups();
10701 for (ArgGroupSpec group : groups) { positionals.removeAll(group.positionalParameters()); }
10702
10703 layout.addPositionalParameters(positionals, paramLabelRenderer);
10704 return layout.toString();
10705 }
10706
10707 private static String heading(Ansi ansi, int usageWidth, String values, Object... params) {
10708 StringBuilder sb = join(ansi, usageWidth, new String[] {values}, new StringBuilder(), params);
10709 return trimLineSeparator(sb.toString()) + new String(spaces(countTrailingSpaces(values)));
10710 }
10711 static String trimLineSeparator(String result) {
10712 return result.endsWith(System.getProperty("line.separator"))
10713 ? result.substring(0, result.length() - System.getProperty("line.separator").length()) : result;
10714 }
10715
10716 private static char[] spaces(int length) { char[] result = new char[length]; Arrays.fill(result, ' '); return result; }
10717 private static int countTrailingSpaces(String str) {
10718 if (str == null) {return 0;}
10719 int trailingSpaces = 0;
10720 for (int i = str.length() - 1; i >= 0 && str.charAt(i) == ' '; i--) { trailingSpaces++; }
10721 return trailingSpaces;
10722 }
10723
10724 /** Formats each of the specified values and appends it to the specified StringBuilder.
10725 * @param ansi whether the result should contain ANSI escape codes or not
10726 * @param usageHelpWidth the width of the usage help message
10727 * @param values the values to format and append to the StringBuilder
10728 * @param sb the StringBuilder to collect the formatted strings
10729 * @param params the parameters to pass to the format method when formatting each value
10730 * @return the specified StringBuilder */
10731 public static StringBuilder join(Ansi ansi, int usageHelpWidth, String[] values, StringBuilder sb, Object... params) {
10732 if (values != null) {
10733 TextTable table = TextTable.forColumnWidths(ansi, usageHelpWidth);
10734 table.indentWrappedLines = 0;
10735 for (String summaryLine : values) {
10736 Text[] lines = ansi.new Text(format(summaryLine, params)).splitLines();
10737 for (Text line : lines) { table.addRowValues(line); }
10738 }
10739 table.toString(sb);
10740 }
10741 return sb;
10742 }
10743 private int width() { return commandSpec.usageMessage().width(); }
10744 /** Returns command custom synopsis as a string. A custom synopsis can be zero or more lines, and can be
10745 * specified declaratively with the {@link Command#customSynopsis()} annotation attribute or programmatically
10746 * by setting the Help instance's {@link Help#customSynopsis} field.
10747 * @param params Arguments referenced by the format specifiers in the synopsis strings
10748 * @return the custom synopsis lines combined into a single String (which may be empty)
10749 */
10750 public String customSynopsis(Object... params) {
10751 return join(ansi(), width(), commandSpec.usageMessage().customSynopsis(), new StringBuilder(), params).toString();
10752 }
10753 /** Returns command description text as a string. Description text can be zero or more lines, and can be specified
10754 * declaratively with the {@link Command#description()} annotation attribute or programmatically by
10755 * setting the Help instance's {@link Help#description} field.
10756 * @param params Arguments referenced by the format specifiers in the description strings
10757 * @return the description lines combined into a single String (which may be empty)
10758 */
10759 public String description(Object... params) {
10760 return join(ansi(), width(), commandSpec.usageMessage().description(), new StringBuilder(), params).toString();
10761 }
10762 /** Returns the command header text as a string. Header text can be zero or more lines, and can be specified
10763 * declaratively with the {@link Command#header()} annotation attribute or programmatically by
10764 * setting the Help instance's {@link Help#header} field.
10765 * @param params Arguments referenced by the format specifiers in the header strings
10766 * @return the header lines combined into a single String (which may be empty)
10767 */
10768 public String header(Object... params) {
10769 return join(ansi(), width(), commandSpec.usageMessage().header(), new StringBuilder(), params).toString();
10770 }
10771 /** Returns command footer text as a string. Footer text can be zero or more lines, and can be specified
10772 * declaratively with the {@link Command#footer()} annotation attribute or programmatically by
10773 * setting the Help instance's {@link Help#footer} field.
10774 * @param params Arguments referenced by the format specifiers in the footer strings
10775 * @return the footer lines combined into a single String (which may be empty)
10776 */
10777 public String footer(Object... params) {
10778 return join(ansi(), width(), commandSpec.usageMessage().footer(), new StringBuilder(), params).toString();
10779 }
10780
10781 /** Returns the text displayed before the header text; the result of {@code String.format(headerHeading, params)}.
10782 * @param params the parameters to use to format the header heading
10783 * @return the formatted header heading */
10784 public String headerHeading(Object... params) {
10785 return heading(ansi(), width(), commandSpec.usageMessage().headerHeading(), params);
10786 }
10787
10788 /** Returns the text displayed before the synopsis text; the result of {@code String.format(synopsisHeading, params)}.
10789 * @param params the parameters to use to format the synopsis heading
10790 * @return the formatted synopsis heading */
10791 public String synopsisHeading(Object... params) {
10792 return heading(ansi(), width(), commandSpec.usageMessage().synopsisHeading(), params);
10793 }
10794
10795 /** Returns the text displayed before the description text; an empty string if there is no description,
10796 * otherwise the result of {@code String.format(descriptionHeading, params)}.
10797 * @param params the parameters to use to format the description heading
10798 * @return the formatted description heading */
10799 public String descriptionHeading(Object... params) {
10800 return empty(commandSpec.usageMessage().descriptionHeading()) ? "" : heading(ansi(), width(), commandSpec.usageMessage().descriptionHeading(), params);
10801 }
10802
10803 /** Returns the text displayed before the positional parameter list; an empty string if there are no positional
10804 * parameters, otherwise the result of {@code String.format(parameterListHeading, params)}.
10805 * @param params the parameters to use to format the parameter list heading
10806 * @return the formatted parameter list heading */
10807 public String parameterListHeading(Object... params) {
10808 return commandSpec.positionalParameters().isEmpty() ? "" : heading(ansi(), width(), commandSpec.usageMessage().parameterListHeading(), params);
10809 }
10810
10811 /** Returns the text displayed before the option list; an empty string if there are no options,
10812 * otherwise the result of {@code String.format(optionListHeading, params)}.
10813 * @param params the parameters to use to format the option list heading
10814 * @return the formatted option list heading */
10815 public String optionListHeading(Object... params) {
10816 return commandSpec.optionsMap().isEmpty() ? "" : heading(ansi(), width(), commandSpec.usageMessage().optionListHeading(), params);
10817 }
10818
10819 /** Returns the text displayed before the command list; an empty string if there are no commands,
10820 * otherwise the result of {@code String.format(commandListHeading, params)}.
10821 * @param params the parameters to use to format the command list heading
10822 * @return the formatted command list heading */
10823 public String commandListHeading(Object... params) {
10824 return commands.isEmpty() ? "" : heading(ansi(), width(), commandSpec.usageMessage().commandListHeading(), params);
10825 }
10826
10827 /** Returns the text displayed before the footer text; the result of {@code String.format(footerHeading, params)}.
10828 * @param params the parameters to use to format the footer heading
10829 * @return the formatted footer heading */
10830 public String footerHeading(Object... params) {
10831 return heading(ansi(), width(), commandSpec.usageMessage().footerHeading(), params);
10832 }
10833 /** Returns a 2-column list with command names and the first line of their header or (if absent) description.
10834 * @return a usage help section describing the added commands */
10835 public String commandList() {
10836 if (subcommands().isEmpty()) { return ""; }
10837 int commandLength = maxLength(subcommands().keySet());
10838 Help.TextTable textTable = Help.TextTable.forColumns(ansi(),
10839 new Help.Column(commandLength + 2, 2, Help.Column.Overflow.SPAN),
10840 new Help.Column(width() - (commandLength + 2), 2, Help.Column.Overflow.WRAP));
10841
10842 for (Map.Entry<String, Help> entry : subcommands().entrySet()) {
10843 Help help = entry.getValue();
10844 UsageMessageSpec usage = help.commandSpec().usageMessage();
10845 String header = !empty(usage.header())
10846 ? usage.header()[0]
10847 : (!empty(usage.description()) ? usage.description()[0] : "");
10848 Text[] lines = ansi().text(format(header)).splitLines();
10849 for (int i = 0; i < lines.length; i++) {
10850 textTable.addRowValues(i == 0 ? help.commandNamesText(", ") : Ansi.EMPTY_TEXT, lines[i]);
10851 }
10852 }
10853 return textTable.toString();
10854 }
10855 private static int maxLength(Collection<String> any) {
10856 List<String> strings = new ArrayList<String>(any);
10857 Collections.sort(strings, Collections.reverseOrder(Help.shortestFirst()));
10858 return strings.get(0).length();
10859 }
10860
10861 /** Returns a {@code Text} object containing the command name and all aliases, separated with the specified separator.
10862 * Command names will use the {@link ColorScheme#commandText(String) command style} for the color scheme of this Help.
10863 * @since 3.9 */
10864 public Text commandNamesText(String separator) {
10865 Text result = colorScheme().commandText(aliases().get(0));
10866 for (int i = 1; i < aliases().size(); i++) {
10867 result = result.concat(separator).concat(colorScheme().commandText(aliases().get(i)));
10868 }
10869 return result;
10870 }
10871 private static String join(String[] names, int offset, int length, String separator) {
10872 if (names == null) { return ""; }
10873 StringBuilder result = new StringBuilder();
10874 for (int i = offset; i < offset + length; i++) {
10875 result.append((i > offset) ? separator : "").append(names[i]);
10876 }
10877 return result.toString();
10878 }
10879 private static String stringOf(char chr, int length) {
10880 char[] buff = new char[length];
10881 Arrays.fill(buff, chr);
10882 return new String(buff);
10883 }
10884
10885 /** Returns a {@code Layout} instance configured with the user preferences captured in this Help instance.
10886 * @return a Layout */
10887 public Layout createDefaultLayout() {
10888 return createLayout(Help.defaultOptionsColumnWidth);
10889 }
10890
10891 private Layout createLayout(int longOptionsColumnWidth) {
10892 return new Layout(colorScheme, TextTable.forDefaultColumns(colorScheme.ansi(), longOptionsColumnWidth, width()), createDefaultOptionRenderer(), createDefaultParameterRenderer());
10893 }
10894
10895 /** Returns a new default OptionRenderer which converts {@link OptionSpec Options} to five columns of text to match
10896 * the default {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
10897 * <ol>
10898 * <li>the required option marker</li>
10899 * <li>2-character short option name (or empty string if no short option exists)</li>
10900 * <li>comma separator (only if both short option and long option exist, empty string otherwise)</li>
10901 * <li>comma-separated string with long option name(s)</li>
10902 * <li>first element of the {@link OptionSpec#description()} array</li>
10903 * </ol>
10904 * <p>Following this, there will be one row for each of the remaining elements of the {@link
10905 * OptionSpec#description()} array, and these rows look like {@code {"", "", "", "", option.description()[i]}}.</p>
10906 * <p>If configured, this option renderer adds an additional row to display the default field value.</p>
10907 * @return a new default OptionRenderer
10908 */
10909 public IOptionRenderer createDefaultOptionRenderer() {
10910 return new DefaultOptionRenderer(commandSpec.usageMessage.showDefaultValues(), "" +commandSpec.usageMessage().requiredOptionMarker());
10911 }
10912 /** Returns a new minimal OptionRenderer which converts {@link OptionSpec Options} to a single row with two columns
10913 * of text: an option name and a description. If multiple names or descriptions exist, the first value is used.
10914 * @return a new minimal OptionRenderer */
10915 public static IOptionRenderer createMinimalOptionRenderer() {
10916 return new MinimalOptionRenderer();
10917 }
10918
10919 /** Returns a new default ParameterRenderer which converts {@linkplain PositionalParamSpec positional parameters} to four columns of
10920 * text to match the default {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
10921 * <ol>
10922 * <li>empty string </li>
10923 * <li>empty string </li>
10924 * <li>parameter(s) label as rendered by the {@link IParamLabelRenderer}</li>
10925 * <li>first element of the {@link PositionalParamSpec#description()} array</li>
10926 * </ol>
10927 * <p>Following this, there will be one row for each of the remaining elements of the {@link
10928 * PositionalParamSpec#description()} array, and these rows look like {@code {"", "", "", param.description()[i]}}.</p>
10929 * <p>If configured, this parameter renderer adds an additional row to display the default field value.</p>
10930 * @return a new default ParameterRenderer
10931 */
10932 public IParameterRenderer createDefaultParameterRenderer() {
10933 return new DefaultParameterRenderer(commandSpec.usageMessage.showDefaultValues(), "" + commandSpec.usageMessage().requiredOptionMarker());
10934 }
10935 /** Returns a new minimal ParameterRenderer which converts {@linkplain PositionalParamSpec positional parameters}
10936 * to a single row with two columns of text: an option name and a description. If multiple descriptions exist, the first value is used.
10937 * @return a new minimal ParameterRenderer */
10938 public static IParameterRenderer createMinimalParameterRenderer() {
10939 return new MinimalParameterRenderer();
10940 }
10941
10942 /** Returns a value renderer that returns the {@code paramLabel} if defined or the field name otherwise.
10943 * @return a new minimal ParamLabelRenderer */
10944 public static IParamLabelRenderer createMinimalParamLabelRenderer() {
10945 return new IParamLabelRenderer() {
10946 public Text renderParameterLabel(ArgSpec argSpec, Ansi ansi, List<IStyle> styles) {
10947 return ansi.apply(argSpec.paramLabel(), styles);
10948 }
10949 public String separator() { return ""; }
10950 };
10951 }
10952 /** Returns a new default param label renderer that separates option parameters from their option name
10953 * with the specified separator string, and, unless {@link ArgSpec#hideParamSyntax()} is true,
10954 * surrounds optional parameters with {@code '['} and {@code ']'}
10955 * characters and uses ellipses ("...") to indicate that any number of a parameter are allowed.
10956 * @return a new default ParamLabelRenderer
10957 */
10958 public IParamLabelRenderer createDefaultParamLabelRenderer() {
10959 return new DefaultParamLabelRenderer(commandSpec);
10960 }
10961 /** Sorts {@link OptionSpec OptionSpecs} by their option name in case-insensitive alphabetic order. If an
10962 * option has multiple names, the shortest name is used for the sorting. Help options follow non-help options.
10963 * @return a comparator that sorts OptionSpecs by their option name in case-insensitive alphabetic order */
10964 public static Comparator<OptionSpec> createShortOptionNameComparator() {
10965 return new SortByShortestOptionNameAlphabetically();
10966 }
10967 /** Sorts {@link OptionSpec OptionSpecs} by their option {@linkplain Range#max max arity} first, by
10968 * {@linkplain Range#min min arity} next, and by {@linkplain #createShortOptionNameComparator() option name} last.
10969 * @return a comparator that sorts OptionSpecs by arity first, then their option name */
10970 public static Comparator<OptionSpec> createShortOptionArityAndNameComparator() {
10971 return new SortByOptionArityAndNameAlphabetically();
10972 }
10973 /** Sorts short strings before longer strings.
10974 * @return a comparators that sorts short strings before longer strings */
10975 public static Comparator<String> shortestFirst() { return new ShortestFirst(); }
10976 /** Sorts {@link OptionSpec options} by their option {@linkplain IOrdered#order() order}, lowest first, highest last.
10977 * @return a comparator that sorts OptionSpecs by their order
10978 * @since 3.9*/
10979 static Comparator<OptionSpec> createOrderComparator() {
10980 return new SortByOrder<OptionSpec>();
10981 }
10982
10983 /** Returns whether ANSI escape codes are enabled or not.
10984 * @return whether ANSI escape codes are enabled or not
10985 */
10986 public Ansi ansi() { return colorScheme.ansi; }
10987
10988 /** Controls the visibility of certain aspects of the usage help message. */
10989 public enum Visibility { ALWAYS, NEVER, ON_DEMAND }
10990
10991 /** When customizing online help for {@link OptionSpec Option} details, a custom {@code IOptionRenderer} can be
10992 * used to create textual representation of an Option in a tabular format: one or more rows, each containing
10993 * one or more columns. The {@link Layout Layout} is responsible for placing these text values in the
10994 * {@link TextTable TextTable}. */
10995 public interface IOptionRenderer {
10996 /**
10997 * Returns a text representation of the specified option and its parameter(s) if any.
10998 * @param option the command line option to show online usage help for
10999 * @param parameterLabelRenderer responsible for rendering option parameters to text
11000 * @param scheme color scheme for applying ansi color styles to options and option parameters
11001 * @return a 2-dimensional array of text values: one or more rows, each containing one or more columns
11002 * @since 3.0
11003 */
11004 Text[][] render(OptionSpec option, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme);
11005 }
11006 /** The DefaultOptionRenderer converts {@link OptionSpec Options} to five columns of text to match the default
11007 * {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
11008 * <ol>
11009 * <li>the required option marker (if the option is required)</li>
11010 * <li>2-character short option name (or empty string if no short option exists)</li>
11011 * <li>comma separator (only if both short option and long option exist, empty string otherwise)</li>
11012 * <li>comma-separated string with long option name(s)</li>
11013 * <li>first element of the {@link OptionSpec#description()} array</li>
11014 * </ol>
11015 * <p>Following this, there will be one row for each of the remaining elements of the {@link
11016 * OptionSpec#description()} array, and these rows look like {@code {"", "", "", option.description()[i]}}.</p>
11017 */
11018 static class DefaultOptionRenderer implements IOptionRenderer {
11019 private String requiredMarker = " ";
11020 private boolean showDefaultValues;
11021 private String sep;
11022 public DefaultOptionRenderer(boolean showDefaultValues, String requiredMarker) {
11023 this.showDefaultValues = showDefaultValues;
11024 this.requiredMarker = Assert.notNull(requiredMarker, "requiredMarker");
11025 }
11026 public Text[][] render(OptionSpec option, IParamLabelRenderer paramLabelRenderer, ColorScheme scheme) {
11027 String[] names = ShortestFirst.sort(option.names());
11028 int shortOptionCount = names[0].length() == 2 ? 1 : 0;
11029 String shortOption = shortOptionCount > 0 ? names[0] : "";
11030 sep = shortOptionCount > 0 && names.length > 1 ? "," : "";
11031
11032 String longOption = join(names, shortOptionCount, names.length - shortOptionCount, ", ");
11033 Text longOptionText = createLongOptionText(option, paramLabelRenderer, scheme, longOption);
11034
11035 String requiredOption = option.required() ? requiredMarker : "";
11036 return renderDescriptionLines(option, scheme, requiredOption, shortOption, longOptionText);
11037 }
11038
11039 private Text createLongOptionText(OptionSpec option, IParamLabelRenderer renderer, ColorScheme scheme, String longOption) {
11040 Text paramLabelText = renderer.renderParameterLabel(option, scheme.ansi(), scheme.optionParamStyles);
11041
11042 // if no long option, fill in the space between the short option name and the param label value
11043 if (paramLabelText.length > 0 && longOption.length() == 0) {
11044 sep = renderer.separator();
11045 // #181 paramLabelText may be =LABEL or [=LABEL...]
11046 int sepStart = paramLabelText.plainString().indexOf(sep);
11047 Text prefix = paramLabelText.substring(0, sepStart);
11048 paramLabelText = prefix.concat(paramLabelText.substring(sepStart + sep.length()));
11049 }
11050 Text longOptionText = scheme.optionText(longOption);
11051 longOptionText = longOptionText.concat(paramLabelText);
11052 return longOptionText;
11053 }
11054
11055 private Text[][] renderDescriptionLines(OptionSpec option,
11056 ColorScheme scheme,
11057 String requiredOption,
11058 String shortOption,
11059 Text longOptionText) {
11060 Text EMPTY = Ansi.EMPTY_TEXT;
11061 boolean[] showDefault = {option.internalShowDefaultValue(showDefaultValues)};
11062 List<Text[]> result = new ArrayList<Text[]>();
11063 String[] description = option.renderedDescription();
11064 Text[] descriptionFirstLines = createDescriptionFirstLines(scheme, option, description, showDefault);
11065 result.add(new Text[] { scheme.optionText(requiredOption), scheme.optionText(shortOption),
11066 scheme.ansi().new Text(sep), longOptionText, descriptionFirstLines[0] });
11067 for (int i = 1; i < descriptionFirstLines.length; i++) {
11068 result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, descriptionFirstLines[i] });
11069 }
11070 for (int i = 1; i < description.length; i++) {
11071 Text[] descriptionNextLines = scheme.ansi().new Text(description[i]).splitLines();
11072 for (Text line : descriptionNextLines) {
11073 result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, line });
11074 }
11075 }
11076 if (showDefault[0]) { addTrailingDefaultLine(result, option, scheme); }
11077 return result.toArray(new Text[result.size()][]);
11078 }
11079 }
11080 /** The MinimalOptionRenderer converts {@link OptionSpec Options} to a single row with two columns of text: an
11081 * option name and a description. If multiple names or description lines exist, the first value is used. */
11082 static class MinimalOptionRenderer implements IOptionRenderer {
11083 public Text[][] render(OptionSpec option, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme) {
11084 Text optionText = scheme.optionText(option.names()[0]);
11085 Text paramLabelText = parameterLabelRenderer.renderParameterLabel(option, scheme.ansi(), scheme.optionParamStyles);
11086 optionText = optionText.concat(paramLabelText);
11087 return new Text[][] {{ optionText,
11088 scheme.ansi().new Text(option.description().length == 0 ? "" : option.description()[0]) }};
11089 }
11090 }
11091 /** The MinimalParameterRenderer converts {@linkplain PositionalParamSpec positional parameters} to a single row with two columns of
11092 * text: the parameters label and a description. If multiple description lines exist, the first value is used. */
11093 static class MinimalParameterRenderer implements IParameterRenderer {
11094 public Text[][] render(PositionalParamSpec param, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme) {
11095 return new Text[][] {{ parameterLabelRenderer.renderParameterLabel(param, scheme.ansi(), scheme.parameterStyles),
11096 scheme.ansi().new Text(param.description().length == 0 ? "" : param.description()[0]) }};
11097 }
11098 }
11099 /** When customizing online help for {@linkplain PositionalParamSpec positional parameters} details, a custom {@code IParameterRenderer}
11100 * can be used to create textual representation of a Parameters field in a tabular format: one or more rows,
11101 * each containing one or more columns. The {@link Layout Layout} is responsible for placing these text
11102 * values in the {@link TextTable TextTable}. */
11103 public interface IParameterRenderer {
11104 /**
11105 * Returns a text representation of the specified positional parameter.
11106 * @param param the positional parameter to show online usage help for
11107 * @param parameterLabelRenderer responsible for rendering parameter labels to text
11108 * @param scheme color scheme for applying ansi color styles to positional parameters
11109 * @return a 2-dimensional array of text values: one or more rows, each containing one or more columns
11110 * @since 3.0
11111 */
11112 Text[][] render(PositionalParamSpec param, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme);
11113 }
11114 /** The DefaultParameterRenderer converts {@linkplain PositionalParamSpec positional parameters} to five columns of text to match the
11115 * default {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
11116 * <ol>
11117 * <li>the required option marker (if the parameter's arity is to have at least one value)</li>
11118 * <li>empty string </li>
11119 * <li>empty string </li>
11120 * <li>parameter(s) label as rendered by the {@link IParamLabelRenderer}</li>
11121 * <li>first element of the {@link PositionalParamSpec#description()} array</li>
11122 * </ol>
11123 * <p>Following this, there will be one row for each of the remaining elements of the {@link
11124 * PositionalParamSpec#description()} array, and these rows look like {@code {"", "", "", param.description()[i]}}.</p>
11125 */
11126 static class DefaultParameterRenderer implements IParameterRenderer {
11127 private String requiredMarker = " ";
11128 private boolean showDefaultValues;
11129 public DefaultParameterRenderer(boolean showDefaultValues, String requiredMarker) {
11130 this.showDefaultValues = showDefaultValues;
11131 this.requiredMarker = Assert.notNull(requiredMarker, "requiredMarker");
11132 }
11133 public Text[][] render(PositionalParamSpec param, IParamLabelRenderer paramLabelRenderer, ColorScheme scheme) {
11134 Text label = paramLabelRenderer.renderParameterLabel(param, scheme.ansi(), scheme.parameterStyles);
11135 Text requiredParameter = scheme.parameterText(param.arity().min > 0 ? requiredMarker : "");
11136
11137 Text EMPTY = Ansi.EMPTY_TEXT;
11138 boolean[] showDefault = {param.internalShowDefaultValue(showDefaultValues)};
11139 List<Text[]> result = new ArrayList<Text[]>();
11140 String[] description = param.renderedDescription();
11141 Text[] descriptionFirstLines = createDescriptionFirstLines(scheme, param, description, showDefault);
11142 result.add(new Text[] { requiredParameter, EMPTY, EMPTY, label, descriptionFirstLines[0] });
11143 for (int i = 1; i < descriptionFirstLines.length; i++) {
11144 result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, descriptionFirstLines[i] });
11145 }
11146 for (int i = 1; i < description.length; i++) {
11147 Text[] descriptionNextLines = scheme.ansi().new Text(description[i]).splitLines();
11148 for (Text line : descriptionNextLines) {
11149 result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, line });
11150 }
11151 }
11152 if (showDefault[0]) { addTrailingDefaultLine(result, param, scheme); }
11153 return result.toArray(new Text[result.size()][]);
11154 }
11155 }
11156
11157 private static void addTrailingDefaultLine(List<Text[]> result, ArgSpec arg, ColorScheme scheme) {
11158 Text EMPTY = Ansi.EMPTY_TEXT;
11159 result.add(new Text[]{EMPTY, EMPTY, EMPTY, EMPTY, scheme.ansi().new Text(" Default: " + arg.defaultValueString())});
11160 }
11161
11162 private static Text[] createDescriptionFirstLines(ColorScheme scheme, ArgSpec arg, String[] description, boolean[] showDefault) {
11163 Text[] result = scheme.ansi().new Text(str(description, 0)).splitLines();
11164 if (result.length == 0 || (result.length == 1 && result[0].plain.length() == 0)) {
11165 if (showDefault[0]) {
11166 result = new Text[]{scheme.ansi().new Text(" Default: " + arg.defaultValueString())};
11167 showDefault[0] = false; // don't show the default value twice
11168 } else {
11169 result = new Text[]{ Ansi.EMPTY_TEXT };
11170 }
11171 }
11172 return result;
11173 }
11174
11175 /** When customizing online usage help for an option parameter or a positional parameter, a custom
11176 * {@code IParamLabelRenderer} can be used to render the parameter name or label to a String. */
11177 public interface IParamLabelRenderer {
11178
11179 /** Returns a text rendering of the option parameter or positional parameter; returns an empty string
11180 * {@code ""} if the option is a boolean and does not take a parameter.
11181 * @param argSpec the named or positional parameter with a parameter label
11182 * @param ansi determines whether ANSI escape codes should be emitted or not
11183 * @param styles the styles to apply to the parameter label
11184 * @return a text rendering of the Option parameter or positional parameter
11185 * @since 3.0 */
11186 Text renderParameterLabel(ArgSpec argSpec, Ansi ansi, List<IStyle> styles);
11187
11188 /** Returns the separator between option name and param label.
11189 * @return the separator between option name and param label */
11190 String separator();
11191 }
11192 /**
11193 * DefaultParamLabelRenderer separates option parameters from their {@linkplain OptionSpec option names} with a
11194 * {@linkplain CommandLine.Model.ParserSpec#separator() separator} string, and, unless
11195 * {@link ArgSpec#hideParamSyntax()} is true, surrounds optional values with {@code '['} and {@code ']'} characters
11196 * and uses ellipses ("...") to indicate that any number of values is allowed for options or parameters with variable arity.
11197 */
11198 static class DefaultParamLabelRenderer implements IParamLabelRenderer {
11199 private final CommandSpec commandSpec;
11200 /** Constructs a new DefaultParamLabelRenderer with the specified separator string. */
11201 public DefaultParamLabelRenderer(CommandSpec commandSpec) {
11202 this.commandSpec = Assert.notNull(commandSpec, "commandSpec");
11203 }
11204 public String separator() { return commandSpec.parser().separator(); }
11205 public Text renderParameterLabel(ArgSpec argSpec, Ansi ansi, List<IStyle> styles) {
11206 Range capacity = argSpec.isOption() ? argSpec.arity() : ((PositionalParamSpec)argSpec).capacity();
11207 if (capacity.max == 0) { return ansi.new Text(""); }
11208 if (argSpec.hideParamSyntax()) { return ansi.apply((argSpec.isOption() ? separator() : "") + argSpec.paramLabel(), styles); }
11209
11210 Text paramName = ansi.apply(argSpec.paramLabel(), styles);
11211 String split = argSpec.splitRegex();
11212 String mandatorySep = empty(split) ? " " : split;
11213 String optionalSep = empty(split) ? " [" : "[" + split;
11214
11215 boolean unlimitedSplit = !empty(split) && !commandSpec.parser().limitSplit();
11216 boolean limitedSplit = !empty(split) && commandSpec.parser().limitSplit();
11217 Text repeating = paramName;
11218 int paramCount = 1;
11219 if (unlimitedSplit) {
11220 repeating = paramName.concat("[" + split).concat(paramName).concat("...]");
11221 paramCount++;
11222 mandatorySep = " ";
11223 optionalSep = " [";
11224 }
11225 Text result = repeating;
11226
11227 int done = 1;
11228 for (; done < capacity.min; done++) {
11229 result = result.concat(mandatorySep).concat(repeating); // " PARAM" or ",PARAM"
11230 paramCount += paramCount;
11231 }
11232 if (!capacity.isVariable) {
11233 for (int i = done; i < capacity.max; i++) {
11234 result = result.concat(optionalSep).concat(paramName); // " [PARAM" or "[,PARAM"
11235 paramCount++;
11236 }
11237 for (int i = done; i < capacity.max; i++) {
11238 result = result.concat("]");
11239 }
11240 }
11241 // show an extra trailing "[,PARAM]" if split and either max=* or splitting is not restricted to max
11242 boolean effectivelyVariable = capacity.isVariable || (limitedSplit && paramCount == 1);
11243 if (limitedSplit && effectivelyVariable && paramCount == 1) {
11244 result = result.concat(optionalSep).concat(repeating).concat("]"); // PARAM[,PARAM]...
11245 }
11246 if (effectivelyVariable) {
11247 if (!argSpec.arity().isVariable && argSpec.arity().min > 1) {
11248 result = ansi.new Text("(").concat(result).concat(")"); // repeating group
11249 }
11250 result = result.concat("..."); // PARAM...
11251 }
11252 String optionSeparator = argSpec.isOption() ? separator() : "";
11253 if (capacity.min == 0) { // optional
11254 String sep2 = empty(optionSeparator.trim()) ? optionSeparator + "[" : "[" + optionSeparator;
11255 result = ansi.new Text(sep2).concat(result).concat("]");
11256 } else {
11257 result = ansi.new Text(optionSeparator).concat(result);
11258 }
11259 return result;
11260 }
11261 }
11262 /** Use a Layout to format usage help text for options and parameters in tabular format.
11263 * <p>Delegates to the renderers to create {@link Text} values for the annotated fields, and uses a
11264 * {@link TextTable} to display these values in tabular format. Layout is responsible for deciding which values
11265 * to display where in the table. By default, Layout shows one option or parameter per table row.</p>
11266 * <p>Customize by overriding the {@link #layout(CommandLine.Model.ArgSpec, CommandLine.Help.Ansi.Text[][])} method.</p>
11267 * @see IOptionRenderer rendering options to text
11268 * @see IParameterRenderer rendering parameters to text
11269 * @see TextTable showing values in a tabular format
11270 */
11271 public static class Layout {
11272 protected final ColorScheme colorScheme;
11273 protected final TextTable table;
11274 protected IOptionRenderer optionRenderer;
11275 protected IParameterRenderer parameterRenderer;
11276
11277 /** Constructs a Layout with the specified color scheme, a new default TextTable, the
11278 * {@linkplain Help#createDefaultOptionRenderer() default option renderer}, and the
11279 * {@linkplain Help#createDefaultParameterRenderer() default parameter renderer}.
11280 * @param colorScheme the color scheme to use for common, auto-generated parts of the usage help message */
11281 public Layout(ColorScheme colorScheme, int tableWidth) { this(colorScheme, TextTable.forDefaultColumns(colorScheme.ansi(), tableWidth)); }
11282
11283 /** Constructs a Layout with the specified color scheme, the specified TextTable, the
11284 * {@linkplain Help#createDefaultOptionRenderer() default option renderer}, and the
11285 * {@linkplain Help#createDefaultParameterRenderer() default parameter renderer}.
11286 * @param colorScheme the color scheme to use for common, auto-generated parts of the usage help message
11287 * @param textTable the TextTable to lay out parts of the usage help message in tabular format */
11288 public Layout(ColorScheme colorScheme, TextTable textTable) {
11289 this(colorScheme, textTable, new DefaultOptionRenderer(false, " "), new DefaultParameterRenderer(false, " "));
11290 }
11291 /** Constructs a Layout with the specified color scheme, the specified TextTable, the
11292 * specified option renderer and the specified parameter renderer.
11293 * @param colorScheme the color scheme to use for common, auto-generated parts of the usage help message
11294 * @param optionRenderer the object responsible for rendering Options to Text
11295 * @param parameterRenderer the object responsible for rendering Parameters to Text
11296 * @param textTable the TextTable to lay out parts of the usage help message in tabular format */
11297 public Layout(ColorScheme colorScheme, TextTable textTable, IOptionRenderer optionRenderer, IParameterRenderer parameterRenderer) {
11298 this.colorScheme = Assert.notNull(colorScheme, "colorScheme");
11299 this.table = Assert.notNull(textTable, "textTable");
11300 this.optionRenderer = Assert.notNull(optionRenderer, "optionRenderer");
11301 this.parameterRenderer = Assert.notNull(parameterRenderer, "parameterRenderer");
11302 }
11303 /**
11304 * Copies the specified text values into the correct cells in the {@link TextTable}. This implementation
11305 * delegates to {@link TextTable#addRowValues(CommandLine.Help.Ansi.Text...)} for each row of values.
11306 * <p>Subclasses may override.</p>
11307 * @param argSpec the Option or Parameters
11308 * @param cellValues the text values representing the Option/Parameters, to be displayed in tabular form
11309 * @since 3.0 */
11310 public void layout(ArgSpec argSpec, Text[][] cellValues) {
11311 for (Text[] oneRow : cellValues) {
11312 table.addRowValues(oneRow);
11313 }
11314 }
11315 /** Calls {@link #addOption(CommandLine.Model.OptionSpec, CommandLine.Help.IParamLabelRenderer)} for all non-hidden Options in the list.
11316 * @param options options to add usage descriptions for
11317 * @param paramLabelRenderer object that knows how to render option parameters
11318 * @since 3.0 */
11319 public void addOptions(List<OptionSpec> options, IParamLabelRenderer paramLabelRenderer) {
11320 for (OptionSpec option : options) {
11321 if (!option.hidden()) {
11322 addOption(option, paramLabelRenderer);
11323 }
11324 }
11325 }
11326 /**
11327 * Delegates to the {@link #optionRenderer option renderer} of this layout to obtain
11328 * text values for the specified {@link OptionSpec}, and then calls the {@link #layout(CommandLine.Model.ArgSpec, CommandLine.Help.Ansi.Text[][])}
11329 * method to write these text values into the correct cells in the TextTable.
11330 * @param option the option argument
11331 * @param paramLabelRenderer knows how to render option parameters
11332 * @since 3.0 */
11333 public void addOption(OptionSpec option, IParamLabelRenderer paramLabelRenderer) {
11334 Text[][] values = optionRenderer.render(option, paramLabelRenderer, colorScheme);
11335 layout(option, values);
11336 }
11337 /** Calls {@link #addPositionalParameter(CommandLine.Model.PositionalParamSpec, CommandLine.Help.IParamLabelRenderer)} for all non-hidden Parameters in the list.
11338 * @param params positional parameters to add usage descriptions for
11339 * @param paramLabelRenderer knows how to render option parameters
11340 * @since 3.0 */
11341 public void addPositionalParameters(List<PositionalParamSpec> params, IParamLabelRenderer paramLabelRenderer) {
11342 for (PositionalParamSpec param : params) {
11343 if (!param.hidden()) {
11344 addPositionalParameter(param, paramLabelRenderer);
11345 }
11346 }
11347 }
11348 /**
11349 * Delegates to the {@link #parameterRenderer parameter renderer} of this layout
11350 * to obtain text values for the specified {@linkplain PositionalParamSpec positional parameter}, and then calls
11351 * {@link #layout(CommandLine.Model.ArgSpec, CommandLine.Help.Ansi.Text[][])} to write these text values into the correct cells in the TextTable.
11352 * @param param the positional parameter
11353 * @param paramLabelRenderer knows how to render option parameters
11354 * @since 3.0 */
11355 public void addPositionalParameter(PositionalParamSpec param, IParamLabelRenderer paramLabelRenderer) {
11356 Text[][] values = parameterRenderer.render(param, paramLabelRenderer, colorScheme);
11357 layout(param, values);
11358 }
11359 /** Returns the section of the usage help message accumulated in the TextTable owned by this layout. */
11360 @Override public String toString() { return table.toString(); }
11361 }
11362 /** Sorts short strings before longer strings. */
11363 static class ShortestFirst implements Comparator<String> {
11364 public int compare(String o1, String o2) {
11365 return o1.length() - o2.length();
11366 }
11367 /** Sorts the specified array of Strings shortest-first and returns it. */
11368 public static String[] sort(String[] names) {
11369 Arrays.sort(names, new ShortestFirst());
11370 return names;
11371 }
11372 /** Sorts the specified array of Strings longest-first and returns it. */
11373 public static String[] longestFirst(String[] names) {
11374 Arrays.sort(names, Collections.reverseOrder(new ShortestFirst()));
11375 return names;
11376 }
11377 }
11378 /** Sorts {@code OptionSpec} instances by their name in case-insensitive alphabetic order. If an option has
11379 * multiple names, the shortest name is used for the sorting. Help options follow non-help options. */
11380 static class SortByShortestOptionNameAlphabetically implements Comparator<OptionSpec> {
11381 public int compare(OptionSpec o1, OptionSpec o2) {
11382 if (o1 == null) { return 1; } else if (o2 == null) { return -1; } // options before params
11383 String[] names1 = ShortestFirst.sort(o1.names());
11384 String[] names2 = ShortestFirst.sort(o2.names());
11385 int result = names1[0].toUpperCase().compareTo(names2[0].toUpperCase()); // case insensitive sort
11386 result = result == 0 ? -names1[0].compareTo(names2[0]) : result; // lower case before upper case
11387 return o1.help() == o2.help() ? result : o2.help() ? -1 : 1; // help options come last
11388 }
11389 }
11390 /** Sorts {@code OptionSpec} instances by their max arity first, then their min arity, then delegates to super class. */
11391 static class SortByOptionArityAndNameAlphabetically extends SortByShortestOptionNameAlphabetically {
11392 public int compare(OptionSpec o1, OptionSpec o2) {
11393 Range arity1 = o1.arity();
11394 Range arity2 = o2.arity();
11395 int result = arity1.max - arity2.max;
11396 if (result == 0) {
11397 result = arity1.min - arity2.min;
11398 }
11399 if (result == 0) { // arity is same
11400 if (o1.isMultiValue() && !o2.isMultiValue()) { result = 1; } // f1 > f2
11401 if (!o1.isMultiValue() && o2.isMultiValue()) { result = -1; } // f1 < f2
11402 }
11403 return result == 0 ? super.compare(o1, o2) : result;
11404 }
11405 }
11406 static class SortByOrder<T extends IOrdered> implements Comparator<T> {
11407 public int compare(T o1, T o2) {
11408 return Integer.signum(o1.order() - o2.order());
11409 }
11410 }
11411 /**
11412 * <p>Responsible for spacing out {@link Text} values according to the {@link Column} definitions the table was
11413 * created with. Columns have a width, indentation, and an overflow policy that decides what to do if a value is
11414 * longer than the column's width.</p>
11415 */
11416 public static class TextTable {
11417 /**
11418 * Helper class to index positions in a {@code Help.TextTable}.
11419 * @since 2.0
11420 */
11421 public static class Cell {
11422 /** Table column index (zero based). */
11423 public final int column;
11424 /** Table row index (zero based). */
11425 public final int row;
11426 /** Constructs a new Cell with the specified coordinates in the table.
11427 * @param column the zero-based table column
11428 * @param row the zero-based table row */
11429 public Cell(int column, int row) { this.column = column; this.row = row; }
11430 }
11431
11432 private static final int OPTION_SEPARATOR_COLUMN = 2;
11433 private static final int LONG_OPTION_COLUMN = 3;
11434
11435 /** The column definitions of this table. */
11436 private final Column[] columns;
11437
11438 /** The {@code char[]} slots of the {@code TextTable} to copy text values into. */
11439 protected final List<Text> columnValues = new ArrayList<Text>();
11440
11441 /** By default, indent wrapped lines by 2 spaces. */
11442 public int indentWrappedLines = 2;
11443
11444 private final Ansi ansi;
11445 private final int tableWidth;
11446
11447 /** Constructs a TextTable with five columns as follows:
11448 * <ol>
11449 * <li>required option/parameter marker (width: 2, indent: 0, TRUNCATE on overflow)</li>
11450 * <li>short option name (width: 2, indent: 0, TRUNCATE on overflow)</li>
11451 * <li>comma separator (width: 1, indent: 0, TRUNCATE on overflow)</li>
11452 * <li>long option name(s) (width: 24, indent: 1, SPAN multiple columns on overflow)</li>
11453 * <li>description line(s) (width: 51, indent: 1, WRAP to next row on overflow)</li>
11454 * </ol>
11455 * @param ansi whether to emit ANSI escape codes or not
11456 * @param usageHelpWidth the total width of the columns combined
11457 */
11458 public static TextTable forDefaultColumns(Ansi ansi, int usageHelpWidth) {
11459 return forDefaultColumns(ansi, defaultOptionsColumnWidth, usageHelpWidth);
11460 }
11461
11462 /** Constructs a TextTable with five columns as follows:
11463 * <ol>
11464 * <li>required option/parameter marker (width: 2, indent: 0, TRUNCATE on overflow)</li>
11465 * <li>short option name (width: 2, indent: 0, TRUNCATE on overflow)</li>
11466 * <li>comma separator (width: 1, indent: 0, TRUNCATE on overflow)</li>
11467 * <li>long option name(s) (width: 24, indent: 1, SPAN multiple columns on overflow)</li>
11468 * <li>description line(s) (width: 51, indent: 1, WRAP to next row on overflow)</li>
11469 * </ol>
11470 * @param ansi whether to emit ANSI escape codes or not
11471 * @param longOptionsColumnWidth the width of the long options column
11472 * @param usageHelpWidth the total width of the columns combined
11473 */
11474 public static TextTable forDefaultColumns(Ansi ansi, int longOptionsColumnWidth, int usageHelpWidth) {
11475 // "* -c, --create Creates a ...."
11476 return forColumns(ansi,
11477 new Column(2, 0, TRUNCATE), // "*"
11478 new Column(2, 0, TRUNCATE), // "-c"
11479 new Column(1, 0, TRUNCATE), // ","
11480 new Column(longOptionsColumnWidth, 1, SPAN), // " --create"
11481 new Column(usageHelpWidth - longOptionsColumnWidth, 1, WRAP)); // " Creates a ..."
11482 }
11483
11484 /** Constructs a new TextTable with columns with the specified width, all SPANning multiple columns on
11485 * overflow except the last column which WRAPS to the next row.
11486 * @param ansi whether to emit ANSI escape codes or not
11487 * @param columnWidths the width of each table column (all columns have zero indent)
11488 */
11489 public static TextTable forColumnWidths(Ansi ansi, int... columnWidths) {
11490 Column[] columns = new Column[columnWidths.length];
11491 for (int i = 0; i < columnWidths.length; i++) {
11492 columns[i] = new Column(columnWidths[i], 0, i == columnWidths.length - 1 ? WRAP : SPAN);
11493 }
11494 return new TextTable(ansi, columns);
11495 }
11496 /** Constructs a {@code TextTable} with the specified columns.
11497 * @param ansi whether to emit ANSI escape codes or not
11498 * @param columns columns to construct this TextTable with */
11499 public static TextTable forColumns(Ansi ansi, Column... columns) { return new TextTable(ansi, columns); }
11500 protected TextTable(Ansi ansi, Column[] columns) {
11501 this.ansi = Assert.notNull(ansi, "ansi");
11502 this.columns = Assert.notNull(columns, "columns").clone();
11503 if (columns.length == 0) { throw new IllegalArgumentException("At least one column is required"); }
11504 int totalWidth = 0;
11505 for (Column col : columns) { totalWidth += col.width; }
11506 tableWidth = totalWidth;
11507 }
11508 /** The column definitions of this table. */
11509 public Column[] columns() { return columns.clone(); }
11510 /** Returns the {@code Text} slot at the specified row and column to write a text value into.
11511 * @param row the row of the cell whose Text to return
11512 * @param col the column of the cell whose Text to return
11513 * @return the Text object at the specified row and column
11514 * @since 2.0 */
11515 public Text textAt(int row, int col) { return columnValues.get(col + (row * columns.length)); }
11516
11517 /** Returns the {@code Text} slot at the specified row and column to write a text value into.
11518 * @param row the row of the cell whose Text to return
11519 * @param col the column of the cell whose Text to return
11520 * @return the Text object at the specified row and column
11521 * @deprecated use {@link #textAt(int, int)} instead */
11522 @Deprecated public Text cellAt(int row, int col) { return textAt(row, col); }
11523
11524 /** Returns the current number of rows of this {@code TextTable}.
11525 * @return the current number of rows in this TextTable */
11526 public int rowCount() { return columnValues.size() / columns.length; }
11527
11528 /** Adds the required {@code char[]} slots for a new row to the {@link #columnValues} field. */
11529 public void addEmptyRow() {
11530 for (int i = 0; i < columns.length; i++) {
11531 columnValues.add(ansi.new Text(columns[i].width));
11532 }
11533 }
11534
11535 /** Delegates to {@link #addRowValues(CommandLine.Help.Ansi.Text...)}.
11536 * @param values the text values to display in each column of the current row */
11537 public void addRowValues(String... values) {
11538 Text[] array = new Text[values.length];
11539 for (int i = 0; i < array.length; i++) {
11540 array[i] = values[i] == null ? Ansi.EMPTY_TEXT : ansi.new Text(values[i]);
11541 }
11542 addRowValues(array);
11543 }
11544 /**
11545 * Adds a new {@linkplain TextTable#addEmptyRow() empty row}, then calls {@link
11546 * TextTable#putValue(int, int, CommandLine.Help.Ansi.Text) putValue} for each of the specified values, adding more empty rows
11547 * if the return value indicates that the value spanned multiple columns or was wrapped to multiple rows.
11548 * @param values the values to write into a new row in this TextTable
11549 * @throws IllegalArgumentException if the number of values exceeds the number of Columns in this table
11550 */
11551 public void addRowValues(Text... values) {
11552 if (values.length > columns.length) {
11553 throw new IllegalArgumentException(values.length + " values don't fit in " +
11554 columns.length + " columns");
11555 }
11556 addEmptyRow();
11557 int oldIndent = unindent(values);
11558 for (int col = 0; col < values.length; col++) {
11559 int row = rowCount() - 1;// write to last row: previous value may have wrapped to next row
11560 Cell cell = putValue(row, col, values[col]);
11561
11562 // add row if a value spanned/wrapped and there are still remaining values
11563 if ((cell.row != row || cell.column != col) && col != values.length - 1) {
11564 addEmptyRow();
11565 }
11566 }
11567 reindent(oldIndent);
11568 }
11569 private int unindent(Text[] values) {
11570 if (columns.length <= LONG_OPTION_COLUMN) { return 0; }
11571 int oldIndent = columns[LONG_OPTION_COLUMN].indent;
11572 if ("=".equals(values[OPTION_SEPARATOR_COLUMN].toString())) {
11573 columns[LONG_OPTION_COLUMN].indent = 0;
11574 }
11575 return oldIndent;
11576 }
11577 private void reindent(int oldIndent) {
11578 if (columns.length <= LONG_OPTION_COLUMN) { return; }
11579 columns[LONG_OPTION_COLUMN].indent = oldIndent;
11580 }
11581
11582 /**
11583 * Writes the specified value into the cell at the specified row and column and returns the last row and
11584 * column written to. Depending on the Column's {@link Column#overflow Overflow} policy, the value may span
11585 * multiple columns or wrap to multiple rows when larger than the column width.
11586 * @param row the target row in the table
11587 * @param col the target column in the table to write to
11588 * @param value the value to write
11589 * @return a Cell indicating the position in the table that was last written to (since 2.0)
11590 * @throws IllegalArgumentException if the specified row exceeds the table's {@linkplain
11591 * TextTable#rowCount() row count}
11592 * @since 2.0 (previous versions returned a {@code java.awt.Point} object)
11593 */
11594 public Cell putValue(int row, int col, Text value) {
11595 if (row > rowCount() - 1) {
11596 throw new IllegalArgumentException("Cannot write to row " + row + ": rowCount=" + rowCount());
11597 }
11598 if (value == null || value.plain.length() == 0) { return new Cell(col, row); }
11599 Column column = columns[col];
11600 int indent = column.indent;
11601 switch (column.overflow) {
11602 case TRUNCATE:
11603 copy(value, textAt(row, col), indent);
11604 return new Cell(col, row);
11605 case SPAN:
11606 int startColumn = col;
11607 do {
11608 boolean lastColumn = col == columns.length - 1;
11609 int charsWritten = lastColumn
11610 ? copy(BreakIterator.getLineInstance(), value, textAt(row, col), indent)
11611 : copy(value, textAt(row, col), indent);
11612 value = value.substring(charsWritten);
11613 indent = 0;
11614 if (value.length > 0) { // value did not fit in column
11615 ++col; // write remainder of value in next column
11616 }
11617 if (value.length > 0 && col >= columns.length) { // we filled up all columns on this row
11618 addEmptyRow();
11619 row++;
11620 col = startColumn;
11621 indent = column.indent + indentWrappedLines;
11622 }
11623 } while (value.length > 0);
11624 return new Cell(col, row);
11625 case WRAP:
11626 BreakIterator lineBreakIterator = BreakIterator.getLineInstance();
11627 do {
11628 int charsWritten = copy(lineBreakIterator, value, textAt(row, col), indent);
11629 value = value.substring(charsWritten);
11630 indent = column.indent + indentWrappedLines;
11631 if (value.length > 0) { // value did not fit in column
11632 ++row; // write remainder of value in next row
11633 addEmptyRow();
11634 }
11635 } while (value.length > 0);
11636 return new Cell(col, row);
11637 }
11638 throw new IllegalStateException(column.overflow.toString());
11639 }
11640 private static int length(Text str) {
11641 return str.length; // TODO count some characters as double length
11642 }
11643
11644 private int copy(BreakIterator line, Text text, Text columnValue, int offset) {
11645 // Deceive the BreakIterator to ensure no line breaks after '-' character
11646 line.setText(text.plainString().replace("-", "\u00ff"));
11647 int done = 0;
11648 for (int start = line.first(), end = line.next(); end != BreakIterator.DONE; start = end, end = line.next()) {
11649 Text word = text.substring(start, end); //.replace("\u00ff", "-"); // not needed
11650 if (columnValue.maxLength >= offset + done + length(word)) {
11651 done += copy(word, columnValue, offset + done); // TODO messages length
11652 } else {
11653 break;
11654 }
11655 }
11656 if (done == 0 && length(text) + offset > columnValue.maxLength) {
11657 // The value is a single word that is too big to be written to the column. Write as much as we can.
11658 done = copy(text, columnValue, offset);
11659 }
11660 return done;
11661 }
11662 private static int copy(Text value, Text destination, int offset) {
11663 int length = Math.min(value.length, destination.maxLength - offset);
11664 value.getStyledChars(value.from, length, destination, offset);
11665 return length;
11666 }
11667
11668 /** Copies the text representation that we built up from the options into the specified StringBuilder.
11669 * @param text the StringBuilder to write into
11670 * @return the specified StringBuilder object (to allow method chaining and a more fluid API) */
11671 public StringBuilder toString(StringBuilder text) {
11672 int columnCount = this.columns.length;
11673 StringBuilder row = new StringBuilder(tableWidth);
11674 for (int i = 0; i < columnValues.size(); i++) {
11675 Text column = columnValues.get(i);
11676 row.append(column.toString());
11677 row.append(new String(spaces(columns[i % columnCount].width - column.length)));
11678 if (i % columnCount == columnCount - 1) {
11679 int lastChar = row.length() - 1;
11680 while (lastChar >= 0 && row.charAt(lastChar) == ' ') {lastChar--;} // rtrim
11681 row.setLength(lastChar + 1);
11682 text.append(row.toString()).append(System.getProperty("line.separator"));
11683 row.setLength(0);
11684 }
11685 }
11686 return text;
11687 }
11688 public String toString() { return toString(new StringBuilder()).toString(); }
11689 }
11690 /** Columns define the width, indent (leading number of spaces in a column before the value) and
11691 * {@linkplain Overflow Overflow} policy of a column in a {@linkplain TextTable TextTable}. */
11692 public static class Column {
11693
11694 /** Policy for handling text that is longer than the column width:
11695 * span multiple columns, wrap to the next row, or simply truncate the portion that doesn't fit. */
11696 public enum Overflow { TRUNCATE, SPAN, WRAP }
11697
11698 /** Column width in characters */
11699 public final int width;
11700
11701 /** Indent (number of empty spaces at the start of the column preceding the text value) */
11702 public int indent;
11703
11704 /** Policy that determines how to handle values larger than the column width. */
11705 public final Overflow overflow;
11706 public Column(int width, int indent, Overflow overflow) {
11707 this.width = width;
11708 this.indent = indent;
11709 this.overflow = Assert.notNull(overflow, "overflow");
11710 }
11711 }
11712
11713 /** All usage help message are generated with a color scheme that assigns certain styles and colors to common
11714 * parts of a usage message: the command name, options, positional parameters and option parameters.
11715 * Users may customize these styles by creating Help with a custom color scheme.
11716 * <p>Note that these options and styles may not be rendered if ANSI escape codes are not
11717 * {@linkplain Ansi#enabled() enabled}.</p>
11718 * @see Help#defaultColorScheme(Ansi)
11719 */
11720 public static class ColorScheme {
11721 public final List<IStyle> commandStyles = new ArrayList<IStyle>();
11722 public final List<IStyle> optionStyles = new ArrayList<IStyle>();
11723 public final List<IStyle> parameterStyles = new ArrayList<IStyle>();
11724 public final List<IStyle> optionParamStyles = new ArrayList<IStyle>();
11725 private final Ansi ansi;
11726
11727 /** Constructs a new empty ColorScheme with {@link Help.Ansi#AUTO}. */
11728 public ColorScheme() { this(Ansi.AUTO); }
11729
11730 /** Constructs a new empty ColorScheme with the specified Ansi enabled mode.
11731 * @see Help#defaultColorScheme(Ansi)
11732 * @param ansi whether to emit ANSI escape codes or not
11733 */
11734 public ColorScheme(Ansi ansi) {this.ansi = Assert.notNull(ansi, "ansi"); }
11735
11736 /** Adds the specified styles to the registered styles for commands in this color scheme and returns this color scheme.
11737 * @param styles the styles to add to the registered styles for commands in this color scheme
11738 * @return this color scheme to enable method chaining for a more fluent API */
11739 public ColorScheme commands(IStyle... styles) { return addAll(commandStyles, styles); }
11740 /** Adds the specified styles to the registered styles for options in this color scheme and returns this color scheme.
11741 * @param styles the styles to add to registered the styles for options in this color scheme
11742 * @return this color scheme to enable method chaining for a more fluent API */
11743 public ColorScheme options(IStyle... styles) { return addAll(optionStyles, styles);}
11744 /** Adds the specified styles to the registered styles for positional parameters in this color scheme and returns this color scheme.
11745 * @param styles the styles to add to registered the styles for parameters in this color scheme
11746 * @return this color scheme to enable method chaining for a more fluent API */
11747 public ColorScheme parameters(IStyle... styles) { return addAll(parameterStyles, styles);}
11748 /** Adds the specified styles to the registered styles for option parameters in this color scheme and returns this color scheme.
11749 * @param styles the styles to add to the registered styles for option parameters in this color scheme
11750 * @return this color scheme to enable method chaining for a more fluent API */
11751 public ColorScheme optionParams(IStyle... styles) { return addAll(optionParamStyles, styles);}
11752 /** Returns a Text with all command styles applied to the specified command string.
11753 * @param command the command string to apply the registered command styles to
11754 * @return a Text with all command styles applied to the specified command string */
11755 public Ansi.Text commandText(String command) { return ansi().apply(command, commandStyles); }
11756 /** Returns a Text with all option styles applied to the specified option string.
11757 * @param option the option string to apply the registered option styles to
11758 * @return a Text with all option styles applied to the specified option string */
11759 public Ansi.Text optionText(String option) { return ansi().apply(option, optionStyles); }
11760 /** Returns a Text with all parameter styles applied to the specified parameter string.
11761 * @param parameter the parameter string to apply the registered parameter styles to
11762 * @return a Text with all parameter styles applied to the specified parameter string */
11763 public Ansi.Text parameterText(String parameter) { return ansi().apply(parameter, parameterStyles); }
11764 /** Returns a Text with all optionParam styles applied to the specified optionParam string.
11765 * @param optionParam the option parameter string to apply the registered option parameter styles to
11766 * @return a Text with all option parameter styles applied to the specified option parameter string */
11767 public Ansi.Text optionParamText(String optionParam) { return ansi().apply(optionParam, optionParamStyles); }
11768
11769 /** Replaces colors and styles in this scheme with ones specified in system properties, and returns this scheme.
11770 * Supported property names:<ul>
11771 * <li>{@code picocli.color.commands}</li>
11772 * <li>{@code picocli.color.options}</li>
11773 * <li>{@code picocli.color.parameters}</li>
11774 * <li>{@code picocli.color.optionParams}</li>
11775 * </ul><p>Property values can be anything that {@link Help.Ansi.Style#parse(String)} can handle.</p>
11776 * @return this ColorScheme
11777 */
11778 public ColorScheme applySystemProperties() {
11779 replace(commandStyles, System.getProperty("picocli.color.commands"));
11780 replace(optionStyles, System.getProperty("picocli.color.options"));
11781 replace(parameterStyles, System.getProperty("picocli.color.parameters"));
11782 replace(optionParamStyles, System.getProperty("picocli.color.optionParams"));
11783 return this;
11784 }
11785 private void replace(List<IStyle> styles, String property) {
11786 if (property != null) {
11787 styles.clear();
11788 addAll(styles, Style.parse(property));
11789 }
11790 }
11791 private ColorScheme addAll(List<IStyle> styles, IStyle... add) {
11792 styles.addAll(Arrays.asList(add));
11793 return this;
11794 }
11795
11796 public Ansi ansi() { return ansi; }
11797 }
11798
11799 /** Creates and returns a new {@link ColorScheme} initialized with picocli default values: commands are bold,
11800 * options and parameters use a yellow foreground, and option parameters use italic.
11801 * @param ansi whether the usage help message should contain ANSI escape codes or not
11802 * @return a new default color scheme
11803 */
11804 public static ColorScheme defaultColorScheme(Ansi ansi) {
11805 return new ColorScheme(ansi)
11806 .commands(Style.bold)
11807 .options(Style.fg_yellow)
11808 .parameters(Style.fg_yellow)
11809 .optionParams(Style.italic);
11810 }
11811
11812 /** Provides methods and inner classes to support using ANSI escape codes in usage help messages. */
11813 public enum Ansi {
11814 /** Only emit ANSI escape codes if the platform supports it and system property {@code "picocli.ansi"}
11815 * is not set to any value other than {@code "true"} (case insensitive). */
11816 AUTO,
11817 /** Forced ON: always emit ANSI escape code regardless of the platform. */
11818 ON,
11819 /** Forced OFF: never emit ANSI escape code regardless of the platform. */
11820 OFF;
11821 static Text EMPTY_TEXT = OFF.new Text(0);
11822
11823 static Boolean tty;
11824 static boolean isTTY() {
11825 if (tty == null) { tty = calcTTY(); }
11826 return tty;
11827 }
11828 static final boolean isWindows() { return System.getProperty("os.name").startsWith("Windows"); }
11829 static final boolean isXterm() { return System.getenv("TERM") != null && System.getenv("TERM").startsWith("xterm"); }
11830 // null on Windows unless on Cygwin or MSYS
11831 static final boolean hasOsType() { return System.getenv("OSTYPE") != null; }
11832
11833 // see Jan Niklas Hasse's https://bixense.com/clicolors/ proposal
11834 // https://conemu.github.io/en/AnsiEscapeCodes.html#Environment_variable
11835 static final boolean hintDisabled() { return "0".equals(System.getenv("CLICOLOR"))
11836 || "OFF".equals(System.getenv("ConEmuANSI")); }
11837
11838 /** https://github.com/adoxa/ansicon/blob/master/readme.txt,
11839 * Jan Niklas Hasse's https://bixense.com/clicolors/ proposal,
11840 * https://conemu.github.io/en/AnsiEscapeCodes.html#Environment_variable */
11841 static final boolean hintEnabled() { return System.getenv("ANSICON") != null
11842 || "1".equals(System.getenv("CLICOLOR"))
11843 || "ON".equals(System.getenv("ConEmuANSI")); }
11844 /** https://no-color.org/ */
11845 static final boolean forceDisabled() { return System.getenv("NO_COLOR") != null; }
11846
11847 /** Jan Niklas Hasse's https://bixense.com/clicolors/ proposal */
11848 static final boolean forceEnabled() { return System.getenv("CLICOLOR_FORCE") != null
11849 && !"0".equals(System.getenv("CLICOLOR_FORCE"));}
11850 /** http://stackoverflow.com/questions/1403772/how-can-i-check-if-a-java-programs-input-output-streams-are-connected-to-a-term */
11851 static boolean calcTTY() {
11852 try { return System.class.getDeclaredMethod("console").invoke(null) != null; }
11853 catch (Throwable reflectionFailed) { return true; }
11854 }
11855 /** Cygwin and MSYS use pseudo-tty and console is always null... */
11856 static boolean isPseudoTTY() { return isWindows() && (isXterm() || hasOsType()); }
11857
11858 static boolean ansiPossible() {
11859 if (forceDisabled()) { return false; }
11860 if (forceEnabled()) { return true; }
11861 if (isWindows() && isJansiConsoleInstalled()) { return true; } // #630 JVM crash loading jansi.AnsiConsole on Linux
11862 if (hintDisabled()) { return false; }
11863 if (!isTTY() && !isPseudoTTY()) { return false; }
11864 return hintEnabled() || !isWindows() || isXterm() || hasOsType();
11865 }
11866 static boolean isJansiConsoleInstalled() {
11867 try {
11868 Class<?> ansiConsole = Class.forName("org.fusesource.jansi.AnsiConsole");
11869 Field out = ansiConsole.getField("out");
11870 return out.get(null) == System.out;
11871 } catch (Exception reflectionFailed) {
11872 return false;
11873 }
11874 }
11875
11876 /** Returns {@code true} if ANSI escape codes should be emitted, {@code false} otherwise.
11877 * @return ON: {@code true}, OFF: {@code false}, AUTO: if system property {@code "picocli.ansi"} is
11878 * defined then return its boolean value, otherwise return whether the platform supports ANSI escape codes */
11879 public boolean enabled() {
11880 if (this == ON) { return true; }
11881 if (this == OFF) { return false; }
11882 String ansi = System.getProperty("picocli.ansi");
11883 boolean auto = ansi == null || "AUTO".equalsIgnoreCase(ansi);
11884 return auto ? ansiPossible() : Boolean.getBoolean("picocli.ansi");
11885 }
11886 /**
11887 * Returns a new Text object for this Ansi mode, encapsulating the specified string
11888 * which may contain markup like {@code @|bg(red),white,underline some text|@}.
11889 * <p>
11890 * Calling {@code toString()} on the returned Text will either include ANSI escape codes
11891 * (if this Ansi mode is ON), or suppress ANSI escape codes (if this Ansi mode is OFF).
11892 * <p>
11893 * Equivalent to {@code this.new Text(stringWithMarkup)}.
11894 * @since 3.4 */
11895 public Text text(String stringWithMarkup) { return this.new Text(stringWithMarkup); }
11896
11897 /**
11898 * Returns a String where any markup like
11899 * {@code @|bg(red),white,underline some text|@} is converted to ANSI escape codes
11900 * if this Ansi is ON, or suppressed if this Ansi is OFF.
11901 * <p>
11902 * Equivalent to {@code this.new Text(stringWithMarkup).toString()}.
11903 * @since 3.4 */
11904 public String string(String stringWithMarkup) { return this.new Text(stringWithMarkup).toString(); }
11905
11906 /** Returns Ansi.ON if the specified {@code enabled} flag is true, Ansi.OFF otherwise.
11907 * @since 3.4 */
11908 public static Ansi valueOf(boolean enabled) {return enabled ? ON : OFF; }
11909
11910 /** Defines the interface for an ANSI escape sequence. */
11911 public interface IStyle {
11912
11913 /** The Control Sequence Introducer (CSI) escape sequence {@value}. */
11914 String CSI = "\u001B[";
11915
11916 /** Returns the ANSI escape code for turning this style on.
11917 * @return the ANSI escape code for turning this style on */
11918 String on();
11919
11920 /** Returns the ANSI escape code for turning this style off.
11921 * @return the ANSI escape code for turning this style off */
11922 String off();
11923 }
11924
11925 /**
11926 * A set of pre-defined ANSI escape code styles and colors, and a set of convenience methods for parsing
11927 * text with embedded markup style names, as well as convenience methods for converting
11928 * styles to strings with embedded escape codes.
11929 */
11930 public enum Style implements IStyle {
11931 reset(0, 0), bold(1, 21), faint(2, 22), italic(3, 23), underline(4, 24), blink(5, 25), reverse(7, 27),
11932 fg_black(30, 39), fg_red(31, 39), fg_green(32, 39), fg_yellow(33, 39), fg_blue(34, 39), fg_magenta(35, 39), fg_cyan(36, 39), fg_white(37, 39),
11933 bg_black(40, 49), bg_red(41, 49), bg_green(42, 49), bg_yellow(43, 49), bg_blue(44, 49), bg_magenta(45, 49), bg_cyan(46, 49), bg_white(47, 49),
11934 ;
11935 private final int startCode;
11936 private final int endCode;
11937
11938 Style(int startCode, int endCode) {this.startCode = startCode; this.endCode = endCode; }
11939 public String on() { return CSI + startCode + "m"; }
11940 public String off() { return CSI + endCode + "m"; }
11941
11942 /** Returns the concatenated ANSI escape codes for turning all specified styles on.
11943 * @param styles the styles to generate ANSI escape codes for
11944 * @return the concatenated ANSI escape codes for turning all specified styles on */
11945 public static String on(IStyle... styles) {
11946 StringBuilder result = new StringBuilder();
11947 for (IStyle style : styles) {
11948 result.append(style.on());
11949 }
11950 return result.toString();
11951 }
11952 /** Returns the concatenated ANSI escape codes for turning all specified styles off.
11953 * @param styles the styles to generate ANSI escape codes for
11954 * @return the concatenated ANSI escape codes for turning all specified styles off */
11955 public static String off(IStyle... styles) {
11956 StringBuilder result = new StringBuilder();
11957 for (IStyle style : styles) {
11958 result.append(style.off());
11959 }
11960 return result.toString();
11961 }
11962 /** Parses the specified style markup and returns the associated style.
11963 * The markup may be one of the Style enum value names, or it may be one of the Style enum value
11964 * names when {@code "fg_"} is prepended, or it may be one of the indexed colors in the 256 color palette.
11965 * @param str the case-insensitive style markup to convert, e.g. {@code "blue"} or {@code "fg_blue"},
11966 * or {@code "46"} (indexed color) or {@code "0;5;0"} (RGB components of an indexed color)
11967 * @return the IStyle for the specified converter
11968 */
11969 public static IStyle fg(String str) {
11970 try { return Style.valueOf(str.toLowerCase(ENGLISH)); } catch (Exception ignored) {}
11971 try { return Style.valueOf("fg_" + str.toLowerCase(ENGLISH)); } catch (Exception ignored) {}
11972 return new Palette256Color(true, str);
11973 }
11974 /** Parses the specified style markup and returns the associated style.
11975 * The markup may be one of the Style enum value names, or it may be one of the Style enum value
11976 * names when {@code "bg_"} is prepended, or it may be one of the indexed colors in the 256 color palette.
11977 * @param str the case-insensitive style markup to convert, e.g. {@code "blue"} or {@code "bg_blue"},
11978 * or {@code "46"} (indexed color) or {@code "0;5;0"} (RGB components of an indexed color)
11979 * @return the IStyle for the specified converter
11980 */
11981 public static IStyle bg(String str) {
11982 try { return Style.valueOf(str.toLowerCase(ENGLISH)); } catch (Exception ignored) {}
11983 try { return Style.valueOf("bg_" + str.toLowerCase(ENGLISH)); } catch (Exception ignored) {}
11984 return new Palette256Color(false, str);
11985 }
11986 /** Parses the specified comma-separated sequence of style descriptors and returns the associated
11987 * styles. For each markup, strings starting with {@code "bg("} are delegated to
11988 * {@link #bg(String)}, others are delegated to {@link #bg(String)}.
11989 * @param commaSeparatedCodes one or more descriptors, e.g. {@code "bg(blue),underline,red"}
11990 * @return an array with all styles for the specified descriptors
11991 */
11992 public static IStyle[] parse(String commaSeparatedCodes) {
11993 String[] codes = commaSeparatedCodes.split(",");
11994 IStyle[] styles = new IStyle[codes.length];
11995 for(int i = 0; i < codes.length; ++i) {
11996 if (codes[i].toLowerCase(ENGLISH).startsWith("fg(")) {
11997 int end = codes[i].indexOf(')');
11998 styles[i] = Style.fg(codes[i].substring(3, end < 0 ? codes[i].length() : end));
11999 } else if (codes[i].toLowerCase(ENGLISH).startsWith("bg(")) {
12000 int end = codes[i].indexOf(')');
12001 styles[i] = Style.bg(codes[i].substring(3, end < 0 ? codes[i].length() : end));
12002 } else {
12003 styles[i] = Style.fg(codes[i]);
12004 }
12005 }
12006 return styles;
12007 }
12008 }
12009
12010 /** Defines a palette map of 216 colors: 6 * 6 * 6 cube (216 colors):
12011 * 16 + 36 * r + 6 * g + b (0 <= r, g, b <= 5). */
12012 static class Palette256Color implements IStyle {
12013 private final int fgbg;
12014 private final int color;
12015
12016 Palette256Color(boolean foreground, String color) {
12017 this.fgbg = foreground ? 38 : 48;
12018 String[] rgb = color.split(";");
12019 if (rgb.length == 3) {
12020 this.color = 16 + 36 * Integer.decode(rgb[0]) + 6 * Integer.decode(rgb[1]) + Integer.decode(rgb[2]);
12021 } else {
12022 this.color = Integer.decode(color);
12023 }
12024 }
12025 public String on() { return String.format(CSI + "%d;5;%dm", fgbg, color); }
12026 public String off() { return CSI + (fgbg + 1) + "m"; }
12027 }
12028 private static class StyledSection {
12029 int startIndex, length;
12030 String startStyles, endStyles;
12031 StyledSection(int start, int len, String style1, String style2) {
12032 startIndex = start; length = len; startStyles = style1; endStyles = style2;
12033 }
12034 StyledSection withStartIndex(int newStart) {
12035 return new StyledSection(newStart, length, startStyles, endStyles);
12036 }
12037 }
12038
12039 /**
12040 * Returns a new Text object where all the specified styles are applied to the full length of the
12041 * specified plain text.
12042 * @param plainText the string to apply all styles to. Must not contain markup!
12043 * @param styles the styles to apply to the full plain text
12044 * @return a new Text object
12045 */
12046 public Text apply(String plainText, List<IStyle> styles) {
12047 if (plainText.length() == 0) { return new Text(0); }
12048 Text result = new Text(plainText.length());
12049 IStyle[] all = styles.toArray(new IStyle[styles.size()]);
12050 result.sections.add(new StyledSection(
12051 0, plainText.length(), Style.on(all), Style.off(reverse(all)) + Style.reset.off()));
12052 result.plain.append(plainText);
12053 result.length = result.plain.length();
12054 return result;
12055 }
12056
12057 private static <T> T[] reverse(T[] all) {
12058 for (int i = 0; i < all.length / 2; i++) {
12059 T temp = all[i];
12060 all[i] = all[all.length - i - 1];
12061 all[all.length - i - 1] = temp;
12062 }
12063 return all;
12064 }
12065 /** Encapsulates rich text with styles and colors. Text objects may be constructed with Strings containing
12066 * markup like {@code @|bg(red),white,underline some text|@}, and this class converts the markup to ANSI
12067 * escape codes.
12068 * <p>
12069 * Internally keeps both an enriched and a plain text representation to allow layout components to calculate
12070 * text width while remaining unaware of the embedded ANSI escape codes.</p> */
12071 public class Text implements Cloneable {
12072 private final int maxLength;
12073 private int from;
12074 private int length;
12075 private StringBuilder plain = new StringBuilder();
12076 private List<StyledSection> sections = new ArrayList<StyledSection>();
12077
12078 /** Constructs a Text with the specified max length (for use in a TextTable Column).
12079 * @param maxLength max length of this text */
12080 public Text(int maxLength) { this.maxLength = maxLength; }
12081
12082 /** Copy constructor.
12083 * @since 3.9 */
12084 public Text(Text other) {
12085 this.maxLength = other.maxLength;
12086 this.from = other.from;
12087 this.length = other.length;
12088 this.plain = new StringBuilder(other.plain);
12089 this.sections = new ArrayList<StyledSection>(other.sections);
12090 }
12091 /**
12092 * Constructs a Text with the specified String, which may contain markup like
12093 * {@code @|bg(red),white,underline some text|@}.
12094 * @param input the string with markup to parse
12095 */
12096 public Text(String input) {
12097 maxLength = -1;
12098 plain.setLength(0);
12099 int i = 0;
12100
12101 while (true) {
12102 int j = input.indexOf("@|", i);
12103 if (j == -1) {
12104 if (i == 0) {
12105 plain.append(input);
12106 length = plain.length();
12107 return;
12108 }
12109 plain.append(input.substring(i, input.length()));
12110 length = plain.length();
12111 return;
12112 }
12113 plain.append(input.substring(i, j));
12114 int k = input.indexOf("|@", j);
12115 if (k == -1) {
12116 plain.append(input);
12117 length = plain.length();
12118 return;
12119 }
12120
12121 j += 2;
12122 String spec = input.substring(j, k);
12123 String[] items = spec.split(" ", 2);
12124 if (items.length == 1) {
12125 plain.append(input);
12126 length = plain.length();
12127 return;
12128 }
12129
12130 IStyle[] styles = Style.parse(items[0]);
12131 addStyledSection(plain.length(), items[1].length(),
12132 Style.on(styles), Style.off(reverse(styles)) + Style.reset.off());
12133 plain.append(items[1]);
12134 i = k + 2;
12135 }
12136 }
12137 private void addStyledSection(int start, int length, String startStyle, String endStyle) {
12138 sections.add(new StyledSection(start, length, startStyle, endStyle));
12139 }
12140 public Object clone() { return new Text(this); }
12141
12142 public Text[] splitLines() {
12143 List<Text> result = new ArrayList<Text>();
12144 int start = 0, end = 0;
12145 for (int i = 0; i < plain.length(); i++, end = i) {
12146 char c = plain.charAt(i);
12147 boolean eol = c == '\n';
12148 if (c == '\r' && i + 1 < plain.length() && plain.charAt(i + 1) == '\n') { eol = true; i++; } // \r\n
12149 eol |= c == '\r';
12150 if (eol) {
12151 result.add(this.substring(start, end));
12152 start = i + 1;
12153 }
12154 }
12155 // add remainder (may be empty string)
12156 result.add(this.substring(start, plain.length()));
12157 return result.toArray(new Text[result.size()]);
12158 }
12159
12160 /** Returns a new {@code Text} instance that is a substring of this Text. Does not modify this instance!
12161 * @param start index in the plain text where to start the substring
12162 * @return a new Text instance that is a substring of this Text */
12163 public Text substring(int start) {
12164 return substring(start, length);
12165 }
12166
12167 /** Returns a new {@code Text} instance that is a substring of this Text. Does not modify this instance!
12168 * @param start index in the plain text where to start the substring
12169 * @param end index in the plain text where to end the substring
12170 * @return a new Text instance that is a substring of this Text */
12171 public Text substring(int start, int end) {
12172 Text result = (Text) clone();
12173 result.from = from + start;
12174 result.length = end - start;
12175 return result;
12176 }
12177 /** @deprecated use {@link #concat(String)} instead */
12178 @Deprecated public Text append(String string) { return concat(string); }
12179 /** @deprecated use {@link #concat(Text)} instead */
12180 @Deprecated public Text append(Text text) { return concat(text); }
12181
12182 /** Returns a copy of this {@code Text} instance with the specified text concatenated to the end. Does not modify this instance!
12183 * @param string the text to concatenate to the end of this Text
12184 * @return a new Text instance
12185 * @since 3.0 */
12186 public Text concat(String string) { return concat(new Text(string)); }
12187
12188 /** Returns a copy of this {@code Text} instance with the specified text concatenated to the end. Does not modify this instance!
12189 * @param other the text to concatenate to the end of this Text
12190 * @return a new Text instance
12191 * @since 3.0 */
12192 public Text concat(Text other) {
12193 Text result = (Text) clone();
12194 result.plain = new StringBuilder(plain.toString().substring(from, from + length));
12195 result.from = 0;
12196 result.sections = new ArrayList<StyledSection>();
12197 for (StyledSection section : sections) {
12198 result.sections.add(section.withStartIndex(section.startIndex - from));
12199 }
12200 result.plain.append(other.plain.toString().substring(other.from, other.from + other.length));
12201 for (StyledSection section : other.sections) {
12202 int index = result.length + section.startIndex - other.from;
12203 result.sections.add(section.withStartIndex(index));
12204 }
12205 result.length = result.plain.length();
12206 return result;
12207 }
12208
12209 /**
12210 * Copies the specified substring of this Text into the specified destination, preserving the markup.
12211 * @param from start of the substring
12212 * @param length length of the substring
12213 * @param destination destination Text to modify
12214 * @param offset indentation (padding)
12215 */
12216 public void getStyledChars(int from, int length, Text destination, int offset) {
12217 if (destination.length < offset) {
12218 for (int i = destination.length; i < offset; i++) {
12219 destination.plain.append(' ');
12220 }
12221 destination.length = offset;
12222 }
12223 for (StyledSection section : sections) {
12224 destination.sections.add(section.withStartIndex(section.startIndex - from + destination.length));
12225 }
12226 destination.plain.append(plain.toString().substring(from, from + length));
12227 destination.length = destination.plain.length();
12228 }
12229 /** Returns the plain text without any formatting.
12230 * @return the plain text without any formatting */
12231 public String plainString() { return plain.toString().substring(from, from + length); }
12232
12233 public boolean equals(Object obj) { return toString().equals(String.valueOf(obj)); }
12234 public int hashCode() { return toString().hashCode(); }
12235
12236 /** Returns a String representation of the text with ANSI escape codes embedded, unless ANSI is
12237 * {@linkplain Ansi#enabled()} not enabled}, in which case the plain text is returned.
12238 * @return a String representation of the text with ANSI escape codes embedded (if enabled) */
12239 public String toString() {
12240 if (!Ansi.this.enabled()) {
12241 return plain.toString().substring(from, from + length);
12242 }
12243 if (length == 0) { return ""; }
12244 StringBuilder sb = new StringBuilder(plain.length() + 20 * sections.size());
12245 StyledSection current = null;
12246 int end = Math.min(from + length, plain.length());
12247 for (int i = from; i < end; i++) {
12248 StyledSection section = findSectionContaining(i);
12249 if (section != current) {
12250 if (current != null) { sb.append(current.endStyles); }
12251 if (section != null) { sb.append(section.startStyles); }
12252 current = section;
12253 }
12254 sb.append(plain.charAt(i));
12255 }
12256 if (current != null) { sb.append(current.endStyles); }
12257 return sb.toString();
12258 }
12259
12260 private StyledSection findSectionContaining(int index) {
12261 for (StyledSection section : sections) {
12262 if (index >= section.startIndex && index < section.startIndex + section.length) {
12263 return section;
12264 }
12265 }
12266 return null;
12267 }
12268 }
12269 }
12270 }
12271
12272 /**
12273 * Utility class providing some defensive coding convenience methods.
12274 */
12275 private static final class Assert {
12276 /**
12277 * Throws a NullPointerException if the specified object is null.
12278 * @param object the object to verify
12279 * @param description error message
12280 * @param <T> type of the object to check
12281 * @return the verified object
12282 */
12283 static <T> T notNull(T object, String description) {
12284 if (object == null) {
12285 throw new NullPointerException(description);
12286 }
12287 return object;
12288 }
12289 static boolean equals(Object obj1, Object obj2) { return obj1 == null ? obj2 == null : obj1.equals(obj2); }
12290 static int hashCode(Object obj) {return obj == null ? 0 : obj.hashCode(); }
12291 static int hashCode(boolean bool) {return bool ? 1 : 0; }
12292 static void assertTrue(boolean condition, String message) {
12293 if (!condition) throw new IllegalStateException(message);
12294 }
12295 private Assert() {} // private constructor: never instantiate
12296 }
12297 private enum TraceLevel { OFF, WARN, INFO, DEBUG;
12298 public boolean isEnabled(TraceLevel other) { return ordinal() >= other.ordinal(); }
12299 private void print(Tracer tracer, String msg, Object... params) {
12300 if (tracer.level.isEnabled(this)) { tracer.stream.printf(prefix(msg), params); }
12301 }
12302 private String prefix(String msg) { return "[picocli " + this + "] " + msg; }
12303 static TraceLevel lookup(String key) { return key == null ? WARN : empty(key) || "true".equalsIgnoreCase(key) ? INFO : valueOf(key); }
12304 }
12305 static class Tracer {
12306 TraceLevel level = TraceLevel.lookup(System.getProperty("picocli.trace"));
12307 PrintStream stream = System.err;
12308 void warn (String msg, Object... params) { TraceLevel.WARN.print(this, msg, params); }
12309 void info (String msg, Object... params) { TraceLevel.INFO.print(this, msg, params); }
12310 void debug(String msg, Object... params) { TraceLevel.DEBUG.print(this, msg, params); }
12311 boolean isWarn() { return level.isEnabled(TraceLevel.WARN); }
12312 boolean isInfo() { return level.isEnabled(TraceLevel.INFO); }
12313 boolean isDebug() { return level.isEnabled(TraceLevel.DEBUG); }
12314 }
12315 /**
12316 * Uses cosine similarity to find matches from a candidate set for a specified input.
12317 * Based on code from http://www.nearinfinity.com/blogs/seth_schroeder/groovy_cosine_similarity_in_grails.html
12318 *
12319 * @author Burt Beckwith
12320 */
12321 private static class CosineSimilarity {
12322 static List<String> mostSimilar(String pattern, Iterable<String> candidates) { return mostSimilar(pattern, candidates, 0); }
12323 static List<String> mostSimilar(String pattern, Iterable<String> candidates, double threshold) {
12324 pattern = pattern.toLowerCase();
12325 SortedMap<Double, String> sorted = new TreeMap<Double, String>();
12326 for (String candidate : candidates) {
12327 double score = similarity(pattern, candidate.toLowerCase(), 2);
12328 if (score > threshold) { sorted.put(score, candidate); }
12329 }
12330 return reverseList(new ArrayList<String>(sorted.values()));
12331 }
12332
12333 private static double similarity(String sequence1, String sequence2, int degree) {
12334 Map<String, Integer> m1 = countNgramFrequency(sequence1, degree);
12335 Map<String, Integer> m2 = countNgramFrequency(sequence2, degree);
12336 return dotProduct(m1, m2) / Math.sqrt(dotProduct(m1, m1) * dotProduct(m2, m2));
12337 }
12338
12339 private static Map<String, Integer> countNgramFrequency(String sequence, int degree) {
12340 Map<String, Integer> m = new HashMap<String, Integer>();
12341 for (int i = 0; i + degree <= sequence.length(); i++) {
12342 String gram = sequence.substring(i, i + degree);
12343 m.put(gram, 1 + (m.containsKey(gram) ? m.get(gram) : 0));
12344 }
12345 return m;
12346 }
12347
12348 private static double dotProduct(Map<String, Integer> m1, Map<String, Integer> m2) {
12349 double result = 0;
12350 for (String key : m1.keySet()) { result += m1.get(key) * (m2.containsKey(key) ? m2.get(key) : 0); }
12351 return result;
12352 }
12353 }
12354 /** Base class of all exceptions thrown by {@code picocli.CommandLine}.
12355 * <h2>Class Diagram of the Picocli Exceptions</h2>
12356 * <p>
12357 * <img src="doc-files/class-diagram-exceptions.png" alt="Class Diagram of the Picocli Exceptions">
12358 * </p>
12359 * @since 2.0 */
12360 public static class PicocliException extends RuntimeException {
12361 private static final long serialVersionUID = -2574128880125050818L;
12362 public PicocliException(String msg) { super(msg); }
12363 public PicocliException(String msg, Throwable t) { super(msg, t); }
12364 }
12365 /** Exception indicating a problem during {@code CommandLine} initialization.
12366 * @since 2.0 */
12367 public static class InitializationException extends PicocliException {
12368 private static final long serialVersionUID = 8423014001666638895L;
12369 public InitializationException(String msg) { super(msg); }
12370 public InitializationException(String msg, Exception ex) { super(msg, ex); }
12371 }
12372 /** Exception indicating a problem while invoking a command or subcommand.
12373 * @since 2.0 */
12374 public static class ExecutionException extends PicocliException {
12375 private static final long serialVersionUID = 7764539594267007998L;
12376 private final CommandLine commandLine;
12377 public ExecutionException(CommandLine commandLine, String msg) {
12378 super(msg);
12379 this.commandLine = Assert.notNull(commandLine, "commandLine");
12380 }
12381 public ExecutionException(CommandLine commandLine, String msg, Throwable t) {
12382 super(msg, t);
12383 this.commandLine = Assert.notNull(commandLine, "commandLine");
12384 }
12385 /** Returns the {@code CommandLine} object for the (sub)command that could not be invoked.
12386 * @return the {@code CommandLine} object for the (sub)command where invocation failed.
12387 */
12388 public CommandLine getCommandLine() { return commandLine; }
12389 }
12390
12391 /** Exception thrown by {@link ITypeConverter} implementations to indicate a String could not be converted. */
12392 public static class TypeConversionException extends PicocliException {
12393 private static final long serialVersionUID = 4251973913816346114L;
12394 public TypeConversionException(String msg) { super(msg); }
12395 }
12396 /** Exception indicating something went wrong while parsing command line options. */
12397 public static class ParameterException extends PicocliException {
12398 private static final long serialVersionUID = 1477112829129763139L;
12399 private final CommandLine commandLine;
12400 private ArgSpec argSpec = null;
12401 private String value = null;
12402
12403 /** Constructs a new ParameterException with the specified CommandLine and error message.
12404 * @param commandLine the command or subcommand whose input was invalid
12405 * @param msg describes the problem
12406 * @since 2.0 */
12407 public ParameterException(CommandLine commandLine, String msg) {
12408 super(msg);
12409 this.commandLine = Assert.notNull(commandLine, "commandLine");
12410 }
12411
12412 /** Constructs a new ParameterException with the specified CommandLine and error message.
12413 * @param commandLine the command or subcommand whose input was invalid
12414 * @param msg describes the problem
12415 * @param t the throwable that caused this ParameterException
12416 * @since 2.0 */
12417 public ParameterException(CommandLine commandLine, String msg, Throwable t) {
12418 super(msg, t);
12419 this.commandLine = Assert.notNull(commandLine, "commandLine");
12420 }
12421
12422 /** Constructs a new ParameterException with the specified CommandLine and error message.
12423 * @param commandLine the command or subcommand whose input was invalid
12424 * @param msg describes the problem
12425 * @param t the throwable that caused this ParameterException
12426 * @param argSpec the argSpec that caused this ParameterException
12427 * @param value the value that caused this ParameterException
12428 * @since 3.2 */
12429 public ParameterException(CommandLine commandLine, String msg, Throwable t, ArgSpec argSpec, String value) {
12430 super(msg, t);
12431 this.commandLine = Assert.notNull(commandLine, "commandLine");
12432 if (argSpec == null && value == null) { throw new IllegalArgumentException("ArgSpec and value cannot both be null"); }
12433 this.argSpec = argSpec;
12434 this.value = value;
12435 }
12436
12437 /** Constructs a new ParameterException with the specified CommandLine and error message.
12438 * @param commandLine the command or subcommand whose input was invalid
12439 * @param msg describes the problem
12440 * @param argSpec the argSpec that caused this ParameterException
12441 * @param value the value that caused this ParameterException
12442 * @since 3.2 */
12443 public ParameterException(CommandLine commandLine, String msg, ArgSpec argSpec, String value) {
12444 super(msg);
12445 this.commandLine = Assert.notNull(commandLine, "commandLine");
12446 if (argSpec == null && value == null) { throw new IllegalArgumentException("ArgSpec and value cannot both be null"); }
12447 this.argSpec = argSpec;
12448 this.value = value;
12449 }
12450
12451
12452 /** Returns the {@code CommandLine} object for the (sub)command whose input could not be parsed.
12453 * @return the {@code CommandLine} object for the (sub)command where parsing failed.
12454 * @since 2.0
12455 */
12456 public CommandLine getCommandLine() { return commandLine; }
12457
12458 /** Returns the {@code ArgSpec} object for the (sub)command whose input could not be parsed.
12459 * @return the {@code ArgSpec} object for the (sub)command where parsing failed.
12460 * @since 3.2
12461 */
12462 public ArgSpec getArgSpec() { return argSpec; }
12463
12464 /** Returns the {@code String} value for the (sub)command whose input could not be parsed.
12465 * @return the {@code String} value for the (sub)command where parsing failed.
12466 * @since 3.2
12467 */
12468 public String getValue() { return value; }
12469
12470 private static ParameterException create(CommandLine cmd, Exception ex, String arg, int i, String[] args) {
12471 String msg = ex.getClass().getSimpleName() + ": " + ex.getLocalizedMessage()
12472 + " while processing argument at or before arg[" + i + "] '" + arg + "' in " + Arrays.toString(args) + ": " + ex.toString();
12473 return new ParameterException(cmd, msg, ex, null, arg);
12474 }
12475 }
12476 /**
12477 * Exception indicating that a required parameter was not specified.
12478 */
12479 public static class MissingParameterException extends ParameterException {
12480 private static final long serialVersionUID = 5075678535706338753L;
12481 private final List<ArgSpec> missing;
12482 public MissingParameterException(CommandLine commandLine, ArgSpec missing, String msg) { this(commandLine, Arrays.asList(missing), msg); }
12483 public MissingParameterException(CommandLine commandLine, Collection<ArgSpec> missing, String msg) {
12484 super(commandLine, msg);
12485 this.missing = Collections.unmodifiableList(new ArrayList<ArgSpec>(missing));
12486 }
12487 public List<ArgSpec> getMissing() { return missing; }
12488 private static MissingParameterException create(CommandLine cmd, Collection<ArgSpec> missing, String separator) {
12489 if (missing.size() == 1) {
12490 return new MissingParameterException(cmd, missing, "Missing required option '"
12491 + ArgSpec.describe(missing.iterator().next(), separator) + "'");
12492 }
12493 List<String> names = new ArrayList<String>(missing.size());
12494 for (ArgSpec argSpec : missing) {
12495 names.add(ArgSpec.describe(argSpec, separator));
12496 }
12497 return new MissingParameterException(cmd, missing, "Missing required options " + names.toString());
12498 }
12499 }
12500 /** Exception indicating that the user input included multiple arguments from a mutually exclusive group.
12501 * @since 4.0 */
12502 public static class MutuallyExclusiveArgsException extends ParameterException {
12503 private static final long serialVersionUID = -5557715106221420986L;
12504 public MutuallyExclusiveArgsException(CommandLine commandLine, String msg) { super(commandLine, msg); }
12505 }
12506
12507 /** Exception indicating that multiple named elements have incorrectly used the same name.
12508 * @since 4.0 */
12509 public static class DuplicateNameException extends InitializationException {
12510 private static final long serialVersionUID = -4126747467955626054L;
12511 public DuplicateNameException(String msg) { super(msg); }
12512 }
12513 /**
12514 * Exception indicating that multiple fields have been annotated with the same Option name.
12515 */
12516 public static class DuplicateOptionAnnotationsException extends DuplicateNameException {
12517 private static final long serialVersionUID = -3355128012575075641L;
12518 public DuplicateOptionAnnotationsException(String msg) { super(msg); }
12519
12520 private static DuplicateOptionAnnotationsException create(String name, ArgSpec argSpec1, ArgSpec argSpec2) {
12521 return new DuplicateOptionAnnotationsException("Option name '" + name + "' is used by both " +
12522 argSpec1.toString() + " and " + argSpec2.toString());
12523 }
12524 }
12525 /** Exception indicating that there was a gap in the indices of the fields annotated with {@link Parameters}. */
12526 public static class ParameterIndexGapException extends InitializationException {
12527 private static final long serialVersionUID = -1520981133257618319L;
12528 public ParameterIndexGapException(String msg) { super(msg); }
12529 }
12530 /** Exception indicating that a command line argument could not be mapped to any of the fields annotated with
12531 * {@link Option} or {@link Parameters}. */
12532 public static class UnmatchedArgumentException extends ParameterException {
12533 private static final long serialVersionUID = -8700426380701452440L;
12534 private List<String> unmatched = Collections.<String>emptyList();
12535 public UnmatchedArgumentException(CommandLine commandLine, String msg) { super(commandLine, msg); }
12536 public UnmatchedArgumentException(CommandLine commandLine, Stack<String> args) { this(commandLine, new ArrayList<String>(reverse(args))); }
12537 public UnmatchedArgumentException(CommandLine commandLine, List<String> args) {
12538 this(commandLine, describe(args, commandLine) + (args.size() == 1 ? ": " : "s: ") + str(args));
12539 unmatched = args == null ? Collections.<String>emptyList() : args;
12540 }
12541 /** Returns {@code true} and prints suggested solutions to the specified stream if such solutions exist, otherwise returns {@code false}.
12542 * @since 3.3.0 */
12543 public static boolean printSuggestions(ParameterException ex, PrintStream out) {
12544 return ex instanceof UnmatchedArgumentException && ((UnmatchedArgumentException) ex).printSuggestions(out);
12545 }
12546 /** Returns the unmatched command line arguments.
12547 * @since 3.3.0 */
12548 public List<String> getUnmatched() { return stripErrorMessage(unmatched); }
12549 static List<String> stripErrorMessage(List<String> unmatched) {
12550 List<String> result = new ArrayList<String>();
12551 for (String s : unmatched) {
12552 if (s == null) { result.add(null); }
12553 int pos = s.indexOf(" (while processing option:");
12554 result.add(pos < 0 ? s : s.substring(0, pos));
12555 }
12556 return Collections.unmodifiableList(result);
12557 }
12558 /** Returns {@code true} if the first unmatched command line arguments resembles an option, {@code false} otherwise.
12559 * @since 3.3.0 */
12560 public boolean isUnknownOption() { return isUnknownOption(unmatched, getCommandLine()); }
12561 /** Returns {@code true} and prints suggested solutions to the specified stream if such solutions exist, otherwise returns {@code false}.
12562 * @since 3.3.0 */
12563 public boolean printSuggestions(PrintStream out) {
12564 List<String> suggestions = getSuggestions();
12565 if (!suggestions.isEmpty()) {
12566 out.println(isUnknownOption()
12567 ? "Possible solutions: " + str(suggestions)
12568 : "Did you mean: " + str(suggestions).replace(", ", " or ") + "?");
12569 }
12570 return !suggestions.isEmpty();
12571 }
12572 /** Returns suggested solutions if such solutions exist, otherwise returns an empty list.
12573 * @since 3.3.0 */
12574 public List<String> getSuggestions() {
12575 if (unmatched == null || unmatched.isEmpty()) { return Collections.emptyList(); }
12576 String arg = unmatched.get(0);
12577 String stripped = CommandSpec.stripPrefix(arg);
12578 CommandSpec spec = getCommandLine().getCommandSpec();
12579 if (spec.resemblesOption(arg, null)) {
12580 return spec.findOptionNamesWithPrefix(stripped.substring(0, Math.min(2, stripped.length())));
12581 } else if (!spec.subcommands().isEmpty()) {
12582 List<String> mostSimilar = CosineSimilarity.mostSimilar(arg, spec.subcommands().keySet());
12583 return mostSimilar.subList(0, Math.min(3, mostSimilar.size()));
12584 }
12585 return Collections.emptyList();
12586 }
12587 private static boolean isUnknownOption(List<String> unmatch, CommandLine cmd) {
12588 return unmatch != null && !unmatch.isEmpty() && cmd.getCommandSpec().resemblesOption(unmatch.get(0), null);
12589 }
12590 private static String describe(List<String> unmatch, CommandLine cmd) {
12591 return isUnknownOption(unmatch, cmd) ? "Unknown option" : "Unmatched argument";
12592 }
12593 static String str(List<String> list) {
12594 String s = list.toString();
12595 return s.substring(0, s.length() - 1).substring(1);
12596 }
12597 }
12598 /** Exception indicating that more values were specified for an option or parameter than its {@link Option#arity() arity} allows. */
12599 public static class MaxValuesExceededException extends ParameterException {
12600 private static final long serialVersionUID = 6536145439570100641L;
12601 public MaxValuesExceededException(CommandLine commandLine, String msg) { super(commandLine, msg); }
12602 }
12603 /** Exception indicating that an option for a single-value option field has been specified multiple times on the command line. */
12604 public static class OverwrittenOptionException extends ParameterException {
12605 private static final long serialVersionUID = 1338029208271055776L;
12606 private final ArgSpec overwrittenArg;
12607 public OverwrittenOptionException(CommandLine commandLine, ArgSpec overwritten, String msg) {
12608 super(commandLine, msg);
12609 overwrittenArg = overwritten;
12610 }
12611 /** Returns the {@link ArgSpec} for the option which was being overwritten.
12612 * @since 3.8 */
12613 public ArgSpec getOverwritten() { return overwrittenArg; }
12614 }
12615 /**
12616 * Exception indicating that an annotated field had a type for which no {@link ITypeConverter} was
12617 * {@linkplain #registerConverter(Class, ITypeConverter) registered}.
12618 */
12619 public static class MissingTypeConverterException extends ParameterException {
12620 private static final long serialVersionUID = -6050931703233083760L;
12621 public MissingTypeConverterException(CommandLine commandLine, String msg) { super(commandLine, msg); }
12622 }
12623}