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 */
141@SuppressWarnings("unchecked") //added by me to suppress warning
142public class CommandLine {
143
144 /** This is picocli version {@value}. */
145 public static final String VERSION = "4.0.0-alpha-2-SNAPSHOT";
146
147 private final Tracer tracer = new Tracer();
148 private final CommandSpec commandSpec;
149 private final Interpreter interpreter;
150 private final IFactory factory;
151
152 /**
153 * 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.
154 * <p>The specified object may be a {@link CommandSpec CommandSpec} object, or it may be a {@code @Command}-annotated
155 * user object with {@code @Option} and {@code @Parameters}-annotated fields, in which case picocli automatically
156 * constructs a {@code CommandSpec} from this user object.
157 * </p><p>
158 * When the {@link #parse(String...)} method is called, the {@link CommandSpec CommandSpec} object will be
159 * initialized based on command line arguments. If the commandSpec is created from an annotated user object, this
160 * user object will be initialized based on the command line arguments.</p>
161 * @param command an annotated user object or a {@code CommandSpec} object to initialize from the command line arguments
162 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
163 */
164 public CommandLine(Object command) {
165 this(command, new DefaultFactory());
166 }
167 /**
168 * 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.
169 * <p>The specified object may be a {@link CommandSpec CommandSpec} object, or it may be a {@code @Command}-annotated
170 * user object with {@code @Option} and {@code @Parameters}-annotated fields, in which case picocli automatically
171 * constructs a {@code CommandSpec} from this user object.
172 * </p><p> If the specified command object is an interface {@code Class} with {@code @Option} and {@code @Parameters}-annotated methods,
173 * picocli creates a {@link java.lang.reflect.Proxy Proxy} whose methods return the matched command line values.
174 * If the specified command object is a concrete {@code Class}, picocli delegates to the {@linkplain IFactory factory} to get an instance.
175 * </p><p>
176 * When the {@link #parse(String...)} method is called, the {@link CommandSpec CommandSpec} object will be
177 * initialized based on command line arguments. If the commandSpec is created from an annotated user object, this
178 * user object will be initialized based on the command line arguments.</p>
179 * @param command an annotated user object or a {@code CommandSpec} object to initialize from the command line arguments
180 * @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
181 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
182 * @since 2.2 */
183 public CommandLine(Object command, IFactory factory) {
184 this.factory = Assert.notNull(factory, "factory");
185 interpreter = new Interpreter();
186 commandSpec = CommandSpec.forAnnotatedObject(command, factory);
187 commandSpec.commandLine(this);
188 commandSpec.validate();
189 if (commandSpec.unmatchedArgsBindings().size() > 0) { setUnmatchedArgumentsAllowed(true); }
190 }
191
192 /**
193 * Returns the {@code CommandSpec} model that this {@code CommandLine} was constructed with.
194 * @return the {@code CommandSpec} model
195 * @since 3.0 */
196 public CommandSpec getCommandSpec() { return commandSpec; }
197
198 /**
199 * Adds the options and positional parameters in the specified mixin to this command.
200 * <p>The specified object may be a {@link CommandSpec CommandSpec} object, or it may be a user object with
201 * {@code @Option} and {@code @Parameters}-annotated fields, in which case picocli automatically
202 * constructs a {@code CommandSpec} from this user object.
203 * </p>
204 * @param name the name by which the mixin object may later be retrieved
205 * @param mixin an annotated user object or a {@link CommandSpec CommandSpec} object whose options and positional parameters to add to this command
206 * @return this CommandLine object, to allow method chaining
207 * @since 3.0 */
208 public CommandLine addMixin(String name, Object mixin) {
209 getCommandSpec().addMixin(name, CommandSpec.forAnnotatedObject(mixin, factory));
210 return this;
211 }
212
213 /**
214 * Returns a map of user objects whose options and positional parameters were added to ("mixed in" with) this command.
215 * @return a new Map containing the user objects mixed in with this command. If {@code CommandSpec} objects without
216 * user objects were programmatically added, use the {@link CommandSpec#mixins() underlying model} directly.
217 * @since 3.0 */
218 public Map<String, Object> getMixins() {
219 Map<String, CommandSpec> mixins = getCommandSpec().mixins();
220 Map<String, Object> result = new LinkedHashMap<String, Object>();
221 for (String name : mixins.keySet()) { result.put(name, mixins.get(name).userObject); }
222 return result;
223 }
224
225 /** Registers a subcommand with the specified name. For example:
226 * <pre>
227 * CommandLine commandLine = new CommandLine(new Git())
228 * .addSubcommand("status", new GitStatus())
229 * .addSubcommand("commit", new GitCommit();
230 * .addSubcommand("add", new GitAdd())
231 * .addSubcommand("branch", new GitBranch())
232 * .addSubcommand("checkout", new GitCheckout())
233 * //...
234 * ;
235 * </pre>
236 *
237 * <p>The specified object can be an annotated object or a
238 * {@code CommandLine} instance with its own nested subcommands. For example:</p>
239 * <pre>
240 * CommandLine commandLine = new CommandLine(new MainCommand())
241 * .addSubcommand("cmd1", new ChildCommand1()) // subcommand
242 * .addSubcommand("cmd2", new ChildCommand2())
243 * .addSubcommand("cmd3", new CommandLine(new ChildCommand3()) // subcommand with nested sub-subcommands
244 * .addSubcommand("cmd3sub1", new GrandChild3Command1())
245 * .addSubcommand("cmd3sub2", new GrandChild3Command2())
246 * .addSubcommand("cmd3sub3", new CommandLine(new GrandChild3Command3()) // deeper nesting
247 * .addSubcommand("cmd3sub3sub1", new GreatGrandChild3Command3_1())
248 * .addSubcommand("cmd3sub3sub2", new GreatGrandChild3Command3_2())
249 * )
250 * );
251 * </pre>
252 * <p>The default type converters are available on all subcommands and nested sub-subcommands, but custom type
253 * converters are registered only with the subcommand hierarchy as it existed when the custom type was registered.
254 * To ensure a custom type converter is available to all subcommands, register the type converter last, after
255 * adding subcommands.</p>
256 * <p>See also the {@link Command#subcommands()} annotation to register subcommands declaratively.</p>
257 *
258 * @param name the string to recognize on the command line as a subcommand
259 * @param command the object to initialize with command line arguments following the subcommand name.
260 * This may be a {@code CommandLine} instance with its own (nested) subcommands
261 * @return this CommandLine object, to allow method chaining
262 * @see #registerConverter(Class, ITypeConverter)
263 * @since 0.9.7
264 * @see Command#subcommands()
265 */
266 public CommandLine addSubcommand(String name, Object command) {
267 return addSubcommand(name, command, new String[0]);
268 }
269
270 /** Registers a subcommand with the specified name and all specified aliases. See also {@link #addSubcommand(String, Object)}.
271 *
272 *
273 * @param name the string to recognize on the command line as a subcommand
274 * @param command the object to initialize with command line arguments following the subcommand name.
275 * This may be a {@code CommandLine} instance with its own (nested) subcommands
276 * @param aliases zero or more alias names that are also recognized on the command line as this subcommand
277 * @return this CommandLine object, to allow method chaining
278 * @since 3.1
279 * @see #addSubcommand(String, Object)
280 */
281 public CommandLine addSubcommand(String name, Object command, String... aliases) {
282 CommandLine subcommandLine = toCommandLine(command, factory);
283 subcommandLine.getCommandSpec().aliases.addAll(Arrays.asList(aliases));
284 getCommandSpec().addSubcommand(name, subcommandLine);
285 CommandLine.Model.CommandReflection.initParentCommand(subcommandLine.getCommandSpec().userObject(), getCommandSpec().userObject());
286 return this;
287 }
288 /** Returns a map with the subcommands {@linkplain #addSubcommand(String, Object) registered} on this instance.
289 * @return a map with the registered subcommands
290 * @since 0.9.7
291 */
292 public Map<String, CommandLine> getSubcommands() {
293 return new LinkedHashMap<String, CommandLine>(getCommandSpec().subcommands());
294 }
295 /**
296 * Returns the command that this is a subcommand of, or {@code null} if this is a top-level command.
297 * @return the command that this is a subcommand of, or {@code null} if this is a top-level command
298 * @see #addSubcommand(String, Object)
299 * @see Command#subcommands()
300 * @since 0.9.8
301 */
302 public CommandLine getParent() {
303 CommandSpec parent = getCommandSpec().parent();
304 return parent == null ? null : parent.commandLine();
305 }
306
307 /** Returns the annotated user object that this {@code CommandLine} instance was constructed with.
308 * @param <T> the type of the variable that the return value is being assigned to
309 * @return the annotated object that this {@code CommandLine} instance was constructed with
310 * @since 0.9.7
311 */
312 @SuppressWarnings("unchecked")
313 public <T> T getCommand() {
314 return (T) getCommandSpec().userObject();
315 }
316
317 /** Returns {@code true} if an option annotated with {@link Option#usageHelp()} was specified on the command line.
318 * @return whether the parser encountered an option annotated with {@link Option#usageHelp()}.
319 * @since 0.9.8 */
320 public boolean isUsageHelpRequested() { return interpreter.parseResultBuilder != null && interpreter.parseResultBuilder.usageHelpRequested; }
321
322 /** Returns {@code true} if an option annotated with {@link Option#versionHelp()} was specified on the command line.
323 * @return whether the parser encountered an option annotated with {@link Option#versionHelp()}.
324 * @since 0.9.8 */
325 public boolean isVersionHelpRequested() { return interpreter.parseResultBuilder != null && interpreter.parseResultBuilder.versionHelpRequested; }
326
327 /** Returns the {@code IHelpFactory} that is used to construct the usage help message.
328 * @see #setHelpFactory(IHelpFactory)
329 * @since 3.9
330 */
331 public IHelpFactory getHelpFactory() {
332 return getCommandSpec().usageMessage().helpFactory();
333 }
334
335 /** Sets a new {@code IHelpFactory} to customize the usage help message.
336 * @param helpFactory the new help factory. Must be non-{@code null}.
337 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
338 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
339 * later will have the default setting. To ensure a setting is applied to all
340 * subcommands, call the setter last, after adding subcommands.</p>
341 * @return this {@code CommandLine} object, to allow method chaining
342 * @since 3.9
343 */
344 public CommandLine setHelpFactory(IHelpFactory helpFactory) {
345 getCommandSpec().usageMessage().helpFactory(helpFactory);
346 for (CommandLine command : getCommandSpec().subcommands().values()) {
347 command.setHelpFactory(helpFactory);
348 }
349 return this;
350 }
351
352 /**
353 * Returns the section keys in the order that the usage help message should render the sections.
354 * This ordering may be modified with {@link #setHelpSectionKeys(List) setSectionKeys}. The default keys are (in order):
355 * <ol>
356 * <li>{@link UsageMessageSpec#SECTION_KEY_HEADER_HEADING SECTION_KEY_HEADER_HEADING}</li>
357 * <li>{@link UsageMessageSpec#SECTION_KEY_HEADER SECTION_KEY_HEADER}</li>
358 * <li>{@link UsageMessageSpec#SECTION_KEY_SYNOPSIS_HEADING SECTION_KEY_SYNOPSIS_HEADING}</li>
359 * <li>{@link UsageMessageSpec#SECTION_KEY_SYNOPSIS SECTION_KEY_SYNOPSIS}</li>
360 * <li>{@link UsageMessageSpec#SECTION_KEY_DESCRIPTION_HEADING SECTION_KEY_DESCRIPTION_HEADING}</li>
361 * <li>{@link UsageMessageSpec#SECTION_KEY_DESCRIPTION SECTION_KEY_DESCRIPTION}</li>
362 * <li>{@link UsageMessageSpec#SECTION_KEY_PARAMETER_LIST_HEADING SECTION_KEY_PARAMETER_LIST_HEADING}</li>
363 * <li>{@link UsageMessageSpec#SECTION_KEY_PARAMETER_LIST SECTION_KEY_PARAMETER_LIST}</li>
364 * <li>{@link UsageMessageSpec#SECTION_KEY_OPTION_LIST_HEADING SECTION_KEY_OPTION_LIST_HEADING}</li>
365 * <li>{@link UsageMessageSpec#SECTION_KEY_OPTION_LIST SECTION_KEY_OPTION_LIST}</li>
366 * <li>{@link UsageMessageSpec#SECTION_KEY_COMMAND_LIST_HEADING SECTION_KEY_COMMAND_LIST_HEADING}</li>
367 * <li>{@link UsageMessageSpec#SECTION_KEY_COMMAND_LIST SECTION_KEY_COMMAND_LIST}</li>
368 * <li>{@link UsageMessageSpec#SECTION_KEY_FOOTER_HEADING SECTION_KEY_FOOTER_HEADING}</li>
369 * <li>{@link UsageMessageSpec#SECTION_KEY_FOOTER SECTION_KEY_FOOTER}</li>
370 * </ol>
371 * @since 3.9
372 */
373 public List<String> getHelpSectionKeys() { return getCommandSpec().usageMessage().sectionKeys(); }
374
375 /**
376 * Sets the section keys in the order that the usage help message should render the sections.
377 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
378 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
379 * later will have the default setting. To ensure a setting is applied to all
380 * subcommands, call the setter last, after adding subcommands.</p>
381 * <p>Use {@link UsageMessageSpec#sectionKeys(List)} to customize a command without affecting its subcommands.</p>
382 * @see #getHelpSectionKeys
383 * @since 3.9
384 */
385 public CommandLine setHelpSectionKeys(List<String> keys) {
386 getCommandSpec().usageMessage().sectionKeys(keys);
387 for (CommandLine command : getCommandSpec().subcommands().values()) {
388 command.setHelpSectionKeys(keys);
389 }
390 return this;
391 }
392
393 /**
394 * Returns the map of section keys and renderers used to construct the usage help message.
395 * The usage help message can be customized by adding, replacing and removing section renderers from this map.
396 * Sections can be reordered with {@link #setHelpSectionKeys(List) setSectionKeys}.
397 * Sections that are either not in this map or not in the list returned by {@link #getHelpSectionKeys() getSectionKeys} are omitted.
398 * <p>
399 * NOTE: By modifying the returned {@code Map}, only the usage help message <em>of this command</em> is affected.
400 * Use {@link #setHelpSectionMap(Map)} to customize the usage help message for this command <em>and all subcommands</em>.
401 * </p>
402 * @since 3.9
403 */
404 public Map<String, IHelpSectionRenderer> getHelpSectionMap() { return getCommandSpec().usageMessage().sectionMap(); }
405
406 /**
407 * Sets the map of section keys and renderers used to construct the usage help message.
408 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
409 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
410 * later will have the default setting. To ensure a setting is applied to all
411 * subcommands, call the setter last, after adding subcommands.</p>
412 * <p>Use {@link UsageMessageSpec#sectionMap(Map)} to customize a command without affecting its subcommands.</p>
413 * @see #getHelpSectionMap
414 * @since 3.9
415 */
416 public CommandLine setHelpSectionMap(Map<String, IHelpSectionRenderer> map) {
417 getCommandSpec().usageMessage().sectionMap(map);
418 for (CommandLine command : getCommandSpec().subcommands().values()) {
419 command.setHelpSectionMap(map);
420 }
421 return this;
422 }
423
424 /** Returns whether the value of boolean flag options should be "toggled" when the option is matched.
425 * By default, flags are toggled, so if the value is {@code true} it is set to {@code false}, and when the value is
426 * {@code false} it is set to {@code true}. If toggling is off, flags are simply set to {@code true}.
427 * @return {@code true} the value of boolean flag options should be "toggled" when the option is matched, {@code false} otherwise
428 * @since 3.0
429 */
430 public boolean isToggleBooleanFlags() {
431 return getCommandSpec().parser().toggleBooleanFlags();
432 }
433
434 /** Sets whether the value of boolean flag options should be "toggled" when the option is matched. The default is {@code true}.
435 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
436 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
437 * later will have the default setting. To ensure a setting is applied to all
438 * subcommands, call the setter last, after adding subcommands.</p>
439 * @param newValue the new setting
440 * @return this {@code CommandLine} object, to allow method chaining
441 * @since 3.0
442 */
443 public CommandLine setToggleBooleanFlags(boolean newValue) {
444 getCommandSpec().parser().toggleBooleanFlags(newValue);
445 for (CommandLine command : getCommandSpec().subcommands().values()) {
446 command.setToggleBooleanFlags(newValue);
447 }
448 return this;
449 }
450
451 /** Returns whether options for single-value fields can be specified multiple times on the command line.
452 * The default is {@code false} and a {@link OverwrittenOptionException} is thrown if this happens.
453 * When {@code true}, the last specified value is retained.
454 * @return {@code true} if options for single-value fields can be specified multiple times on the command line, {@code false} otherwise
455 * @since 0.9.7
456 */
457 public boolean isOverwrittenOptionsAllowed() {
458 return getCommandSpec().parser().overwrittenOptionsAllowed();
459 }
460
461 /** Sets whether options for single-value fields can be specified multiple times on the command line without a {@link OverwrittenOptionException} being thrown.
462 * The default is {@code false}.
463 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
464 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
465 * later will have the default setting. To ensure a setting is applied to all
466 * subcommands, call the setter last, after adding subcommands.</p>
467 * @param newValue the new setting
468 * @return this {@code CommandLine} object, to allow method chaining
469 * @since 0.9.7
470 */
471 public CommandLine setOverwrittenOptionsAllowed(boolean newValue) {
472 getCommandSpec().parser().overwrittenOptionsAllowed(newValue);
473 for (CommandLine command : getCommandSpec().subcommands().values()) {
474 command.setOverwrittenOptionsAllowed(newValue);
475 }
476 return this;
477 }
478
479 /** Returns whether the parser accepts clustered short options. The default is {@code true}.
480 * @return {@code true} if short options like {@code -x -v -f SomeFile} can be clustered together like {@code -xvfSomeFile}, {@code false} otherwise
481 * @since 3.0 */
482 public boolean isPosixClusteredShortOptionsAllowed() { return getCommandSpec().parser().posixClusteredShortOptionsAllowed(); }
483
484 /** Sets whether short options like {@code -x -v -f SomeFile} can be clustered together like {@code -xvfSomeFile}. The default is {@code true}.
485 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
486 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
487 * later will have the default setting. To ensure a setting is applied to all
488 * subcommands, call the setter last, after adding subcommands.</p>
489 * @param newValue the new setting
490 * @return this {@code CommandLine} object, to allow method chaining
491 * @since 3.0
492 */
493 public CommandLine setPosixClusteredShortOptionsAllowed(boolean newValue) {
494 getCommandSpec().parser().posixClusteredShortOptionsAllowed(newValue);
495 for (CommandLine command : getCommandSpec().subcommands().values()) {
496 command.setPosixClusteredShortOptionsAllowed(newValue);
497 }
498 return this;
499 }
500
501 /** Returns whether the parser should ignore case when converting arguments to {@code enum} values. The default is {@code false}.
502 * @return {@code true} if enum values can be specified that don't match the {@code toString()} value of the enum constant, {@code false} otherwise;
503 * 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>,
504 * values {@code MonDaY}, {@code monday} and {@code MONDAY} are all recognized if {@code true}.
505 * @since 3.4 */
506 public boolean isCaseInsensitiveEnumValuesAllowed() { return getCommandSpec().parser().caseInsensitiveEnumValuesAllowed(); }
507
508 /** Sets whether the parser should ignore case when converting arguments to {@code enum} values. The default is {@code false}.
509 * 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>,
510 * values {@code MonDaY}, {@code monday} and {@code MONDAY} are all recognized if {@code true}.
511 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
512 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
513 * later will have the default setting. To ensure a setting is applied to all
514 * subcommands, call the setter last, after adding subcommands.</p>
515 * @param newValue the new setting
516 * @return this {@code CommandLine} object, to allow method chaining
517 * @since 3.4
518 */
519 public CommandLine setCaseInsensitiveEnumValuesAllowed(boolean newValue) {
520 getCommandSpec().parser().caseInsensitiveEnumValuesAllowed(newValue);
521 for (CommandLine command : getCommandSpec().subcommands().values()) {
522 command.setCaseInsensitiveEnumValuesAllowed(newValue);
523 }
524 return this;
525 }
526
527 /** Returns whether the parser should trim quotes from command line arguments before processing them. The default is
528 * read from the system property "picocli.trimQuotes" and will be {@code true} if the property is present and empty,
529 * or if its value is "true".
530 * @return {@code true} if the parser should trim quotes from command line arguments before processing them, {@code false} otherwise;
531 * @since 3.7 */
532 public boolean isTrimQuotes() { return getCommandSpec().parser().trimQuotes(); }
533
534 /** Sets whether the parser should trim quotes from command line arguments before processing them. The default is
535 * read from the system property "picocli.trimQuotes" and will be {@code true} if the property is set and empty, or
536 * if its value is "true".
537 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
538 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
539 * later will have the default setting. To ensure a setting is applied to all
540 * subcommands, call the setter last, after adding subcommands.</p>
541 * <p>Calling this method will cause the "picocli.trimQuotes" property to have no effect.</p>
542 * @param newValue the new setting
543 * @return this {@code CommandLine} object, to allow method chaining
544 * @since 3.7
545 */
546 public CommandLine setTrimQuotes(boolean newValue) {
547 getCommandSpec().parser().trimQuotes(newValue);
548 for (CommandLine command : getCommandSpec().subcommands().values()) {
549 command.setTrimQuotes(newValue);
550 }
551 return this;
552 }
553
554 /** Returns whether the parser is allowed to split quoted Strings or not. The default is {@code false},
555 * so quoted strings are treated as a single value that cannot be split.
556 * @return {@code true} if the parser is allowed to split quoted Strings, {@code false} otherwise;
557 * @see ArgSpec#splitRegex()
558 * @since 3.7 */
559 public boolean isSplitQuotedStrings() { return getCommandSpec().parser().splitQuotedStrings(); }
560
561 /** Sets whether the parser is allowed to split quoted Strings. The default is {@code false}.
562 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
563 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
564 * later will have the default setting. To ensure a setting is applied to all
565 * subcommands, call the setter last, after adding subcommands.</p>
566 * @param newValue the new setting
567 * @return this {@code CommandLine} object, to allow method chaining
568 * @see ArgSpec#splitRegex()
569 * @since 3.7
570 */
571 public CommandLine setSplitQuotedStrings(boolean newValue) {
572 getCommandSpec().parser().splitQuotedStrings(newValue);
573 for (CommandLine command : getCommandSpec().subcommands().values()) {
574 command.setSplitQuotedStrings(newValue);
575 }
576 return this;
577 }
578
579 /** Returns the end-of-options delimiter that signals that the remaining command line arguments should be treated as positional parameters.
580 * @return the end-of-options delimiter. The default is {@code "--"}.
581 * @since 3.5 */
582 public String getEndOfOptionsDelimiter() { return getCommandSpec().parser().endOfOptionsDelimiter(); }
583
584 /** Sets the end-of-options delimiter that signals that the remaining command line arguments should be treated as positional parameters.
585 * @param delimiter the end-of-options delimiter; must not be {@code null}. The default is {@code "--"}.
586 * @return this {@code CommandLine} object, to allow method chaining
587 * @since 3.5 */
588 public CommandLine setEndOfOptionsDelimiter(String delimiter) {
589 getCommandSpec().parser().endOfOptionsDelimiter(delimiter);
590 for (CommandLine command : getCommandSpec().subcommands().values()) {
591 command.setEndOfOptionsDelimiter(delimiter);
592 }
593 return this;
594 }
595
596 /** Returns the default value provider for the command, or {@code null} if none has been set.
597 * @return the default value provider for this command, or {@code null}
598 * @since 3.6
599 * @see Command#defaultValueProvider()
600 * @see CommandSpec#defaultValueProvider()
601 * @see ArgSpec#defaultValueString()
602 */
603 public IDefaultValueProvider getDefaultValueProvider() {
604 return getCommandSpec().defaultValueProvider();
605 }
606
607 /** Sets a default value provider for the command and sub-commands
608 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
609 * sub-commands and nested sub-subcommands <em>at the moment this method is called</em>. Sub-commands added
610 * later will have the default setting. To ensure a setting is applied to all
611 * sub-commands, call the setter last, after adding sub-commands.</p>
612 * @param newValue the default value provider to use
613 * @return this {@code CommandLine} object, to allow method chaining
614 * @since 3.6
615 */
616 public CommandLine setDefaultValueProvider(IDefaultValueProvider newValue) {
617 getCommandSpec().defaultValueProvider(newValue);
618 for (CommandLine command : getCommandSpec().subcommands().values()) {
619 command.setDefaultValueProvider(newValue);
620 }
621 return this;
622 }
623
624 /** Returns whether the parser interprets the first positional parameter as "end of options" so the remaining
625 * arguments are all treated as positional parameters. The default is {@code false}.
626 * @return {@code true} if all values following the first positional parameter should be treated as positional parameters, {@code false} otherwise
627 * @since 2.3
628 */
629 public boolean isStopAtPositional() {
630 return getCommandSpec().parser().stopAtPositional();
631 }
632
633 /** Sets whether the parser interprets the first positional parameter as "end of options" so the remaining
634 * arguments are all treated as positional parameters. The default is {@code false}.
635 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
636 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
637 * later will have the default setting. To ensure a setting is applied to all
638 * subcommands, call the setter last, after adding subcommands.</p>
639 * @param newValue {@code true} if all values following the first positional parameter should be treated as positional parameters, {@code false} otherwise
640 * @return this {@code CommandLine} object, to allow method chaining
641 * @since 2.3
642 */
643 public CommandLine setStopAtPositional(boolean newValue) {
644 getCommandSpec().parser().stopAtPositional(newValue);
645 for (CommandLine command : getCommandSpec().subcommands().values()) {
646 command.setStopAtPositional(newValue);
647 }
648 return this;
649 }
650
651 /** Returns whether the parser should stop interpreting options and positional parameters as soon as it encounters an
652 * unmatched option. Unmatched options are arguments that look like an option but are not one of the known options, or
653 * positional arguments for which there is no available slots (the command has no positional parameters or their size is limited).
654 * The default is {@code false}.
655 * <p>Setting this flag to {@code true} automatically sets the {@linkplain #isUnmatchedArgumentsAllowed() unmatchedArgumentsAllowed} flag to {@code true} also.</p>
656 * @return {@code true} when an unmatched option should result in the remaining command line arguments to be added to the
657 * {@linkplain #getUnmatchedArguments() unmatchedArguments list}
658 * @since 2.3
659 */
660 public boolean isStopAtUnmatched() {
661 return getCommandSpec().parser().stopAtUnmatched();
662 }
663
664 /** Sets whether the parser should stop interpreting options and positional parameters as soon as it encounters an
665 * unmatched option. Unmatched options are arguments that look like an option but are not one of the known options, or
666 * positional arguments for which there is no available slots (the command has no positional parameters or their size is limited).
667 * The default is {@code false}.
668 * <p>Setting this flag to {@code true} automatically sets the {@linkplain #setUnmatchedArgumentsAllowed(boolean) unmatchedArgumentsAllowed} flag to {@code true} also.</p>
669 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
670 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
671 * later will have the default setting. To ensure a setting is applied to all
672 * subcommands, call the setter last, after adding subcommands.</p>
673 * @param newValue {@code true} when an unmatched option should result in the remaining command line arguments to be added to the
674 * {@linkplain #getUnmatchedArguments() unmatchedArguments list}
675 * @return this {@code CommandLine} object, to allow method chaining
676 * @since 2.3
677 */
678 public CommandLine setStopAtUnmatched(boolean newValue) {
679 getCommandSpec().parser().stopAtUnmatched(newValue);
680 for (CommandLine command : getCommandSpec().subcommands().values()) {
681 command.setStopAtUnmatched(newValue);
682 }
683 if (newValue) { setUnmatchedArgumentsAllowed(true); }
684 return this;
685 }
686
687 /** Returns whether arguments on the command line that resemble an option should be treated as positional parameters.
688 * The default is {@code false} and the parser behaviour depends on {@link #isUnmatchedArgumentsAllowed()}.
689 * @return {@code true} arguments on the command line that resemble an option should be treated as positional parameters, {@code false} otherwise
690 * @see #getUnmatchedArguments()
691 * @since 3.0
692 */
693 public boolean isUnmatchedOptionsArePositionalParams() {
694 return getCommandSpec().parser().unmatchedOptionsArePositionalParams();
695 }
696
697 /** Sets whether arguments on the command line that resemble an option should be treated as positional parameters.
698 * The default is {@code false}.
699 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
700 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
701 * later will have the default setting. To ensure a setting is applied to all
702 * subcommands, call the setter last, after adding subcommands.</p>
703 * @param newValue the new setting. When {@code true}, arguments on the command line that resemble an option should be treated as positional parameters.
704 * @return this {@code CommandLine} object, to allow method chaining
705 * @since 3.0
706 * @see #getUnmatchedArguments()
707 * @see #isUnmatchedArgumentsAllowed
708 */
709 public CommandLine setUnmatchedOptionsArePositionalParams(boolean newValue) {
710 getCommandSpec().parser().unmatchedOptionsArePositionalParams(newValue);
711 for (CommandLine command : getCommandSpec().subcommands().values()) {
712 command.setUnmatchedOptionsArePositionalParams(newValue);
713 }
714 return this;
715 }
716
717 /** Returns whether the end user may specify arguments on the command line that are not matched to any option or parameter fields.
718 * The default is {@code false} and a {@link UnmatchedArgumentException} is thrown if this happens.
719 * When {@code true}, the last unmatched arguments are available via the {@link #getUnmatchedArguments()} method.
720 * @return {@code true} if the end use may specify unmatched arguments on the command line, {@code false} otherwise
721 * @see #getUnmatchedArguments()
722 * @since 0.9.7
723 */
724 public boolean isUnmatchedArgumentsAllowed() {
725 return getCommandSpec().parser().unmatchedArgumentsAllowed();
726 }
727
728 /** Sets whether the end user may specify unmatched arguments on the command line without a {@link UnmatchedArgumentException} being thrown.
729 * The default is {@code false}.
730 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
731 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
732 * later will have the default setting. To ensure a setting is applied to all
733 * subcommands, call the setter last, after adding subcommands.</p>
734 * @param newValue the new setting. When {@code true}, the last unmatched arguments are available via the {@link #getUnmatchedArguments()} method.
735 * @return this {@code CommandLine} object, to allow method chaining
736 * @since 0.9.7
737 * @see #getUnmatchedArguments()
738 */
739 public CommandLine setUnmatchedArgumentsAllowed(boolean newValue) {
740 getCommandSpec().parser().unmatchedArgumentsAllowed(newValue);
741 for (CommandLine command : getCommandSpec().subcommands().values()) {
742 command.setUnmatchedArgumentsAllowed(newValue);
743 }
744 return this;
745 }
746
747 /** Returns the list of unmatched command line arguments, if any.
748 * @return the list of unmatched command line arguments or an empty list
749 * @see #isUnmatchedArgumentsAllowed()
750 * @since 0.9.7
751 */
752 public List<String> getUnmatchedArguments() {
753 return interpreter.parseResultBuilder == null ? Collections.<String>emptyList() : UnmatchedArgumentException.stripErrorMessage(interpreter.parseResultBuilder.unmatched);
754 }
755
756 /**
757 * <p>
758 * Convenience method that initializes the specified annotated object from the specified command line arguments.
759 * </p><p>
760 * This is equivalent to
761 * </p><pre>
762 * CommandLine cli = new CommandLine(command);
763 * cli.parse(args);
764 * return command;
765 * </pre>
766 *
767 * @param command the object to initialize. This object contains fields annotated with
768 * {@code @Option} or {@code @Parameters}.
769 * @param args the command line arguments to parse
770 * @param <T> the type of the annotated object
771 * @return the specified annotated object
772 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
773 * @throws ParameterException if the specified command line arguments are invalid
774 * @since 0.9.7
775 */
776 public static <T> T populateCommand(T command, String... args) {
777 CommandLine cli = toCommandLine(command, new DefaultFactory());
778 cli.parse(args);
779 return command;
780 }
781
782 /**
783 * <p>
784 * Convenience method that derives the command specification from the specified interface class, and returns an
785 * instance of the specified interface. The interface is expected to have annotated getter methods. Picocli will
786 * instantiate the interface and the getter methods will return the option and positional parameter values matched on the command line.
787 * </p><p>
788 * This is equivalent to
789 * </p><pre>
790 * CommandLine cli = new CommandLine(spec);
791 * cli.parse(args);
792 * return cli.getCommand();
793 * </pre>
794 *
795 * @param spec the interface that defines the command specification. This object contains getter methods annotated with
796 * {@code @Option} or {@code @Parameters}.
797 * @param args the command line arguments to parse
798 * @param <T> the type of the annotated object
799 * @return an instance of the specified annotated interface
800 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
801 * @throws ParameterException if the specified command line arguments are invalid
802 * @since 3.1
803 */
804 public static <T> T populateSpec(Class<T> spec, String... args) {
805 CommandLine cli = toCommandLine(spec, new DefaultFactory());
806 cli.parse(args);
807 return cli.getCommand();
808 }
809
810 /** Parses the specified command line arguments and returns a list of {@code CommandLine} objects representing the
811 * top-level command and any subcommands (if any) that were recognized and initialized during the parsing process.
812 * <p>
813 * If parsing succeeds, the first element in the returned list is always {@code this CommandLine} object. The
814 * returned list may contain more elements if subcommands were {@linkplain #addSubcommand(String, Object) registered}
815 * and these subcommands were initialized by matching command line arguments. If parsing fails, a
816 * {@link ParameterException} is thrown.
817 * </p>
818 *
819 * @param args the command line arguments to parse
820 * @return a list with the top-level command and any subcommands initialized by this method
821 * @throws ParameterException if the specified command line arguments are invalid; use
822 * {@link ParameterException#getCommandLine()} to get the command or subcommand whose user input was invalid
823 */
824 public List<CommandLine> parse(String... args) {
825 return interpreter.parse(args);
826 }
827 /** Parses the specified command line arguments and returns a list of {@code ParseResult} with the options, positional
828 * parameters, and subcommands (if any) that were recognized and initialized during the parsing process.
829 * <p>If parsing fails, a {@link ParameterException} is thrown.</p>
830 *
831 * @param args the command line arguments to parse
832 * @return a list with the top-level command and any subcommands initialized by this method
833 * @throws ParameterException if the specified command line arguments are invalid; use
834 * {@link ParameterException#getCommandLine()} to get the command or subcommand whose user input was invalid
835 */
836 public ParseResult parseArgs(String... args) {
837 interpreter.parse(args);
838 return getParseResult();
839 }
840 public ParseResult getParseResult() { return interpreter.parseResultBuilder == null ? null : interpreter.parseResultBuilder.build(); }
841 /**
842 * Represents a function that can process a List of {@code CommandLine} objects resulting from successfully
843 * {@linkplain #parse(String...) parsing} the command line arguments. This is a
844 * <a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">functional interface</a>
845 * whose functional method is {@link #handleParseResult(List, PrintStream, CommandLine.Help.Ansi)}.
846 * <p>
847 * Implementations of this functions can be passed to the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) CommandLine::parseWithHandler}
848 * methods to take some next step after the command line was successfully parsed.
849 * </p>
850 * @see RunFirst
851 * @see RunLast
852 * @see RunAll
853 * @deprecated Use {@link IParseResultHandler2} instead.
854 * @since 2.0 */
855 @Deprecated public static interface IParseResultHandler {
856 /** Processes a List of {@code CommandLine} objects resulting from successfully
857 * {@linkplain #parse(String...) parsing} the command line arguments and optionally returns a list of results.
858 * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
859 * @param out the {@code PrintStream} to print help to if requested
860 * @param ansi for printing help messages using ANSI styles and colors
861 * @return a list of results, or an empty list if there are no results
862 * @throws ParameterException if a help command was invoked for an unknown subcommand. Any {@code ParameterExceptions}
863 * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler}
864 * @throws ExecutionException if a problem occurred while processing the parse results; use
865 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
866 */
867 List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) throws ExecutionException;
868 }
869
870 /**
871 * Represents a function that can process the {@code ParseResult} object resulting from successfully
872 * {@linkplain #parseArgs(String...) parsing} the command line arguments. This is a
873 * <a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">functional interface</a>
874 * whose functional method is {@link IParseResultHandler2#handleParseResult(CommandLine.ParseResult)}.
875 * <p>
876 * Implementations of this function can be passed to the {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) CommandLine::parseWithHandlers}
877 * methods to take some next step after the command line was successfully parsed.
878 * </p><p>
879 * This interface replaces the {@link IParseResultHandler} interface; it takes the parse result as a {@code ParseResult}
880 * object instead of a List of {@code CommandLine} objects, and it has the freedom to select the {@link Help.Ansi} style
881 * to use and what {@code PrintStreams} to print to.
882 * </p>
883 * @param <R> the return type of this handler
884 * @see RunFirst
885 * @see RunLast
886 * @see RunAll
887 * @since 3.0 */
888 public static interface IParseResultHandler2<R> {
889 /** Processes the {@code ParseResult} object resulting from successfully
890 * {@linkplain CommandLine#parseArgs(String...) parsing} the command line arguments and returns a return value.
891 * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments
892 * @throws ParameterException if a help command was invoked for an unknown subcommand. Any {@code ParameterExceptions}
893 * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler2}
894 * @throws ExecutionException if a problem occurred while processing the parse results; use
895 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
896 */
897 R handleParseResult(ParseResult parseResult) throws ExecutionException;
898 }
899 /**
900 * Represents a function that can handle a {@code ParameterException} that occurred while
901 * {@linkplain #parse(String...) parsing} the command line arguments. This is a
902 * <a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">functional interface</a>
903 * whose functional method is {@link #handleException(CommandLine.ParameterException, PrintStream, CommandLine.Help.Ansi, String...)}.
904 * <p>
905 * Implementations of this function can be passed to the {@link #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) CommandLine::parseWithHandlers}
906 * methods to handle situations when the command line could not be parsed.
907 * </p>
908 * @deprecated Use {@link IExceptionHandler2} instead.
909 * @see DefaultExceptionHandler
910 * @since 2.0 */
911 @Deprecated public static interface IExceptionHandler {
912 /** Handles a {@code ParameterException} that occurred while {@linkplain #parse(String...) parsing} the command
913 * line arguments and optionally returns a list of results.
914 * @param ex the ParameterException describing the problem that occurred while parsing the command line arguments,
915 * and the CommandLine representing the command or subcommand whose input was invalid
916 * @param out the {@code PrintStream} to print help to if requested
917 * @param ansi for printing help messages using ANSI styles and colors
918 * @param args the command line arguments that could not be parsed
919 * @return a list of results, or an empty list if there are no results
920 */
921 List<Object> handleException(ParameterException ex, PrintStream out, Help.Ansi ansi, String... args);
922 }
923 /**
924 * Classes implementing this interface know how to handle {@code ParameterExceptions} (usually from invalid user input)
925 * and {@code ExecutionExceptions} that occurred while executing the {@code Runnable} or {@code Callable} command.
926 * <p>
927 * Implementations of this interface can be passed to the
928 * {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) CommandLine::parseWithHandlers} method.
929 * </p><p>
930 * This interface replaces the {@link IParseResultHandler} interface.
931 * </p>
932 * @param <R> the return type of this handler
933 * @see DefaultExceptionHandler
934 * @since 3.0 */
935 public static interface IExceptionHandler2<R> {
936 /** Handles a {@code ParameterException} that occurred while {@linkplain #parseArgs(String...) parsing} the command
937 * line arguments and optionally returns a list of results.
938 * @param ex the ParameterException describing the problem that occurred while parsing the command line arguments,
939 * and the CommandLine representing the command or subcommand whose input was invalid
940 * @param args the command line arguments that could not be parsed
941 * @return an object resulting from handling the exception
942 */
943 R handleParseException(ParameterException ex, String[] args);
944 /** Handles a {@code ExecutionException} that occurred while executing the {@code Runnable} or
945 * {@code Callable} command and optionally returns a list of results.
946 * @param ex the ExecutionException describing the problem that occurred while executing the {@code Runnable} or
947 * {@code Callable} command, and the CommandLine representing the command or subcommand that was being executed
948 * @param parseResult the result of parsing the command line arguments
949 * @return an object resulting from handling the exception
950 */
951 R handleExecutionException(ExecutionException ex, ParseResult parseResult);
952 }
953
954 /** Abstract superclass for {@link IParseResultHandler2} and {@link IExceptionHandler2} implementations.
955 * <p>Note that {@code AbstractHandler} is a generic type. This, along with the abstract {@code self} method,
956 * allows method chaining to work properly in subclasses, without the need for casts. An example subclass can look like this:</p>
957 * <pre>{@code
958 * class MyResultHandler extends AbstractHandler<MyReturnType, MyResultHandler> implements IParseResultHandler2<MyReturnType> {
959 *
960 * public MyReturnType handleParseResult(ParseResult parseResult) { ... }
961 *
962 * protected MyResultHandler self() { return this; }
963 * }
964 * }</pre>
965 * @param <R> the return type of this handler
966 * @param <T> The type of the handler subclass; for fluent API method chaining
967 * @since 3.0 */
968 public static abstract class AbstractHandler<R, T extends AbstractHandler<R, T>> {
969 private Help.Ansi ansi = Help.Ansi.AUTO;
970 private Integer exitCode;
971 private PrintStream out = System.out;
972 private PrintStream err = System.err;
973
974 /** Returns the stream to print command output to. Defaults to {@code System.out}, unless {@link #useOut(PrintStream)}
975 * was called with a different stream.
976 * <p>{@code IParseResultHandler2} implementations should use this stream.
977 * By <a href="http://www.gnu.org/prep/standards/html_node/_002d_002dhelp.html">convention</a>, when the user requests
978 * 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> */
979 public PrintStream out() { return out; }
980 /** Returns the stream to print diagnostic messages to. Defaults to {@code System.err}, unless {@link #useErr(PrintStream)}
981 * was called with a different stream. <p>{@code IExceptionHandler2} implementations should use this stream to print error
982 * messages (which may include a usage help message) when an unexpected error occurs.</p> */
983 public PrintStream err() { return err; }
984 /** Returns the ANSI style to use. Defaults to {@code Help.Ansi.AUTO}, unless {@link #useAnsi(CommandLine.Help.Ansi)} was called with a different setting. */
985 public Help.Ansi ansi() { return ansi; }
986 /** Returns the exit code to use as the termination status, or {@code null} (the default) if the handler should
987 * not call {@link System#exit(int)} after processing completes.
988 * @see #andExit(int) */
989 public Integer exitCode() { return exitCode; }
990 /** Returns {@code true} if an exit code was set with {@link #andExit(int)}, or {@code false} (the default) if
991 * the handler should not call {@link System#exit(int)} after processing completes. */
992 public boolean hasExitCode() { return exitCode != null; }
993
994 /** Convenience method for subclasses that returns the specified result object if no exit code was set,
995 * or otherwise, if an exit code {@linkplain #andExit(int) was set}, calls {@code System.exit} with the configured
996 * exit code to terminate the currently running Java virtual machine. */
997 protected R returnResultOrExit(R result) {
998 if (hasExitCode()) { exit(exitCode()); }
999 return result;
1000 }
1001
1002 /** Convenience method for subclasses that throws the specified ExecutionException if no exit code was set,
1003 * or otherwise, if an exit code {@linkplain #andExit(int) was set}, prints the stacktrace of the specified exception
1004 * to the diagnostic error stream and calls {@code System.exit} with the configured
1005 * exit code to terminate the currently running Java virtual machine. */
1006 protected R throwOrExit(ExecutionException ex) {
1007 if (hasExitCode()) {
1008 ex.printStackTrace(this.err());
1009 exit(exitCode());
1010 }
1011 throw ex;
1012 }
1013 /** Calls {@code System.exit(int)} with the specified exit code. */
1014 protected void exit(int exitCode) { System.exit(exitCode); }
1015
1016 /** Returns {@code this} to allow method chaining when calling the setters for a fluent API. */
1017 protected abstract T self();
1018
1019 /** Sets the stream to print command output to. For use by {@code IParseResultHandler2} implementations.
1020 * @see #out() */
1021 public T useOut(PrintStream out) { this.out = Assert.notNull(out, "out"); return self(); }
1022 /** Sets the stream to print diagnostic messages to. For use by {@code IExceptionHandler2} implementations.
1023 * @see #err()*/
1024 public T useErr(PrintStream err) { this.err = Assert.notNull(err, "err"); return self(); }
1025 /** Sets the ANSI style to use.
1026 * @see #ansi() */
1027 public T useAnsi(Help.Ansi ansi) { this.ansi = Assert.notNull(ansi, "ansi"); return self(); }
1028 /** Indicates that the handler should call {@link System#exit(int)} after processing completes and sets the exit code to use as the termination status. */
1029 public T andExit(int exitCode) { this.exitCode = exitCode; return self(); }
1030 }
1031
1032 /**
1033 * Default exception handler that handles invalid user input by printing the exception message, followed by the usage
1034 * message for the command or subcommand whose input was invalid.
1035 * <p>{@code ParameterExceptions} (invalid user input) is handled like this:</p>
1036 * <pre>
1037 * err().println(paramException.getMessage());
1038 * paramException.getCommandLine().usage(err(), ansi());
1039 * if (hasExitCode()) System.exit(exitCode()); else return returnValue;
1040 * </pre>
1041 * <p>{@code ExecutionExceptions} that occurred while executing the {@code Runnable} or {@code Callable} command are simply rethrown and not handled.</p>
1042 * @since 2.0 */
1043 @SuppressWarnings("deprecation")
1044 public static class DefaultExceptionHandler<R> extends AbstractHandler<R, DefaultExceptionHandler<R>> implements IExceptionHandler, IExceptionHandler2<R> {
1045 public List<Object> handleException(ParameterException ex, PrintStream out, Help.Ansi ansi, String... args) {
1046 internalHandleParseException(ex, out, ansi, args); return Collections.<Object>emptyList(); }
1047
1048 /** Prints the message of the specified exception, followed by the usage message for the command or subcommand
1049 * whose input was invalid, to the stream returned by {@link #err()}.
1050 * @param ex the ParameterException describing the problem that occurred while parsing the command line arguments,
1051 * and the CommandLine representing the command or subcommand whose input was invalid
1052 * @param args the command line arguments that could not be parsed
1053 * @return the empty list
1054 * @since 3.0 */
1055 public R handleParseException(ParameterException ex, String[] args) {
1056 internalHandleParseException(ex, err(), ansi(), args); return returnResultOrExit(null); }
1057
1058 private void internalHandleParseException(ParameterException ex, PrintStream out, Help.Ansi ansi, String[] args) {
1059 out.println(ex.getMessage());
1060 if (!UnmatchedArgumentException.printSuggestions(ex, out)) {
1061 ex.getCommandLine().usage(out, ansi);
1062 }
1063 }
1064 /** This implementation always simply rethrows the specified exception.
1065 * @param ex the ExecutionException describing the problem that occurred while executing the {@code Runnable} or {@code Callable} command
1066 * @param parseResult the result of parsing the command line arguments
1067 * @return nothing: this method always rethrows the specified exception
1068 * @throws ExecutionException always rethrows the specified exception
1069 * @since 3.0 */
1070 public R handleExecutionException(ExecutionException ex, ParseResult parseResult) { return throwOrExit(ex); }
1071
1072 @Override protected DefaultExceptionHandler<R> self() { return this; }
1073 }
1074 /** Convenience method that returns {@code new DefaultExceptionHandler<List<Object>>()}. */
1075 public static DefaultExceptionHandler<List<Object>> defaultExceptionHandler() { return new DefaultExceptionHandler<List<Object>>(); }
1076
1077 /** @deprecated use {@link #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi)} instead
1078 * @since 2.0 */
1079 @Deprecated public static boolean printHelpIfRequested(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
1080 return printHelpIfRequested(parsedCommands, out, out, ansi);
1081 }
1082
1083 /** Delegates to {@link #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi)} with
1084 * {@code parseResult.asCommandLineList(), System.out, System.err, Help.Ansi.AUTO}.
1085 * @since 3.0 */
1086 public static boolean printHelpIfRequested(ParseResult parseResult) {
1087 return printHelpIfRequested(parseResult.asCommandLineList(), System.out, System.err, Help.Ansi.AUTO);
1088 }
1089 /**
1090 * Helper method that may be useful when processing the list of {@code CommandLine} objects that result from successfully
1091 * {@linkplain #parse(String...) parsing} command line arguments. This method prints out
1092 * {@linkplain #usage(PrintStream, Help.Ansi) usage help} if {@linkplain #isUsageHelpRequested() requested}
1093 * or {@linkplain #printVersionHelp(PrintStream, Help.Ansi) version help} if {@linkplain #isVersionHelpRequested() requested}
1094 * and returns {@code true}. If the command is a {@link Command#helpCommand()} and {@code runnable} or {@code callable},
1095 * that command is executed and this method returns {@code true}.
1096 * Otherwise, if none of the specified {@code CommandLine} objects have help requested,
1097 * this method returns {@code false}.<p>
1098 * Note that this method <em>only</em> looks at the {@link Option#usageHelp() usageHelp} and
1099 * {@link Option#versionHelp() versionHelp} attributes. The {@link Option#help() help} attribute is ignored.
1100 * </p><p><b>Implementation note:</b></p><p>
1101 * When an error occurs while processing the help request, it is recommended custom Help commands throw a
1102 * {@link ParameterException} with a reference to the parent command. This will print the error message and the
1103 * usage for the parent command, and will use the exit code of the exception handler if one was set.
1104 * </p>
1105 * @param parsedCommands the list of {@code CommandLine} objects to check if help was requested
1106 * @param out the {@code PrintStream} to print help to if requested
1107 * @param err the error string to print diagnostic messages to, in addition to the output from the exception handler
1108 * @param ansi for printing help messages using ANSI styles and colors
1109 * @return {@code true} if help was printed, {@code false} otherwise
1110 * @see IHelpCommandInitializable
1111 * @since 3.0 */
1112 public static boolean printHelpIfRequested(List<CommandLine> parsedCommands, PrintStream out, PrintStream err, Help.Ansi ansi) {
1113 return printHelpIfRequested(parsedCommands, out, err, Help.defaultColorScheme(ansi));
1114 }
1115 /**
1116 * Helper method that may be useful when processing the list of {@code CommandLine} objects that result from successfully
1117 * {@linkplain #parse(String...) parsing} command line arguments. This method prints out
1118 * {@linkplain #usage(PrintStream, Help.ColorScheme) usage help} if {@linkplain #isUsageHelpRequested() requested}
1119 * or {@linkplain #printVersionHelp(PrintStream, Help.Ansi) version help} if {@linkplain #isVersionHelpRequested() requested}
1120 * and returns {@code true}. If the command is a {@link Command#helpCommand()} and {@code runnable} or {@code callable},
1121 * that command is executed and this method returns {@code true}.
1122 * Otherwise, if none of the specified {@code CommandLine} objects have help requested,
1123 * this method returns {@code false}.<p>
1124 * Note that this method <em>only</em> looks at the {@link Option#usageHelp() usageHelp} and
1125 * {@link Option#versionHelp() versionHelp} attributes. The {@link Option#help() help} attribute is ignored.
1126 * </p><p><b>Implementation note:</b></p><p>
1127 * When an error occurs while processing the help request, it is recommended custom Help commands throw a
1128 * {@link ParameterException} with a reference to the parent command. This will print the error message and the
1129 * usage for the parent command, and will use the exit code of the exception handler if one was set.
1130 * </p>
1131 * @param parsedCommands the list of {@code CommandLine} objects to check if help was requested
1132 * @param out the {@code PrintStream} to print help to if requested
1133 * @param err the error string to print diagnostic messages to, in addition to the output from the exception handler
1134 * @param colorScheme for printing help messages using ANSI styles and colors
1135 * @return {@code true} if help was printed, {@code false} otherwise
1136 * @see IHelpCommandInitializable
1137 * @since 3.6 */
1138 public static boolean printHelpIfRequested(List<CommandLine> parsedCommands, PrintStream out, PrintStream err, Help.ColorScheme colorScheme) {
1139 for (int i = 0; i < parsedCommands.size(); i++) {
1140 CommandLine parsed = parsedCommands.get(i);
1141 if (parsed.isUsageHelpRequested()) {
1142 parsed.usage(out, colorScheme);
1143 return true;
1144 } else if (parsed.isVersionHelpRequested()) {
1145 parsed.printVersionHelp(out, colorScheme.ansi);
1146 return true;
1147 } else if (parsed.getCommandSpec().helpCommand()) {
1148 if (parsed.getCommand() instanceof IHelpCommandInitializable) {
1149 ((IHelpCommandInitializable) parsed.getCommand()).init(parsed, colorScheme.ansi, out, err);
1150 }
1151 execute(parsed, new ArrayList<Object>());
1152 return true;
1153 }
1154 }
1155 return false;
1156 }
1157 private static List<Object> execute(CommandLine parsed, List<Object> executionResult) {
1158 Object command = parsed.getCommand();
1159 if (command instanceof Runnable) {
1160 try {
1161 ((Runnable) command).run();
1162 executionResult.add(null); // for compatibility with picocli 2.x
1163 return executionResult;
1164 } catch (ParameterException ex) {
1165 throw ex;
1166 } catch (ExecutionException ex) {
1167 throw ex;
1168 } catch (Exception ex) {
1169 throw new ExecutionException(parsed, "Error while running command (" + command + "): " + ex, ex);
1170 }
1171 } else if (command instanceof Callable) {
1172 try {
1173 @SuppressWarnings("unchecked") Callable<Object> callable = (Callable<Object>) command;
1174 executionResult.add(callable.call());
1175 return executionResult;
1176 } catch (ParameterException ex) {
1177 throw ex;
1178 } catch (ExecutionException ex) {
1179 throw ex;
1180 } catch (Exception ex) {
1181 throw new ExecutionException(parsed, "Error while calling command (" + command + "): " + ex, ex);
1182 }
1183 } else if (command instanceof Method) {
1184 try {
1185 if (Modifier.isStatic(((Method) command).getModifiers())) {
1186 // invoke static method
1187 executionResult.add(((Method) command).invoke(null, parsed.getCommandSpec().argValues()));
1188 return executionResult;
1189 } else if (parsed.getCommandSpec().parent() != null) {
1190 executionResult.add(((Method) command).invoke(parsed.getCommandSpec().parent().userObject(), parsed.getCommandSpec().argValues()));
1191 return executionResult;
1192 } else {
1193 for (Constructor<?> constructor : ((Method) command).getDeclaringClass().getDeclaredConstructors()) {
1194 if (constructor.getParameterTypes().length == 0) {
1195 executionResult.add(((Method) command).invoke(constructor.newInstance(), parsed.getCommandSpec().argValues()));
1196 return executionResult;
1197 }
1198 }
1199 throw new UnsupportedOperationException("Invoking non-static method without default constructor not implemented");
1200 }
1201 } catch (InvocationTargetException ex) {
1202 Throwable t = ex.getTargetException();
1203 if (t instanceof ParameterException) {
1204 throw (ParameterException) t;
1205 } else if (t instanceof ExecutionException) {
1206 throw (ExecutionException) t;
1207 } else {
1208 throw new ExecutionException(parsed, "Error while calling command (" + command + "): " + t, t);
1209 }
1210 } catch (Exception ex) {
1211 throw new ExecutionException(parsed, "Unhandled error while calling command (" + command + "): " + ex, ex);
1212 }
1213 }
1214 throw new ExecutionException(parsed, "Parsed command (" + command + ") is not Method, Runnable or Callable");
1215 }
1216 /** Command line parse result handler that returns a value. This handler prints help if requested, and otherwise calls
1217 * {@link #handle(CommandLine.ParseResult)} with the parse result. Facilitates implementation of the {@link IParseResultHandler2} interface.
1218 * <p>Note that {@code AbstractParseResultHandler} is a generic type. This, along with the abstract {@code self} method,
1219 * allows method chaining to work properly in subclasses, without the need for casts. An example subclass can look like this:</p>
1220 * <pre>{@code
1221 * class MyResultHandler extends AbstractParseResultHandler<MyReturnType> {
1222 *
1223 * protected MyReturnType handle(ParseResult parseResult) throws ExecutionException { ... }
1224 *
1225 * protected MyResultHandler self() { return this; }
1226 * }
1227 * }</pre>
1228 * @since 3.0 */
1229 public abstract static class AbstractParseResultHandler<R> extends AbstractHandler<R, AbstractParseResultHandler<R>> implements IParseResultHandler2<R> {
1230 /** Prints help if requested, and otherwise calls {@link #handle(CommandLine.ParseResult)}.
1231 * Finally, either a list of result objects is returned, or the JVM is terminated if an exit code {@linkplain #andExit(int) was set}.
1232 *
1233 * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments
1234 * @return the result of {@link #handle(ParseResult) processing parse results}
1235 * @throws ParameterException if the {@link HelpCommand HelpCommand} was invoked for an unknown subcommand. Any {@code ParameterExceptions}
1236 * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler2}
1237 * @throws ExecutionException if a problem occurred while processing the parse results; client code can use
1238 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1239 */
1240 public R handleParseResult(ParseResult parseResult) throws ExecutionException {
1241 if (printHelpIfRequested(parseResult.asCommandLineList(), out(), err(), ansi())) {
1242 return returnResultOrExit(null);
1243 }
1244 return returnResultOrExit(handle(parseResult));
1245 }
1246
1247 /** Processes the specified {@code ParseResult} and returns the result as a list of objects.
1248 * Implementations are responsible for catching any exceptions thrown in the {@code handle} method, and
1249 * rethrowing an {@code ExecutionException} that details the problem and captures the offending {@code CommandLine} object.
1250 *
1251 * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments
1252 * @return the result of processing parse results
1253 * @throws ExecutionException if a problem occurred while processing the parse results; client code can use
1254 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1255 */
1256 protected abstract R handle(ParseResult parseResult) throws ExecutionException;
1257 }
1258 /**
1259 * Command line parse result handler that prints help if requested, and otherwise executes the top-level
1260 * {@code Runnable} or {@code Callable} command.
1261 * For use in the {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) parseWithHandler} methods.
1262 * @since 2.0 */
1263 public static class RunFirst extends AbstractParseResultHandler<List<Object>> implements IParseResultHandler {
1264 /** Prints help if requested, and otherwise executes the top-level {@code Runnable} or {@code Callable} command.
1265 * Finally, either a list of result objects is returned, or the JVM is terminated if an exit code {@linkplain #andExit(int) was set}.
1266 * If the top-level command does not implement either {@code Runnable} or {@code Callable}, an {@code ExecutionException}
1267 * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
1268 *
1269 * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
1270 * @param out the {@code PrintStream} to print help to if requested
1271 * @param ansi for printing help messages using ANSI styles and colors
1272 * @return an empty list if help was requested, or a list containing a single element: the result of calling the
1273 * {@code Callable}, or a {@code null} element if the top-level command was a {@code Runnable}
1274 * @throws ParameterException if the {@link HelpCommand HelpCommand} was invoked for an unknown subcommand. Any {@code ParameterExceptions}
1275 * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler}
1276 * @throws ExecutionException if a problem occurred while processing the parse results; use
1277 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1278 */
1279 public List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
1280 if (printHelpIfRequested(parsedCommands, out, err(), ansi)) { return returnResultOrExit(Collections.emptyList()); }
1281 return returnResultOrExit(execute(parsedCommands.get(0), new ArrayList<Object>()));
1282 }
1283 /** Executes the top-level {@code Runnable} or {@code Callable} subcommand.
1284 * If the top-level command does not implement either {@code Runnable} or {@code Callable}, an {@code ExecutionException}
1285 * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
1286 *
1287 * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments
1288 * @return an empty list if help was requested, or a list containing a single element: the result of calling the
1289 * {@code Callable}, or a {@code null} element if the last (sub)command was a {@code Runnable}
1290 * @throws ExecutionException if a problem occurred while processing the parse results; use
1291 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1292 * @since 3.0 */
1293 protected List<Object> handle(ParseResult parseResult) throws ExecutionException {
1294 return execute(parseResult.commandSpec().commandLine(), new ArrayList<Object>()); // first
1295 }
1296 @Override protected RunFirst self() { return this; }
1297 }
1298 /**
1299 * Command line parse result handler that prints help if requested, and otherwise executes the most specific
1300 * {@code Runnable} or {@code Callable} subcommand.
1301 * For use in the {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) parseWithHandler} methods.
1302 * <p>
1303 * Something like this:</p>
1304 * <pre>{@code
1305 * // RunLast implementation: print help if requested, otherwise execute the most specific subcommand
1306 * List<CommandLine> parsedCommands = parseResult.asCommandLineList();
1307 * if (CommandLine.printHelpIfRequested(parsedCommands, out(), err(), ansi())) {
1308 * return emptyList();
1309 * }
1310 * CommandLine last = parsedCommands.get(parsedCommands.size() - 1);
1311 * Object command = last.getCommand();
1312 * Object result = null;
1313 * if (command instanceof Runnable) {
1314 * try {
1315 * ((Runnable) command).run();
1316 * } catch (Exception ex) {
1317 * throw new ExecutionException(last, "Error in runnable " + command, ex);
1318 * }
1319 * } else if (command instanceof Callable) {
1320 * try {
1321 * result = ((Callable) command).call();
1322 * } catch (Exception ex) {
1323 * throw new ExecutionException(last, "Error in callable " + command, ex);
1324 * }
1325 * } else {
1326 * throw new ExecutionException(last, "Parsed command (" + command + ") is not Runnable or Callable");
1327 * }
1328 * if (hasExitCode()) { System.exit(exitCode()); }
1329 * return Arrays.asList(result);
1330 * }</pre>
1331 * <p>
1332 * From picocli v2.0, {@code RunLast} is used to implement the {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...) run}
1333 * and {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...) call} convenience methods.
1334 * </p>
1335 * @since 2.0 */
1336 public static class RunLast extends AbstractParseResultHandler<List<Object>> implements IParseResultHandler {
1337 /** Prints help if requested, and otherwise executes the most specific {@code Runnable} or {@code Callable} subcommand.
1338 * Finally, either a list of result objects is returned, or the JVM is terminated if an exit code {@linkplain #andExit(int) was set}.
1339 * If the last (sub)command does not implement either {@code Runnable} or {@code Callable}, an {@code ExecutionException}
1340 * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
1341 *
1342 * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
1343 * @param out the {@code PrintStream} to print help to if requested
1344 * @param ansi for printing help messages using ANSI styles and colors
1345 * @return an empty list if help was requested, or a list containing a single element: the result of calling the
1346 * {@code Callable}, or a {@code null} element if the last (sub)command was a {@code Runnable}
1347 * @throws ParameterException if the {@link HelpCommand HelpCommand} was invoked for an unknown subcommand. Any {@code ParameterExceptions}
1348 * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler}
1349 * @throws ExecutionException if a problem occurred while processing the parse results; use
1350 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1351 */
1352 public List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
1353 if (printHelpIfRequested(parsedCommands, out, err(), ansi)) { return returnResultOrExit(Collections.emptyList()); }
1354 return returnResultOrExit(execute(parsedCommands.get(parsedCommands.size() - 1), new ArrayList<Object>()));
1355 }
1356 /** Executes the most specific {@code Runnable} or {@code Callable} subcommand.
1357 * If the last (sub)command does not implement either {@code Runnable} or {@code Callable}, an {@code ExecutionException}
1358 * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
1359 *
1360 * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments
1361 * @return an empty list if help was requested, or a list containing a single element: the result of calling the
1362 * {@code Callable}, or a {@code null} element if the last (sub)command was a {@code Runnable}
1363 * @throws ExecutionException if a problem occurred while processing the parse results; use
1364 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1365 * @since 3.0 */
1366 protected List<Object> handle(ParseResult parseResult) throws ExecutionException {
1367 List<CommandLine> parsedCommands = parseResult.asCommandLineList();
1368 return execute(parsedCommands.get(parsedCommands.size() - 1), new ArrayList<Object>());
1369 }
1370 @Override protected RunLast self() { return this; }
1371 }
1372 /**
1373 * Command line parse result handler that prints help if requested, and otherwise executes the top-level command and
1374 * all subcommands as {@code Runnable} or {@code Callable}.
1375 * For use in the {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...) parseWithHandler} methods.
1376 * @since 2.0 */
1377 public static class RunAll extends AbstractParseResultHandler<List<Object>> implements IParseResultHandler {
1378 /** Prints help if requested, and otherwise executes the top-level command and all subcommands as {@code Runnable}
1379 * or {@code Callable}. Finally, either a list of result objects is returned, or the JVM is terminated if an exit
1380 * code {@linkplain #andExit(int) was set}. If any of the {@code CommandLine} commands does not implement either
1381 * {@code Runnable} or {@code Callable}, an {@code ExecutionException}
1382 * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
1383 *
1384 * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments
1385 * @param out the {@code PrintStream} to print help to if requested
1386 * @param ansi for printing help messages using ANSI styles and colors
1387 * @return an empty list if help was requested, or a list containing the result of executing all commands:
1388 * the return values from calling the {@code Callable} commands, {@code null} elements for commands that implement {@code Runnable}
1389 * @throws ParameterException if the {@link HelpCommand HelpCommand} was invoked for an unknown subcommand. Any {@code ParameterExceptions}
1390 * thrown from this method are treated as if this exception was thrown during parsing and passed to the {@link IExceptionHandler}
1391 * @throws ExecutionException if a problem occurred while processing the parse results; use
1392 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1393 */
1394 public List<Object> handleParseResult(List<CommandLine> parsedCommands, PrintStream out, Help.Ansi ansi) {
1395 if (printHelpIfRequested(parsedCommands, out, err(), ansi)) { return returnResultOrExit(Collections.emptyList()); }
1396 List<Object> result = new ArrayList<Object>();
1397 for (CommandLine parsed : parsedCommands) {
1398 execute(parsed, result);
1399 }
1400 return returnResultOrExit(result);
1401 }
1402 /** Executes the top-level command and all subcommands as {@code Runnable} or {@code Callable}.
1403 * If any of the {@code CommandLine} commands does not implement either {@code Runnable} or {@code Callable}, an {@code ExecutionException}
1404 * is thrown detailing the problem and capturing the offending {@code CommandLine} object.
1405 *
1406 * @param parseResult the {@code ParseResult} that resulted from successfully parsing the command line arguments
1407 * @return an empty list if help was requested, or a list containing the result of executing all commands:
1408 * the return values from calling the {@code Callable} commands, {@code null} elements for commands that implement {@code Runnable}
1409 * @throws ExecutionException if a problem occurred while processing the parse results; use
1410 * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1411 * @since 3.0 */
1412 protected List<Object> handle(ParseResult parseResult) throws ExecutionException {
1413 List<Object> result = new ArrayList<Object>();
1414 execute(parseResult.commandSpec().commandLine(), result);
1415 while (parseResult.hasSubcommand()) {
1416 parseResult = parseResult.subcommand();
1417 execute(parseResult.commandSpec().commandLine(), result);
1418 }
1419 return returnResultOrExit(result);
1420 }
1421 @Override protected RunAll self() { return this; }
1422 }
1423
1424 /** @deprecated use {@link #parseWithHandler(IParseResultHandler2, String[])} instead
1425 * @since 2.0 */
1426 @Deprecated public List<Object> parseWithHandler(IParseResultHandler handler, PrintStream out, String... args) {
1427 return parseWithHandlers(handler, out, Help.Ansi.AUTO, defaultExceptionHandler(), args);
1428 }
1429 /**
1430 * Returns the result of calling {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)} with
1431 * a new {@link DefaultExceptionHandler} in addition to the specified parse result handler and the specified command line arguments.
1432 * <p>
1433 * This is a convenience method intended to offer the same ease of use as the {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...) run}
1434 * and {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...) call} methods, but with more flexibility and better
1435 * support for nested subcommands.
1436 * </p>
1437 * <p>Calling this method roughly expands to:</p>
1438 * <pre>{@code
1439 * try {
1440 * ParseResult parseResult = parseArgs(args);
1441 * return handler.handleParseResult(parseResult);
1442 * } catch (ParameterException ex) {
1443 * return new DefaultExceptionHandler<R>().handleParseException(ex, args);
1444 * }
1445 * }</pre>
1446 * <p>
1447 * Picocli provides some default handlers that allow you to accomplish some common tasks with very little code.
1448 * The following handlers are available:</p>
1449 * <ul>
1450 * <li>{@link RunLast} handler prints help if requested, and otherwise gets the last specified command or subcommand
1451 * and tries to execute it as a {@code Runnable} or {@code Callable}.</li>
1452 * <li>{@link RunFirst} handler prints help if requested, and otherwise executes the top-level command as a {@code Runnable} or {@code Callable}.</li>
1453 * <li>{@link RunAll} handler prints help if requested, and otherwise executes all recognized commands and subcommands as {@code Runnable} or {@code Callable} tasks.</li>
1454 * <li>{@link DefaultExceptionHandler} prints the error message followed by usage help</li>
1455 * </ul>
1456 * @param <R> the return type of this handler
1457 * @param handler the function that will handle the result of successfully parsing the command line arguments
1458 * @param args the command line arguments
1459 * @return an object resulting from handling the parse result or the exception that occurred while parsing the input
1460 * @throws ExecutionException if the command line arguments were parsed successfully but a problem occurred while processing the
1461 * parse results; use {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1462 * @see RunLast
1463 * @see RunAll
1464 * @since 3.0 */
1465 public <R> R parseWithHandler(IParseResultHandler2<R> handler, String[] args) {
1466 return parseWithHandlers(handler, new DefaultExceptionHandler<R>(), args);
1467 }
1468
1469 /** @deprecated use {@link #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)} instead
1470 * @since 2.0 */
1471 @Deprecated public List<Object> parseWithHandlers(IParseResultHandler handler, PrintStream out, Help.Ansi ansi, IExceptionHandler exceptionHandler, String... args) {
1472 try {
1473 List<CommandLine> result = parse(args);
1474 return handler.handleParseResult(result, out, ansi);
1475 } catch (ParameterException ex) {
1476 return exceptionHandler.handleException(ex, out, ansi, args);
1477 }
1478 }
1479 /**
1480 * Tries to {@linkplain #parseArgs(String...) parse} the specified command line arguments, and if successful, delegates
1481 * the processing of the resulting {@code ParseResult} object to the specified {@linkplain IParseResultHandler2 handler}.
1482 * If the command line arguments were invalid, the {@code ParameterException} thrown from the {@code parse} method
1483 * is caught and passed to the specified {@link IExceptionHandler2}.
1484 * <p>
1485 * This is a convenience method intended to offer the same ease of use as the {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...) run}
1486 * and {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...) call} methods, but with more flexibility and better
1487 * support for nested subcommands.
1488 * </p>
1489 * <p>Calling this method roughly expands to:</p>
1490 * <pre>
1491 * ParseResult parseResult = null;
1492 * try {
1493 * parseResult = parseArgs(args);
1494 * return handler.handleParseResult(parseResult);
1495 * } catch (ParameterException ex) {
1496 * return exceptionHandler.handleParseException(ex, (String[]) args);
1497 * } catch (ExecutionException ex) {
1498 * return exceptionHandler.handleExecutionException(ex, parseResult);
1499 * }
1500 * </pre>
1501 * <p>
1502 * Picocli provides some default handlers that allow you to accomplish some common tasks with very little code.
1503 * The following handlers are available:</p>
1504 * <ul>
1505 * <li>{@link RunLast} handler prints help if requested, and otherwise gets the last specified command or subcommand
1506 * and tries to execute it as a {@code Runnable} or {@code Callable}.</li>
1507 * <li>{@link RunFirst} handler prints help if requested, and otherwise executes the top-level command as a {@code Runnable} or {@code Callable}.</li>
1508 * <li>{@link RunAll} handler prints help if requested, and otherwise executes all recognized commands and subcommands as {@code Runnable} or {@code Callable} tasks.</li>
1509 * <li>{@link DefaultExceptionHandler} prints the error message followed by usage help</li>
1510 * </ul>
1511 *
1512 * @param handler the function that will handle the result of successfully parsing the command line arguments
1513 * @param exceptionHandler the function that can handle the {@code ParameterException} thrown when the command line arguments are invalid
1514 * @param args the command line arguments
1515 * @return an object resulting from handling the parse result or the exception that occurred while parsing the input
1516 * @throws ExecutionException if the command line arguments were parsed successfully but a problem occurred while processing the parse
1517 * result {@code ParseResult} object; use {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed
1518 * @param <R> the return type of the result handler and exception handler
1519 * @see RunLast
1520 * @see RunAll
1521 * @see DefaultExceptionHandler
1522 * @since 3.0 */
1523 public <R> R parseWithHandlers(IParseResultHandler2<R> handler, IExceptionHandler2<R> exceptionHandler, String... args) {
1524 ParseResult parseResult = null;
1525 try {
1526 parseResult = parseArgs(args);
1527 return handler.handleParseResult(parseResult);
1528 } catch (ParameterException ex) {
1529 return exceptionHandler.handleParseException(ex, args);
1530 } catch (ExecutionException ex) {
1531 return exceptionHandler.handleExecutionException(ex, parseResult);
1532 }
1533 }
1534 static String versionString() {
1535 return String.format("%s, JVM: %s (%s %s %s), OS: %s %s %s", VERSION,
1536 System.getProperty("java.version"), System.getProperty("java.vendor"), System.getProperty("java.vm.name"), System.getProperty("java.vm.version"),
1537 System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch"));
1538 }
1539 /**
1540 * Equivalent to {@code new CommandLine(command).usage(out)}. See {@link #usage(PrintStream)} for details.
1541 * @param command the object annotated with {@link Command}, {@link Option} and {@link Parameters}
1542 * @param out the print stream to print the help message to
1543 * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1544 */
1545 public static void usage(Object command, PrintStream out) {
1546 toCommandLine(command, new DefaultFactory()).usage(out);
1547 }
1548
1549 /**
1550 * Equivalent to {@code new CommandLine(command).usage(out, ansi)}.
1551 * See {@link #usage(PrintStream, Help.Ansi)} for details.
1552 * @param command the object annotated with {@link Command}, {@link Option} and {@link Parameters}
1553 * @param out the print stream to print the help message to
1554 * @param ansi whether the usage message should contain ANSI escape codes or not
1555 * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1556 */
1557 public static void usage(Object command, PrintStream out, Help.Ansi ansi) {
1558 toCommandLine(command, new DefaultFactory()).usage(out, ansi);
1559 }
1560
1561 /**
1562 * Equivalent to {@code new CommandLine(command).usage(out, colorScheme)}.
1563 * See {@link #usage(PrintStream, Help.ColorScheme)} for details.
1564 * @param command the object annotated with {@link Command}, {@link Option} and {@link Parameters}
1565 * @param out the print stream to print the help message to
1566 * @param colorScheme the {@code ColorScheme} defining the styles for options, parameters and commands when ANSI is enabled
1567 * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1568 */
1569 public static void usage(Object command, PrintStream out, Help.ColorScheme colorScheme) {
1570 toCommandLine(command, new DefaultFactory()).usage(out, colorScheme);
1571 }
1572
1573 /**
1574 * Delegates to {@link #usage(PrintStream, Help.Ansi)} with the {@linkplain Help.Ansi#AUTO platform default}.
1575 * @param out the printStream to print to
1576 * @see #usage(PrintStream, Help.ColorScheme)
1577 */
1578 public void usage(PrintStream out) { usage(out, Help.Ansi.AUTO); }
1579 /**
1580 * Delegates to {@link #usage(PrintWriter, Help.Ansi)} with the {@linkplain Help.Ansi#AUTO platform default}.
1581 * @param writer the PrintWriter to print to
1582 * @see #usage(PrintWriter, Help.ColorScheme)
1583 * @since 3.0 */
1584 public void usage(PrintWriter writer) { usage(writer, Help.Ansi.AUTO); }
1585
1586 /**
1587 * Delegates to {@link #usage(PrintStream, Help.ColorScheme)} with the {@linkplain Help#defaultColorScheme(CommandLine.Help.Ansi) default color scheme}.
1588 * @param out the printStream to print to
1589 * @param ansi whether the usage message should include ANSI escape codes or not
1590 * @see #usage(PrintStream, Help.ColorScheme)
1591 */
1592 public void usage(PrintStream out, Help.Ansi ansi) { usage(out, Help.defaultColorScheme(ansi)); }
1593 /** Similar to {@link #usage(PrintStream, Help.Ansi)} but with the specified {@code PrintWriter} instead of a {@code PrintStream}.
1594 * @since 3.0 */
1595 public void usage(PrintWriter writer, Help.Ansi ansi) { usage(writer, Help.defaultColorScheme(ansi)); }
1596
1597 /**
1598 * Prints a usage help message for the annotated command class to the specified {@code PrintStream}.
1599 * Delegates construction of the usage help message to the {@link Help} inner class and is equivalent to:
1600 * <pre>
1601 * Help.ColorScheme colorScheme = Help.defaultColorScheme(Help.Ansi.AUTO);
1602 * Help help = getHelpFactory().create(getCommandSpec(), colorScheme)
1603 * StringBuilder sb = new StringBuilder();
1604 * for (String key : getHelpSectionKeys()) {
1605 * IHelpSectionRenderer renderer = getHelpSectionMap().get(key);
1606 * if (renderer != null) { sb.append(renderer.render(help)); }
1607 * }
1608 * out.print(sb);
1609 * </pre>
1610 * <p>Annotate your class with {@link Command} to control many aspects of the usage help message, including
1611 * the program name, text of section headings and section contents, and some aspects of the auto-generated sections
1612 * of the usage help message.
1613 * <p>To customize the auto-generated sections of the usage help message, like how option details are displayed,
1614 * instantiate a {@link Help} object and use a {@link Help.TextTable} with more of fewer columns, a custom
1615 * {@linkplain Help.Layout layout}, and/or a custom option {@linkplain Help.IOptionRenderer renderer}
1616 * for ultimate control over which aspects of an Option or Field are displayed where.</p>
1617 * @param out the {@code PrintStream} to print the usage help message to
1618 * @param colorScheme the {@code ColorScheme} defining the styles for options, parameters and commands when ANSI is enabled
1619 * @see UsageMessageSpec
1620 */
1621 public void usage(PrintStream out, Help.ColorScheme colorScheme) {
1622 out.print(usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), colorScheme)));
1623 }
1624 /** Similar to {@link #usage(PrintStream, Help.ColorScheme)}, but with the specified {@code PrintWriter} instead of a {@code PrintStream}.
1625 * @since 3.0 */
1626 public void usage(PrintWriter writer, Help.ColorScheme colorScheme) {
1627 writer.print(usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), colorScheme)));
1628 }
1629 /** Similar to {@link #usage(PrintStream)}, but returns the usage help message as a String instead of printing it to the {@code PrintStream}.
1630 * @since 3.2 */
1631 public String getUsageMessage() {
1632 return usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), Help.defaultColorScheme(Help.Ansi.AUTO))).toString();
1633 }
1634 /** Similar to {@link #usage(PrintStream, Help.Ansi)}, but returns the usage help message as a String instead of printing it to the {@code PrintStream}.
1635 * @since 3.2 */
1636 public String getUsageMessage(Help.Ansi ansi) {
1637 return usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), Help.defaultColorScheme(ansi))).toString();
1638 }
1639 /** Similar to {@link #usage(PrintStream, Help.ColorScheme)}, but returns the usage help message as a String instead of printing it to the {@code PrintStream}.
1640 * @since 3.2 */
1641 public String getUsageMessage(Help.ColorScheme colorScheme) {
1642 return usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), colorScheme)).toString();
1643 }
1644
1645 private StringBuilder usage(StringBuilder sb, Help help) {
1646 for (String key : getHelpSectionKeys()) {
1647 IHelpSectionRenderer renderer = getHelpSectionMap().get(key);
1648 if (renderer != null) { sb.append(renderer.render(help)); }
1649 }
1650 return sb;
1651 }
1652
1653 /**
1654 * Delegates to {@link #printVersionHelp(PrintStream, Help.Ansi)} with the {@linkplain Help.Ansi#AUTO platform default}.
1655 * @param out the printStream to print to
1656 * @see #printVersionHelp(PrintStream, Help.Ansi)
1657 * @since 0.9.8
1658 */
1659 public void printVersionHelp(PrintStream out) { printVersionHelp(out, Help.Ansi.AUTO); }
1660
1661 /**
1662 * Prints version information from the {@link Command#version()} annotation to the specified {@code PrintStream}.
1663 * Each element of the array of version strings is printed on a separate line. Version strings may contain
1664 * <a href="http://picocli.info/#_usage_help_with_styles_and_colors">markup for colors and style</a>.
1665 * @param out the printStream to print to
1666 * @param ansi whether the usage message should include ANSI escape codes or not
1667 * @see Command#version()
1668 * @see Option#versionHelp()
1669 * @see #isVersionHelpRequested()
1670 * @since 0.9.8
1671 */
1672 public void printVersionHelp(PrintStream out, Help.Ansi ansi) {
1673 for (String versionInfo : getCommandSpec().version()) {
1674 out.println(ansi.new Text(versionInfo));
1675 }
1676 }
1677 /**
1678 * Prints version information from the {@link Command#version()} annotation to the specified {@code PrintStream}.
1679 * Each element of the array of version strings is {@linkplain String#format(String, Object...) formatted} with the
1680 * specified parameters, and printed on a separate line. Both version strings and parameters may contain
1681 * <a href="http://picocli.info/#_usage_help_with_styles_and_colors">markup for colors and style</a>.
1682 * @param out the printStream to print to
1683 * @param ansi whether the usage message should include ANSI escape codes or not
1684 * @param params Arguments referenced by the format specifiers in the version strings
1685 * @see Command#version()
1686 * @see Option#versionHelp()
1687 * @see #isVersionHelpRequested()
1688 * @since 1.0.0
1689 */
1690 public void printVersionHelp(PrintStream out, Help.Ansi ansi, Object... params) {
1691 for (String versionInfo : getCommandSpec().version()) {
1692 out.println(ansi.new Text(format(versionInfo, params)));
1693 }
1694 }
1695
1696 /**
1697 * Delegates to {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.out} for
1698 * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
1699 * @param callable the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
1700 * @param args the command line arguments to parse
1701 * @param <C> the annotated object must implement Callable
1702 * @param <T> the return type of the most specific command (must implement {@code Callable})
1703 * @see #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)
1704 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1705 * @throws ExecutionException if the Callable throws an exception
1706 * @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
1707 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1708 * @since 3.0
1709 */
1710 public static <C extends Callable<T>, T> T call(C callable, String... args) {
1711 return call(callable, System.out, System.err, Help.Ansi.AUTO, args);
1712 }
1713
1714 /**
1715 * Delegates to {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.err} for
1716 * diagnostic error messages and {@link Help.Ansi#AUTO}.
1717 * @param callable the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
1718 * @param out the printStream to print the usage help message to when the user requested help
1719 * @param args the command line arguments to parse
1720 * @param <C> the annotated object must implement Callable
1721 * @param <T> the return type of the most specific command (must implement {@code Callable})
1722 * @see #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)
1723 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1724 * @throws ExecutionException if the Callable throws an exception
1725 * @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
1726 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1727 * @see RunLast
1728 */
1729 public static <C extends Callable<T>, T> T call(C callable, PrintStream out, String... args) {
1730 return call(callable, out, System.err, Help.Ansi.AUTO, args);
1731 }
1732 /**
1733 * Delegates to {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.err} for diagnostic error messages.
1734 * @param callable the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
1735 * @param out the printStream to print the usage help message to when the user requested help
1736 * @param ansi the ANSI style to use
1737 * @param args the command line arguments to parse
1738 * @param <C> the annotated object must implement Callable
1739 * @param <T> the return type of the most specific command (must implement {@code Callable})
1740 * @see #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)
1741 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1742 * @throws ExecutionException if the Callable throws an exception
1743 * @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
1744 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1745 * @see RunLast
1746 */
1747 public static <C extends Callable<T>, T> T call(C callable, PrintStream out, Help.Ansi ansi, String... args) {
1748 return call(callable, out, System.err, ansi, args);
1749 }
1750 /**
1751 * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
1752 * The annotated object needs to implement {@link Callable}. Calling this method is equivalent to:
1753 * <pre>{@code
1754 * CommandLine cmd = new CommandLine(callable);
1755 * List<Object> results = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi),
1756 * new DefaultExceptionHandler().useErr(err).useAnsi(ansi),
1757 * args);
1758 * T result = results == null || results.isEmpty() ? null : (T) results.get(0);
1759 * return result;
1760 * }</pre>
1761 * <p>
1762 * If the specified Callable command has subcommands, the {@linkplain RunLast last} subcommand specified on the
1763 * command line is executed.
1764 * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler2, String[]) parseWithHandler}
1765 * method with the {@link RunAll} handler or a custom handler.
1766 * </p><p>
1767 * Use {@link #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...) call(Class, IFactory, ...)} instead of this method
1768 * if you want to use a factory that performs Dependency Injection.
1769 * </p>
1770 * @param callable the command to call when {@linkplain #parse(String...) parsing} succeeds.
1771 * @param out the printStream to print the usage help message to when the user requested help
1772 * @param err the printStream to print diagnostic messages to
1773 * @param ansi whether the usage message should include ANSI escape codes or not
1774 * @param args the command line arguments to parse
1775 * @param <C> the annotated object must implement Callable
1776 * @param <T> the return type of the specified {@code Callable}
1777 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1778 * @throws ExecutionException if the Callable throws an exception
1779 * @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
1780 * @see #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
1781 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1782 * @see RunLast
1783 * @since 3.0
1784 */
1785 public static <C extends Callable<T>, T> T call(C callable, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) {
1786 CommandLine cmd = new CommandLine(callable);
1787 List<Object> results = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler<List<Object>>().useErr(err).useAnsi(ansi), args);
1788 @SuppressWarnings("unchecked") T result = (results == null || results.isEmpty()) ? null : (T) results.get(0);
1789 return result;
1790 }
1791 /**
1792 * Delegates to {@link #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.out} for
1793 * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
1794 * @param callableClass class of the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
1795 * @param factory the factory responsible for instantiating the specified callable class and potentially inject other components
1796 * @param args the command line arguments to parse
1797 * @param <C> the annotated class must implement Callable
1798 * @param <T> the return type of the most specific command (must implement {@code Callable})
1799 * @see #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
1800 * @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
1801 * @throws ExecutionException if the Callable throws an exception
1802 * @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
1803 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1804 * @since 3.2
1805 */
1806 public static <C extends Callable<T>, T> T call(Class<C> callableClass, IFactory factory, String... args) {
1807 return call(callableClass, factory, System.out, System.err, Help.Ansi.AUTO, args);
1808 }
1809 /**
1810 * Delegates to {@link #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with
1811 * {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
1812 * @param callableClass class of the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
1813 * @param factory the factory responsible for instantiating the specified callable class and potentially injecting other components
1814 * @param out the printStream to print the usage help message to when the user requested help
1815 * @param args the command line arguments to parse
1816 * @param <C> the annotated class must implement Callable
1817 * @param <T> the return type of the most specific command (must implement {@code Callable})
1818 * @see #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
1819 * @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
1820 * @throws ExecutionException if the Callable throws an exception
1821 * @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
1822 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1823 * @since 3.2
1824 */
1825 public static <C extends Callable<T>, T> T call(Class<C> callableClass, IFactory factory, PrintStream out, String... args) {
1826 return call(callableClass, factory, out, System.err, Help.Ansi.AUTO, args);
1827 }
1828 /**
1829 * Delegates to {@link #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with
1830 * {@code System.err} for diagnostic error messages.
1831 * @param callableClass class of the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
1832 * @param factory the factory responsible for instantiating the specified callable class and potentially injecting other components
1833 * @param out the printStream to print the usage help message to when the user requested help
1834 * @param ansi the ANSI style to use
1835 * @param args the command line arguments to parse
1836 * @param <C> the annotated class must implement Callable
1837 * @param <T> the return type of the most specific command (must implement {@code Callable})
1838 * @see #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
1839 * @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
1840 * @throws ExecutionException if the Callable throws an exception
1841 * @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
1842 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1843 * @since 3.2
1844 */
1845 public static <C extends Callable<T>, T> T call(Class<C> callableClass, IFactory factory, PrintStream out, Help.Ansi ansi, String... args) {
1846 return call(callableClass, factory, out, System.err, ansi, args);
1847 }
1848 /**
1849 * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
1850 * The specified {@linkplain IFactory factory} will create an instance of the specified {@code callableClass};
1851 * use this method instead of {@link #call(Callable, PrintStream, PrintStream, Help.Ansi, String...) call(Callable, ...)}
1852 * if you want to use a factory that performs Dependency Injection.
1853 * The annotated class needs to implement {@link Callable}. Calling this method is equivalent to:
1854 * <pre>{@code
1855 * CommandLine cmd = new CommandLine(callableClass, factory);
1856 * List<Object> results = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi),
1857 * new DefaultExceptionHandler().useErr(err).useAnsi(ansi),
1858 * args);
1859 * T result = results == null || results.isEmpty() ? null : (T) results.get(0);
1860 * return result;
1861 * }</pre>
1862 * <p>
1863 * If the specified Callable command has subcommands, the {@linkplain RunLast last} subcommand specified on the
1864 * command line is executed.
1865 * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler2, String[]) parseWithHandler}
1866 * method with the {@link RunAll} handler or a custom handler.
1867 * </p>
1868 * @param callableClass class of the command to call when {@linkplain #parseArgs(String...) parsing} succeeds.
1869 * @param factory the factory responsible for instantiating the specified callable class and potentially injecting other components
1870 * @param out the printStream to print the usage help message to when the user requested help
1871 * @param err the printStream to print diagnostic messages to
1872 * @param ansi the ANSI style to use
1873 * @param args the command line arguments to parse
1874 * @param <C> the annotated class must implement Callable
1875 * @param <T> the return type of the most specific command (must implement {@code Callable})
1876 * @see #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
1877 * @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
1878 * @throws ExecutionException if the Callable throws an exception
1879 * @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
1880 * @see #call(Callable, PrintStream, PrintStream, Help.Ansi, String...)
1881 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1882 * @since 3.2
1883 */
1884 public static <C extends Callable<T>, T> T call(Class<C> callableClass, IFactory factory, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) {
1885 CommandLine cmd = new CommandLine(callableClass, factory);
1886 List<Object> results = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler<List<Object>>().useErr(err).useAnsi(ansi), args);
1887 @SuppressWarnings("unchecked") T result = (results == null || results.isEmpty()) ? null : (T) results.get(0);
1888 return result;
1889 }
1890
1891 /**
1892 * Delegates to {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.out} for
1893 * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
1894 * @param runnable the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
1895 * @param args the command line arguments to parse
1896 * @param <R> the annotated object must implement Runnable
1897 * @see #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)
1898 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1899 * @throws ExecutionException if the Runnable throws an exception
1900 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1901 * @see RunLast
1902 * @since 3.0
1903 */
1904 public static <R extends Runnable> void run(R runnable, String... args) {
1905 run(runnable, System.out, System.err, Help.Ansi.AUTO, args);
1906 }
1907
1908 /**
1909 * Delegates to {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.err} for diagnostic error messages and {@link Help.Ansi#AUTO}.
1910 * @param runnable the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
1911 * @param out the printStream to print the usage help message to when the user requested help
1912 * @param args the command line arguments to parse
1913 * @param <R> the annotated object must implement Runnable
1914 * @see #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)
1915 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1916 * @throws ExecutionException if the Runnable throws an exception
1917 * @see #parseWithHandler(IParseResultHandler2, String[])
1918 * @see RunLast
1919 */
1920 public static <R extends Runnable> void run(R runnable, PrintStream out, String... args) {
1921 run(runnable, out, System.err, Help.Ansi.AUTO, args);
1922 }
1923 /**
1924 * Delegates to {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.err} for diagnostic error messages.
1925 * @param runnable the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
1926 * @param out the printStream to print the usage help message to when the user requested help
1927 * @param ansi whether the usage message should include ANSI escape codes or not
1928 * @param args the command line arguments to parse
1929 * @param <R> the annotated object must implement Runnable
1930 * @see #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)
1931 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1932 * @throws ExecutionException if the Runnable throws an exception
1933 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1934 * @see RunLast
1935 */
1936 public static <R extends Runnable> void run(R runnable, PrintStream out, Help.Ansi ansi, String... args) {
1937 run(runnable, out, System.err, ansi, args);
1938 }
1939 /**
1940 * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
1941 * The annotated object needs to implement {@link Runnable}. Calling this method is equivalent to:
1942 * <pre>{@code
1943 * CommandLine cmd = new CommandLine(runnable);
1944 * cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi),
1945 * new DefaultExceptionHandler().useErr(err).useAnsi(ansi),
1946 * args);
1947 * }</pre>
1948 * <p>
1949 * If the specified Runnable command has subcommands, the {@linkplain RunLast last} subcommand specified on the
1950 * command line is executed.
1951 * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler2, String[]) parseWithHandler}
1952 * method with the {@link RunAll} handler or a custom handler.
1953 * </p><p>
1954 * From picocli v2.0, this method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi) requested},
1955 * and any exceptions thrown by the {@code Runnable} are caught and rethrown wrapped in an {@code ExecutionException}.
1956 * </p><p>
1957 * Use {@link #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...) run(Class, IFactory, ...)} instead of this method
1958 * if you want to use a factory that performs Dependency Injection.
1959 * </p>
1960 * @param runnable the command to run when {@linkplain #parse(String...) parsing} succeeds.
1961 * @param out the printStream to print the usage help message to when the user requested help
1962 * @param err the printStream to print diagnostic messages to
1963 * @param ansi whether the usage message should include ANSI escape codes or not
1964 * @param args the command line arguments to parse
1965 * @param <R> the annotated object must implement Runnable
1966 * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation
1967 * @throws ExecutionException if the Runnable throws an exception
1968 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1969 * @see RunLast
1970 * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
1971 * @since 3.0
1972 */
1973 public static <R extends Runnable> void run(R runnable, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) {
1974 CommandLine cmd = new CommandLine(runnable);
1975 cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler<List<Object>>().useErr(err).useAnsi(ansi), args);
1976 }
1977 /**
1978 * Delegates to {@link #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.out} for
1979 * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
1980 * @param runnableClass class of the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
1981 * @param factory the factory responsible for instantiating the specified Runnable class and potentially injecting other components
1982 * @param args the command line arguments to parse
1983 * @param <R> the annotated class must implement Runnable
1984 * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
1985 * @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
1986 * @throws ExecutionException if the Runnable throws an exception
1987 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
1988 * @see RunLast
1989 * @since 3.2
1990 */
1991 public static <R extends Runnable> void run(Class<R> runnableClass, IFactory factory, String... args) {
1992 run(runnableClass, factory, System.out, System.err, Help.Ansi.AUTO, args);
1993 }
1994 /**
1995 * Delegates to {@link #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with
1996 * {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
1997 * @param runnableClass class of the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
1998 * @param factory the factory responsible for instantiating the specified Runnable class and potentially injecting other components
1999 * @param out the printStream to print the usage help message to when the user requested help
2000 * @param args the command line arguments to parse
2001 * @param <R> the annotated class must implement Runnable
2002 * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
2003 * @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
2004 * @throws ExecutionException if the Runnable throws an exception
2005 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
2006 * @see RunLast
2007 * @since 3.2
2008 */
2009 public static <R extends Runnable> void run(Class<R> runnableClass, IFactory factory, PrintStream out, String... args) {
2010 run(runnableClass, factory, out, System.err, Help.Ansi.AUTO, args);
2011 }
2012 /**
2013 * Delegates to {@link #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)} with
2014 * {@code System.err} for diagnostic error messages.
2015 * @param runnableClass class of the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
2016 * @param factory the factory responsible for instantiating the specified Runnable class and potentially injecting other components
2017 * @param out the printStream to print the usage help message to when the user requested help
2018 * @param ansi whether the usage message should include ANSI escape codes or not
2019 * @param args the command line arguments to parse
2020 * @param <R> the annotated class must implement Runnable
2021 * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
2022 * @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
2023 * @throws ExecutionException if the Runnable throws an exception
2024 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
2025 * @see RunLast
2026 * @since 3.2
2027 */
2028 public static <R extends Runnable> void run(Class<R> runnableClass, IFactory factory, PrintStream out, Help.Ansi ansi, String... args) {
2029 run(runnableClass, factory, out, System.err, ansi, args);
2030 }
2031 /**
2032 * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
2033 * The specified {@linkplain IFactory factory} will create an instance of the specified {@code runnableClass};
2034 * use this method instead of {@link #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...) run(Runnable, ...)}
2035 * if you want to use a factory that performs Dependency Injection.
2036 * The annotated class needs to implement {@link Runnable}. Calling this method is equivalent to:
2037 * <pre>{@code
2038 * CommandLine cmd = new CommandLine(runnableClass, factory);
2039 * cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi),
2040 * new DefaultExceptionHandler().useErr(err).useAnsi(ansi),
2041 * args);
2042 * }</pre>
2043 * <p>
2044 * If the specified Runnable command has subcommands, the {@linkplain RunLast last} subcommand specified on the
2045 * command line is executed.
2046 * Commands with subcommands may be interested in calling the {@link #parseWithHandler(IParseResultHandler2, String[]) parseWithHandler}
2047 * method with the {@link RunAll} handler or a custom handler.
2048 * </p><p>
2049 * This method prints usage help or version help if {@linkplain #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi) requested},
2050 * and any exceptions thrown by the {@code Runnable} are caught and rethrown wrapped in an {@code ExecutionException}.
2051 * </p>
2052 * @param runnableClass class of the command to run when {@linkplain #parseArgs(String...) parsing} succeeds.
2053 * @param factory the factory responsible for instantiating the specified Runnable class and potentially injecting other components
2054 * @param out the printStream to print the usage help message to when the user requested help
2055 * @param err the printStream to print diagnostic messages to
2056 * @param ansi whether the usage message should include ANSI escape codes or not
2057 * @param args the command line arguments to parse
2058 * @param <R> the annotated class must implement Runnable
2059 * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
2060 * @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
2061 * @throws ExecutionException if the Runnable throws an exception
2062 * @see #run(Runnable, PrintStream, PrintStream, Help.Ansi, String...)
2063 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
2064 * @see RunLast
2065 * @since 3.2
2066 */
2067 public static <R extends Runnable> void run(Class<R> runnableClass, IFactory factory, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) {
2068 CommandLine cmd = new CommandLine(runnableClass, factory);
2069 cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler<List<Object>>().useErr(err).useAnsi(ansi), args);
2070 }
2071
2072 /**
2073 * Delegates to {@link #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)} with {@code System.out} for
2074 * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
2075 * @param methodName the {@code @Command}-annotated method to build a {@link CommandSpec} model from,
2076 * and run when {@linkplain #parseArgs(String...) parsing} succeeds.
2077 * @param cls the class where the {@code @Command}-annotated method is declared, or a subclass
2078 * @param args the command line arguments to parse
2079 * @see #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)
2080 * @throws InitializationException if the specified method does not have a {@link Command} annotation,
2081 * or if the specified class contains multiple {@code @Command}-annotated methods with the specified name
2082 * @throws ExecutionException if the Runnable throws an exception
2083 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
2084 * @since 3.6
2085 */
2086 public static Object invoke(String methodName, Class<?> cls, String... args) {
2087 return invoke(methodName, cls, System.out, System.err, Help.Ansi.AUTO, args);
2088 }
2089 /**
2090 * Delegates to {@link #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)} with the specified stream for
2091 * requested usage help messages, {@code System.err} for diagnostic error messages, and {@link Help.Ansi#AUTO}.
2092 * @param methodName the {@code @Command}-annotated method to build a {@link CommandSpec} model from,
2093 * and run when {@linkplain #parseArgs(String...) parsing} succeeds.
2094 * @param cls the class where the {@code @Command}-annotated method is declared, or a subclass
2095 * @param out the printstream to print requested help message to
2096 * @param args the command line arguments to parse
2097 * @see #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)
2098 * @throws InitializationException if the specified method does not have a {@link Command} annotation,
2099 * or if the specified class contains multiple {@code @Command}-annotated methods with the specified name
2100 * @throws ExecutionException if the Runnable throws an exception
2101 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
2102 * @since 3.6
2103 */
2104 public static Object invoke(String methodName, Class<?> cls, PrintStream out, String... args) {
2105 return invoke(methodName, cls, out, System.err, Help.Ansi.AUTO, args);
2106 }
2107 /**
2108 * Delegates to {@link #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)} with the specified stream for
2109 * requested usage help messages, {@code System.err} for diagnostic error messages, and the specified Ansi mode.
2110 * @param methodName the {@code @Command}-annotated method to build a {@link CommandSpec} model from,
2111 * and run when {@linkplain #parseArgs(String...) parsing} succeeds.
2112 * @param cls the class where the {@code @Command}-annotated method is declared, or a subclass
2113 * @param out the printstream to print requested help message to
2114 * @param ansi whether the usage message should include ANSI escape codes or not
2115 * @param args the command line arguments to parse
2116 * @see #invoke(String, Class, PrintStream, PrintStream, Help.Ansi, String...)
2117 * @throws InitializationException if the specified method does not have a {@link Command} annotation,
2118 * or if the specified class contains multiple {@code @Command}-annotated methods with the specified name
2119 * @throws ExecutionException if the Runnable throws an exception
2120 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
2121 * @since 3.6
2122 */
2123 public static Object invoke(String methodName, Class<?> cls, PrintStream out, Help.Ansi ansi, String... args) {
2124 return invoke(methodName, cls, out, System.err, ansi, args);
2125 }
2126 /**
2127 * Convenience method to allow command line application authors to avoid some boilerplate code in their application.
2128 * Constructs a {@link CommandSpec} model from the {@code @Option} and {@code @Parameters}-annotated method parameters
2129 * of the {@code @Command}-annotated method, parses the specified command line arguments and invokes the specified method.
2130 * Calling this method is equivalent to:
2131 * <pre>{@code
2132 * Method commandMethod = getCommandMethods(cls, methodName).get(0);
2133 * CommandLine cmd = new CommandLine(commandMethod);
2134 * List<Object> list = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi),
2135 * new DefaultExceptionHandler().useErr(err).useAnsi(ansi),
2136 * args);
2137 * return list == null ? null : list.get(0);
2138 * }</pre>
2139 * @param methodName the {@code @Command}-annotated method to build a {@link CommandSpec} model from,
2140 * and run when {@linkplain #parseArgs(String...) parsing} succeeds.
2141 * @param cls the class where the {@code @Command}-annotated method is declared, or a subclass
2142 * @param out the printStream to print the usage help message to when the user requested help
2143 * @param err the printStream to print diagnostic messages to
2144 * @param ansi whether the usage message should include ANSI escape codes or not
2145 * @param args the command line arguments to parse
2146 * @throws InitializationException if the specified method does not have a {@link Command} annotation,
2147 * or if the specified class contains multiple {@code @Command}-annotated methods with the specified name
2148 * @throws ExecutionException if the method throws an exception
2149 * @see #parseWithHandlers(IParseResultHandler2, IExceptionHandler2, String...)
2150 * @since 3.6
2151 */
2152 public static Object invoke(String methodName, Class<?> cls, PrintStream out, PrintStream err, Help.Ansi ansi, String... args) {
2153 List<Method> candidates = getCommandMethods(cls, methodName);
2154 if (candidates.size() != 1) { throw new InitializationException("Expected exactly one @Command-annotated method for " + cls.getName() + "::" + methodName + "(...), but got: " + candidates); }
2155 Method method = candidates.get(0);
2156 CommandLine cmd = new CommandLine(method);
2157 List<Object> list = cmd.parseWithHandlers(new RunLast().useOut(out).useAnsi(ansi), new DefaultExceptionHandler<List<Object>>().useErr(err).useAnsi(ansi), args);
2158 return list == null ? null : list.get(0);
2159 }
2160
2161 /**
2162 * 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}).
2163 * 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.
2164 *
2165 * @param cls the class to search for methods annotated with {@code @Command}
2166 * @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}.
2167 * @return the matching command methods, or an empty list
2168 * @see #invoke(String, Class, String...)
2169 * @since 3.6.0
2170 */
2171 public static List<Method> getCommandMethods(Class<?> cls, String methodName) {
2172 Set<Method> candidates = new HashSet<Method>();
2173 // traverse public member methods (excludes static/non-public, includes inherited)
2174 candidates.addAll(Arrays.asList(Assert.notNull(cls, "class").getMethods()));
2175 // traverse directly declared methods (includes static/non-public, excludes inherited)
2176 candidates.addAll(Arrays.asList(Assert.notNull(cls, "class").getDeclaredMethods()));
2177
2178 List<Method> result = new ArrayList<Method>();
2179 for (Method method : candidates) {
2180 if (method.isAnnotationPresent(Command.class)) {
2181 if (methodName == null || methodName.equals(method.getName())) { result.add(method); }
2182 }
2183 }
2184 Collections.sort(result, new Comparator<Method>() {
2185 public int compare(Method o1, Method o2) { return o1.getName().compareTo(o2.getName()); }
2186 });
2187 return result;
2188 }
2189
2190 /**
2191 * Registers the specified type converter for the specified class. When initializing fields annotated with
2192 * {@link Option}, the field's type is used as a lookup key to find the associated type converter, and this
2193 * type converter converts the original command line argument string value to the correct type.
2194 * <p>
2195 * Java 8 lambdas make it easy to register custom type converters:
2196 * </p>
2197 * <pre>
2198 * commandLine.registerConverter(java.nio.file.Path.class, s -> java.nio.file.Paths.get(s));
2199 * commandLine.registerConverter(java.time.Duration.class, s -> java.time.Duration.parse(s));</pre>
2200 * <p>
2201 * Built-in type converters are pre-registered for the following java 1.5 types:
2202 * </p>
2203 * <ul>
2204 * <li>all primitive types</li>
2205 * <li>all primitive wrapper types: Boolean, Byte, Character, Double, Float, Integer, Long, Short</li>
2206 * <li>any enum</li>
2207 * <li>java.io.File</li>
2208 * <li>java.math.BigDecimal</li>
2209 * <li>java.math.BigInteger</li>
2210 * <li>java.net.InetAddress</li>
2211 * <li>java.net.URI</li>
2212 * <li>java.net.URL</li>
2213 * <li>java.nio.charset.Charset</li>
2214 * <li>java.sql.Time</li>
2215 * <li>java.util.Date</li>
2216 * <li>java.util.UUID</li>
2217 * <li>java.util.regex.Pattern</li>
2218 * <li>StringBuilder</li>
2219 * <li>CharSequence</li>
2220 * <li>String</li>
2221 * </ul>
2222 * <p>The specified converter will be registered with this {@code CommandLine} and the full hierarchy of its
2223 * subcommands and nested sub-subcommands <em>at the moment the converter is registered</em>. Subcommands added
2224 * later will not have this converter added automatically. To ensure a custom type converter is available to all
2225 * subcommands, register the type converter last, after adding subcommands.</p>
2226 *
2227 * @param cls the target class to convert parameter string values to
2228 * @param converter the class capable of converting string values to the specified target type
2229 * @param <K> the target type
2230 * @return this CommandLine object, to allow method chaining
2231 * @see #addSubcommand(String, Object)
2232 */
2233 public <K> CommandLine registerConverter(Class<K> cls, ITypeConverter<K> converter) {
2234 interpreter.converterRegistry.put(Assert.notNull(cls, "class"), Assert.notNull(converter, "converter"));
2235 for (CommandLine command : getCommandSpec().commands.values()) {
2236 command.registerConverter(cls, converter);
2237 }
2238 return this;
2239 }
2240
2241 /** Returns the String that separates option names from option values when parsing command line options.
2242 * @return the String the parser uses to separate option names from option values
2243 * @see ParserSpec#separator() */
2244 public String getSeparator() { return getCommandSpec().parser().separator(); }
2245
2246 /** Sets the String the parser uses to separate option names from option values to the specified value.
2247 * The separator may also be set declaratively with the {@link CommandLine.Command#separator()} annotation attribute.
2248 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
2249 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
2250 * later will have the default setting. To ensure a setting is applied to all
2251 * subcommands, call the setter last, after adding subcommands.</p>
2252 * @param separator the String that separates option names from option values
2253 * @see ParserSpec#separator(String)
2254 * @return this {@code CommandLine} object, to allow method chaining */
2255 public CommandLine setSeparator(String separator) {
2256 getCommandSpec().parser().separator(Assert.notNull(separator, "separator"));
2257 for (CommandLine command : getCommandSpec().subcommands().values()) {
2258 command.setSeparator(separator);
2259 }
2260 return this;
2261 }
2262
2263 /** Returns the ResourceBundle of this command or {@code null} if no resource bundle is set.
2264 * @see Command#resourceBundle()
2265 * @see CommandSpec#resourceBundle()
2266 * @since 3.6 */
2267 public ResourceBundle getResourceBundle() { return getCommandSpec().resourceBundle(); }
2268
2269 /** Sets the ResourceBundle containing usage help message strings.
2270 * <p>The specified bundle will be registered with this {@code CommandLine} and the full hierarchy of its
2271 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
2272 * later will not be impacted. To ensure a setting is applied to all
2273 * subcommands, call the setter last, after adding subcommands.</p>
2274 * @param bundle the ResourceBundle containing usage help message strings
2275 * @return this {@code CommandLine} object, to allow method chaining
2276 * @see Command#resourceBundle()
2277 * @see CommandSpec#resourceBundle(ResourceBundle)
2278 * @since 3.6 */
2279 public CommandLine setResourceBundle(ResourceBundle bundle) {
2280 getCommandSpec().resourceBundle(bundle);
2281 for (CommandLine command : getCommandSpec().subcommands().values()) {
2282 command.getCommandSpec().resourceBundle(bundle);
2283 }
2284 return this;
2285 }
2286
2287 /** Returns the maximum width of the usage help message. The default is 80.
2288 * @see UsageMessageSpec#width() */
2289 public int getUsageHelpWidth() { return getCommandSpec().usageMessage().width(); }
2290
2291 /** Sets the maximum width of the usage help message. Longer lines are wrapped.
2292 * <p>The specified setting will be registered with this {@code CommandLine} and the full hierarchy of its
2293 * subcommands and nested sub-subcommands <em>at the moment this method is called</em>. Subcommands added
2294 * later will have the default setting. To ensure a setting is applied to all
2295 * subcommands, call the setter last, after adding subcommands.</p>
2296 * @param width the maximum width of the usage help message
2297 * @see UsageMessageSpec#width(int)
2298 * @return this {@code CommandLine} object, to allow method chaining */
2299 public CommandLine setUsageHelpWidth(int width) {
2300 getCommandSpec().usageMessage().width(width);
2301 for (CommandLine command : getCommandSpec().subcommands().values()) {
2302 command.setUsageHelpWidth(width);
2303 }
2304 return this;
2305 }
2306
2307 /** Returns the command name (also called program name) displayed in the usage help synopsis.
2308 * @return the command name (also called program name) displayed in the usage
2309 * @see CommandSpec#name()
2310 * @since 2.0 */
2311 public String getCommandName() { return getCommandSpec().name(); }
2312
2313 /** Sets the command name (also called program name) displayed in the usage help synopsis to the specified value.
2314 * Note that this method only modifies the usage help message, it does not impact parsing behaviour.
2315 * The command name may also be set declaratively with the {@link CommandLine.Command#name()} annotation attribute.
2316 * @param commandName command name (also called program name) displayed in the usage help synopsis
2317 * @return this {@code CommandLine} object, to allow method chaining
2318 * @see CommandSpec#name(String)
2319 * @since 2.0 */
2320 public CommandLine setCommandName(String commandName) {
2321 getCommandSpec().name(Assert.notNull(commandName, "commandName"));
2322 return this;
2323 }
2324
2325 /** Returns whether arguments starting with {@code '@'} should be treated as the path to an argument file and its
2326 * contents should be expanded into separate arguments for each line in the specified file.
2327 * This property is {@code true} by default.
2328 * @return whether "argument files" or {@code @files} should be expanded into their content
2329 * @since 2.1 */
2330 public boolean isExpandAtFiles() { return getCommandSpec().parser().expandAtFiles(); }
2331
2332 /** Sets whether arguments starting with {@code '@'} should be treated as the path to an argument file and its
2333 * contents should be expanded into separate arguments for each line in the specified file. ({@code true} by default.)
2334 * @param expandAtFiles whether "argument files" or {@code @files} should be expanded into their content
2335 * @return this {@code CommandLine} object, to allow method chaining
2336 * @since 2.1 */
2337 public CommandLine setExpandAtFiles(boolean expandAtFiles) {
2338 getCommandSpec().parser().expandAtFiles(expandAtFiles);
2339 return this;
2340 }
2341
2342 /** Returns the character that starts a single-line comment or {@code null} if all content of argument files should
2343 * be interpreted as arguments (without comments).
2344 * If specified, all characters from the comment character to the end of the line are ignored.
2345 * @return the character that starts a single-line comment or {@code null}. The default is {@code '#'}.
2346 * @since 3.5 */
2347 public Character getAtFileCommentChar() { return getCommandSpec().parser().atFileCommentChar(); }
2348
2349 /** Sets the character that starts a single-line comment or {@code null} if all content of argument files should
2350 * be interpreted as arguments (without comments).
2351 * If specified, all characters from the comment character to the end of the line are ignored.
2352 * @param atFileCommentChar the character that starts a single-line comment or {@code null}. The default is {@code '#'}.
2353 * @return this {@code CommandLine} object, to allow method chaining
2354 * @since 3.5 */
2355 public CommandLine setAtFileCommentChar(Character atFileCommentChar) {
2356 getCommandSpec().parser().atFileCommentChar(atFileCommentChar);
2357 for (CommandLine command : getCommandSpec().subcommands().values()) {
2358 command.setAtFileCommentChar(atFileCommentChar);
2359 }
2360 return this;
2361 }
2362
2363 /** Returns whether to use a simplified argument file format that is compatible with JCommander.
2364 * In this format, every line (except empty lines and comment lines)
2365 * is interpreted as a single argument. Arguments containing whitespace do not need to be quoted.
2366 * When system property {@code "picocli.useSimplifiedAtFiles"} is defined, the system property value overrides the programmatically set value.
2367 * @return whether to use a simplified argument file format. The default is {@code false}.
2368 * @since 3.9 */
2369 public boolean isUseSimplifiedAtFiles() { return getCommandSpec().parser().useSimplifiedAtFiles(); }
2370
2371 /** Sets whether to use a simplified argument file format that is compatible with JCommander.
2372 * In this format, every line (except empty lines and comment lines)
2373 * is interpreted as a single argument. Arguments containing whitespace do not need to be quoted.
2374 * When system property {@code "picocli.useSimplifiedAtFiles"} is defined, the system property value overrides the programmatically set value.
2375 * @param simplifiedAtFiles whether to use a simplified argument file format. The default is {@code false}.
2376 * @return this {@code CommandLine} object, to allow method chaining
2377 * @since 3.9 */
2378 public CommandLine setUseSimplifiedAtFiles(boolean simplifiedAtFiles) {
2379 getCommandSpec().parser().useSimplifiedAtFiles(simplifiedAtFiles);
2380 for (CommandLine command : getCommandSpec().subcommands().values()) {
2381 command.setUseSimplifiedAtFiles(simplifiedAtFiles);
2382 }
2383 return this;
2384 }
2385 private static boolean empty(String str) { return str == null || str.trim().length() == 0; }
2386 private static boolean empty(Object[] array) { return array == null || array.length == 0; }
2387 private static String str(String[] arr, int i) { return (arr == null || arr.length <= i) ? "" : arr[i]; }
2388 private static boolean isBoolean(Class<?> type) { return type == Boolean.class || type == Boolean.TYPE; }
2389 private static CommandLine toCommandLine(Object obj, IFactory factory) { return obj instanceof CommandLine ? (CommandLine) obj : new CommandLine(obj, factory);}
2390 private static boolean isMultiValue(Class<?> cls) { return cls.isArray() || Collection.class.isAssignableFrom(cls) || Map.class.isAssignableFrom(cls); }
2391 private static String format(String formatString, Object... params) {
2392 try {
2393 return formatString == null ? "" : String.format(formatString, params);
2394 } catch (IllegalFormatException ex) {
2395 new Tracer().warn("Could not format '%s' (Underlying error: %s). " +
2396 "Using raw String: '%%n' format strings have not been replaced with newlines. " +
2397 "Please ensure to escape '%%' characters with another '%%'.%n", formatString, ex.getMessage());
2398 return formatString;
2399 }
2400 }
2401
2402 private static class NoCompletionCandidates implements Iterable<String> {
2403 public Iterator<String> iterator() { throw new UnsupportedOperationException(); }
2404 }
2405 /**
2406 * <p>
2407 * Annotate fields in your class with {@code @Option} and picocli will initialize these fields when matching
2408 * arguments are specified on the command line. In the case of command methods (annotated with {@code @Command}),
2409 * command options can be defined by annotating method parameters with {@code @Option}.
2410 * </p><p>
2411 * Command class example:
2412 * </p>
2413 * <pre>
2414 * import static picocli.CommandLine.*;
2415 *
2416 * public class MyClass {
2417 * @Parameters(description = "Any number of input files")
2418 * private List<File> files = new ArrayList<File>();
2419 *
2420 * @Option(names = { "-o", "--out" }, description = "Output file (default: print to console)")
2421 * private File outputFile;
2422 *
2423 * @Option(names = { "-v", "--verbose"}, description = "Verbose mode. Helpful for troubleshooting. Multiple -v options increase the verbosity.")
2424 * private boolean[] verbose;
2425 *
2426 * @Option(names = { "-h", "--help", "-?", "-help"}, usageHelp = true, description = "Display this help and exit")
2427 * private boolean help;
2428 * }
2429 * </pre>
2430 * <p>
2431 * A field cannot be annotated with both {@code @Parameters} and {@code @Option} or a
2432 * {@code ParameterException} is thrown.
2433 * </p>
2434 */
2435 @Retention(RetentionPolicy.RUNTIME)
2436 @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
2437 public @interface Option {
2438 /**
2439 * One or more option names. At least one option name is required.
2440 * <p>
2441 * Different environments have different conventions for naming options, but usually options have a prefix
2442 * that sets them apart from parameters.
2443 * Picocli supports all of the below styles. The default separator is {@code '='}, but this can be configured.
2444 * </p><p>
2445 * <b>*nix</b>
2446 * </p><p>
2447 * In Unix and Linux, options have a short (single-character) name, a long name or both.
2448 * Short options
2449 * (<a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02">POSIX
2450 * style</a> are single-character and are preceded by the {@code '-'} character, e.g., {@code `-v'}.
2451 * <a href="https://www.gnu.org/software/tar/manual/html_node/Long-Options.html">GNU-style</a> long
2452 * (or <em>mnemonic</em>) options start with two dashes in a row, e.g., {@code `--file'}.
2453 * </p><p>Picocli supports the POSIX convention that short options can be grouped, with the last option
2454 * optionally taking a parameter, which may be attached to the option name or separated by a space or
2455 * a {@code '='} character. The below examples are all equivalent:
2456 * </p><pre>
2457 * -xvfFILE
2458 * -xvf FILE
2459 * -xvf=FILE
2460 * -xv --file FILE
2461 * -xv --file=FILE
2462 * -x -v --file FILE
2463 * -x -v --file=FILE
2464 * </pre><p>
2465 * <b>DOS</b>
2466 * </p><p>
2467 * DOS options mostly have upper case single-character names and start with a single slash {@code '/'} character.
2468 * Option parameters are separated by a {@code ':'} character. Options cannot be grouped together but
2469 * must be specified separately. For example:
2470 * </p><pre>
2471 * DIR /S /A:D /T:C
2472 * </pre><p>
2473 * <b>PowerShell</b>
2474 * </p><p>
2475 * Windows PowerShell options generally are a word preceded by a single {@code '-'} character, e.g., {@code `-Help'}.
2476 * Option parameters are separated by a space or by a {@code ':'} character.
2477 * </p>
2478 * @return one or more option names
2479 */
2480 String[] names();
2481
2482 /**
2483 * Indicates whether this option is required. By default this is false.
2484 * <p>If an option is required, but a user invokes the program without specifying the required option,
2485 * a {@link MissingParameterException} is thrown from the {@link #parse(String...)} method.</p>
2486 * <p>Required options that are part of a {@linkplain ArgGroup group} are required <em>within the group</em>, not required within the command:
2487 * the group's {@linkplain ArgGroup#multiplicity() multiplicity} determines whether the group itself is required or optional.</p>
2488 * @return whether this option is required
2489 */
2490 boolean required() default false;
2491
2492 /**
2493 * Set {@code help=true} if this option should disable validation of the remaining arguments:
2494 * If the {@code help} option is specified, no error message is generated for missing required options.
2495 * <p>
2496 * This attribute is useful for special options like help ({@code -h} and {@code --help} on unix,
2497 * {@code -?} and {@code -Help} on Windows) or version ({@code -V} and {@code --version} on unix,
2498 * {@code -Version} on Windows).
2499 * </p>
2500 * <p>
2501 * Note that the {@link #parse(String...)} method will not print help documentation. It will only set
2502 * the value of the annotated field. It is the responsibility of the caller to inspect the annotated fields
2503 * and take the appropriate action.
2504 * </p>
2505 * @return whether this option disables validation of the other arguments
2506 * @deprecated Use {@link #usageHelp()} and {@link #versionHelp()} instead. See {@link #printHelpIfRequested(List, PrintStream, CommandLine.Help.Ansi)}
2507 */
2508 @Deprecated boolean help() default false;
2509
2510 /**
2511 * Set {@code usageHelp=true} for the {@code --help} option that triggers display of the usage help message.
2512 * The <a href="http://picocli.info/#_printing_help_automatically">convenience methods</a> {@code Commandline.call},
2513 * {@code Commandline.run}, and {@code Commandline.parseWithHandler(s)} will automatically print usage help
2514 * when an option with {@code usageHelp=true} was specified on the command line.
2515 * <p>
2516 * By default, <em>all</em> options and positional parameters are included in the usage help message
2517 * <em>except when explicitly marked {@linkplain #hidden() hidden}.</em>
2518 * </p><p>
2519 * If this option is specified on the command line, picocli will not validate the remaining arguments (so no "missing required
2520 * option" errors) and the {@link CommandLine#isUsageHelpRequested()} method will return {@code true}.
2521 * </p><p>
2522 * Alternatively, consider annotating your command with {@linkplain Command#mixinStandardHelpOptions() @Command(mixinStandardHelpOptions = true)}.
2523 * </p>
2524 * @return whether this option allows the user to request usage help
2525 * @since 0.9.8
2526 * @see #hidden()
2527 * @see #run(Runnable, String...)
2528 * @see #call(Callable, String...)
2529 * @see #parseWithHandler(IParseResultHandler2, String[])
2530 * @see #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi)
2531 */
2532 boolean usageHelp() default false;
2533
2534 /**
2535 * Set {@code versionHelp=true} for the {@code --version} option that triggers display of the version information.
2536 * The <a href="http://picocli.info/#_printing_help_automatically">convenience methods</a> {@code Commandline.call},
2537 * {@code Commandline.run}, and {@code Commandline.parseWithHandler(s)} will automatically print version information
2538 * when an option with {@code versionHelp=true} was specified on the command line.
2539 * <p>
2540 * The version information string is obtained from the command's {@linkplain Command#version() version} annotation
2541 * or from the {@linkplain Command#versionProvider() version provider}.
2542 * </p><p>
2543 * If this option is specified on the command line, picocli will not validate the remaining arguments (so no "missing required
2544 * option" errors) and the {@link CommandLine#isUsageHelpRequested()} method will return {@code true}.
2545 * </p><p>
2546 * Alternatively, consider annotating your command with {@linkplain Command#mixinStandardHelpOptions() @Command(mixinStandardHelpOptions = true)}.
2547 * </p>
2548 * @return whether this option allows the user to request version information
2549 * @since 0.9.8
2550 * @see #hidden()
2551 * @see #run(Runnable, String...)
2552 * @see #call(Callable, String...)
2553 * @see #parseWithHandler(IParseResultHandler2, String[])
2554 * @see #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi)
2555 */
2556 boolean versionHelp() default false;
2557
2558 /**
2559 * Description of this option, used when generating the usage documentation. Each element of the array is rendered on a separate line.
2560 * <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 %}.
2561 * </p><p>
2562 * The description may contain variables that are rendered when help is requested.
2563 * The string {@code ${DEFAULT-VALUE}} is replaced with the default value of the option. This is regardless of
2564 * the command's {@link Command#showDefaultValues() showDefaultValues} setting or the option's {@link #showDefaultValue() showDefaultValue} setting.
2565 * The string {@code ${COMPLETION-CANDIDATES}} is replaced with the completion candidates generated by
2566 * {@link #completionCandidates()} in the description for this option.
2567 * Also, embedded {@code %n} newline markers are converted to actual newlines.
2568 * </p>
2569 * @return the description of this option
2570 */
2571 String[] description() default {};
2572
2573 /**
2574 * Specifies the minimum number of required parameters and the maximum number of accepted parameters.
2575 * If an option declares a positive arity, and the user specifies an insufficient number of parameters on the
2576 * command line, a {@link MissingParameterException} is thrown by the {@link #parse(String...)} method.
2577 * <p>
2578 * In many cases picocli can deduce the number of required parameters from the field's type.
2579 * By default, flags (boolean options) have arity zero,
2580 * and single-valued type fields (String, int, Integer, double, Double, File, Date, etc) have arity one.
2581 * Generally, fields with types that cannot hold multiple values can omit the {@code arity} attribute.
2582 * </p><p>
2583 * Fields used to capture options with arity two or higher should have a type that can hold multiple values,
2584 * like arrays or Collections. See {@link #type()} for strongly-typed Collection fields.
2585 * </p><p>
2586 * For example, if an option has 2 required parameters and any number of optional parameters,
2587 * specify {@code @Option(names = "-example", arity = "2..*")}.
2588 * </p>
2589 * <b>A note on boolean options</b>
2590 * <p>
2591 * By default picocli does not expect boolean options (also called "flags" or "switches") to have a parameter.
2592 * You can make a boolean option take a required parameter by annotating your field with {@code arity="1"}.
2593 * For example: </p>
2594 * <pre>@Option(names = "-v", arity = "1") boolean verbose;</pre>
2595 * <p>
2596 * Because this boolean field is defined with arity 1, the user must specify either {@code <program> -v false}
2597 * or {@code <program> -v true}
2598 * on the command line, or a {@link MissingParameterException} is thrown by the {@link #parse(String...)}
2599 * method.
2600 * </p><p>
2601 * To make the boolean parameter possible but optional, define the field with {@code arity = "0..1"}.
2602 * For example: </p>
2603 * <pre>@Option(names="-v", arity="0..1") boolean verbose;</pre>
2604 * <p>This will accept any of the below without throwing an exception:</p>
2605 * <pre>
2606 * -v
2607 * -v true
2608 * -v false
2609 * </pre>
2610 * @return how many arguments this option requires
2611 */
2612 String arity() default "";
2613
2614 /**
2615 * Specify a {@code paramLabel} for the option parameter to be used in the usage help message. If omitted,
2616 * picocli uses the field name in fish brackets ({@code '<'} and {@code '>'}) by default. Example:
2617 * <pre>class Example {
2618 * @Option(names = {"-o", "--output"}, paramLabel="FILE", description="path of the output file")
2619 * private File out;
2620 * @Option(names = {"-j", "--jobs"}, arity="0..1", description="Allow N jobs at once; infinite jobs with no arg.")
2621 * private int maxJobs = -1;
2622 * }</pre>
2623 * <p>By default, the above gives a usage help message like the following:</p><pre>
2624 * Usage: <main class> [OPTIONS]
2625 * -o, --output FILE path of the output file
2626 * -j, --jobs [<maxJobs>] Allow N jobs at once; infinite jobs with no arg.
2627 * </pre>
2628 * @return name of the option parameter used in the usage help message
2629 */
2630 String paramLabel() default "";
2631
2632 /** Returns whether usage syntax decorations around the {@linkplain #paramLabel() paramLabel} should be suppressed.
2633 * The default is {@code false}: by default, the paramLabel is surrounded with {@code '['} and {@code ']'} characters
2634 * if the value is optional and followed by ellipses ("...") when multiple values can be specified.
2635 * @since 3.6.0 */
2636 boolean hideParamSyntax() default false;
2637
2638 /** <p>
2639 * Optionally specify a {@code type} to control exactly what Class the option parameter should be converted
2640 * to. This may be useful when the field type is an interface or an abstract class. For example, a field can
2641 * be declared to have type {@code java.lang.Number}, and annotating {@code @Option(type=Short.class)}
2642 * ensures that the option parameter value is converted to a {@code Short} before setting the field value.
2643 * </p><p>
2644 * For array fields whose <em>component</em> type is an interface or abstract class, specify the concrete <em>component</em> type.
2645 * For example, a field with type {@code Number[]} may be annotated with {@code @Option(type=Short.class)}
2646 * to ensure that option parameter values are converted to {@code Short} before adding an element to the array.
2647 * </p><p>
2648 * Picocli will use the {@link ITypeConverter} that is
2649 * {@linkplain #registerConverter(Class, ITypeConverter) registered} for the specified type to convert
2650 * the raw String values before modifying the field value.
2651 * </p><p>
2652 * Prior to 2.0, the {@code type} attribute was necessary for {@code Collection} and {@code Map} fields,
2653 * but starting from 2.0 picocli will infer the component type from the generic type's type arguments.
2654 * For example, for a field of type {@code Map<TimeUnit, Long>} picocli will know the option parameter
2655 * should be split up in key=value pairs, where the key should be converted to a {@code java.util.concurrent.TimeUnit}
2656 * enum value, and the value should be converted to a {@code Long}. No {@code @Option(type=...)} type attribute
2657 * is required for this. For generic types with wildcards, picocli will take the specified upper or lower bound
2658 * as the Class to convert to, unless the {@code @Option} annotation specifies an explicit {@code type} attribute.
2659 * </p><p>
2660 * If the field type is a raw collection or a raw map, and you want it to contain other values than Strings,
2661 * or if the generic type's type arguments are interfaces or abstract classes, you may
2662 * specify a {@code type} attribute to control the Class that the option parameter should be converted to.
2663 * @return the type(s) to convert the raw String values
2664 */
2665 Class<?>[] type() default {};
2666
2667 /**
2668 * Optionally specify one or more {@link ITypeConverter} classes to use to convert the command line argument into
2669 * a strongly typed value (or key-value pair for map fields). This is useful when a particular field should
2670 * use a custom conversion that is different from the normal conversion for the field's type.
2671 * <p>For example, for a specific field you may want to use a converter that maps the constant names defined
2672 * in {@link java.sql.Types java.sql.Types} to the {@code int} value of these constants, but any other {@code int} fields should
2673 * not be affected by this and should continue to use the standard int converter that parses numeric values.</p>
2674 * @return the type converter(s) to use to convert String values to strongly typed values for this field
2675 * @see CommandLine#registerConverter(Class, ITypeConverter)
2676 */
2677 Class<? extends ITypeConverter<?>>[] converter() default {};
2678
2679 /**
2680 * Specify a regular expression to use to split option parameter values before applying them to the field.
2681 * All elements resulting from the split are added to the array or Collection. Ignored for single-value fields.
2682 * @return a regular expression to split option parameter values or {@code ""} if the value should not be split
2683 * @see String#split(String)
2684 */
2685 String split() default "";
2686
2687 /**
2688 * Set {@code hidden=true} if this option should not be included in the usage help message.
2689 * @return whether this option should be excluded from the usage documentation
2690 */
2691 boolean hidden() default false;
2692
2693 /** Returns the default value of this option, before splitting and type conversion.
2694 * @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
2695 * @since 3.2 */
2696 String defaultValue() default "__no_default_value__";
2697
2698 /** Use this attribute to control for a specific option whether its default value should be shown in the usage
2699 * help message. If not specified, the default value is only shown when the {@link Command#showDefaultValues()}
2700 * is set {@code true} on the command. Use this attribute to specify whether the default value
2701 * for this specific option should always be shown or never be shown, regardless of the command setting.
2702 * <p>Note that picocli 3.2 allows {@linkplain #description() embedding default values} anywhere in the description that ignores this setting.</p>
2703 * @return whether this option's default value should be shown in the usage help message
2704 */
2705 Help.Visibility showDefaultValue() default Help.Visibility.ON_DEMAND;
2706
2707 /** Use this attribute to specify an {@code Iterable<String>} class that generates completion candidates for this option.
2708 * For map fields, completion candidates should be in {@code key=value} form.
2709 * <p>
2710 * Completion candidates are used in bash completion scripts generated by the {@code picocli.AutoComplete} class.
2711 * Bash has special completion options to generate file names and host names, and the bash completion scripts
2712 * generated by {@code AutoComplete} delegate to these bash built-ins for {@code @Options} whose {@code type} is
2713 * {@code java.io.File}, {@code java.nio.file.Path} or {@code java.net.InetAddress}.
2714 * </p><p>
2715 * For {@code @Options} whose {@code type} is a Java {@code enum}, {@code AutoComplete} can generate completion
2716 * candidates from the type. For other types, use this attribute to specify completion candidates.
2717 * </p>
2718 *
2719 * @return a class whose instances can iterate over the completion candidates for this option
2720 * @see picocli.CommandLine.IFactory
2721 * @since 3.2 */
2722 Class<? extends Iterable<String>> completionCandidates() default NoCompletionCandidates.class;
2723
2724 /**
2725 * Set {@code interactive=true} if this option will prompt the end user for a value (like a password).
2726 * Only supported for single-value options (not arrays, collections or maps).
2727 * 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.
2728 * @return whether this option prompts the end user for a value to be entered on the command line
2729 * @since 3.5
2730 */
2731 boolean interactive() default false;
2732
2733 /** ResourceBundle key for this option. If not specified, (and a ResourceBundle {@linkplain Command#resourceBundle() exists for this command}) an attempt
2734 * is made to find the option description using any of the option names (without leading hyphens) as key.
2735 * @see OptionSpec#description()
2736 * @since 3.6
2737 */
2738 String descriptionKey() default "";
2739
2740 /**
2741 * 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.
2742 * @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.
2743 * @since 3.9
2744 */
2745 int order() default -1;
2746
2747 /**
2748 * Specify the name of one or more options that this option is mutually exclusive with.
2749 * Picocli will internally create a mutually exclusive {@linkplain ArgGroup group} with all specified options (and
2750 * any options that the specified options are mutually exclusive with).
2751 * <p>
2752 * Options cannot be part of multiple groups to avoid ambiguity for the parser. Constructions
2753 * where an option is part of multiple groups must be simplified so that the option is in just one group.
2754 * For example: {@code (-a | -b) | (-a -x)} can be simplified to {@code (-a [-x] | -b)}.
2755 * </p>
2756 * @return the name or names of the option(s) that this option is mutually exclusive with.
2757 * @since 4.0
2758 */
2759 String[] excludes() default {};
2760
2761 /**
2762 * Specify the name of one or more options that this option must co-occur with.
2763 * Picocli will internally create a co-occurring {@linkplain ArgGroup group} with all specified options (and
2764 * any options that the specified options must co-occur with).
2765 * <p>
2766 * Options cannot be part of multiple groups to avoid ambiguity for the parser. Constructions
2767 * where an option is part of multiple groups must be simplified so that the option is in just one group.
2768 * For example: {@code (-a -x) | (-a -y)} can be simplified to {@code (-a [-x | -y])}.
2769 * </p>
2770 * @return the name or names of the option(s) that this option must co-occur with.
2771 * @since 4.0
2772 */
2773 String[] needs() default {};
2774 }
2775 /**
2776 * <p>
2777 * Fields annotated with {@code @Parameters} will be initialized with positional parameters. By specifying the
2778 * {@link #index()} attribute you can pick the exact position or a range of positional parameters to apply. If no
2779 * index is specified, the field will get all positional parameters (and so it should be an array or a collection).
2780 * </p><p>
2781 * In the case of command methods (annotated with {@code @Command}), method parameters may be annotated with {@code @Parameters},
2782 * but are are considered positional parameters by default, unless they are annotated with {@code @Option}.
2783 * </p><p>
2784 * Command class example:
2785 * </p>
2786 * <pre>
2787 * import static picocli.CommandLine.*;
2788 *
2789 * public class MyCalcParameters {
2790 * @Parameters(description = "Any number of input numbers")
2791 * private List<BigDecimal> files = new ArrayList<BigDecimal>();
2792 *
2793 * @Option(names = { "-h", "--help" }, usageHelp = true, description = "Display this help and exit")
2794 * private boolean help;
2795 * }
2796 * </pre><p>
2797 * A field cannot be annotated with both {@code @Parameters} and {@code @Option} or a {@code ParameterException}
2798 * is thrown.</p>
2799 */
2800 @Retention(RetentionPolicy.RUNTIME)
2801 @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
2802 public @interface Parameters {
2803 /** Specify an index ("0", or "1", etc.) to pick which of the command line arguments should be assigned to this
2804 * field. For array or Collection fields, you can also specify an index range ("0..3", or "2..*", etc.) to assign
2805 * a subset of the command line arguments to this field. The default is "*", meaning all command line arguments.
2806 * @return an index or range specifying which of the command line arguments should be assigned to this field
2807 */
2808 String index() default "";
2809
2810 /** Description of the parameter(s), used when generating the usage documentation. Each element of the array is rendered on a separate line.
2811 * <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 %}.
2812 * </p><p>
2813 * The description may contain variables that are rendered when help is requested.
2814 * The string {@code ${DEFAULT-VALUE}} is replaced with the default value of the positional parameter. This is regardless of
2815 * the command's {@link Command#showDefaultValues() showDefaultValues} setting or the positional parameter's {@link #showDefaultValue() showDefaultValue} setting.
2816 * The string {@code ${COMPLETION-CANDIDATES}} is replaced with the completion candidates generated by
2817 * {@link #completionCandidates()} in the description for this positional parameter.
2818 * Also, embedded {@code %n} newline markers are converted to actual newlines.
2819 * </p>
2820 * @return the description of the parameter(s)
2821 */
2822 String[] description() default {};
2823
2824 /**
2825 * Specifies the minimum number of required parameters and the maximum number of accepted parameters. If a
2826 * positive arity is declared, and the user specifies an insufficient number of parameters on the command line,
2827 * {@link MissingParameterException} is thrown by the {@link #parse(String...)} method.
2828 * <p>The default depends on the type of the parameter: booleans require no parameters, arrays and Collections
2829 * accept zero to any number of parameters, and any other type accepts one parameter.</p>
2830 * <p>For single-value parameters, setting {@code arity = "0..1"} makes a positional parameter optional, while setting {@code arity = "1"} makes it required.</p>
2831 * <p>Required parameters that are part of a {@linkplain ArgGroup group} are required <em>within the group</em>, not required within the command:
2832 * the group's {@linkplain ArgGroup#multiplicity() multiplicity} determines whether the group itself is required or optional.</p>
2833 * @return the range of minimum and maximum parameters accepted by this command
2834 */
2835 String arity() default "";
2836
2837 /**
2838 * Specify a {@code paramLabel} for the parameter to be used in the usage help message. If omitted,
2839 * picocli uses the field name in fish brackets ({@code '<'} and {@code '>'}) by default. Example:
2840 * <pre>class Example {
2841 * @Parameters(paramLabel="FILE", description="path of the input FILE(s)")
2842 * private File[] inputFiles;
2843 * }</pre>
2844 * <p>By default, the above gives a usage help message like the following:</p><pre>
2845 * Usage: <main class> [FILE...]
2846 * [FILE...] path of the input FILE(s)
2847 * </pre>
2848 * @return name of the positional parameter used in the usage help message
2849 */
2850 String paramLabel() default "";
2851
2852 /** Returns whether usage syntax decorations around the {@linkplain #paramLabel() paramLabel} should be suppressed.
2853 * The default is {@code false}: by default, the paramLabel is surrounded with {@code '['} and {@code ']'} characters
2854 * if the value is optional and followed by ellipses ("...") when multiple values can be specified.
2855 * @since 3.6.0 */
2856 boolean hideParamSyntax() default false;
2857
2858 /**
2859 * <p>
2860 * Optionally specify a {@code type} to control exactly what Class the positional parameter should be converted
2861 * to. This may be useful when the field type is an interface or an abstract class. For example, a field can
2862 * be declared to have type {@code java.lang.Number}, and annotating {@code @Parameters(type=Short.class)}
2863 * ensures that the positional parameter value is converted to a {@code Short} before setting the field value.
2864 * </p><p>
2865 * For array fields whose <em>component</em> type is an interface or abstract class, specify the concrete <em>component</em> type.
2866 * For example, a field with type {@code Number[]} may be annotated with {@code @Parameters(type=Short.class)}
2867 * to ensure that positional parameter values are converted to {@code Short} before adding an element to the array.
2868 * </p><p>
2869 * Picocli will use the {@link ITypeConverter} that is
2870 * {@linkplain #registerConverter(Class, ITypeConverter) registered} for the specified type to convert
2871 * the raw String values before modifying the field value.
2872 * </p><p>
2873 * Prior to 2.0, the {@code type} attribute was necessary for {@code Collection} and {@code Map} fields,
2874 * but starting from 2.0 picocli will infer the component type from the generic type's type arguments.
2875 * For example, for a field of type {@code Map<TimeUnit, Long>} picocli will know the positional parameter
2876 * should be split up in key=value pairs, where the key should be converted to a {@code java.util.concurrent.TimeUnit}
2877 * enum value, and the value should be converted to a {@code Long}. No {@code @Parameters(type=...)} type attribute
2878 * is required for this. For generic types with wildcards, picocli will take the specified upper or lower bound
2879 * as the Class to convert to, unless the {@code @Parameters} annotation specifies an explicit {@code type} attribute.
2880 * </p><p>
2881 * If the field type is a raw collection or a raw map, and you want it to contain other values than Strings,
2882 * or if the generic type's type arguments are interfaces or abstract classes, you may
2883 * specify a {@code type} attribute to control the Class that the positional parameter should be converted to.
2884 * @return the type(s) to convert the raw String values
2885 */
2886 Class<?>[] type() default {};
2887
2888 /**
2889 * Optionally specify one or more {@link ITypeConverter} classes to use to convert the command line argument into
2890 * a strongly typed value (or key-value pair for map fields). This is useful when a particular field should
2891 * use a custom conversion that is different from the normal conversion for the field's type.
2892 * <p>For example, for a specific field you may want to use a converter that maps the constant names defined
2893 * in {@link java.sql.Types java.sql.Types} to the {@code int} value of these constants, but any other {@code int} fields should
2894 * not be affected by this and should continue to use the standard int converter that parses numeric values.</p>
2895 * @return the type converter(s) to use to convert String values to strongly typed values for this field
2896 * @see CommandLine#registerConverter(Class, ITypeConverter)
2897 */
2898 Class<? extends ITypeConverter<?>>[] converter() default {};
2899
2900 /**
2901 * Specify a regular expression to use to split positional parameter values before applying them to the field.
2902 * All elements resulting from the split are added to the array or Collection. Ignored for single-value fields.
2903 * @return a regular expression to split operand values or {@code ""} if the value should not be split
2904 * @see String#split(String)
2905 */
2906 String split() default "";
2907
2908 /**
2909 * Set {@code hidden=true} if this parameter should not be included in the usage message.
2910 * @return whether this parameter should be excluded from the usage message
2911 */
2912 boolean hidden() default false;
2913
2914 /** Returns the default value of this positional parameter, before splitting and type conversion.
2915 * @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
2916 * @since 3.2 */
2917 String defaultValue() default "__no_default_value__";
2918
2919 /** Use this attribute to control for a specific positional parameter whether its default value should be shown in the usage
2920 * help message. If not specified, the default value is only shown when the {@link Command#showDefaultValues()}
2921 * is set {@code true} on the command. Use this attribute to specify whether the default value
2922 * for this specific positional parameter should always be shown or never be shown, regardless of the command setting.
2923 * <p>Note that picocli 3.2 allows {@linkplain #description() embedding default values} anywhere in the description that ignores this setting.</p>
2924 * @return whether this positional parameter's default value should be shown in the usage help message
2925 */
2926 Help.Visibility showDefaultValue() default Help.Visibility.ON_DEMAND;
2927
2928 /** Use this attribute to specify an {@code Iterable<String>} class that generates completion candidates for
2929 * this positional parameter. For map fields, completion candidates should be in {@code key=value} form.
2930 * <p>
2931 * Completion candidates are used in bash completion scripts generated by the {@code picocli.AutoComplete} class.
2932 * Unfortunately, {@code picocli.AutoComplete} is not very good yet at generating completions for positional parameters.
2933 * </p>
2934 *
2935 * @return a class whose instances can iterate over the completion candidates for this positional parameter
2936 * @see picocli.CommandLine.IFactory
2937 * @since 3.2 */
2938 Class<? extends Iterable<String>> completionCandidates() default NoCompletionCandidates.class;
2939
2940 /**
2941 * Set {@code interactive=true} if this positional parameter will prompt the end user for a value (like a password).
2942 * Only supported for single-value positional parameters (not arrays, collections or maps).
2943 * 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.
2944 * @return whether this positional parameter prompts the end user for a value to be entered on the command line
2945 * @since 3.5
2946 */
2947 boolean interactive() default false;
2948
2949 /** ResourceBundle key for this option. If not specified, (and a ResourceBundle {@linkplain Command#resourceBundle() exists for this command}) an attempt
2950 * is made to find the positional parameter description using {@code paramLabel() + "[" + index() + "]"} as key.
2951 *
2952 * @see PositionalParamSpec#description()
2953 * @since 3.6
2954 */
2955 String descriptionKey() default "";
2956
2957 /**
2958 * Specify the name of one or more options that this positional parameter is mutually exclusive with.
2959 * Picocli will internally create a mutually exclusive {@linkplain ArgGroup group} with all specified options (and
2960 * any options and positional parameters that the specified options are mutually exclusive with).
2961 * <p>
2962 * An option or positional parameter cannot be part of multiple groups to avoid ambiguity for the parser. Constructions
2963 * where an option is part of multiple groups must be simplified so that the option is in just one group.
2964 * For example: {@code (-a | -b) | (-a -x)} can be simplified to {@code (-a [-x] | -b)}.
2965 * </p>
2966 * @return the name or names of the option(s) that this positional parameter is mutually exclusive with.
2967 * @since 4.0
2968 */
2969 String[] excludes() default {};
2970
2971 /**
2972 * Specify the name of one or more options that this option must co-occur with.
2973 * Picocli will internally create a co-occurring {@linkplain ArgGroup group} with all specified options (and
2974 * any options that the specified options must co-occur with).
2975 * <p>
2976 * Options cannot be part of multiple groups to avoid ambiguity for the parser. Constructions
2977 * where an option is part of multiple groups must be simplified so that the option is in just one group.
2978 * For example: {@code (-a -x) | (-a -y)} can be simplified to {@code (-a [-x | -y])}.
2979 * </p>
2980 * @return the name or names of the option(s) that this option must co-occur with.
2981 * @since 4.0
2982 */
2983 String[] needs() default {};
2984 }
2985
2986 /**
2987 * <p>
2988 * Fields annotated with {@code @ParentCommand} will be initialized with the parent command of the current subcommand.
2989 * If the current command does not have a parent command, this annotation has no effect.
2990 * </p><p>
2991 * Parent commands often define options that apply to all the subcommands.
2992 * This annotation offers a convenient way to inject a reference to the parent command into a subcommand, so the
2993 * subcommand can access its parent options. For example:
2994 * </p><pre>
2995 * @Command(name = "top", subcommands = Sub.class)
2996 * class Top implements Runnable {
2997 *
2998 * @Option(names = {"-d", "--directory"}, description = "this option applies to all subcommands")
2999 * File baseDirectory;
3000 *
3001 * public void run() { System.out.println("Hello from top"); }
3002 * }
3003 *
3004 * @Command(name = "sub")
3005 * class Sub implements Runnable {
3006 *
3007 * @ParentCommand
3008 * private Top parent;
3009 *
3010 * public void run() {
3011 * System.out.println("Subcommand: parent command 'directory' is " + parent.baseDirectory);
3012 * }
3013 * }
3014 * </pre>
3015 * @since 2.2
3016 */
3017 @Retention(RetentionPolicy.RUNTIME)
3018 @Target(ElementType.FIELD)
3019 public @interface ParentCommand { }
3020
3021 /**
3022 * Fields annotated with {@code @Unmatched} will be initialized with the list of unmatched command line arguments, if any.
3023 * If this annotation is found, picocli automatically sets {@linkplain CommandLine#setUnmatchedArgumentsAllowed(boolean) unmatchedArgumentsAllowed} to {@code true}.
3024 * @see CommandLine#isUnmatchedArgumentsAllowed()
3025 * @since 3.0
3026 */
3027 @Retention(RetentionPolicy.RUNTIME)
3028 @Target(ElementType.FIELD)
3029 public @interface Unmatched { }
3030
3031 /**
3032 * <p>
3033 * Fields annotated with {@code @Mixin} are "expanded" into the current command: {@link Option @Option} and
3034 * {@link Parameters @Parameters} in the mixin class are added to the options and positional parameters of this command.
3035 * A {@link DuplicateOptionAnnotationsException} is thrown if any of the options in the mixin has the same name as
3036 * an option in this command.
3037 * </p><p>
3038 * The {@code Mixin} annotation provides a way to reuse common options and parameters without subclassing. For example:
3039 * </p><pre>
3040 * class HelloWorld implements Runnable {
3041 *
3042 * // adds the --help and --version options to this command
3043 * @Mixin
3044 * private HelpOptions = new HelpOptions();
3045 *
3046 * @Option(names = {"-u", "--userName"}, required = true, description = "The user name")
3047 * String userName;
3048 *
3049 * public void run() { System.out.println("Hello, " + userName); }
3050 * }
3051 *
3052 * // Common reusable help options.
3053 * class HelpOptions {
3054 *
3055 * @Option(names = { "-h", "--help"}, usageHelp = true, description = "Display this help and exit")
3056 * private boolean help;
3057 *
3058 * @Option(names = { "-V", "--version"}, versionHelp = true, description = "Display version info and exit")
3059 * private boolean versionHelp;
3060 * }
3061 * </pre>
3062 * @since 3.0
3063 */
3064 @Retention(RetentionPolicy.RUNTIME)
3065 @Target({ElementType.FIELD, ElementType.PARAMETER})
3066 public @interface Mixin {
3067 /** Optionally specify a name that the mixin object can be retrieved with from the {@code CommandSpec}.
3068 * If not specified the name of the annotated field is used.
3069 * @return a String to register the mixin object with, or an empty String if the name of the annotated field should be used */
3070 String name() default "";
3071 }
3072 /**
3073 * Fields annotated with {@code @Spec} will be initialized with the {@code CommandSpec} for the command the field is part of. Example usage:
3074 * <pre>
3075 * class InjectSpecExample implements Runnable {
3076 * @Spec CommandSpec commandSpec;
3077 * //...
3078 * public void run() {
3079 * // do something with the injected objects
3080 * }
3081 * }
3082 * </pre>
3083 * @since 3.2
3084 */
3085 @Retention(RetentionPolicy.RUNTIME)
3086 @Target({ElementType.FIELD, ElementType.METHOD})
3087 public @interface Spec { }
3088 /**
3089 * <p>Annotate your class with {@code @Command} when you want more control over the format of the generated help
3090 * message. From 3.6, methods can also be annotated with {@code @Command}, where the method parameters define the
3091 * command options and positional parameters.
3092 * </p><pre>
3093 * @Command(name = "Encrypt", mixinStandardHelpOptions = true,
3094 * description = "Encrypt FILE(s), or standard input, to standard output or to the output file.",
3095 * version = "Encrypt version 1.0",
3096 * footer = "Copyright (c) 2017")
3097 * public class Encrypt {
3098 * @Parameters(paramLabel = "FILE", description = "Any number of input files")
3099 * private List<File> files = new ArrayList<File>();
3100 *
3101 * @Option(names = { "-o", "--out" }, description = "Output file (default: print to console)")
3102 * private File outputFile;
3103 *
3104 * @Option(names = { "-v", "--verbose"}, description = "Verbose mode. Helpful for troubleshooting. Multiple -v options increase the verbosity.")
3105 * private boolean[] verbose;
3106 * }</pre>
3107 * <p>
3108 * The structure of a help message looks like this:
3109 * </p><ul>
3110 * <li>[header]</li>
3111 * <li>[synopsis]: {@code Usage: <commandName> [OPTIONS] [FILE...]}</li>
3112 * <li>[description]</li>
3113 * <li>[parameter list]: {@code [FILE...] Any number of input files}</li>
3114 * <li>[option list]: {@code -h, --help prints this help message and exits}</li>
3115 * <li>[footer]</li>
3116 * </ul> */
3117 @Retention(RetentionPolicy.RUNTIME)
3118 @Target({ElementType.TYPE, ElementType.LOCAL_VARIABLE, ElementType.FIELD, ElementType.PACKAGE, ElementType.METHOD})
3119 public @interface Command {
3120 /** Program name to show in the synopsis. If omitted, {@code "<main class>"} is used.
3121 * For {@linkplain #subcommands() declaratively added} subcommands, this attribute is also used
3122 * by the parser to recognize subcommands in the command line arguments.
3123 * @return the program name to show in the synopsis
3124 * @see CommandSpec#name()
3125 * @see Help#commandName() */
3126 String name() default "<main class>";
3127
3128 /** Alternative command names by which this subcommand is recognized on the command line.
3129 * @return one or more alternative command names
3130 * @since 3.1 */
3131 String[] aliases() default {};
3132
3133 /** A list of classes to instantiate and register as subcommands. When registering subcommands declaratively
3134 * like this, you don't need to call the {@link CommandLine#addSubcommand(String, Object)} method. For example, this:
3135 * <pre>
3136 * @Command(subcommands = {
3137 * GitStatus.class,
3138 * GitCommit.class,
3139 * GitBranch.class })
3140 * public class Git { ... }
3141 *
3142 * CommandLine commandLine = new CommandLine(new Git());
3143 * </pre> is equivalent to this:
3144 * <pre>
3145 * // alternative: programmatically add subcommands.
3146 * // NOTE: in this case there should be no `subcommands` attribute on the @Command annotation.
3147 * @Command public class Git { ... }
3148 *
3149 * CommandLine commandLine = new CommandLine(new Git())
3150 * .addSubcommand("status", new GitStatus())
3151 * .addSubcommand("commit", new GitCommit())
3152 * .addSubcommand("branch", new GitBranch());
3153 * </pre>
3154 * @return the declaratively registered subcommands of this command, or an empty array if none
3155 * @see CommandLine#addSubcommand(String, Object)
3156 * @see HelpCommand
3157 * @since 0.9.8
3158 */
3159 Class<?>[] subcommands() default {};
3160
3161 /** Specify whether methods annotated with {@code @Command} should be registered as subcommands of their
3162 * enclosing {@code @Command} class.
3163 * The default is {@code true}. For example:
3164 * <pre>
3165 * @Command
3166 * public class Git {
3167 * @Command
3168 * void status() { ... }
3169 * }
3170 *
3171 * CommandLine git = new CommandLine(new Git());
3172 * </pre> is equivalent to this:
3173 * <pre>
3174 * // don't add command methods as subcommands automatically
3175 * @Command(addMethodSubcommands = false)
3176 * public class Git {
3177 * @Command
3178 * void status() { ... }
3179 * }
3180 *
3181 * // add command methods as subcommands programmatically
3182 * CommandLine git = new CommandLine(new Git());
3183 * CommandLine status = new CommandLine(CommandLine.getCommandMethods(Git.class, "status").get(0));
3184 * git.addSubcommand("status", status);
3185 * </pre>
3186 * @return whether methods annotated with {@code @Command} should be registered as subcommands
3187 * @see CommandLine#addSubcommand(String, Object)
3188 * @see CommandLine#getCommandMethods(Class, String)
3189 * @see CommandSpec#addMethodSubcommands()
3190 * @since 3.6.0 */
3191 boolean addMethodSubcommands() default true;
3192
3193 /** String that separates options from option parameters. Default is {@code "="}. Spaces are also accepted.
3194 * @return the string that separates options from option parameters, used both when parsing and when generating usage help
3195 * @see CommandLine#setSeparator(String) */
3196 String separator() default "=";
3197
3198 /** Version information for this command, to print to the console when the user specifies an
3199 * {@linkplain Option#versionHelp() option} to request version help. Each element of the array is rendered on a separate line.
3200 * <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>
3201 * <p>This is not part of the usage help message.</p>
3202 *
3203 * @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).
3204 * @since 0.9.8
3205 * @see CommandLine#printVersionHelp(PrintStream)
3206 */
3207 String[] version() default {};
3208
3209 /** Class that can provide version information dynamically at runtime. An implementation may return version
3210 * information obtained from the JAR manifest, a properties file or some other source.
3211 * @return a Class that can provide version information dynamically at runtime
3212 * @since 2.2 */
3213 Class<? extends IVersionProvider> versionProvider() default NoVersionProvider.class;
3214
3215 /**
3216 * Adds the standard {@code -h} and {@code --help} {@linkplain Option#usageHelp() usageHelp} options and {@code -V}
3217 * and {@code --version} {@linkplain Option#versionHelp() versionHelp} options to the options of this command.
3218 * <p>
3219 * Note that if no {@link #version()} or {@link #versionProvider()} is specified, the {@code --version} option will not print anything.
3220 * </p><p>
3221 * For {@linkplain #resourceBundle() internationalization}: the help option has {@code descriptionKey = "mixinStandardHelpOptions.help"},
3222 * and the version option has {@code descriptionKey = "mixinStandardHelpOptions.version"}.
3223 * </p>
3224 * @return whether the auto-help mixin should be added to this command
3225 * @since 3.0 */
3226 boolean mixinStandardHelpOptions() default false;
3227
3228 /** Set this attribute to {@code true} if this subcommand is a help command, and required options and positional
3229 * parameters of the parent command should not be validated. If a subcommand marked as {@code helpCommand} is
3230 * specified on the command line, picocli will not validate the parent arguments (so no "missing required
3231 * option" errors) and the {@link CommandLine#printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi)} method will return {@code true}.
3232 * @return {@code true} if this subcommand is a help command and picocli should not check for missing required
3233 * options and positional parameters on the parent command
3234 * @since 3.0 */
3235 boolean helpCommand() default false;
3236
3237 /** Set the heading preceding the header section.
3238 * <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>
3239 * @return the heading preceding the header section
3240 * @see UsageMessageSpec#headerHeading()
3241 * @see Help#headerHeading(Object...) */
3242 String headerHeading() default "";
3243
3244 /** Optional summary description of the command, shown before the synopsis. Each element of the array is rendered on a separate line.
3245 * <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>
3246 * @return summary description of the command
3247 * @see UsageMessageSpec#header()
3248 * @see Help#header(Object...) */
3249 String[] header() default {};
3250
3251 /** Set the heading preceding the synopsis text. The default heading is {@code "Usage: "} (without a line break between the heading and the synopsis text).
3252 * <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>
3253 * @return the heading preceding the synopsis text
3254 * @see Help#synopsisHeading(Object...) */
3255 String synopsisHeading() default "Usage: ";
3256
3257 /** Specify {@code true} to generate an abbreviated synopsis like {@code "<main> [OPTIONS] [PARAMETERS...]"}.
3258 * By default, a detailed synopsis with individual option names and parameters is generated.
3259 * @return whether the synopsis should be abbreviated
3260 * @see Help#abbreviatedSynopsis()
3261 * @see Help#detailedSynopsis(Comparator, boolean) */
3262 boolean abbreviateSynopsis() default false;
3263
3264 /** 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.
3265 * <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>
3266 * @return custom synopsis text to replace the auto-generated synopsis
3267 * @see Help#customSynopsis(Object...) */
3268 String[] customSynopsis() default {};
3269
3270 /** Set the heading preceding the description section.
3271 * <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>
3272 * @return the heading preceding the description section
3273 * @see Help#descriptionHeading(Object...) */
3274 String descriptionHeading() default "";
3275
3276 /** 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.
3277 * <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>
3278 * @return description of this command
3279 * @see Help#description(Object...) */
3280 String[] description() default {};
3281
3282 /** Set the heading preceding the parameters list.
3283 * <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>
3284 * @return the heading preceding the parameters list
3285 * @see Help#parameterListHeading(Object...) */
3286 String parameterListHeading() default "";
3287
3288 /** Set the heading preceding the options list.
3289 * <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>
3290 * @return the heading preceding the options list
3291 * @see Help#optionListHeading(Object...) */
3292 String optionListHeading() default "";
3293
3294 /** Specify {@code false} to show Options in declaration order. The default is to sort alphabetically.
3295 * @return whether options should be shown in alphabetic order. */
3296 boolean sortOptions() default true;
3297
3298 /** Prefix required options with this character in the options list. The default is no marker: the synopsis
3299 * indicates which options and parameters are required.
3300 * @return the character to show in the options list to mark required options */
3301 char requiredOptionMarker() default ' ';
3302
3303 /** Class that can provide default values dynamically at runtime. An implementation may return default
3304 * value obtained from a configuration file like a properties file or some other source.
3305 * @return a Class that can provide default values dynamically at runtime
3306 * @since 3.6 */
3307 Class<? extends IDefaultValueProvider> defaultValueProvider() default NoDefaultProvider.class;
3308
3309 /** Specify {@code true} to show default values in the description column of the options list (except for
3310 * boolean options). False by default.
3311 * <p>Note that picocli 3.2 allows {@linkplain Option#description() embedding default values} anywhere in the
3312 * option or positional parameter description that ignores this setting.</p>
3313 * @return whether the default values for options and parameters should be shown in the description column */
3314 boolean showDefaultValues() default false;
3315
3316 /** Set the heading preceding the subcommands list. The default heading is {@code "Commands:%n"} (with a line break at the end).
3317 * <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>
3318 * @return the heading preceding the subcommands list
3319 * @see Help#commandListHeading(Object...) */
3320 String commandListHeading() default "Commands:%n";
3321
3322 /** Set the heading preceding the footer section.
3323 * <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>
3324 * @return the heading preceding the footer section
3325 * @see Help#footerHeading(Object...) */
3326 String footerHeading() default "";
3327
3328 /** Optional text to display after the list of options. Each element of the array is rendered on a separate line.
3329 * <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>
3330 * @return text to display after the list of options
3331 * @see Help#footer(Object...) */
3332 String[] footer() default {};
3333
3334 /**
3335 * Set {@code hidden=true} if this command should not be included in the list of commands in the usage help of the parent command.
3336 * @return whether this command should be excluded from the usage message
3337 * @since 3.0
3338 */
3339 boolean hidden() default false;
3340
3341 /** Set the base name of the ResourceBundle to find option and positional parameters descriptions, as well as
3342 * usage help message sections and section headings. <p>See {@link Messages} for more details and an example.</p>
3343 * @return the base name of the ResourceBundle for usage help strings
3344 * @see ArgSpec#messages()
3345 * @see UsageMessageSpec#messages()
3346 * @see CommandSpec#resourceBundle()
3347 * @see CommandLine#setResourceBundle(ResourceBundle)
3348 * @since 3.6
3349 */
3350 String resourceBundle() default "";
3351
3352 /** Set the {@link UsageMessageSpec#width(int) usage help message width}. The default is 80.
3353 * @since 3.7
3354 */
3355 int usageHelpWidth() default 80;
3356 }
3357 /** A {@code Command} may define one or more {@code ArgGroups}: a group of options, positional parameters or a mixture of the two.
3358 * Groups can be used to:
3359 * <ul>
3360 * <li>define <b>mutually exclusive</b> arguments. By default, options and positional parameters
3361 * in a group are mutually exclusive. This can be controlled with the {@link #exclusive() exclusive} attribute.
3362 * Picocli will throw a {@link MutuallyExclusiveArgsException} if the command line contains multiple arguments that are mutually exclusive.</li>
3363 * <li>define a set of arguments that <b>must co-occur</b>. Set {@link #exclusive() exclusive = false}
3364 * to define a group of options and positional parameters that must always be specified together.
3365 * Picocli will throw a {@link MissingParameterException MissingParameterException} if not all the options and positional parameters in a co-occurring group are specified together.</li>
3366 * <li>create an <b>option section</b> in the usage help message.
3367 * 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}).
3368 * Groups without a heading are only used for validation.
3369 * Set {@link #validate() validate = false} for groups whose purpose is only to customize the usage help message.</li>
3370 * <li>define <b>composite repeating argument groups</b>. Groups may contain other groups to create composite groups.</li>
3371 * </ul>
3372 * <p>Groups may be optional ({@code multiplicity = "0..1"}), required ({@code multiplicity = "1"}), or repeating groups ({@code multiplicity = "0..*"} or {@code multiplicity = "1..*"}).
3373 * 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.
3374 * For a group of co-occurring arguments, all arguments in the group must appear on the command line.
3375 * </p>
3376 * <p>Groups can be composed for validation purposes:</p>
3377 * <ul>
3378 * <li>When the parent group is mutually exclusive, only one of the subgroups may be present.</li>
3379 * <li>When the parent group is a co-occurring group, all subgroups must be present.</li>
3380 * <li>When the parent group is required, at least one subgroup must be present.</li>
3381 * </ul>
3382 * <p>
3383 * Below is an example of an {@code ArgGroup} defining a set of dependent options that must occur together.
3384 * All options are required <em>within the group</em>, while the group itself is optional:</p>
3385 * <pre>
3386 * public class DependentOptions {
3387 * @ArgGroup(exclusive = false, multiplicity = "0..1")
3388 * Dependent group;
3389 *
3390 * static class Dependent {
3391 * @Option(names = "-a", required = true) int a;
3392 * @Option(names = "-b", required = true) int b;
3393 * @Option(names = "-c", required = true) int c;
3394 * }
3395 * }</pre>
3396 * @see ArgGroupSpec
3397 * @since 4.0 */
3398 @Retention(RetentionPolicy.RUNTIME)
3399 @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
3400 public @interface ArgGroup {
3401 /** The heading of this group, used when generating the usage documentation.
3402 * When neither a {@link #heading() heading} nor a {@link #headingKey() headingKey} are specified,
3403 * this group is used for validation only and does not change the usage help message. */
3404 String heading() default "__no_heading__";
3405
3406 /** ResourceBundle key for this group's usage help message section heading.
3407 * When neither a {@link #heading() heading} nor a {@link #headingKey() headingKey} are specified,
3408 * this group is used for validation only and does not change the usage help message. */
3409 String headingKey() default "__no_heading_key__";
3410 /** Determines whether this is a mutually exclusive group; {@code true} by default.
3411 * If {@code false}, this is a co-occurring group. Ignored if {@link #validate()} is {@code false}. */
3412 boolean exclusive() default true;
3413 /** Determines how often this group can be specified on the command line; {@code "0..1"} (optional) by default.
3414 * For a group of mutually exclusive arguments, making the group required {@code multiplicity = "1"} means that
3415 * one of the arguments in the group must appear on the command line, or a MissingParameterException is thrown.
3416 * For a group of co-occurring arguments, making the group required means that all arguments in the group must appear on the command line.
3417 * Ignored if {@link #validate()} is {@code false}. */
3418 String multiplicity() default "0..1";
3419 /** Determines whether picocli should validate the rules of this group ({@code true} by default).
3420 * For a mutually exclusive group validation means verifying that no more than one elements of the group is specified on the command line;
3421 * for a co-ocurring group validation means verifying that all elements of the group are specified on the command line.
3422 * Set {@link #validate() validate = false} for groups whose purpose is only to customize the usage help message.
3423 * @see #multiplicity()
3424 * @see #heading() */
3425 boolean validate() default true;
3426 /** Determines the position in the options list in the usage help message at which this group should be shown.
3427 * Options with a lower number are shown before options with a higher number.
3428 * This attribute is only honored if {@link UsageMessageSpec#sortOptions()} is {@code false} for this command.*/
3429 int order() default -1;
3430 }
3431 /**
3432 * <p>
3433 * When parsing command line arguments and initializing
3434 * fields annotated with {@link Option @Option} or {@link Parameters @Parameters},
3435 * String values can be converted to any type for which a {@code ITypeConverter} is registered.
3436 * </p><p>
3437 * This interface defines the contract for classes that know how to convert a String into some domain object.
3438 * Custom converters can be registered with the {@link #registerConverter(Class, ITypeConverter)} method.
3439 * </p><p>
3440 * Java 8 lambdas make it easy to register custom type converters:
3441 * </p>
3442 * <pre>
3443 * commandLine.registerConverter(java.nio.file.Path.class, s -> java.nio.file.Paths.get(s));
3444 * commandLine.registerConverter(java.time.Duration.class, s -> java.time.Duration.parse(s));</pre>
3445 * <p>
3446 * Built-in type converters are pre-registered for the following java 1.5 types:
3447 * </p>
3448 * <ul>
3449 * <li>all primitive types</li>
3450 * <li>all primitive wrapper types: Boolean, Byte, Character, Double, Float, Integer, Long, Short</li>
3451 * <li>any enum</li>
3452 * <li>java.io.File</li>
3453 * <li>java.math.BigDecimal</li>
3454 * <li>java.math.BigInteger</li>
3455 * <li>java.net.InetAddress</li>
3456 * <li>java.net.URI</li>
3457 * <li>java.net.URL</li>
3458 * <li>java.nio.charset.Charset</li>
3459 * <li>java.sql.Time</li>
3460 * <li>java.util.Date</li>
3461 * <li>java.util.UUID</li>
3462 * <li>java.util.regex.Pattern</li>
3463 * <li>StringBuilder</li>
3464 * <li>CharSequence</li>
3465 * <li>String</li>
3466 * </ul>
3467 * @param <K> the type of the object that is the result of the conversion
3468 */
3469 public interface ITypeConverter<K> {
3470 /**
3471 * Converts the specified command line argument value to some domain object.
3472 * @param value the command line argument String value
3473 * @return the resulting domain object
3474 * @throws Exception an exception detailing what went wrong during the conversion
3475 */
3476 K convert(String value) throws Exception;
3477 }
3478
3479 /**
3480 * Provides version information for a command. Commands may configure a provider with the
3481 * {@link Command#versionProvider()} annotation attribute.
3482 * @since 2.2 */
3483 public interface IVersionProvider {
3484 /**
3485 * Returns version information for a command.
3486 * @return version information (each string in the array is displayed on a separate line)
3487 * @throws Exception an exception detailing what went wrong when obtaining version information
3488 */
3489 String[] getVersion() throws Exception;
3490 }
3491 private static class NoVersionProvider implements IVersionProvider {
3492 public String[] getVersion() throws Exception { throw new UnsupportedOperationException(); }
3493 }
3494
3495 /**
3496 * Provides default value for a command. Commands may configure a provider with the
3497 * {@link Command#defaultValueProvider()} annotation attribute.
3498 * @since 3.6 */
3499 public interface IDefaultValueProvider {
3500
3501 /** Returns the default value for an option or positional parameter or {@code null}.
3502 * The returned value is converted to the type of the option/positional parameter
3503 * via the same type converter used when populating this option/positional
3504 * parameter from a command line argument.
3505 * @param argSpec the option or positional parameter, never {@code null}
3506 * @return the default value for the option or positional parameter, or {@code null} if
3507 * this provider has no default value for the specified option or positional parameter
3508 * @throws Exception when there was a problem obtaining the default value
3509 */
3510 String defaultValue(ArgSpec argSpec) throws Exception;
3511 }
3512 private static class NoDefaultProvider implements IDefaultValueProvider {
3513 public String defaultValue(ArgSpec argSpec) { throw new UnsupportedOperationException(); }
3514 }
3515
3516 /**
3517 * Creates the {@link Help} instance used to render the usage help message.
3518 * @since 3.9
3519 */
3520 public interface IHelpFactory {
3521 /** Returns a {@code Help} instance to assist in rendering the usage help message
3522 * @param commandSpec the command to create usage help for
3523 * @param colorScheme the color scheme to use when rendering usage help
3524 * @return a {@code Help} instance
3525 */
3526 Help create(CommandSpec commandSpec, Help.ColorScheme colorScheme);
3527 }
3528
3529 private static class DefaultHelpFactory implements IHelpFactory {
3530 public Help create(CommandSpec commandSpec, Help.ColorScheme colorScheme) {
3531 return new Help(commandSpec, colorScheme);
3532 }
3533 }
3534
3535 /**
3536 * Factory for instantiating classes that are registered declaratively with annotation attributes, like
3537 * {@link Command#subcommands()}, {@link Option#converter()}, {@link Parameters#converter()} and {@link Command#versionProvider()}.
3538 * <p>The default factory implementation simply creates a new instance of the specified class when {@link #create(Class)} is invoked.
3539 * </p><p>
3540 * You may provide a custom implementation of this interface.
3541 * For example, a custom factory implementation could delegate to a dependency injection container that provides the requested instance.
3542 * </p>
3543 * @see picocli.CommandLine#CommandLine(Object, IFactory)
3544 * @see #call(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
3545 * @see #run(Class, IFactory, PrintStream, PrintStream, Help.Ansi, String...)
3546 * @since 2.2 */
3547 public interface IFactory {
3548 /**
3549 * Returns an instance of the specified class.
3550 * @param cls the class of the object to return
3551 * @param <K> the type of the object to return
3552 * @return the instance
3553 * @throws Exception an exception detailing what went wrong when creating or obtaining the instance
3554 */
3555 <K> K create(Class<K> cls) throws Exception;
3556 }
3557 /** Returns a default {@link IFactory} implementation. Package-protected for testing purposes. */
3558 static IFactory defaultFactory() { return new DefaultFactory(); }
3559 private static class DefaultFactory implements IFactory {
3560 public <T> T create(Class<T> cls) throws Exception {
3561 if (cls.isInterface() && Collection.class.isAssignableFrom(cls)) {
3562 if (List.class.isAssignableFrom(cls)) {
3563 return cls.cast(new ArrayList<Object>());
3564 } else if (SortedSet.class.isAssignableFrom(cls)) {
3565 return cls.cast(new TreeSet<Object>());
3566 } else if (Set.class.isAssignableFrom(cls)) {
3567 return cls.cast(new LinkedHashSet<Object>());
3568 } else if (Queue.class.isAssignableFrom(cls)) {
3569 return cls.cast(new LinkedList<Object>()); // ArrayDeque is only available since 1.6
3570 }
3571 return cls.cast(new ArrayList<Object>());
3572 }
3573 if (Map.class.isAssignableFrom(cls)) {
3574 try { // if it is an implementation class, instantiate it
3575 return cls.cast(cls.getDeclaredConstructor().newInstance());
3576 } catch (Exception ignored) { }
3577 return cls.cast(new LinkedHashMap<Object, Object>());
3578 }
3579 try {
3580 return cls.newInstance();
3581 } catch (Exception ex) {
3582 Constructor<T> constructor = cls.getDeclaredConstructor();
3583 constructor.setAccessible(true);
3584 return constructor.newInstance();
3585 }
3586 }
3587 private static ITypeConverter<?>[] createConverter(IFactory factory, Class<? extends ITypeConverter<?>>[] classes) {
3588 ITypeConverter<?>[] result = new ITypeConverter<?>[classes.length];
3589 for (int i = 0; i < classes.length; i++) { result[i] = create(factory, classes[i]); }
3590 return result;
3591 }
3592 static IVersionProvider createVersionProvider(IFactory factory, Class<? extends IVersionProvider> cls) {
3593 return create(factory, cls);
3594 }
3595 static IDefaultValueProvider createDefaultValueProvider(IFactory factory, Class<? extends IDefaultValueProvider> cls) {
3596 return create(factory, cls);
3597 }
3598 static Iterable<String> createCompletionCandidates(IFactory factory, Class<? extends Iterable<String>> cls) {
3599 return create(factory, cls);
3600 }
3601 static <T> T create(IFactory factory, Class<T> cls) {
3602 try { return factory.create(cls); }
3603 catch (Exception ex) { throw new InitializationException("Could not instantiate " + cls + ": " + ex, ex); }
3604 }
3605 }
3606 /** Describes the number of parameters required and accepted by an option or a positional parameter.
3607 * @since 0.9.7
3608 */
3609 public static class Range implements Comparable<Range> {
3610 /** Required number of parameters for an option or positional parameter. */
3611 public final int min;
3612 /** Maximum accepted number of parameters for an option or positional parameter. */
3613 public final int max;
3614 public final boolean isVariable;
3615 private final boolean isUnspecified;
3616 private final String originalValue;
3617
3618 /** Constructs a new Range object with the specified parameters.
3619 * @param min minimum number of required parameters
3620 * @param max maximum number of allowed parameters (or Integer.MAX_VALUE if variable)
3621 * @param variable {@code true} if any number or parameters is allowed, {@code false} otherwise
3622 * @param unspecified {@code true} if no arity was specified on the option/parameter (value is based on type)
3623 * @param originalValue the original value that was specified on the option or parameter
3624 */
3625 public Range(int min, int max, boolean variable, boolean unspecified, String originalValue) {
3626 if (min < 0 || max < 0) { throw new InitializationException("Invalid negative range (min=" + min + ", max=" + max + ")"); }
3627 if (min > max) { throw new InitializationException("Invalid range (min=" + min + ", max=" + max + ")"); }
3628 this.min = min;
3629 this.max = max;
3630 this.isVariable = variable;
3631 this.isUnspecified = unspecified;
3632 this.originalValue = originalValue;
3633 }
3634 /** Returns a new {@code Range} based on the {@link Option#arity()} annotation on the specified field,
3635 * or the field type's default arity if no arity was specified.
3636 * @param field the field whose Option annotation to inspect
3637 * @return a new {@code Range} based on the Option arity annotation on the specified field */
3638 public static Range optionArity(Field field) { return optionArity(new TypedMember(field)); }
3639 private static Range optionArity(IAnnotatedElement member) {
3640 return member.isAnnotationPresent(Option.class)
3641 ? adjustForType(Range.valueOf(member.getAnnotation(Option.class).arity()), member)
3642 : new Range(0, 0, false, true, "0");
3643 }
3644 /** Returns a new {@code Range} based on the {@link Parameters#arity()} annotation on the specified field,
3645 * or the field type's default arity if no arity was specified.
3646 * @param field the field whose Parameters annotation to inspect
3647 * @return a new {@code Range} based on the Parameters arity annotation on the specified field */
3648 public static Range parameterArity(Field field) { return parameterArity(new TypedMember(field)); }
3649 private static Range parameterArity(IAnnotatedElement member) {
3650 if (member.isAnnotationPresent(Parameters.class)) {
3651 return adjustForType(Range.valueOf(member.getAnnotation(Parameters.class).arity()), member);
3652 } else {
3653 return member.isMethodParameter()
3654 ? adjustForType(Range.valueOf(""), member)
3655 : new Range(0, 0, false, true, "0");
3656 }
3657 }
3658 /** Returns a new {@code Range} based on the {@link Parameters#index()} annotation on the specified field.
3659 * @param field the field whose Parameters annotation to inspect
3660 * @return a new {@code Range} based on the Parameters index annotation on the specified field */
3661 public static Range parameterIndex(Field field) { return parameterIndex(new TypedMember(field)); }
3662 private static Range parameterIndex(IAnnotatedElement member) {
3663 if (member.isAnnotationPresent(Parameters.class)) {
3664 Range result = Range.valueOf(member.getAnnotation(Parameters.class).index());
3665 if (!result.isUnspecified) { return result; }
3666 }
3667 if (member.isMethodParameter()) {
3668 int min = member.getMethodParamPosition();
3669 int max = member.isMultiValue() ? Integer.MAX_VALUE : min;
3670 return new Range(min, max, member.isMultiValue(), false, "");
3671 }
3672 return Range.valueOf("*"); // the default
3673 }
3674 static Range adjustForType(Range result, IAnnotatedElement member) {
3675 return result.isUnspecified ? defaultArity(member) : result;
3676 }
3677 /** Returns the default arity {@code Range}: for {@link Option options} this is 0 for booleans and 1 for
3678 * other types, for {@link Parameters parameters} booleans have arity 0, arrays or Collections have
3679 * arity "0..*", and other types have arity 1.
3680 * @param field the field whose default arity to return
3681 * @return a new {@code Range} indicating the default arity of the specified field
3682 * @since 2.0 */
3683 public static Range defaultArity(Field field) { return defaultArity(new TypedMember(field)); }
3684 private static Range defaultArity(IAnnotatedElement member) {
3685 ITypeInfo info = member.getTypeInfo();
3686 if (member.isAnnotationPresent(Option.class)) {
3687 boolean zeroArgs = info.isBoolean() || (info.isMultiValue() && info.getAuxiliaryTypeInfos().get(0).isBoolean());
3688 return zeroArgs ? Range.valueOf("0").unspecified(true)
3689 : Range.valueOf("1").unspecified(true);
3690 }
3691 if (info.isMultiValue()) {
3692 return Range.valueOf("0..1").unspecified(true);
3693 }
3694 return Range.valueOf("1").unspecified(true);// for single-valued fields (incl. boolean positional parameters)
3695 }
3696 /** Returns the default arity {@code Range} for {@link Option options}: booleans have arity 0, other types have arity 1.
3697 * @param type the type whose default arity to return
3698 * @return a new {@code Range} indicating the default arity of the specified type
3699 * @deprecated use {@link #defaultArity(Field)} instead */
3700 @Deprecated public static Range defaultArity(Class<?> type) {
3701 return isBoolean(type) ? Range.valueOf("0").unspecified(true) : Range.valueOf("1").unspecified(true);
3702 }
3703 private int size() { return 1 + max - min; }
3704 static Range parameterCapacity(IAnnotatedElement member) {
3705 Range arity = parameterArity(member);
3706 if (!member.isMultiValue()) { return arity; }
3707 Range index = parameterIndex(member);
3708 return parameterCapacity(arity, index);
3709 }
3710 private static Range parameterCapacity(Range arity, Range index) {
3711 if (arity.max == 0) { return arity; }
3712 if (index.size() == 1) { return arity; }
3713 if (index.isVariable) { return Range.valueOf(arity.min + "..*"); }
3714 if (arity.size() == 1) { return Range.valueOf(arity.min * index.size() + ""); }
3715 if (arity.isVariable) { return Range.valueOf(arity.min * index.size() + "..*"); }
3716 return Range.valueOf(arity.min * index.size() + ".." + arity.max * index.size());
3717 }
3718
3719 /** Leniently parses the specified String as an {@code Range} value and return the result. A range string can
3720 * be a fixed integer value or a range of the form {@code MIN_VALUE + ".." + MAX_VALUE}. If the
3721 * {@code MIN_VALUE} string is not numeric, the minimum is zero. If the {@code MAX_VALUE} is not numeric, the
3722 * range is taken to be variable and the maximum is {@code Integer.MAX_VALUE}.
3723 * @param range the value range string to parse
3724 * @return a new {@code Range} value */
3725 public static Range valueOf(String range) {
3726 range = range.trim();
3727 boolean unspecified = range.length() == 0 || range.startsWith(".."); // || range.endsWith("..");
3728 int min = -1, max = -1;
3729 boolean variable = false;
3730 int dots = -1;
3731 if ((dots = range.indexOf("..")) >= 0) {
3732 min = parseInt(range.substring(0, dots), 0);
3733 max = parseInt(range.substring(dots + 2), Integer.MAX_VALUE);
3734 variable = max == Integer.MAX_VALUE;
3735 } else {
3736 max = parseInt(range, Integer.MAX_VALUE);
3737 variable = max == Integer.MAX_VALUE;
3738 min = variable ? 0 : max;
3739 }
3740 Range result = new Range(min, max, variable, unspecified, range);
3741 return result;
3742 }
3743 private static int parseInt(String str, int defaultValue) {
3744 try {
3745 return Integer.parseInt(str);
3746 } catch (Exception ex) {
3747 return defaultValue;
3748 }
3749 }
3750 /** Returns a new Range object with the {@code min} value replaced by the specified value.
3751 * The {@code max} of the returned Range is guaranteed not to be less than the new {@code min} value.
3752 * @param newMin the {@code min} value of the returned Range object
3753 * @return a new Range object with the specified {@code min} value */
3754 public Range min(int newMin) { return new Range(newMin, Math.max(newMin, max), isVariable, isUnspecified, originalValue); }
3755
3756 /** Returns a new Range object with the {@code max} value replaced by the specified value.
3757 * The {@code min} of the returned Range is guaranteed not to be greater than the new {@code max} value.
3758 * @param newMax the {@code max} value of the returned Range object
3759 * @return a new Range object with the specified {@code max} value */
3760 public Range max(int newMax) { return new Range(Math.min(min, newMax), newMax, isVariable, isUnspecified, originalValue); }
3761
3762 /** Returns a new Range object with the {@code isUnspecified} value replaced by the specified value.
3763 * @param unspecified the {@code unspecified} value of the returned Range object
3764 * @return a new Range object with the specified {@code unspecified} value */
3765 public Range unspecified(boolean unspecified) { return new Range(min, max, isVariable, unspecified, originalValue); }
3766 /** Returns {@code true} if this Range is a default value, {@code false} if the user specified this value.
3767 * @since 4.0 */
3768 public boolean isUnspecified() { return isUnspecified; }
3769
3770 /**
3771 * Returns {@code true} if this Range includes the specified value, {@code false} otherwise.
3772 * @param value the value to check
3773 * @return {@code true} if the specified value is not less than the minimum and not greater than the maximum of this Range
3774 */
3775 public boolean contains(int value) { return min <= value && max >= value; }
3776
3777 public boolean equals(Object object) {
3778 if (!(object instanceof Range)) { return false; }
3779 Range other = (Range) object;
3780 return other.max == this.max && other.min == this.min && other.isVariable == this.isVariable;
3781 }
3782 public int hashCode() {
3783 return ((17 * 37 + max) * 37 + min) * 37 + (isVariable ? 1 : 0);
3784 }
3785 public String toString() {
3786 return min == max ? String.valueOf(min) : min + ".." + (isVariable ? "*" : max);
3787 }
3788 public int compareTo(Range other) {
3789 int result = min - other.min;
3790 return (result == 0) ? max - other.max : result;
3791 }
3792
3793 boolean overlaps(Range index) {
3794 return contains(index.min) || contains(index.max) || index.contains(min) || index.contains(max);
3795 }
3796 }
3797 private static void validatePositionalParameters(List<PositionalParamSpec> positionalParametersFields) {
3798 int min = 0;
3799 for (PositionalParamSpec positional : positionalParametersFields) {
3800 Range index = positional.index();
3801 if (index.min > min) {
3802 throw new ParameterIndexGapException("Command definition should have a positional parameter with index=" + min +
3803 ". Nearest positional parameter '" + positional.paramLabel() + "' has index=" + index.min);
3804 }
3805 min = Math.max(min, index.max);
3806 min = min == Integer.MAX_VALUE ? min : min + 1;
3807 }
3808 }
3809 @SuppressWarnings("unchecked") private static Stack<String> copy(Stack<String> stack) { return (Stack<String>) stack.clone(); }
3810 private static <T> Stack<T> reverse(Stack<T> stack) {
3811 Collections.reverse(stack);
3812 return stack;
3813 }
3814 private static <T> List<T> reverseList(List<T> list) {
3815 Collections.reverse(list);
3816 return list;
3817 }
3818
3819 /** This class provides a namespace for classes and interfaces that model concepts and attributes of command line interfaces in picocli.
3820 * @since 3.0 */
3821 public static final class Model {
3822 private Model() {}
3823
3824 /** The scope of a binding is the context where the current value should be gotten from or set to.
3825 * For a field, the scope is the object whose field value to get/set. For a method binding, it is the
3826 * object on which the method should be invoked.
3827 * <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>
3828 * @since 4.0
3829 */
3830 public interface IScope extends IGetter, ISetter {}
3831
3832 /** Customizable getter for obtaining the current value of an option or positional parameter.
3833 * When an option or positional parameter is matched on the command line, its getter or setter is invoked to capture the value.
3834 * For example, an option can be bound to a field or a method, and when the option is matched on the command line, the
3835 * field's value is set or the method is invoked with the option parameter value.
3836 * @since 3.0 */
3837 public static interface IGetter {
3838 /** Returns the current value of the binding. For multi-value options and positional parameters,
3839 * this method returns an array, collection or map to add values to.
3840 * @throws PicocliException if a problem occurred while obtaining the current value
3841 * @throws Exception internally, picocli call sites will catch any exceptions thrown from here and rethrow them wrapped in a PicocliException */
3842 <T> T get() throws Exception;
3843 }
3844 /** Customizable setter for modifying the value of an option or positional parameter.
3845 * When an option or positional parameter is matched on the command line, its setter is invoked to capture the value.
3846 * For example, an option can be bound to a field or a method, and when the option is matched on the command line, the
3847 * field's value is set or the method is invoked with the option parameter value.
3848 * @since 3.0 */
3849 public static interface ISetter {
3850 /** Sets the new value of the option or positional parameter.
3851 *
3852 * @param value the new value of the option or positional parameter
3853 * @param <T> type of the value
3854 * @return the previous value of the binding (if supported by this binding)
3855 * @throws PicocliException if a problem occurred while setting the new value
3856 * @throws Exception internally, picocli call sites will catch any exceptions thrown from here and rethrow them wrapped in a PicocliException */
3857 <T> T set(T value) throws Exception;
3858 }
3859
3860 /** The {@code CommandSpec} class models a command specification, including the options, positional parameters and subcommands
3861 * supported by the command, as well as attributes for the version help message and the usage help message of the command.
3862 * <p>
3863 * Picocli views a command line application as a hierarchy of commands: there is a top-level command (usually the Java
3864 * class with the {@code main} method) with optionally a set of command line options, positional parameters and subcommands.
3865 * Subcommands themselves can have options, positional parameters and nested sub-subcommands to any level of depth.
3866 * </p><p>
3867 * The object model has a corresponding hierarchy of {@code CommandSpec} objects, each with a set of {@link OptionSpec},
3868 * {@link PositionalParamSpec} and {@linkplain CommandLine subcommands} associated with it.
3869 * This object model is used by the picocli command line interpreter and help message generator.
3870 * </p><p>Picocli can construct a {@code CommandSpec} automatically from classes with {@link Command @Command}, {@link Option @Option} and
3871 * {@link Parameters @Parameters} annotations. Alternatively a {@code CommandSpec} can be constructed programmatically.
3872 * </p>
3873 * @since 3.0 */
3874 public static class CommandSpec {
3875 /** Constant String holding the default program name: {@code "<main class>" }. */
3876 static final String DEFAULT_COMMAND_NAME = "<main class>";
3877
3878 /** Constant Boolean holding the default setting for whether this is a help command: <code>{@value}</code>.*/
3879 static final Boolean DEFAULT_IS_HELP_COMMAND = Boolean.FALSE;
3880
3881 /** Constant Boolean holding the default setting for whether method commands should be added as subcommands: <code>{@value}</code>.*/
3882 static final Boolean DEFAULT_IS_ADD_METHOD_SUBCOMMANDS = Boolean.TRUE;
3883
3884 private final Map<String, CommandLine> commands = new LinkedHashMap<String, CommandLine>();
3885 private final Map<String, OptionSpec> optionsByNameMap = new LinkedHashMap<String, OptionSpec>();
3886 private final Map<Character, OptionSpec> posixOptionsByKeyMap = new LinkedHashMap<Character, OptionSpec>();
3887 private final Map<String, CommandSpec> mixins = new LinkedHashMap<String, CommandSpec>();
3888 private final List<ArgSpec> requiredArgs = new ArrayList<ArgSpec>();
3889 private final List<ArgSpec> args = new ArrayList<ArgSpec>();
3890 private final List<OptionSpec> options = new ArrayList<OptionSpec>();
3891 private final List<PositionalParamSpec> positionalParameters = new ArrayList<PositionalParamSpec>();
3892 private final List<UnmatchedArgsBinding> unmatchedArgs = new ArrayList<UnmatchedArgsBinding>();
3893 private final List<ArgGroupSpec> groups = new ArrayList<ArgGroupSpec>();
3894 private final ParserSpec parser = new ParserSpec();
3895 private final UsageMessageSpec usageMessage = new UsageMessageSpec();
3896
3897 private final Object userObject;
3898 private CommandLine commandLine;
3899 private CommandSpec parent;
3900 private Boolean isAddMethodSubcommands;
3901
3902 private String name;
3903 private Set<String> aliases = new LinkedHashSet<String>();
3904 private Boolean isHelpCommand;
3905 private IVersionProvider versionProvider;
3906 private IDefaultValueProvider defaultValueProvider;
3907 private String[] version;
3908 private String toString;
3909
3910 private CommandSpec(Object userObject) { this.userObject = userObject; }
3911
3912 /** Creates and returns a new {@code CommandSpec} without any associated user object. */
3913 public static CommandSpec create() { return wrapWithoutInspection(null); }
3914
3915 /** Creates and returns a new {@code CommandSpec} with the specified associated user object.
3916 * The specified user object is <em>not</em> inspected for annotations.
3917 * @param userObject the associated user object. May be any object, may be {@code null}.
3918 */
3919 public static CommandSpec wrapWithoutInspection(Object userObject) { return new CommandSpec(userObject); }
3920
3921 /** Creates and returns a new {@code CommandSpec} initialized from the specified associated user object. The specified
3922 * user object must have at least one {@link Command}, {@link Option} or {@link Parameters} annotation.
3923 * @param userObject the user object annotated with {@link Command}, {@link Option} and/or {@link Parameters} annotations.
3924 * @throws InitializationException if the specified object has no picocli annotations or has invalid annotations
3925 */
3926 public static CommandSpec forAnnotatedObject(Object userObject) { return forAnnotatedObject(userObject, new DefaultFactory()); }
3927
3928 /** Creates and returns a new {@code CommandSpec} initialized from the specified associated user object. The specified
3929 * user object must have at least one {@link Command}, {@link Option} or {@link Parameters} annotation.
3930 * @param userObject the user object annotated with {@link Command}, {@link Option} and/or {@link Parameters} annotations.
3931 * @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
3932 * @throws InitializationException if the specified object has no picocli annotations or has invalid annotations
3933 */
3934 public static CommandSpec forAnnotatedObject(Object userObject, IFactory factory) { return CommandReflection.extractCommandSpec(userObject, factory, true); }
3935
3936 /** Creates and returns a new {@code CommandSpec} initialized from the specified associated user object. If the specified
3937 * user object has no {@link Command}, {@link Option} or {@link Parameters} annotations, an empty {@code CommandSpec} is returned.
3938 * @param userObject the user object annotated with {@link Command}, {@link Option} and/or {@link Parameters} annotations.
3939 * @throws InitializationException if the specified object has invalid annotations
3940 */
3941 public static CommandSpec forAnnotatedObjectLenient(Object userObject) { return forAnnotatedObjectLenient(userObject, new DefaultFactory()); }
3942
3943 /** Creates and returns a new {@code CommandSpec} initialized from the specified associated user object. If the specified
3944 * user object has no {@link Command}, {@link Option} or {@link Parameters} annotations, an empty {@code CommandSpec} is returned.
3945 * @param userObject the user object annotated with {@link Command}, {@link Option} and/or {@link Parameters} annotations.
3946 * @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
3947 * @throws InitializationException if the specified object has invalid annotations
3948 */
3949 public static CommandSpec forAnnotatedObjectLenient(Object userObject, IFactory factory) { return CommandReflection.extractCommandSpec(userObject, factory, false); }
3950
3951 /** Ensures all attributes of this {@code CommandSpec} have a valid value; throws an {@link InitializationException} if this cannot be achieved. */
3952 void validate() {
3953 Collections.sort(positionalParameters, new PositionalParametersSorter());
3954 validatePositionalParameters(positionalParameters);
3955 List<String> wrongUsageHelpAttr = new ArrayList<String>();
3956 List<String> wrongVersionHelpAttr = new ArrayList<String>();
3957 List<String> usageHelpAttr = new ArrayList<String>();
3958 List<String> versionHelpAttr = new ArrayList<String>();
3959 for (OptionSpec option : options()) {
3960 if (option.usageHelp()) {
3961 usageHelpAttr.add(option.longestName());
3962 if (!isBoolean(option.type())) { wrongUsageHelpAttr.add(option.longestName()); }
3963 }
3964 if (option.versionHelp()) {
3965 versionHelpAttr.add(option.longestName());
3966 if (!isBoolean(option.type())) { wrongVersionHelpAttr.add(option.longestName()); }
3967 }
3968 }
3969 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.";
3970 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";
3971 if (!wrongUsageHelpAttr.isEmpty()) {
3972 throw new InitializationException(String.format(wrongType, wrongUsageHelpAttr, "usageHelp", "--help", "usage help message"));
3973 }
3974 if (!wrongVersionHelpAttr.isEmpty()) {
3975 throw new InitializationException(String.format(wrongType, wrongVersionHelpAttr, "versionHelp", "--version", "version information"));
3976 }
3977 if (usageHelpAttr.size() > 1) { new Tracer().warn(multiple, usageHelpAttr, "usageHelp", "--help", "usage help message"); }
3978 if (versionHelpAttr.size() > 1) { new Tracer().warn(multiple, versionHelpAttr, "versionHelp", "--version", "version information"); }
3979 }
3980
3981 /** Returns the user object associated with this command.
3982 * @see CommandLine#getCommand() */
3983 public Object userObject() { return userObject; }
3984
3985 /** Returns the CommandLine constructed with this {@code CommandSpec} model. */
3986 public CommandLine commandLine() { return commandLine;}
3987
3988 /** Sets the CommandLine constructed with this {@code CommandSpec} model. */
3989 protected CommandSpec commandLine(CommandLine commandLine) {
3990 this.commandLine = commandLine;
3991 for (CommandSpec mixedInSpec : mixins.values()) {
3992 mixedInSpec.commandLine(commandLine);
3993 }
3994 for (CommandLine sub : commands.values()) {
3995 sub.getCommandSpec().parent(this);
3996 }
3997 return this;
3998 }
3999
4000 /** Returns the parser specification for this command. */
4001 public ParserSpec parser() { return parser; }
4002 /** Initializes the parser specification for this command from the specified settings and returns this commandSpec.*/
4003 public CommandSpec parser(ParserSpec settings) { parser.initFrom(settings); return this; }
4004
4005 /** Returns the usage help message specification for this command. */
4006 public UsageMessageSpec usageMessage() { return usageMessage; }
4007 /** Initializes the usageMessage specification for this command from the specified settings and returns this commandSpec.*/
4008 public CommandSpec usageMessage(UsageMessageSpec settings) { usageMessage.initFrom(settings, this); return this; }
4009
4010 /** Returns the resource bundle base name for this command.
4011 * @return the resource bundle base name from the {@linkplain UsageMessageSpec#messages()}
4012 * @since 4.0 */
4013 public String resourceBundleBaseName() { return Messages.resourceBundleBaseName(usageMessage.messages()); }
4014 /** Initializes the resource bundle for this command: sets the {@link UsageMessageSpec#messages(Messages) UsageMessageSpec.messages} to
4015 * a {@link Messages Messages} object created from this command spec and the specified bundle, and then sets the
4016 * {@link ArgSpec#messages(Messages) ArgSpec.messages} of all options and positional parameters in this command
4017 * to the same {@code Messages} instance. Subcommands are not modified.
4018 * <p>This method is preferable to {@link #resourceBundle(ResourceBundle)} for pre-Java 8</p>
4019 * @param resourceBundleBaseName the base name of the ResourceBundle to set, may be {@code null}
4020 * @return this commandSpec
4021 * @see #addSubcommand(String, CommandLine)
4022 * @since 4.0 */
4023 public CommandSpec resourceBundleBaseName(String resourceBundleBaseName) {
4024 ResourceBundle bundle = empty(resourceBundleBaseName) ? null : ResourceBundle.getBundle(resourceBundleBaseName);
4025 setBundle(resourceBundleBaseName, bundle);
4026 return this;
4027 }
4028 /** Returns the resource bundle for this command.
4029 * @return the resource bundle from the {@linkplain UsageMessageSpec#messages()}
4030 * @since 3.6 */
4031 public ResourceBundle resourceBundle() { return Messages.resourceBundle(usageMessage.messages()); }
4032 /** Initializes the resource bundle for this command: sets the {@link UsageMessageSpec#messages(Messages) UsageMessageSpec.messages} to
4033 * a {@link Messages Messages} object created from this command spec and the specified bundle, and then sets the
4034 * {@link ArgSpec#messages(Messages) ArgSpec.messages} of all options and positional parameters in this command
4035 * to the same {@code Messages} instance. Subcommands are not modified.
4036 * @param bundle the ResourceBundle to set, may be {@code null}
4037 * @return this commandSpec
4038 * @see #addSubcommand(String, CommandLine)
4039 * @since 3.6 */
4040 public CommandSpec resourceBundle(ResourceBundle bundle) {
4041 setBundle(Messages.extractName(bundle), bundle);
4042 return this;
4043 }
4044 private void setBundle(String bundleBaseName, ResourceBundle bundle) {
4045 usageMessage().messages(new Messages(this, bundleBaseName, bundle));
4046 updateArgSpecMessages();
4047 }
4048 private void updateArgSpecMessages() {
4049 for (OptionSpec opt : options()) { opt.messages(usageMessage().messages()); }
4050 for (PositionalParamSpec pos : positionalParameters()) { pos.messages(usageMessage().messages()); }
4051 for (ArgGroupSpec group : argGroups()) { group.messages(usageMessage().messages()); }
4052 }
4053
4054 /** Returns a read-only view of the subcommand map. */
4055 public Map<String, CommandLine> subcommands() { return Collections.unmodifiableMap(commands); }
4056
4057 /** Adds the specified subcommand with the specified name.
4058 * If the specified subcommand does not have a ResourceBundle set, it is initialized to the ResourceBundle of this command spec.
4059 * @param name subcommand name - when this String is encountered in the command line arguments the subcommand is invoked
4060 * @param subcommand describes the subcommand to envoke when the name is encountered on the command line
4061 * @return this {@code CommandSpec} object for method chaining */
4062 public CommandSpec addSubcommand(String name, CommandSpec subcommand) {
4063 return addSubcommand(name, new CommandLine(subcommand));
4064 }
4065
4066 /** Adds the specified subcommand with the specified name.
4067 * If the specified subcommand does not have a ResourceBundle set, it is initialized to the ResourceBundle of this command spec.
4068 * @param name subcommand name - when this String is encountered in the command line arguments the subcommand is invoked
4069 * @param subCommandLine the subcommand to envoke when the name is encountered on the command line
4070 * @return this {@code CommandSpec} object for method chaining */
4071 public CommandSpec addSubcommand(String name, CommandLine subCommandLine) {
4072 Tracer t = new Tracer();
4073 if (t.isDebug()) {t.debug("Adding subcommand '%s' to '%s'%n", name, this.qualifiedName());}
4074 CommandLine previous = commands.put(name, subCommandLine);
4075 if (previous != null && previous != subCommandLine) { throw new InitializationException("Another subcommand named '" + name + "' already exists for command '" + this.name() + "'"); }
4076 CommandSpec subSpec = subCommandLine.getCommandSpec();
4077 if (subSpec.name == null) { subSpec.name(name); }
4078 subSpec.parent(this);
4079 for (String alias : subSpec.aliases()) {
4080 if (t.isDebug()) {t.debug("Adding alias '%s' for subcommand '%s' to '%s'%n", alias, name, this.qualifiedName());}
4081 previous = commands.put(alias, subCommandLine);
4082 if (previous != null && previous != subCommandLine) { throw new InitializationException("Alias '" + alias + "' for subcommand '" + name + "' is already used by another subcommand of '" + this.name() + "'"); }
4083 }
4084 subSpec.initCommandHierarchyWithResourceBundle(resourceBundleBaseName(), resourceBundle());
4085 return this;
4086 }
4087 private void initCommandHierarchyWithResourceBundle(String bundleBaseName, ResourceBundle rb) {
4088 if (resourceBundle() == null) {
4089 setBundle(bundleBaseName, rb);
4090 }
4091 for (CommandLine sub : commands.values()) { // percolate down the hierarchy
4092 sub.getCommandSpec().initCommandHierarchyWithResourceBundle(bundleBaseName, rb);
4093 }
4094 }
4095
4096 /** Returns whether method commands should be added as subcommands. Used by the annotation processor.
4097 * @since 4.0 */
4098 public boolean isAddMethodSubcommands() { return (isAddMethodSubcommands == null) ? DEFAULT_IS_ADD_METHOD_SUBCOMMANDS : isAddMethodSubcommands; }
4099 /** Sets whether method commands should be added as subcommands. Used by the annotation processor.
4100 * @since 4.0 */
4101 public CommandSpec setAddMethodSubcommands(Boolean addMethodSubcommands) { isAddMethodSubcommands = addMethodSubcommands; return this; }
4102
4103 /** Reflects on the class of the {@linkplain #userObject() user object} and registers any command methods
4104 * (class methods annotated with {@code @Command}) as subcommands.
4105 *
4106 * @return this {@link CommandSpec} object for method chaining
4107 * @see #addMethodSubcommands(IFactory)
4108 * @see #addSubcommand(String, CommandLine)
4109 * @since 3.6.0
4110 */
4111 public CommandSpec addMethodSubcommands() { return addMethodSubcommands(new DefaultFactory()); }
4112
4113 /** Reflects on the class of the {@linkplain #userObject() user object} and registers any command methods
4114 * (class methods annotated with {@code @Command}) as subcommands.
4115 * @param factory the factory used to create instances of subcommands, converters, etc., that are registered declaratively with annotation attributes
4116 * @return this {@link CommandSpec} object for method chaining
4117 * @see #addSubcommand(String, CommandLine)
4118 * @since 3.7.0
4119 */
4120 public CommandSpec addMethodSubcommands(IFactory factory) {
4121 if (userObject() instanceof Method) {
4122 throw new InitializationException("Cannot discover subcommand methods of this Command Method: " + userObject());
4123 }
4124 for (CommandLine sub : createMethodSubcommands(userObject().getClass(), factory)) {
4125 addSubcommand(sub.getCommandName(), sub);
4126 }
4127 isAddMethodSubcommands = true;
4128 return this;
4129 }
4130 static List<CommandLine> createMethodSubcommands(Class<?> cls, IFactory factory) {
4131 List<CommandLine> result = new ArrayList<CommandLine>();
4132 for (Method method : getCommandMethods(cls, null)) {
4133 result.add(new CommandLine(method, factory));
4134 }
4135 return result;
4136 }
4137
4138 /** Returns the parent command of this subcommand, or {@code null} if this is a top-level command. */
4139 public CommandSpec parent() { return parent; }
4140
4141 /** Sets the parent command of this subcommand.
4142 * @return this CommandSpec for method chaining */
4143 public CommandSpec parent(CommandSpec parent) { this.parent = parent; return this; }
4144
4145 /** Adds the specified option spec or positional parameter spec to the list of configured arguments to expect.
4146 * @param arg the option spec or positional parameter spec to add
4147 * @return this CommandSpec for method chaining */
4148 public CommandSpec add(ArgSpec arg) { return arg.isOption() ? addOption((OptionSpec) arg) : addPositional((PositionalParamSpec) arg); }
4149
4150 /** Adds the specified option spec to the list of configured arguments to expect.
4151 * The option's {@linkplain OptionSpec#description()} may now return Strings from this
4152 * CommandSpec's {@linkplain UsageMessageSpec#messages() messages}.
4153 * The option parameter's {@linkplain OptionSpec#defaultValueString()} may
4154 * now return Strings from this CommandSpec's {@link CommandSpec#defaultValueProvider()} IDefaultValueProvider}.
4155 * @param option the option spec to add
4156 * @return this CommandSpec for method chaining
4157 * @throws DuplicateOptionAnnotationsException if any of the names of the specified option is the same as the name of another option */
4158 public CommandSpec addOption(OptionSpec option) {
4159 for (String name : option.names()) { // cannot be null or empty
4160 OptionSpec existing = optionsByNameMap.put(name, option);
4161 if (existing != null) { /* was: && !existing.equals(option)) {*/ // since 4.0 ArgGroups: an option cannot be in multiple groups
4162 throw DuplicateOptionAnnotationsException.create(name, option, existing);
4163 }
4164 if (name.length() == 2 && name.startsWith("-")) { posixOptionsByKeyMap.put(name.charAt(1), option); }
4165 }
4166 options.add(option);
4167 return addArg(option);
4168 }
4169 /** Adds the specified positional parameter spec to the list of configured arguments to expect.
4170 * The positional parameter's {@linkplain PositionalParamSpec#description()} may
4171 * now return Strings from this CommandSpec's {@linkplain UsageMessageSpec#messages() messages}.
4172 * The positional parameter's {@linkplain PositionalParamSpec#defaultValueString()} may
4173 * now return Strings from this CommandSpec's {@link CommandSpec#defaultValueProvider()} IDefaultValueProvider}.
4174 * @param positional the positional parameter spec to add
4175 * @return this CommandSpec for method chaining */
4176 public CommandSpec addPositional(PositionalParamSpec positional) {
4177 positionalParameters.add(positional);
4178 return addArg(positional);
4179 }
4180 private CommandSpec addArg(ArgSpec arg) {
4181 args.add(arg);
4182 if (arg.required() && arg.group() == null) { requiredArgs.add(arg); }
4183 arg.messages(usageMessage().messages());
4184 arg.commandSpec = this;
4185 return this;
4186 }
4187
4188 /** Adds the specified {@linkplain ArgGroupSpec argument group} to the groups in this command.
4189 * @param group the group spec to add
4190 * @return this CommandSpec for method chaining
4191 * @throws InitializationException if the specified group or one of its {@linkplain ArgGroupSpec#parentGroup() ancestors} has already been added
4192 * @since 4.0 */
4193 public CommandSpec addArgGroup(ArgGroupSpec group) {
4194 Assert.notNull(group, "group");
4195 if (group.parentGroup() != null) {
4196 throw new InitializationException("Groups that are part of another group should not be added to a command. Add only the top-level group.");
4197 }
4198 check(group, flatten(groups, new HashSet<ArgGroupSpec>()));
4199 this.groups.add(group);
4200 addGroupArgsToCommand(group, new HashMap<String, ArgGroupSpec>());
4201 return this;
4202 }
4203 private void addGroupArgsToCommand(ArgGroupSpec group, Map<String, ArgGroupSpec> added) {
4204 for (ArgSpec arg : group.args()) {
4205 if (arg.isOption()) {
4206 for (String name : ((OptionSpec) arg).names()) {
4207 if (added.containsKey(name)) {
4208 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)).");
4209 }
4210 }
4211 for (String name : ((OptionSpec) arg).names()) { added.put(name, group); }
4212 }
4213 add(arg);
4214 }
4215 for (ArgGroupSpec sub : group.subgroups()) { addGroupArgsToCommand(sub, added); }
4216 }
4217 private Set<ArgGroupSpec> flatten(Collection<ArgGroupSpec> groups, Set<ArgGroupSpec> result) {
4218 for (ArgGroupSpec group : groups) { flatten(group, result); } return result;
4219 }
4220 private Set<ArgGroupSpec> flatten(ArgGroupSpec group, Set<ArgGroupSpec> result) {
4221 result.add(group);
4222 for (ArgGroupSpec sub : group.subgroups()) { flatten(sub, result); }
4223 return result;
4224 }
4225 private void check(ArgGroupSpec group, Set<ArgGroupSpec> existing) {
4226 if (existing.contains(group)) {
4227 throw new InitializationException("The specified group " + group.synopsis() + " has already been added to the " + qualifiedName() + " command.");
4228 }
4229 for (ArgGroupSpec sub : group.subgroups()) { check(sub, existing); }
4230 }
4231
4232 /** Adds the specified mixin {@code CommandSpec} object to the map of mixins for this command.
4233 * @param name the name that can be used to later retrieve the mixin
4234 * @param mixin the mixin whose options and positional parameters and other attributes to add to this command
4235 * @return this CommandSpec for method chaining */
4236 public CommandSpec addMixin(String name, CommandSpec mixin) {
4237 mixins.put(name, mixin);
4238
4239 parser.initSeparator(mixin.parser.separator());
4240 initName(mixin.name());
4241 initVersion(mixin.version());
4242 initHelpCommand(mixin.helpCommand());
4243 initVersionProvider(mixin.versionProvider());
4244 initDefaultValueProvider(mixin.defaultValueProvider());
4245 usageMessage.initFromMixin(mixin.usageMessage, this);
4246
4247 for (Map.Entry<String, CommandLine> entry : mixin.subcommands().entrySet()) {
4248 addSubcommand(entry.getKey(), entry.getValue());
4249 }
4250 for (OptionSpec optionSpec : mixin.options()) { addOption(optionSpec); }
4251 for (PositionalParamSpec paramSpec : mixin.positionalParameters()) { addPositional(paramSpec); }
4252 return this;
4253 }
4254
4255 /** Adds the specified {@code UnmatchedArgsBinding} to the list of model objects to capture unmatched arguments for this command.
4256 * @param spec the unmatched arguments binding to capture unmatched arguments
4257 * @return this CommandSpec for method chaining */
4258 public CommandSpec addUnmatchedArgsBinding(UnmatchedArgsBinding spec) { unmatchedArgs.add(spec); parser().unmatchedArgumentsAllowed(true); return this; }
4259
4260 /** Returns a map of the mixin names to mixin {@code CommandSpec} objects configured for this command.
4261 * @return an immutable map of mixins added to this command. */
4262 public Map<String, CommandSpec> mixins() { return Collections.unmodifiableMap(mixins); }
4263
4264 /** Returns the list of options configured for this command.
4265 * @return an immutable list of options that this command recognizes. */
4266 public List<OptionSpec> options() { return Collections.unmodifiableList(options); }
4267
4268 /** Returns the list of positional parameters configured for this command.
4269 * @return an immutable list of positional parameters that this command recognizes. */
4270 public List<PositionalParamSpec> positionalParameters() { return Collections.unmodifiableList(positionalParameters); }
4271
4272 /** Returns the {@linkplain ArgGroupSpec argument groups} in this command.
4273 * @return an immutable list of groups of options and positional parameters in this command
4274 * @since 4.0 */
4275 public List<ArgGroupSpec> argGroups() { return Collections.unmodifiableList(groups); }
4276
4277 /** Returns a map of the option names to option spec objects configured for this command.
4278 * @return an immutable map of options that this command recognizes. */
4279 public Map<String, OptionSpec> optionsMap() { return Collections.unmodifiableMap(optionsByNameMap); }
4280
4281 /** Returns a map of the short (single character) option names to option spec objects configured for this command.
4282 * @return an immutable map of options that this command recognizes. */
4283 public Map<Character, OptionSpec> posixOptionsMap() { return Collections.unmodifiableMap(posixOptionsByKeyMap); }
4284
4285 /** Returns the list of required options and positional parameters configured for this command.
4286 * This does not include options and positional parameters that are part of a {@linkplain ArgGroupSpec group}.
4287 * @return an immutable list of the required options and positional parameters for this command. */
4288 public List<ArgSpec> requiredArgs() { return Collections.unmodifiableList(requiredArgs); }
4289
4290 /** Returns the list of {@link UnmatchedArgsBinding UnmatchedArgumentsBindings} configured for this command;
4291 * each {@code UnmatchedArgsBinding} captures the arguments that could not be matched to any options or positional parameters. */
4292 public List<UnmatchedArgsBinding> unmatchedArgsBindings() { return Collections.unmodifiableList(unmatchedArgs); }
4293
4294 /** Returns name of this command. Used in the synopsis line of the help message.
4295 * {@link #DEFAULT_COMMAND_NAME} by default, initialized from {@link Command#name()} if defined.
4296 * @see #qualifiedName() */
4297 public String name() { return (name == null) ? DEFAULT_COMMAND_NAME : name; }
4298
4299 /** Returns the alias command names of this subcommand.
4300 * @since 3.1 */
4301 public String[] aliases() { return aliases.toArray(new String[0]); }
4302
4303 /** Returns all names of this command, including {@link #name()} and {@link #aliases()}.
4304 * @since 3.9 */
4305 public Set<String> names() {
4306 Set<String> result = new LinkedHashSet<String>();
4307 result.add(name());
4308 result.addAll(Arrays.asList(aliases()));
4309 return result;
4310 }
4311
4312 /** Returns the list of all options and positional parameters configured for this command.
4313 * @return an immutable list of all options and positional parameters for this command. */
4314 public List<ArgSpec> args() { return Collections.unmodifiableList(args); }
4315 Object[] argValues() {
4316 Map<Class<?>, CommandSpec> allMixins = null;
4317 int argsLength = args.size();
4318 int shift = 0;
4319 for (Map.Entry<String, CommandSpec> mixinEntry : mixins.entrySet()) {
4320 if (mixinEntry.getKey().equals(AutoHelpMixin.KEY)) {
4321 shift = 2;
4322 argsLength -= shift;
4323 continue;
4324 }
4325 CommandSpec mixin = mixinEntry.getValue();
4326 int mixinArgs = mixin.args.size();
4327 argsLength -= (mixinArgs - 1); // subtract 1 because that's the mixin
4328 if (allMixins == null) {
4329 allMixins = new IdentityHashMap<Class<?>, CommandSpec>(mixins.size());
4330 }
4331 allMixins.put(mixin.userObject.getClass(), mixin);
4332 }
4333
4334 Object[] values = new Object[argsLength];
4335 if (allMixins == null) {
4336 for (int i = 0; i < values.length; i++) { values[i] = args.get(i + shift).getValue(); }
4337 } else {
4338 int argIndex = shift;
4339 Class<?>[] methodParams = ((Method) userObject).getParameterTypes();
4340 for (int i = 0; i < methodParams.length; i++) {
4341 final Class<?> param = methodParams[i];
4342 CommandSpec mixin = allMixins.remove(param);
4343 if (mixin == null) {
4344 values[i] = args.get(argIndex++).getValue();
4345 } else {
4346 values[i] = mixin.userObject;
4347 argIndex += mixin.args.size();
4348 }
4349 }
4350 }
4351 return values;
4352 }
4353
4354 /** Returns the String to use as the program name in the synopsis line of the help message:
4355 * this command's {@link #name() name}, preceded by the qualified name of the parent command, if any, separated by a space.
4356 * @return {@link #DEFAULT_COMMAND_NAME} by default, initialized from {@link Command#name()} and the parent command if defined.
4357 * @since 3.0.1 */
4358 public String qualifiedName() { return qualifiedName(" "); }
4359 /** 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.
4360 * @return {@link #DEFAULT_COMMAND_NAME} by default, initialized from {@link Command#name()} and the parent command if any.
4361 * @param separator the string to put between the names of the commands in the hierarchy
4362 * @since 3.6 */
4363 public String qualifiedName(String separator) {
4364 String result = name();
4365 if (parent() != null) { result = parent().qualifiedName(separator) + separator + result; }
4366 return result;
4367 }
4368
4369 /** Returns version information for this command, to print to the console when the user specifies an
4370 * {@linkplain OptionSpec#versionHelp() option} to request version help. This is not part of the usage help message.
4371 * @return the version strings generated by the {@link #versionProvider() version provider} if one is set, otherwise the {@linkplain #version(String...) version literals}*/
4372 public String[] version() {
4373 if (versionProvider != null) {
4374 try {
4375 return versionProvider.getVersion();
4376 } catch (Exception ex) {
4377 String msg = "Could not get version info from " + versionProvider + ": " + ex;
4378 throw new ExecutionException(this.commandLine, msg, ex);
4379 }
4380 }
4381 return version == null ? UsageMessageSpec.DEFAULT_MULTI_LINE : version;
4382 }
4383
4384 /** Returns the version provider for this command, to generate the {@link #version()} strings.
4385 * @return the version provider or {@code null} if the version strings should be returned from the {@linkplain #version(String...) version literals}.*/
4386 public IVersionProvider versionProvider() { return versionProvider; }
4387
4388 /** Returns whether this subcommand is a help command, and required options and positional
4389 * parameters of the parent command should not be validated.
4390 * @return {@code true} if this subcommand is a help command and picocli should not check for missing required
4391 * options and positional parameters on the parent command
4392 * @see Command#helpCommand() */
4393 public boolean helpCommand() { return (isHelpCommand == null) ? DEFAULT_IS_HELP_COMMAND : isHelpCommand; }
4394
4395 /** Returns {@code true} if the standard help options have been mixed in with this command, {@code false} otherwise. */
4396 public boolean mixinStandardHelpOptions() { return mixins.containsKey(AutoHelpMixin.KEY); }
4397
4398 /** Returns a string representation of this command, used in error messages and trace messages. */
4399 public String toString() { return toString; }
4400
4401 /** Sets the String to use as the program name in the synopsis line of the help message.
4402 * @return this CommandSpec for method chaining */
4403 public CommandSpec name(String name) { this.name = name; return this; }
4404
4405 /** Sets the alternative names by which this subcommand is recognized on the command line.
4406 * @return this CommandSpec for method chaining
4407 * @since 3.1 */
4408 public CommandSpec aliases(String... aliases) {
4409 this.aliases = new LinkedHashSet<String>(Arrays.asList(aliases == null ? new String[0] : aliases));
4410 return this;
4411 }
4412
4413 /** Returns the default value provider for this command.
4414 * @return the default value provider or {@code null}
4415 * @since 3.6 */
4416 public IDefaultValueProvider defaultValueProvider() { return defaultValueProvider; }
4417
4418 /** Sets default value provider for this command.
4419 * @param defaultValueProvider the default value provider to use, or {@code null}.
4420 * @return this CommandSpec for method chaining
4421 * @since 3.6 */
4422 public CommandSpec defaultValueProvider(IDefaultValueProvider defaultValueProvider) { this.defaultValueProvider = defaultValueProvider; return this; }
4423
4424 /** Sets version information literals for this command, to print to the console when the user specifies an
4425 * {@linkplain OptionSpec#versionHelp() option} to request version help. Only used if no {@link #versionProvider() versionProvider} is set.
4426 * @return this CommandSpec for method chaining */
4427 public CommandSpec version(String... version) { this.version = version; return this; }
4428
4429 /** Sets version provider for this command, to generate the {@link #version()} strings.
4430 * @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.
4431 * @return this CommandSpec for method chaining */
4432 public CommandSpec versionProvider(IVersionProvider versionProvider) { this.versionProvider = versionProvider; return this; }
4433
4434 /** Sets whether this is a help command and required parameter checking should be suspended.
4435 * @return this CommandSpec for method chaining
4436 * @see Command#helpCommand() */
4437 public CommandSpec helpCommand(boolean newValue) {isHelpCommand = newValue; return this;}
4438
4439 /** Sets whether the standard help options should be mixed in with this command.
4440 * @return this CommandSpec for method chaining
4441 * @see Command#mixinStandardHelpOptions() */
4442 public CommandSpec mixinStandardHelpOptions(boolean newValue) {
4443 if (newValue) {
4444 CommandSpec mixin = CommandSpec.forAnnotatedObject(new AutoHelpMixin(), new DefaultFactory());
4445 addMixin(AutoHelpMixin.KEY, mixin);
4446 } else {
4447 CommandSpec helpMixin = mixins.remove(AutoHelpMixin.KEY);
4448 if (helpMixin != null) {
4449 options.removeAll(helpMixin.options);
4450 for (OptionSpec option : helpMixin.options()) {
4451 for (String name : option.names) {
4452 optionsByNameMap.remove(name);
4453 if (name.length() == 2 && name.startsWith("-")) { posixOptionsByKeyMap.remove(name.charAt(1)); }
4454 }
4455 }
4456 }
4457 }
4458 return this;
4459 }
4460
4461 /** Sets the string representation of this command, used in error messages and trace messages.
4462 * @param newValue the string representation
4463 * @return this CommandSpec for method chaining */
4464 public CommandSpec withToString(String newValue) { this.toString = newValue; return this; }
4465
4466 /**
4467 * Updates the following attributes from the specified {@code @Command} annotation:
4468 * aliases, {@link ParserSpec#separator() parser separator}, command name, version, help command,
4469 * version provider, default provider and {@link UsageMessageSpec usage message spec}.
4470 * @param cmd the {@code @Command} annotation to get attribute values from
4471 * @param factory factory used to instantiate classes
4472 * @since 3.7
4473 */
4474 public void updateCommandAttributes(Command cmd, IFactory factory) {
4475 aliases(cmd.aliases());
4476 parser().updateSeparator(cmd.separator());
4477 updateName(cmd.name());
4478 updateVersion(cmd.version());
4479 updateHelpCommand(cmd.helpCommand());
4480 updateAddMethodSubcommands(cmd.addMethodSubcommands());
4481 usageMessage().updateFromCommand(cmd, this);
4482
4483 if (factory != null) {
4484 updateVersionProvider(cmd.versionProvider(), factory);
4485 initDefaultValueProvider(cmd.defaultValueProvider(), factory);
4486 }
4487 }
4488
4489 void initName(String value) { if (initializable(name, value, DEFAULT_COMMAND_NAME)) {name = value;} }
4490 void initHelpCommand(boolean value) { if (initializable(isHelpCommand, value, DEFAULT_IS_HELP_COMMAND)) {isHelpCommand = value;} }
4491 void initVersion(String[] value) { if (initializable(version, value, UsageMessageSpec.DEFAULT_MULTI_LINE)) {version = value.clone();} }
4492 void initVersionProvider(IVersionProvider value) { if (versionProvider == null) { versionProvider = value; } }
4493 void initDefaultValueProvider(IDefaultValueProvider value) { if (defaultValueProvider == null) { defaultValueProvider = value; } }
4494 void initDefaultValueProvider(Class<? extends IDefaultValueProvider> value, IFactory factory) {
4495 if (initializable(defaultValueProvider, value, NoDefaultProvider.class)) { defaultValueProvider = (DefaultFactory.createDefaultValueProvider(factory, value)); }
4496 }
4497 void updateName(String value) { if (isNonDefault(value, DEFAULT_COMMAND_NAME)) {name = value;} }
4498 void updateHelpCommand(boolean value) { if (isNonDefault(value, DEFAULT_IS_HELP_COMMAND)) {isHelpCommand = value;} }
4499 void updateAddMethodSubcommands(boolean value) { if (isNonDefault(value, DEFAULT_IS_ADD_METHOD_SUBCOMMANDS)) {isAddMethodSubcommands = value;} }
4500 void updateVersion(String[] value) { if (isNonDefault(value, UsageMessageSpec.DEFAULT_MULTI_LINE)) {version = value.clone();} }
4501 void updateVersionProvider(Class<? extends IVersionProvider> value, IFactory factory) {
4502 if (isNonDefault(value, NoVersionProvider.class)) { versionProvider = (DefaultFactory.createVersionProvider(factory, value)); }
4503 }
4504
4505 /** Returns the option with the specified short name, or {@code null} if no option with that name is defined for this command. */
4506 public OptionSpec findOption(char shortName) { return findOption(shortName, options()); }
4507 /** Returns the option with the specified name, or {@code null} if no option with that name is defined for this command.
4508 * @param name used to search the options. May include option name prefix characters or not. */
4509 public OptionSpec findOption(String name) { return findOption(name, options()); }
4510
4511 static OptionSpec findOption(char shortName, Iterable<OptionSpec> options) {
4512 for (OptionSpec option : options) {
4513 for (String name : option.names()) {
4514 if (name.length() == 2 && name.charAt(0) == '-' && name.charAt(1) == shortName) { return option; }
4515 if (name.length() == 1 && name.charAt(0) == shortName) { return option; }
4516 }
4517 }
4518 return null;
4519 }
4520 static OptionSpec findOption(String name, List<OptionSpec> options) {
4521 for (OptionSpec option : options) {
4522 for (String prefixed : option.names()) {
4523 if (prefixed.equals(name) || stripPrefix(prefixed).equals(name)) { return option; }
4524 }
4525 }
4526 return null;
4527 }
4528 static String stripPrefix(String prefixed) {
4529 for (int i = 0; i < prefixed.length(); i++) {
4530 if (Character.isJavaIdentifierPart(prefixed.charAt(i))) { return prefixed.substring(i); }
4531 }
4532 return prefixed;
4533 }
4534 List<String> findOptionNamesWithPrefix(String prefix) {
4535 List<String> result = new ArrayList<String>();
4536 for (OptionSpec option : options()) {
4537 for (String name : option.names()) {
4538 if (stripPrefix(name).startsWith(prefix)) { result.add(name); }
4539 }
4540 }
4541 return result;
4542 }
4543
4544 boolean resemblesOption(String arg, Tracer tracer) {
4545 if (parser().unmatchedOptionsArePositionalParams()) {
4546 if (tracer != null && tracer.isDebug()) {tracer.debug("Parser is configured to treat all unmatched options as positional parameter%n", arg);}
4547 return false;
4548 }
4549 if (arg.length() == 1) {
4550 if (tracer != null && tracer.isDebug()) {tracer.debug("Single-character arguments that don't match known options are considered positional parameters%n", arg);}
4551 return false;
4552 }
4553 if (options().isEmpty()) {
4554 boolean result = arg.startsWith("-");
4555 if (tracer != null && tracer.isDebug()) {tracer.debug("'%s' %s an option%n", arg, (result ? "resembles" : "doesn't resemble"));}
4556 return result;
4557 }
4558 int count = 0;
4559 for (String optionName : optionsMap().keySet()) {
4560 for (int i = 0; i < arg.length(); i++) {
4561 if (optionName.length() > i && arg.charAt(i) == optionName.charAt(i)) { count++; } else { break; }
4562 }
4563 }
4564 boolean result = count > 0 && count * 10 >= optionsMap().size() * 9; // at least one prefix char in common with 9 out of 10 options
4565 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());}
4566 return result;
4567 }
4568 }
4569 private static boolean initializable(Object current, Object candidate, Object defaultValue) {
4570 return current == null && isNonDefault(candidate, defaultValue);
4571 }
4572 private static boolean initializable(Object current, Object[] candidate, Object[] defaultValue) {
4573 return current == null && isNonDefault(candidate, defaultValue);
4574 }
4575 private static boolean isNonDefault(Object candidate, Object defaultValue) {
4576 return !Assert.notNull(defaultValue, "defaultValue").equals(candidate);
4577 }
4578 private static boolean isNonDefault(Object[] candidate, Object[] defaultValue) {
4579 return !Arrays.equals(Assert.notNull(defaultValue, "defaultValue"), candidate);
4580 }
4581 /** Models the usage help message specification and can be used to customize the usage help message.
4582 * <p>
4583 * This class provides two ways to customize the usage help message:
4584 * </p>
4585 * <ul>
4586 * <li>Change the text of the predefined sections (this may also be done declaratively using the annotations)</li>
4587 * <li>Add custom sections, or remove or re-order predefined sections</li>
4588 * </ul>
4589 * <p>
4590 * The pre-defined sections have getters and setters that return a String (or array of Strings). For example:
4591 * {@link #description()} and {@link #description(String...)} or {@link #header()} and {@link #header(String...)}.
4592 * </p><p>
4593 * Changing the section order, or adding custom sections can be accomplished with {@link #sectionKeys(List)} and {@link #sectionMap(Map)}.
4594 * This gives complete freedom on how a usage help message section is rendered, but it also means that the {@linkplain IHelpSectionRenderer section renderer}
4595 * is responsible for all aspects of rendering the section, including layout and emitting ANSI escape codes.
4596 * 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.
4597 * </p><p>
4598 * The usage help message is created more or less like this:
4599 * </p>
4600 * <pre>
4601 * // CommandLine.usage(...) or CommandLine.getUsageMessage(...)
4602 * Help.ColorScheme colorScheme = Help.defaultColorScheme(Help.Ansi.AUTO);
4603 * Help help = getHelpFactory().create(getCommandSpec(), colorScheme)
4604 * StringBuilder result = new StringBuilder();
4605 * for (String key : getHelpSectionKeys()) {
4606 * IHelpSectionRenderer renderer = getHelpSectionMap().get(key);
4607 * if (renderer != null) { result.append(renderer.render(help)); }
4608 * }
4609 * // return or print result
4610 * </pre>
4611 * <p>
4612 * Where the default {@linkplain #sectionMap() help section map} is constructed like this:</p>
4613 * <pre>{@code
4614 * // The default section renderers delegate to methods in Help for their implementation
4615 * // (using Java 8 lambda notation for brevity):
4616 * Map<String, IHelpSectionRenderer> sectionMap = new HashMap<>();
4617 * sectionMap.put(SECTION_KEY_HEADER_HEADING, help -> help.headerHeading());
4618 * sectionMap.put(SECTION_KEY_HEADER, help -> help.header());
4619 * sectionMap.put(SECTION_KEY_SYNOPSIS_HEADING, help -> help.synopsisHeading()); //e.g. Usage:
4620 * sectionMap.put(SECTION_KEY_SYNOPSIS, help -> help.synopsis(help.synopsisHeadingLength())); //e.g. <cmd> [OPTIONS] <subcmd> [COMMAND-OPTIONS] [ARGUMENTS]
4621 * sectionMap.put(SECTION_KEY_DESCRIPTION_HEADING, help -> help.descriptionHeading()); //e.g. %nDescription:%n%n
4622 * sectionMap.put(SECTION_KEY_DESCRIPTION, help -> help.description()); //e.g. {"Converts foos to bars.", "Use options to control conversion mode."}
4623 * sectionMap.put(SECTION_KEY_PARAMETER_LIST_HEADING, help -> help.parameterListHeading()); //e.g. %nPositional parameters:%n%n
4624 * sectionMap.put(SECTION_KEY_PARAMETER_LIST, help -> help.parameterList()); //e.g. [FILE...] the files to convert
4625 * sectionMap.put(SECTION_KEY_OPTION_LIST_HEADING, help -> help.optionListHeading()); //e.g. %nOptions:%n%n
4626 * sectionMap.put(SECTION_KEY_OPTION_LIST, help -> help.optionList()); //e.g. -h, --help displays this help and exits
4627 * sectionMap.put(SECTION_KEY_COMMAND_LIST_HEADING, help -> help.commandListHeading()); //e.g. %nCommands:%n%n
4628 * sectionMap.put(SECTION_KEY_COMMAND_LIST, help -> help.commandList()); //e.g. add adds the frup to the frooble
4629 * sectionMap.put(SECTION_KEY_FOOTER_HEADING, help -> help.footerHeading());
4630 * sectionMap.put(SECTION_KEY_FOOTER, help -> help.footer());
4631 * }</pre>
4632 *
4633 * @since 3.0 */
4634 public static class UsageMessageSpec {
4635
4636 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Header Heading section.
4637 * The default renderer for this section calls {@link Help#headerHeading(Object...)}.
4638 * @since 3.9 */
4639 public static final String SECTION_KEY_HEADER_HEADING = "headerHeading";
4640
4641 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Header section.
4642 * The default renderer for this section calls {@link Help#header(Object...)}.
4643 * @since 3.9 */
4644 public static final String SECTION_KEY_HEADER = "header";
4645
4646 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Synopsis Heading section.
4647 * The default renderer for this section calls {@link Help#synopsisHeading(Object...)}.
4648 * @since 3.9 */
4649 public static final String SECTION_KEY_SYNOPSIS_HEADING = "synopsisHeading";
4650
4651 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Synopsis section.
4652 * The default renderer for this section calls {@link Help#synopsis(int)}.
4653 * @since 3.9 */
4654 public static final String SECTION_KEY_SYNOPSIS = "synopsis";
4655
4656 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Description Heading section.
4657 * The default renderer for this section calls {@link Help#descriptionHeading(Object...)}.
4658 * @since 3.9 */
4659 public static final String SECTION_KEY_DESCRIPTION_HEADING = "descriptionHeading";
4660
4661 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Description section.
4662 * The default renderer for this section calls {@link Help#description(Object...)}.
4663 * @since 3.9 */
4664 public static final String SECTION_KEY_DESCRIPTION = "description";
4665
4666 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Parameter List Heading section.
4667 * The default renderer for this section calls {@link Help#parameterListHeading(Object...)}.
4668 * @since 3.9 */
4669 public static final String SECTION_KEY_PARAMETER_LIST_HEADING = "parameterListHeading";
4670
4671 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Parameter List section.
4672 * The default renderer for this section calls {@link Help#parameterList()}.
4673 * @since 3.9 */
4674 public static final String SECTION_KEY_PARAMETER_LIST = "parameterList";
4675
4676 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Option List Heading section.
4677 * The default renderer for this section calls {@link Help#optionListHeading(Object...)}.
4678 * @since 3.9 */
4679 public static final String SECTION_KEY_OPTION_LIST_HEADING = "optionListHeading";
4680
4681 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Option List section.
4682 * The default renderer for this section calls {@link Help#optionList()}.
4683 * @since 3.9 */
4684 public static final String SECTION_KEY_OPTION_LIST = "optionList";
4685
4686 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Subcommand List Heading section.
4687 * The default renderer for this section calls {@link Help#commandListHeading(Object...)}.
4688 * @since 3.9 */
4689 public static final String SECTION_KEY_COMMAND_LIST_HEADING = "commandListHeading";
4690
4691 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Subcommand List section.
4692 * The default renderer for this section calls {@link Help#commandList()}.
4693 * @since 3.9 */
4694 public static final String SECTION_KEY_COMMAND_LIST = "commandList";
4695
4696 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Footer Heading section.
4697 * The default renderer for this section calls {@link Help#footerHeading(Object...)}.
4698 * @since 3.9 */
4699 public static final String SECTION_KEY_FOOTER_HEADING = "footerHeading";
4700
4701 /** {@linkplain #sectionKeys() Section key} to {@linkplain #sectionMap() control} the {@linkplain IHelpSectionRenderer section renderer} for the Footer section.
4702 * The default renderer for this section calls {@link Help#footer(Object...)}.
4703 * @since 3.9 */
4704 public static final String SECTION_KEY_FOOTER = "footer";
4705
4706 /** Constant holding the default usage message width: <code>{@value}</code>. */
4707 public final static int DEFAULT_USAGE_WIDTH = 80;
4708 private final static int MINIMUM_USAGE_WIDTH = 55;
4709
4710 /** Constant String holding the default synopsis heading: <code>{@value}</code>. */
4711 static final String DEFAULT_SYNOPSIS_HEADING = "Usage: ";
4712
4713 /** Constant String holding the default command list heading: <code>{@value}</code>. */
4714 static final String DEFAULT_COMMAND_LIST_HEADING = "Commands:%n";
4715
4716 /** Constant String holding the default string that separates options from option parameters: {@code ' '} ({@value}). */
4717 static final char DEFAULT_REQUIRED_OPTION_MARKER = ' ';
4718
4719 /** Constant Boolean holding the default setting for whether to abbreviate the synopsis: <code>{@value}</code>.*/
4720 static final Boolean DEFAULT_ABBREVIATE_SYNOPSIS = Boolean.FALSE;
4721
4722 /** Constant Boolean holding the default setting for whether to sort the options alphabetically: <code>{@value}</code>.*/
4723 static final Boolean DEFAULT_SORT_OPTIONS = Boolean.TRUE;
4724
4725 /** Constant Boolean holding the default setting for whether to show default values in the usage help message: <code>{@value}</code>.*/
4726 static final Boolean DEFAULT_SHOW_DEFAULT_VALUES = Boolean.FALSE;
4727
4728 /** Constant Boolean holding the default setting for whether this command should be listed in the usage help of the parent command: <code>{@value}</code>.*/
4729 static final Boolean DEFAULT_HIDDEN = Boolean.FALSE;
4730
4731 static final String DEFAULT_SINGLE_VALUE = "";
4732 static final String[] DEFAULT_MULTI_LINE = {};
4733
4734 private IHelpFactory helpFactory;
4735
4736 private List<String> sectionKeys = Collections.unmodifiableList(Arrays.asList(
4737 SECTION_KEY_HEADER_HEADING,
4738 SECTION_KEY_HEADER,
4739 SECTION_KEY_SYNOPSIS_HEADING,
4740 SECTION_KEY_SYNOPSIS,
4741 SECTION_KEY_DESCRIPTION_HEADING,
4742 SECTION_KEY_DESCRIPTION,
4743 SECTION_KEY_PARAMETER_LIST_HEADING,
4744 SECTION_KEY_PARAMETER_LIST,
4745 SECTION_KEY_OPTION_LIST_HEADING,
4746 SECTION_KEY_OPTION_LIST,
4747 SECTION_KEY_COMMAND_LIST_HEADING,
4748 SECTION_KEY_COMMAND_LIST,
4749 SECTION_KEY_FOOTER_HEADING,
4750 SECTION_KEY_FOOTER));
4751
4752 private Map<String, IHelpSectionRenderer> helpSectionRendererMap = createHelpSectionRendererMap();
4753
4754 private String[] description;
4755 private String[] customSynopsis;
4756 private String[] header;
4757 private String[] footer;
4758 private Boolean abbreviateSynopsis;
4759 private Boolean sortOptions;
4760 private Boolean showDefaultValues;
4761 private Boolean hidden;
4762 private Character requiredOptionMarker;
4763 private String headerHeading;
4764 private String synopsisHeading;
4765 private String descriptionHeading;
4766 private String parameterListHeading;
4767 private String optionListHeading;
4768 private String commandListHeading;
4769 private String footerHeading;
4770 private int width = DEFAULT_USAGE_WIDTH;
4771
4772 private Messages messages;
4773
4774 /**
4775 * Sets the maximum usage help message width to the specified value. Longer values are wrapped.
4776 * @param newValue the new maximum usage help message width. Must be 55 or greater.
4777 * @return this {@code UsageMessageSpec} for method chaining
4778 * @throws IllegalArgumentException if the specified width is less than 55
4779 */
4780 public UsageMessageSpec width(int newValue) {
4781 if (newValue < MINIMUM_USAGE_WIDTH) {
4782 throw new InitializationException("Invalid usage message width " + newValue + ". Minimum value is " + MINIMUM_USAGE_WIDTH);
4783 }
4784 width = newValue; return this;
4785 }
4786
4787 private static int getSysPropertyWidthOrDefault(int defaultWidth) {
4788 String userValue = System.getProperty("picocli.usage.width");
4789 if (userValue == null) { return defaultWidth; }
4790 try {
4791 int width = Integer.parseInt(userValue);
4792 if (width < MINIMUM_USAGE_WIDTH) {
4793 new Tracer().warn("Invalid picocli.usage.width value %d. Using minimum usage width %d.%n", width, MINIMUM_USAGE_WIDTH);
4794 return MINIMUM_USAGE_WIDTH;
4795 }
4796 return width;
4797 } catch (NumberFormatException ex) {
4798 new Tracer().warn("Invalid picocli.usage.width value '%s'. Using usage width %d.%n", userValue, defaultWidth);
4799 return defaultWidth;
4800 }
4801 }
4802
4803 /** Returns the maximum usage help message width. Derived from system property {@code "picocli.usage.width"}
4804 * if set, otherwise returns the value set via the {@link #width(int)} method, or if not set, the {@linkplain #DEFAULT_USAGE_WIDTH default width}.
4805 * @return the maximum usage help message width. Never returns less than 55. */
4806 public int width() { return getSysPropertyWidthOrDefault(width); }
4807
4808 /** Returns the help section renderers for the predefined section keys. see: {@link #sectionKeys()} */
4809 private Map<String, IHelpSectionRenderer> createHelpSectionRendererMap() {
4810 Map<String, IHelpSectionRenderer> result = new HashMap<String, IHelpSectionRenderer>();
4811
4812 result.put(SECTION_KEY_HEADER_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.headerHeading(); } });
4813 result.put(SECTION_KEY_HEADER, new IHelpSectionRenderer() { public String render(Help help) { return help.header(); } });
4814 //e.g. Usage:
4815 result.put(SECTION_KEY_SYNOPSIS_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.synopsisHeading(); } });
4816 //e.g. <main class> [OPTIONS] <command> [COMMAND-OPTIONS] [ARGUMENTS]
4817 result.put(SECTION_KEY_SYNOPSIS, new IHelpSectionRenderer() { public String render(Help help) { return help.synopsis(help.synopsisHeadingLength()); } });
4818 //e.g. %nDescription:%n%n
4819 result.put(SECTION_KEY_DESCRIPTION_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.descriptionHeading(); } });
4820 //e.g. {"Converts foos to bars.", "Use options to control conversion mode."}
4821 result.put(SECTION_KEY_DESCRIPTION, new IHelpSectionRenderer() { public String render(Help help) { return help.description(); } });
4822 //e.g. %nPositional parameters:%n%n
4823 result.put(SECTION_KEY_PARAMETER_LIST_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.parameterListHeading(); } });
4824 //e.g. [FILE...] the files to convert
4825 result.put(SECTION_KEY_PARAMETER_LIST, new IHelpSectionRenderer() { public String render(Help help) { return help.parameterList(); } });
4826 //e.g. %nOptions:%n%n
4827 result.put(SECTION_KEY_OPTION_LIST_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.optionListHeading(); } });
4828 //e.g. -h, --help displays this help and exits
4829 result.put(SECTION_KEY_OPTION_LIST, new IHelpSectionRenderer() { public String render(Help help) { return help.optionList(); } });
4830 //e.g. %nCommands:%n%n
4831 result.put(SECTION_KEY_COMMAND_LIST_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.commandListHeading(); } });
4832 //e.g. add adds the frup to the frooble
4833 result.put(SECTION_KEY_COMMAND_LIST, new IHelpSectionRenderer() { public String render(Help help) { return help.commandList(); } });
4834 result.put(SECTION_KEY_FOOTER_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.footerHeading(); } });
4835 result.put(SECTION_KEY_FOOTER, new IHelpSectionRenderer() { public String render(Help help) { return help.footer(); } });
4836 return result;
4837 }
4838
4839 /**
4840 * Returns the section keys in the order that the usage help message should render the sections.
4841 * This ordering may be modified with the {@link #sectionKeys(List) sectionKeys setter}. The default keys are (in order):
4842 * <ol>
4843 * <li>{@link UsageMessageSpec#SECTION_KEY_HEADER_HEADING SECTION_KEY_HEADER_HEADING}</li>
4844 * <li>{@link UsageMessageSpec#SECTION_KEY_HEADER SECTION_KEY_HEADER}</li>
4845 * <li>{@link UsageMessageSpec#SECTION_KEY_SYNOPSIS_HEADING SECTION_KEY_SYNOPSIS_HEADING}</li>
4846 * <li>{@link UsageMessageSpec#SECTION_KEY_SYNOPSIS SECTION_KEY_SYNOPSIS}</li>
4847 * <li>{@link UsageMessageSpec#SECTION_KEY_DESCRIPTION_HEADING SECTION_KEY_DESCRIPTION_HEADING}</li>
4848 * <li>{@link UsageMessageSpec#SECTION_KEY_DESCRIPTION SECTION_KEY_DESCRIPTION}</li>
4849 * <li>{@link UsageMessageSpec#SECTION_KEY_PARAMETER_LIST_HEADING SECTION_KEY_PARAMETER_LIST_HEADING}</li>
4850 * <li>{@link UsageMessageSpec#SECTION_KEY_PARAMETER_LIST SECTION_KEY_PARAMETER_LIST}</li>
4851 * <li>{@link UsageMessageSpec#SECTION_KEY_OPTION_LIST_HEADING SECTION_KEY_OPTION_LIST_HEADING}</li>
4852 * <li>{@link UsageMessageSpec#SECTION_KEY_OPTION_LIST SECTION_KEY_OPTION_LIST}</li>
4853 * <li>{@link UsageMessageSpec#SECTION_KEY_COMMAND_LIST_HEADING SECTION_KEY_COMMAND_LIST_HEADING}</li>
4854 * <li>{@link UsageMessageSpec#SECTION_KEY_COMMAND_LIST SECTION_KEY_COMMAND_LIST}</li>
4855 * <li>{@link UsageMessageSpec#SECTION_KEY_FOOTER_HEADING SECTION_KEY_FOOTER_HEADING}</li>
4856 * <li>{@link UsageMessageSpec#SECTION_KEY_FOOTER SECTION_KEY_FOOTER}</li>
4857 * </ol>
4858 * @since 3.9
4859 */
4860 public List<String> sectionKeys() { return sectionKeys; }
4861
4862 /**
4863 * Sets the section keys in the order that the usage help message should render the sections.
4864 * @see #sectionKeys
4865 * @since 3.9
4866 */
4867 public UsageMessageSpec sectionKeys(List<String> keys) { sectionKeys = Collections.unmodifiableList(new ArrayList<String>(keys)); return this; }
4868
4869 /**
4870 * Returns the map of section keys and renderers used to construct the usage help message.
4871 * The usage help message can be customized by adding, replacing and removing section renderers from this map.
4872 * Sections can be reordered with the {@link #sectionKeys(List) sectionKeys setter}.
4873 * Sections that are either not in this map or not in the list returned by {@link #sectionKeys() sectionKeys} are omitted.
4874 * @see #sectionKeys
4875 * @since 3.9
4876 */
4877 public Map<String, IHelpSectionRenderer> sectionMap() { return helpSectionRendererMap; }
4878
4879 /**
4880 * Sets the map of section keys and renderers used to construct the usage help message to a copy of the specified map.
4881 * @param map the mapping of section keys to their renderers, must be non-{@code null}.
4882 * @return this UsageMessageSpec for method chaining
4883 * @see #sectionKeys
4884 * @see #setHelpSectionMap(Map)
4885 * @since 3.9
4886 */
4887 public UsageMessageSpec sectionMap(Map<String, IHelpSectionRenderer> map) { this.helpSectionRendererMap = new HashMap<String, IHelpSectionRenderer>(map); return this; }
4888
4889 /** Returns the {@code IHelpFactory} that is used to construct the usage help message.
4890 * @see #setHelpFactory(IHelpFactory)
4891 * @since 3.9
4892 */
4893 public IHelpFactory helpFactory() {
4894 if (helpFactory == null) {
4895 helpFactory = new DefaultHelpFactory();
4896 }
4897 return helpFactory;
4898 }
4899
4900 /** Sets a new {@code IHelpFactory} to customize the usage help message.
4901 * @param helpFactory the new help factory. Must be non-{@code null}.
4902 * @return this {@code UsageMessageSpec} object, to allow method chaining
4903 */
4904 public UsageMessageSpec helpFactory(IHelpFactory helpFactory) {
4905 this.helpFactory = Assert.notNull(helpFactory, "helpFactory");
4906 return this;
4907 }
4908
4909 private String str(String localized, String value, String defaultValue) {
4910 return localized != null ? localized : (value != null ? value : defaultValue);
4911 }
4912 private String[] arr(String[] localized, String[] value, String[] defaultValue) {
4913 return localized != null ? localized : (value != null ? value.clone() : defaultValue);
4914 }
4915 private String resourceStr(String key) { return messages == null ? null : messages.getString(key, null); }
4916 private String[] resourceArr(String key) { return messages == null ? null : messages.getStringArray(key, null); }
4917
4918 /** Returns the optional heading preceding the header section. Initialized from {@link Command#headerHeading()}, or null. */
4919 public String headerHeading() { return str(resourceStr("usage.headerHeading"), headerHeading, DEFAULT_SINGLE_VALUE); }
4920
4921 /** Returns the optional header lines displayed at the top of the help message. For subcommands, the first header line is
4922 * displayed in the list of commands. Values are initialized from {@link Command#header()}
4923 * if the {@code Command} annotation is present, otherwise this is an empty array and the help message has no
4924 * header. Applications may programmatically set this field to create a custom help message. */
4925 public String[] header() { return arr(resourceArr("usage.header"), header, DEFAULT_MULTI_LINE); }
4926
4927 /** Returns the optional heading preceding the synopsis. Initialized from {@link Command#synopsisHeading()}, {@code "Usage: "} by default. */
4928 public String synopsisHeading() { return str(resourceStr("usage.synopsisHeading"), synopsisHeading, DEFAULT_SYNOPSIS_HEADING); }
4929
4930 /** Returns whether the synopsis line(s) should show an abbreviated synopsis without detailed option names. */
4931 public boolean abbreviateSynopsis() { return (abbreviateSynopsis == null) ? DEFAULT_ABBREVIATE_SYNOPSIS : abbreviateSynopsis; }
4932
4933 /** Returns the optional custom synopsis lines to use instead of the auto-generated synopsis.
4934 * Initialized from {@link Command#customSynopsis()} if the {@code Command} annotation is present,
4935 * otherwise this is an empty array and the synopsis is generated.
4936 * Applications may programmatically set this field to create a custom help message. */
4937 public String[] customSynopsis() { return arr(resourceArr("usage.customSynopsis"), customSynopsis, DEFAULT_MULTI_LINE); }
4938
4939 /** Returns the optional heading preceding the description section. Initialized from {@link Command#descriptionHeading()}, or null. */
4940 public String descriptionHeading() { return str(resourceStr("usage.descriptionHeading"), descriptionHeading, DEFAULT_SINGLE_VALUE); }
4941
4942 /** Returns the optional text lines to use as the description of the help message, displayed between the synopsis and the
4943 * options list. Initialized from {@link Command#description()} if the {@code Command} annotation is present,
4944 * otherwise this is an empty array and the help message has no description.
4945 * Applications may programmatically set this field to create a custom help message. */
4946 public String[] description() { return arr(resourceArr("usage.description"), description, DEFAULT_MULTI_LINE); }
4947
4948 /** Returns the optional heading preceding the parameter list. Initialized from {@link Command#parameterListHeading()}, or null. */
4949 public String parameterListHeading() { return str(resourceStr("usage.parameterListHeading"), parameterListHeading, DEFAULT_SINGLE_VALUE); }
4950
4951 /** Returns the optional heading preceding the options list. Initialized from {@link Command#optionListHeading()}, or null. */
4952 public String optionListHeading() { return str(resourceStr("usage.optionListHeading"), optionListHeading, DEFAULT_SINGLE_VALUE); }
4953
4954 /** Returns whether the options list in the usage help message should be sorted alphabetically. */
4955 public boolean sortOptions() { return (sortOptions == null) ? DEFAULT_SORT_OPTIONS : sortOptions; }
4956
4957 /** Returns the character used to prefix required options in the options list. */
4958 public char requiredOptionMarker() { return (requiredOptionMarker == null) ? DEFAULT_REQUIRED_OPTION_MARKER : requiredOptionMarker; }
4959
4960 /** Returns whether the options list in the usage help message should show default values for all non-boolean options. */
4961 public boolean showDefaultValues() { return (showDefaultValues == null) ? DEFAULT_SHOW_DEFAULT_VALUES : showDefaultValues; }
4962
4963 /**
4964 * Returns whether this command should be hidden from the usage help message of the parent command.
4965 * @return {@code true} if this command should not appear in the usage help message of the parent command
4966 */
4967 public boolean hidden() { return (hidden == null) ? DEFAULT_HIDDEN : hidden; }
4968
4969 /** Returns the optional heading preceding the subcommand list. Initialized from {@link Command#commandListHeading()}. {@code "Commands:%n"} by default. */
4970 public String commandListHeading() { return str(resourceStr("usage.commandListHeading"), commandListHeading, DEFAULT_COMMAND_LIST_HEADING); }
4971
4972 /** Returns the optional heading preceding the footer section. Initialized from {@link Command#footerHeading()}, or null. */
4973 public String footerHeading() { return str(resourceStr("usage.footerHeading"), footerHeading, DEFAULT_SINGLE_VALUE); }
4974
4975 /** Returns the optional footer text lines displayed at the bottom of the help message. Initialized from
4976 * {@link Command#footer()} if the {@code Command} annotation is present, otherwise this is an empty array and
4977 * the help message has no footer.
4978 * Applications may programmatically set this field to create a custom help message. */
4979 public String[] footer() { return arr(resourceArr("usage.footer"), footer, DEFAULT_MULTI_LINE); }
4980
4981 /** Sets the heading preceding the header section. Initialized from {@link Command#headerHeading()}, or null.
4982 * @return this UsageMessageSpec for method chaining */
4983 public UsageMessageSpec headerHeading(String headerHeading) { this.headerHeading = headerHeading; return this; }
4984
4985 /** Sets the optional header lines displayed at the top of the help message. For subcommands, the first header line is
4986 * displayed in the list of commands.
4987 * @return this UsageMessageSpec for method chaining */
4988 public UsageMessageSpec header(String... header) { this.header = header; return this; }
4989
4990 /** Sets the optional heading preceding the synopsis.
4991 * @return this UsageMessageSpec for method chaining */
4992 public UsageMessageSpec synopsisHeading(String newValue) {synopsisHeading = newValue; return this;}
4993
4994 /** Sets whether the synopsis line(s) should show an abbreviated synopsis without detailed option names.
4995 * @return this UsageMessageSpec for method chaining */
4996 public UsageMessageSpec abbreviateSynopsis(boolean newValue) {abbreviateSynopsis = newValue; return this;}
4997
4998 /** Sets the optional custom synopsis lines to use instead of the auto-generated synopsis.
4999 * @return this UsageMessageSpec for method chaining */
5000 public UsageMessageSpec customSynopsis(String... customSynopsis) { this.customSynopsis = customSynopsis; return this; }
5001
5002 /** Sets the heading preceding the description section.
5003 * @return this UsageMessageSpec for method chaining */
5004 public UsageMessageSpec descriptionHeading(String newValue) {descriptionHeading = newValue; return this;}
5005
5006 /** Sets the optional text lines to use as the description of the help message, displayed between the synopsis and the
5007 * options list.
5008 * @return this UsageMessageSpec for method chaining */
5009 public UsageMessageSpec description(String... description) { this.description = description; return this; }
5010
5011 /** Sets the optional heading preceding the parameter list.
5012 * @return this UsageMessageSpec for method chaining */
5013 public UsageMessageSpec parameterListHeading(String newValue) {parameterListHeading = newValue; return this;}
5014
5015 /** Sets the heading preceding the options list.
5016 * @return this UsageMessageSpec for method chaining */
5017 public UsageMessageSpec optionListHeading(String newValue) {optionListHeading = newValue; return this;}
5018
5019 /** Sets whether the options list in the usage help message should be sorted alphabetically.
5020 * @return this UsageMessageSpec for method chaining */
5021 public UsageMessageSpec sortOptions(boolean newValue) {sortOptions = newValue; return this;}
5022
5023 /** Sets the character used to prefix required options in the options list.
5024 * @return this UsageMessageSpec for method chaining */
5025 public UsageMessageSpec requiredOptionMarker(char newValue) {requiredOptionMarker = newValue; return this;}
5026
5027 /** Sets whether the options list in the usage help message should show default values for all non-boolean options.
5028 * @return this UsageMessageSpec for method chaining */
5029 public UsageMessageSpec showDefaultValues(boolean newValue) {showDefaultValues = newValue; return this;}
5030
5031 /**
5032 * Set the hidden flag on this command to control whether to show or hide it in the help usage text of the parent command.
5033 * @param value enable or disable the hidden flag
5034 * @return this UsageMessageSpec for method chaining
5035 * @see Command#hidden() */
5036 public UsageMessageSpec hidden(boolean value) { hidden = value; return this; }
5037
5038 /** Sets the optional heading preceding the subcommand list.
5039 * @return this UsageMessageSpec for method chaining */
5040 public UsageMessageSpec commandListHeading(String newValue) {commandListHeading = newValue; return this;}
5041
5042 /** Sets the optional heading preceding the footer section.
5043 * @return this UsageMessageSpec for method chaining */
5044 public UsageMessageSpec footerHeading(String newValue) {footerHeading = newValue; return this;}
5045
5046 /** Sets the optional footer text lines displayed at the bottom of the help message.
5047 * @return this UsageMessageSpec for method chaining */
5048 public UsageMessageSpec footer(String... footer) { this.footer = footer; return this; }
5049 /** Returns the Messages for this usage help message specification, or {@code null}.
5050 * @return the Messages object that encapsulates this {@linkplain CommandSpec#resourceBundle() command's resource bundle}
5051 * @since 3.6 */
5052 public Messages messages() { return messages; }
5053 /** Sets the Messages for this usageMessage specification, and returns this UsageMessageSpec.
5054 * @param msgs the new Messages value that encapsulates this {@linkplain CommandSpec#resourceBundle() command's resource bundle}, may be {@code null}
5055 * @since 3.6 */
5056 public UsageMessageSpec messages(Messages msgs) { messages = msgs; return this; }
5057 void updateFromCommand(Command cmd, CommandSpec commandSpec) {
5058 if (isNonDefault(cmd.synopsisHeading(), DEFAULT_SYNOPSIS_HEADING)) {synopsisHeading = cmd.synopsisHeading();}
5059 if (isNonDefault(cmd.commandListHeading(), DEFAULT_COMMAND_LIST_HEADING)) {commandListHeading = cmd.commandListHeading();}
5060 if (isNonDefault(cmd.requiredOptionMarker(), DEFAULT_REQUIRED_OPTION_MARKER)) {requiredOptionMarker = cmd.requiredOptionMarker();}
5061 if (isNonDefault(cmd.abbreviateSynopsis(), DEFAULT_ABBREVIATE_SYNOPSIS)) {abbreviateSynopsis = cmd.abbreviateSynopsis();}
5062 if (isNonDefault(cmd.sortOptions(), DEFAULT_SORT_OPTIONS)) {sortOptions = cmd.sortOptions();}
5063 if (isNonDefault(cmd.showDefaultValues(), DEFAULT_SHOW_DEFAULT_VALUES)) {showDefaultValues = cmd.showDefaultValues();}
5064 if (isNonDefault(cmd.hidden(), DEFAULT_HIDDEN)) {hidden = cmd.hidden();}
5065 if (isNonDefault(cmd.customSynopsis(), DEFAULT_MULTI_LINE)) {customSynopsis = cmd.customSynopsis().clone();}
5066 if (isNonDefault(cmd.description(), DEFAULT_MULTI_LINE)) {description = cmd.description().clone();}
5067 if (isNonDefault(cmd.descriptionHeading(), DEFAULT_SINGLE_VALUE)) {descriptionHeading = cmd.descriptionHeading();}
5068 if (isNonDefault(cmd.header(), DEFAULT_MULTI_LINE)) {header = cmd.header().clone();}
5069 if (isNonDefault(cmd.headerHeading(), DEFAULT_SINGLE_VALUE)) {headerHeading = cmd.headerHeading();}
5070 if (isNonDefault(cmd.footer(), DEFAULT_MULTI_LINE)) {footer = cmd.footer().clone();}
5071 if (isNonDefault(cmd.footerHeading(), DEFAULT_SINGLE_VALUE)) {footerHeading = cmd.footerHeading();}
5072 if (isNonDefault(cmd.parameterListHeading(), DEFAULT_SINGLE_VALUE)) {parameterListHeading = cmd.parameterListHeading();}
5073 if (isNonDefault(cmd.optionListHeading(), DEFAULT_SINGLE_VALUE)) {optionListHeading = cmd.optionListHeading();}
5074 if (isNonDefault(cmd.usageHelpWidth(), DEFAULT_USAGE_WIDTH)) {width(cmd.usageHelpWidth());} // validate
5075
5076 if (!empty(cmd.resourceBundle())) { // else preserve superclass bundle
5077 messages(new Messages(commandSpec, cmd.resourceBundle()));
5078 }
5079 }
5080 void initFromMixin(UsageMessageSpec mixin, CommandSpec commandSpec) {
5081 if (initializable(synopsisHeading, mixin.synopsisHeading(), DEFAULT_SYNOPSIS_HEADING)) {synopsisHeading = mixin.synopsisHeading();}
5082 if (initializable(commandListHeading, mixin.commandListHeading(), DEFAULT_COMMAND_LIST_HEADING)) {commandListHeading = mixin.commandListHeading();}
5083 if (initializable(requiredOptionMarker, mixin.requiredOptionMarker(), DEFAULT_REQUIRED_OPTION_MARKER)) {requiredOptionMarker = mixin.requiredOptionMarker();}
5084 if (initializable(abbreviateSynopsis, mixin.abbreviateSynopsis(), DEFAULT_ABBREVIATE_SYNOPSIS)) {abbreviateSynopsis = mixin.abbreviateSynopsis();}
5085 if (initializable(sortOptions, mixin.sortOptions(), DEFAULT_SORT_OPTIONS)) {sortOptions = mixin.sortOptions();}
5086 if (initializable(showDefaultValues, mixin.showDefaultValues(), DEFAULT_SHOW_DEFAULT_VALUES)) {showDefaultValues = mixin.showDefaultValues();}
5087 if (initializable(hidden, mixin.hidden(), DEFAULT_HIDDEN)) {hidden = mixin.hidden();}
5088 if (initializable(customSynopsis, mixin.customSynopsis(), DEFAULT_MULTI_LINE)) {customSynopsis = mixin.customSynopsis().clone();}
5089 if (initializable(description, mixin.description(), DEFAULT_MULTI_LINE)) {description = mixin.description().clone();}
5090 if (initializable(descriptionHeading, mixin.descriptionHeading(), DEFAULT_SINGLE_VALUE)) {descriptionHeading = mixin.descriptionHeading();}
5091 if (initializable(header, mixin.header(), DEFAULT_MULTI_LINE)) {header = mixin.header().clone();}
5092 if (initializable(headerHeading, mixin.headerHeading(), DEFAULT_SINGLE_VALUE)) {headerHeading = mixin.headerHeading();}
5093 if (initializable(footer, mixin.footer(), DEFAULT_MULTI_LINE)) {footer = mixin.footer().clone();}
5094 if (initializable(footerHeading, mixin.footerHeading(), DEFAULT_SINGLE_VALUE)) {footerHeading = mixin.footerHeading();}
5095 if (initializable(parameterListHeading, mixin.parameterListHeading(), DEFAULT_SINGLE_VALUE)) {parameterListHeading = mixin.parameterListHeading();}
5096 if (initializable(optionListHeading, mixin.optionListHeading(), DEFAULT_SINGLE_VALUE)) {optionListHeading = mixin.optionListHeading();}
5097 if (Messages.empty(messages)) { messages(Messages.copy(commandSpec, mixin.messages())); }
5098 }
5099 void initFrom(UsageMessageSpec settings, CommandSpec commandSpec) {
5100 description = settings.description;
5101 customSynopsis = settings.customSynopsis;
5102 header = settings.header;
5103 footer = settings.footer;
5104 abbreviateSynopsis = settings.abbreviateSynopsis;
5105 sortOptions = settings.sortOptions;
5106 showDefaultValues = settings.showDefaultValues;
5107 hidden = settings.hidden;
5108 requiredOptionMarker = settings.requiredOptionMarker;
5109 headerHeading = settings.headerHeading;
5110 synopsisHeading = settings.synopsisHeading;
5111 descriptionHeading = settings.descriptionHeading;
5112 parameterListHeading = settings.parameterListHeading;
5113 optionListHeading = settings.optionListHeading;
5114 commandListHeading = settings.commandListHeading;
5115 footerHeading = settings.footerHeading;
5116 width = settings.width;
5117 messages = Messages.copy(commandSpec, settings.messages());
5118 }
5119 }
5120 /** Models parser configuration specification.
5121 * @since 3.0 */
5122 public static class ParserSpec {
5123
5124 /** Constant String holding the default separator between options and option parameters: <code>{@value}</code>.*/
5125 static final String DEFAULT_SEPARATOR = "=";
5126 private String separator;
5127 private boolean stopAtUnmatched = false;
5128 private boolean stopAtPositional = false;
5129 private String endOfOptionsDelimiter = "--";
5130 private boolean toggleBooleanFlags = true;
5131 private boolean overwrittenOptionsAllowed = false;
5132 private boolean unmatchedArgumentsAllowed = false;
5133 private boolean expandAtFiles = true;
5134 private boolean useSimplifiedAtFiles = false;
5135 private Character atFileCommentChar = '#';
5136 private boolean posixClusteredShortOptionsAllowed = true;
5137 private boolean unmatchedOptionsArePositionalParams = false;
5138 private boolean limitSplit = false;
5139 private boolean aritySatisfiedByAttachedOptionParam = false;
5140 private boolean collectErrors = false;
5141 private boolean caseInsensitiveEnumValuesAllowed = false;
5142 private boolean trimQuotes = shouldTrimQuotes();
5143 private boolean splitQuotedStrings = false;
5144
5145 /** Returns the String to use as the separator between options and option parameters. {@code "="} by default,
5146 * initialized from {@link Command#separator()} if defined.*/
5147 public String separator() { return (separator == null) ? DEFAULT_SEPARATOR : separator; }
5148
5149 /** @see CommandLine#isStopAtUnmatched() */
5150 public boolean stopAtUnmatched() { return stopAtUnmatched; }
5151 /** @see CommandLine#isStopAtPositional() */
5152 public boolean stopAtPositional() { return stopAtPositional; }
5153 /** @see CommandLine#getEndOfOptionsDelimiter()
5154 * @since 3.5 */
5155 public String endOfOptionsDelimiter() { return endOfOptionsDelimiter; }
5156 /** @see CommandLine#isToggleBooleanFlags() */
5157 public boolean toggleBooleanFlags() { return toggleBooleanFlags; }
5158 /** @see CommandLine#isOverwrittenOptionsAllowed() */
5159 public boolean overwrittenOptionsAllowed() { return overwrittenOptionsAllowed; }
5160 /** @see CommandLine#isUnmatchedArgumentsAllowed() */
5161 public boolean unmatchedArgumentsAllowed() { return unmatchedArgumentsAllowed; }
5162 /** @see CommandLine#isExpandAtFiles() */
5163 public boolean expandAtFiles() { return expandAtFiles; }
5164 /** @see CommandLine#getAtFileCommentChar()
5165 * @since 3.5 */
5166 public Character atFileCommentChar() { return atFileCommentChar; }
5167 /** @see CommandLine#isUseSimplifiedAtFiles()
5168 * @since 3.9 */
5169 public boolean useSimplifiedAtFiles() {
5170 String value = System.getProperty("picocli.useSimplifiedAtFiles");
5171 if (value != null) {
5172 return "".equals(value) || Boolean.valueOf(value);
5173 }
5174 return useSimplifiedAtFiles;
5175 }
5176 /** @see CommandLine#isPosixClusteredShortOptionsAllowed() */
5177 public boolean posixClusteredShortOptionsAllowed() { return posixClusteredShortOptionsAllowed; }
5178 /** @see CommandLine#isCaseInsensitiveEnumValuesAllowed()
5179 * @since 3.4 */
5180 public boolean caseInsensitiveEnumValuesAllowed() { return caseInsensitiveEnumValuesAllowed; }
5181 /** @see CommandLine#isTrimQuotes()
5182 * @since 3.7 */
5183 public boolean trimQuotes() { return trimQuotes; }
5184 /** @see CommandLine#isSplitQuotedStrings()
5185 * @since 3.7 */
5186 public boolean splitQuotedStrings() { return splitQuotedStrings; }
5187 /** @see CommandLine#isUnmatchedOptionsArePositionalParams() */
5188 public boolean unmatchedOptionsArePositionalParams() { return unmatchedOptionsArePositionalParams; }
5189 private boolean splitFirst() { return limitSplit(); }
5190 /** Returns true if arguments should be split first before any further processing and the number of
5191 * parts resulting from the split is limited to the max arity of the argument. */
5192 public boolean limitSplit() { return limitSplit; }
5193 /** Returns true if options with attached arguments should not consume subsequent arguments and should not validate arity. The default is {@code false}. */
5194 public boolean aritySatisfiedByAttachedOptionParam() { return aritySatisfiedByAttachedOptionParam; }
5195 /** Returns true if exceptions during parsing should be collected instead of thrown.
5196 * Multiple errors may be encountered during parsing. These can be obtained from {@link ParseResult#errors()}.
5197 * @since 3.2 */
5198 public boolean collectErrors() { return collectErrors; }
5199
5200 /** Sets the String to use as the separator between options and option parameters.
5201 * @return this ParserSpec for method chaining */
5202 public ParserSpec separator(String separator) { this.separator = separator; return this; }
5203 /** @see CommandLine#setStopAtUnmatched(boolean) */
5204 public ParserSpec stopAtUnmatched(boolean stopAtUnmatched) { this.stopAtUnmatched = stopAtUnmatched; return this; }
5205 /** @see CommandLine#setStopAtPositional(boolean) */
5206 public ParserSpec stopAtPositional(boolean stopAtPositional) { this.stopAtPositional = stopAtPositional; return this; }
5207 /** @see CommandLine#setEndOfOptionsDelimiter(String)
5208 * @since 3.5 */
5209 public ParserSpec endOfOptionsDelimiter(String delimiter) { this.endOfOptionsDelimiter = Assert.notNull(delimiter, "end-of-options delimiter"); return this; }
5210 /** @see CommandLine#setToggleBooleanFlags(boolean) */
5211 public ParserSpec toggleBooleanFlags(boolean toggleBooleanFlags) { this.toggleBooleanFlags = toggleBooleanFlags; return this; }
5212 /** @see CommandLine#setOverwrittenOptionsAllowed(boolean) */
5213 public ParserSpec overwrittenOptionsAllowed(boolean overwrittenOptionsAllowed) { this.overwrittenOptionsAllowed = overwrittenOptionsAllowed; return this; }
5214 /** @see CommandLine#setUnmatchedArgumentsAllowed(boolean) */
5215 public ParserSpec unmatchedArgumentsAllowed(boolean unmatchedArgumentsAllowed) { this.unmatchedArgumentsAllowed = unmatchedArgumentsAllowed; return this; }
5216 /** @see CommandLine#setExpandAtFiles(boolean) */
5217 public ParserSpec expandAtFiles(boolean expandAtFiles) { this.expandAtFiles = expandAtFiles; return this; }
5218 /** @see CommandLine#setAtFileCommentChar(Character)
5219 * @since 3.5 */
5220 public ParserSpec atFileCommentChar(Character atFileCommentChar) { this.atFileCommentChar = atFileCommentChar; return this; }
5221 /** @see CommandLine#setUseSimplifiedAtFiles(boolean)
5222 * @since 3.9 */
5223 public ParserSpec useSimplifiedAtFiles(boolean useSimplifiedAtFiles) { this.useSimplifiedAtFiles = useSimplifiedAtFiles; return this; }
5224 /** @see CommandLine#setPosixClusteredShortOptionsAllowed(boolean) */
5225 public ParserSpec posixClusteredShortOptionsAllowed(boolean posixClusteredShortOptionsAllowed) { this.posixClusteredShortOptionsAllowed = posixClusteredShortOptionsAllowed; return this; }
5226 /** @see CommandLine#setCaseInsensitiveEnumValuesAllowed(boolean)
5227 * @since 3.4 */
5228 public ParserSpec caseInsensitiveEnumValuesAllowed(boolean caseInsensitiveEnumValuesAllowed) { this.caseInsensitiveEnumValuesAllowed = caseInsensitiveEnumValuesAllowed; return this; }
5229 /** @see CommandLine#setTrimQuotes(boolean)
5230 * @since 3.7 */
5231 public ParserSpec trimQuotes(boolean trimQuotes) { this.trimQuotes = trimQuotes; return this; }
5232 /** @see CommandLine#setSplitQuotedStrings(boolean)
5233 * @since 3.7 */
5234 public ParserSpec splitQuotedStrings(boolean splitQuotedStrings) { this.splitQuotedStrings = splitQuotedStrings; return this; }
5235 /** @see CommandLine#setUnmatchedOptionsArePositionalParams(boolean) */
5236 public ParserSpec unmatchedOptionsArePositionalParams(boolean unmatchedOptionsArePositionalParams) { this.unmatchedOptionsArePositionalParams = unmatchedOptionsArePositionalParams; return this; }
5237 /** Sets whether exceptions during parsing should be collected instead of thrown.
5238 * Multiple errors may be encountered during parsing. These can be obtained from {@link ParseResult#errors()}.
5239 * @since 3.2 */
5240 public ParserSpec collectErrors(boolean collectErrors) { this.collectErrors = collectErrors; return this; }
5241
5242 /** Returns true if options with attached arguments should not consume subsequent arguments and should not validate arity. The default is {@code false}.*/
5243 public ParserSpec aritySatisfiedByAttachedOptionParam(boolean newValue) { aritySatisfiedByAttachedOptionParam = newValue; return this; }
5244
5245 /** Sets whether arguments should be {@linkplain ArgSpec#splitRegex() split} first before any further processing.
5246 * If true, the original argument will only be split into as many parts as allowed by max arity. */
5247 public ParserSpec limitSplit(boolean limitSplit) { this.limitSplit = limitSplit; return this; }
5248
5249 private boolean shouldTrimQuotes() {
5250 String value = System.getProperty("picocli.trimQuotes");
5251 if ("".equals(value)) { value = "true"; }
5252 return Boolean.valueOf(value);
5253 }
5254
5255 void initSeparator(String value) { if (initializable(separator, value, DEFAULT_SEPARATOR)) {separator = value;} }
5256 void updateSeparator(String value) { if (isNonDefault(value, DEFAULT_SEPARATOR)) {separator = value;} }
5257 public String toString() {
5258 return String.format("posixClusteredShortOptionsAllowed=%s, stopAtPositional=%s, stopAtUnmatched=%s, " +
5259 "separator=%s, overwrittenOptionsAllowed=%s, unmatchedArgumentsAllowed=%s, expandAtFiles=%s, " +
5260 "atFileCommentChar=%s, useSimplifiedAtFiles=%s, endOfOptionsDelimiter=%s, limitSplit=%s, aritySatisfiedByAttachedOptionParam=%s, " +
5261 "toggleBooleanFlags=%s, unmatchedOptionsArePositionalParams=%s, collectErrors=%s," +
5262 "caseInsensitiveEnumValuesAllowed=%s, trimQuotes=%s, splitQuotedStrings=%s",
5263 posixClusteredShortOptionsAllowed, stopAtPositional, stopAtUnmatched,
5264 separator, overwrittenOptionsAllowed, unmatchedArgumentsAllowed, expandAtFiles,
5265 atFileCommentChar, useSimplifiedAtFiles, endOfOptionsDelimiter, limitSplit, aritySatisfiedByAttachedOptionParam,
5266 toggleBooleanFlags, unmatchedOptionsArePositionalParams, collectErrors,
5267 caseInsensitiveEnumValuesAllowed, trimQuotes, splitQuotedStrings);
5268 }
5269
5270 void initFrom(ParserSpec settings) {
5271 separator = settings.separator;
5272 stopAtUnmatched = settings.stopAtUnmatched;
5273 stopAtPositional = settings.stopAtPositional;
5274 endOfOptionsDelimiter = settings.endOfOptionsDelimiter;
5275 toggleBooleanFlags = settings.toggleBooleanFlags;
5276 overwrittenOptionsAllowed = settings.overwrittenOptionsAllowed;
5277 unmatchedArgumentsAllowed = settings.unmatchedArgumentsAllowed;
5278 expandAtFiles = settings.expandAtFiles;
5279 atFileCommentChar = settings.atFileCommentChar;
5280 posixClusteredShortOptionsAllowed = settings.posixClusteredShortOptionsAllowed;
5281 unmatchedOptionsArePositionalParams = settings.unmatchedOptionsArePositionalParams;
5282 limitSplit = settings.limitSplit;
5283 aritySatisfiedByAttachedOptionParam = settings.aritySatisfiedByAttachedOptionParam;
5284 collectErrors = settings.collectErrors;
5285 caseInsensitiveEnumValuesAllowed = settings.caseInsensitiveEnumValuesAllowed;
5286 trimQuotes = settings.trimQuotes;
5287 splitQuotedStrings = settings.splitQuotedStrings;
5288 }
5289 }
5290 /** Models the shared attributes of {@link OptionSpec} and {@link PositionalParamSpec}.
5291 * @since 3.0 */
5292 public abstract static class ArgSpec {
5293 static final String DESCRIPTION_VARIABLE_DEFAULT_VALUE = "${DEFAULT-VALUE}";
5294 static final String DESCRIPTION_VARIABLE_COMPLETION_CANDIDATES = "${COMPLETION-CANDIDATES}";
5295 private static final String NO_DEFAULT_VALUE = "__no_default_value__";
5296
5297 // help-related fields
5298 private final boolean hidden;
5299 private final String paramLabel;
5300 private final boolean hideParamSyntax;
5301 private final String[] description;
5302 private final String descriptionKey;
5303 private final Help.Visibility showDefaultValue;
5304 private Messages messages;
5305 CommandSpec commandSpec;
5306 private ArgGroupSpec group;
5307 private final Object userObject;
5308
5309 // parser fields
5310 private final boolean interactive;
5311 private final boolean required;
5312 private final String splitRegex;
5313 private final ITypeInfo typeInfo;
5314 private final ITypeConverter<?>[] converters;
5315 private final Iterable<String> completionCandidates;
5316 private final String defaultValue;
5317 private final Object initialValue;
5318 private final boolean hasInitialValue;
5319 private final IGetter getter;
5320 private final ISetter setter;
5321 private final IScope scope;
5322 private final Range arity;
5323 private List<String> stringValues = new ArrayList<String>();
5324 private List<String> originalStringValues = new ArrayList<String>();
5325 protected String toString;
5326 private List<Object> typedValues = new ArrayList<Object>();
5327 Map<Integer, Object> typedValueAtPosition = new TreeMap<Integer, Object>();
5328
5329 /** Constructs a new {@code ArgSpec}. */
5330 private <T extends Builder<T>> ArgSpec(Builder<T> builder) {
5331 userObject = builder.userObject;
5332 description = builder.description == null ? new String[0] : builder.description;
5333 descriptionKey = builder.descriptionKey;
5334 splitRegex = builder.splitRegex == null ? "" : builder.splitRegex;
5335 paramLabel = empty(builder.paramLabel) ? "PARAM" : builder.paramLabel;
5336 hideParamSyntax = builder.hideParamSyntax;
5337 converters = builder.converters == null ? new ITypeConverter<?>[0] : builder.converters;
5338 showDefaultValue = builder.showDefaultValue == null ? Help.Visibility.ON_DEMAND : builder.showDefaultValue;
5339 hidden = builder.hidden;
5340 interactive = builder.interactive;
5341 initialValue = builder.initialValue;
5342 hasInitialValue = builder.hasInitialValue;
5343 defaultValue = NO_DEFAULT_VALUE.equals(builder.defaultValue) ? null : builder.defaultValue;
5344 required = builder.required && defaultValue == null; //#261 not required if it has a default
5345 toString = builder.toString;
5346 getter = builder.getter;
5347 setter = builder.setter;
5348 scope = builder.scope;
5349
5350 Range tempArity = builder.arity;
5351 if (tempArity == null) {
5352 if (isOption()) {
5353 tempArity = (builder.type == null || isBoolean(builder.type)) ? Range.valueOf("0") : Range.valueOf("1");
5354 } else {
5355 tempArity = Range.valueOf("1");
5356 }
5357 tempArity = tempArity.unspecified(true);
5358 }
5359 arity = tempArity;
5360
5361 if (builder.typeInfo == null) {
5362 this.typeInfo = RuntimeTypeInfo.create(builder.type, builder.auxiliaryTypes,
5363 Collections.<String>emptyList(), arity, (isOption() ? boolean.class : String.class));
5364 } else {
5365 this.typeInfo = builder.typeInfo;
5366 }
5367
5368 if (builder.completionCandidates == null && typeInfo.isEnum()) {
5369 List<String> list = new ArrayList<String>();
5370 for (Object c : typeInfo.getEnumConstantNames()) { list.add(c.toString()); }
5371 completionCandidates = Collections.unmodifiableList(list);
5372 } else {
5373 completionCandidates = builder.completionCandidates;
5374 }
5375 if (interactive && (arity.min != 1 || arity.max != 1)) {
5376 throw new InitializationException("Interactive options and positional parameters are only supported for arity=1, not for arity=" + arity);
5377 }
5378 }
5379 void applyInitialValue(Tracer tracer) {
5380 if (hasInitialValue()) {
5381 try {
5382 setter().set(initialValue());
5383 tracer.debug("Set initial value for %s of type %s to %s.%n", this, type(), String.valueOf(initialValue()));
5384 } catch (Exception ex) {
5385 tracer.warn("Could not set initial value for %s of type %s to %s: %s%n", this, type(), String.valueOf(initialValue()), ex);
5386 }
5387 } else {
5388 tracer.debug("Initial value not available for %s%n", this);
5389 }
5390 }
5391
5392 /** Returns whether this is a required option or positional parameter.
5393 * 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).
5394 * @see Option#required() */
5395 public boolean required() { return required; }
5396
5397 /** Returns whether this option will prompt the user to enter a value on the command line.
5398 * @see Option#interactive() */
5399 public boolean interactive() { return interactive; }
5400
5401 /** Returns the description template of this option, before variables are rendered.
5402 * @see Option#description() */
5403 public String[] description() { return description.clone(); }
5404
5405 /** Returns the description key of this arg spec, used to get the description from a resource bundle.
5406 * @see Option#descriptionKey()
5407 * @see Parameters#descriptionKey()
5408 * @since 3.6 */
5409 public String descriptionKey() { return descriptionKey; }
5410
5411 /** Returns the description of this option, after variables are rendered. Used when generating the usage documentation.
5412 * @see Option#description()
5413 * @since 3.2 */
5414 public String[] renderedDescription() {
5415 String[] desc = description();
5416 if (desc.length == 0) { return desc; }
5417 StringBuilder candidates = new StringBuilder();
5418 if (completionCandidates() != null) {
5419 for (String c : completionCandidates()) {
5420 if (candidates.length() > 0) { candidates.append(", "); }
5421 candidates.append(c);
5422 }
5423 }
5424 String defaultValueString = defaultValueString();
5425 String[] result = new String[desc.length];
5426 for (int i = 0; i < desc.length; i++) {
5427 result[i] = format(desc[i].replace(DESCRIPTION_VARIABLE_DEFAULT_VALUE, defaultValueString.replace("%", "%%"))
5428 .replace(DESCRIPTION_VARIABLE_COMPLETION_CANDIDATES, candidates.toString()));
5429 }
5430 return result;
5431 }
5432
5433 /** Returns how many arguments this option or positional parameter requires.
5434 * @see Option#arity() */
5435 public Range arity() { return arity; }
5436
5437 /** Returns the name of the option or positional parameter used in the usage help message.
5438 * @see Option#paramLabel() {@link Parameters#paramLabel()} */
5439 public String paramLabel() { return paramLabel; }
5440
5441 /** Returns whether usage syntax decorations around the {@linkplain #paramLabel() paramLabel} should be suppressed.
5442 * The default is {@code false}: by default, the paramLabel is surrounded with {@code '['} and {@code ']'} characters
5443 * if the value is optional and followed by ellipses ("...") when multiple values can be specified.
5444 * @since 3.6.0 */
5445 public boolean hideParamSyntax() { return hideParamSyntax; }
5446
5447 /** Returns auxiliary type information used when the {@link #type()} is a generic {@code Collection}, {@code Map} or an abstract class.
5448 * @see Option#type() */
5449 public Class<?>[] auxiliaryTypes() { return typeInfo.getAuxiliaryTypes(); }
5450
5451 /** Returns one or more {@link CommandLine.ITypeConverter type converters} to use to convert the command line
5452 * argument into a strongly typed value (or key-value pair for map fields). This is useful when a particular
5453 * option or positional parameter should use a custom conversion that is different from the normal conversion for the arg spec's type.
5454 * @see Option#converter() */
5455 public ITypeConverter<?>[] converters() { return converters.clone(); }
5456
5457 /** Returns a regular expression to split option parameter values or {@code ""} if the value should not be split.
5458 * @see Option#split() */
5459 public String splitRegex() { return splitRegex; }
5460
5461 /** Returns whether this option should be excluded from the usage message.
5462 * @see Option#hidden() */
5463 public boolean hidden() { return hidden; }
5464
5465 /** Returns the type to convert the option or positional parameter to before {@linkplain #setValue(Object) setting} the value. */
5466 public Class<?> type() { return typeInfo.getType(); }
5467
5468 /** Returns the {@code ITypeInfo} that can be used both at compile time (by annotation processors) and at runtime.
5469 * @since 4.0 */
5470 public ITypeInfo typeInfo() { return typeInfo; }
5471
5472 /** Returns the user object associated with this option or positional parameters.
5473 * @return may return the annotated program element, or some other useful object
5474 * @since 4.0 */
5475 public Object userObject() { return userObject; }
5476
5477 /** Returns the default value of this option or positional parameter, before splitting and type conversion.
5478 * This method returns the programmatically set value; this may differ from the default value that is actually used:
5479 * if this ArgSpec is part of a CommandSpec with a {@link IDefaultValueProvider}, picocli will first try to obtain
5480 * the default value from the default value provider, and this method is only called if the default provider is
5481 * {@code null} or returned a {@code null} value.
5482 * @return the programmatically set default value of this option/positional parameter,
5483 * returning {@code null} means this option or positional parameter does not have a default
5484 * @see CommandSpec#defaultValueProvider()
5485 */
5486 public String defaultValue() { return defaultValue; }
5487 /** Returns the initial value this option or positional parameter. If {@link #hasInitialValue()} is true,
5488 * the option will be reset to the initial value before parsing (regardless of whether a default value exists),
5489 * to clear values that would otherwise remain from parsing previous input. */
5490 public Object initialValue() { return initialValue; }
5491 /** Determines whether the option or positional parameter will be reset to the {@link #initialValue()}
5492 * before parsing new input.*/
5493 public boolean hasInitialValue() { return hasInitialValue; }
5494
5495 /** Returns whether this option or positional parameter's default value should be shown in the usage help. */
5496 public Help.Visibility showDefaultValue() { return showDefaultValue; }
5497
5498 /** Returns the default value String displayed in the description. If this ArgSpec is part of a
5499 * CommandSpec with a {@link IDefaultValueProvider}, this method will first try to obtain
5500 * the default value from the default value provider; if the provider is {@code null} or if it
5501 * returns a {@code null} value, then next any value set to {@link ArgSpec#defaultValue()}
5502 * is returned, and if this is also {@code null}, finally the {@linkplain ArgSpec#initialValue() initial value} is returned.
5503 * @see CommandSpec#defaultValueProvider()
5504 * @see ArgSpec#defaultValue() */
5505 public String defaultValueString() {
5506 String fromProvider = defaultValueFromProvider();
5507 String defaultVal = fromProvider == null ? this.defaultValue() : fromProvider;
5508 Object value = defaultVal == null ? initialValue() : defaultVal;
5509 if (value != null && value.getClass().isArray()) {
5510 StringBuilder sb = new StringBuilder();
5511 for (int i = 0; i < Array.getLength(value); i++) {
5512 sb.append(i > 0 ? ", " : "").append(Array.get(value, i));
5513 }
5514 return sb.insert(0, "[").append("]").toString();
5515 }
5516 return String.valueOf(value);
5517 }
5518
5519 private String defaultValueFromProvider() {
5520 String fromProvider = null;
5521 IDefaultValueProvider defaultValueProvider = null;
5522 try {
5523 defaultValueProvider = commandSpec.defaultValueProvider();
5524 fromProvider = defaultValueProvider == null ? null : defaultValueProvider.defaultValue(this);
5525 } catch (Exception ex) {
5526 new Tracer().info("Error getting default value for %s from %s: %s", this, defaultValueProvider, ex);
5527 }
5528 return fromProvider;
5529 }
5530
5531 /** Returns the explicitly set completion candidates for this option or positional parameter, valid enum
5532 * constant names, or {@code null} if this option or positional parameter does not have any completion
5533 * candidates and its type is not an enum.
5534 * @return the completion candidates for this option or positional parameter, valid enum constant names,
5535 * or {@code null}
5536 * @since 3.2 */
5537 public Iterable<String> completionCandidates() { return completionCandidates; }
5538
5539 /** Returns the {@link IGetter} that is responsible for supplying the value of this argument. */
5540 public IGetter getter() { return getter; }
5541 /** Returns the {@link ISetter} that is responsible for modifying the value of this argument. */
5542 public ISetter setter() { return setter; }
5543 /** Returns the {@link IScope} that determines on which object to set the value (or from which object to get the value) of this argument. */
5544 public IScope scope() { return scope; }
5545
5546 /** Returns the current value of this argument. Delegates to the current {@link #getter()}. */
5547 public <T> T getValue() throws PicocliException {
5548 try {
5549 return getter.get();
5550 } catch (PicocliException ex) { throw ex;
5551 } catch (Exception ex) { throw new PicocliException("Could not get value for " + this + ": " + ex, ex);
5552 }
5553 }
5554 /** Sets the value of this argument to the specified value and returns the previous value. Delegates to the current {@link #setter()}. */
5555 public <T> T setValue(T newValue) throws PicocliException {
5556 try {
5557 return setter.set(newValue);
5558 } catch (PicocliException ex) { throw ex;
5559 } catch (Exception ex) { throw new PicocliException("Could not set value (" + newValue + ") for " + this + ": " + ex, ex);
5560 }
5561 }
5562 /** Sets the value of this argument to the specified value and returns the previous value. Delegates to the current {@link #setter()}.
5563 * @deprecated use {@link #setValue(Object)} instead. This was a design mistake.
5564 * @since 3.5 */
5565 @Deprecated public <T> T setValue(T newValue, CommandLine commandLine) throws PicocliException {
5566 return setValue(newValue);
5567 }
5568
5569 /** Returns {@code true} if this argument's {@link #type()} is an array, a {@code Collection} or a {@code Map}, {@code false} otherwise. */
5570 public boolean isMultiValue() { return typeInfo.isMultiValue(); }
5571 /** Returns {@code true} if this argument is a named option, {@code false} otherwise. */
5572 public abstract boolean isOption();
5573 /** Returns {@code true} if this argument is a positional parameter, {@code false} otherwise. */
5574 public abstract boolean isPositional();
5575
5576 /** Returns the groups this option or positional parameter belongs to, or {@code null} if this option is not part of a group.
5577 * @since 4.0 */
5578 public ArgGroupSpec group() { return group; }
5579
5580 /** Returns the untyped command line arguments matched by this option or positional parameter spec.
5581 * @return the matched arguments after {@linkplain #splitRegex() splitting}, but before type conversion.
5582 * For map properties, {@code "key=value"} values are split into the key and the value part. */
5583 public List<String> stringValues() { return Collections.unmodifiableList(stringValues); }
5584
5585 /** Returns the typed command line arguments matched by this option or positional parameter spec.
5586 * @return the matched arguments after {@linkplain #splitRegex() splitting} and type conversion.
5587 * For map properties, {@code "key=value"} values are split into the key and the value part. */
5588 public List<Object> typedValues() { return Collections.unmodifiableList(typedValues); }
5589
5590 /** Sets the {@code stringValues} to a new list instance. */
5591 protected void resetStringValues() { stringValues = new ArrayList<String>(); }
5592
5593 /** Returns the original command line arguments matched by this option or positional parameter spec.
5594 * @return the matched arguments as found on the command line: empty Strings for options without value, the
5595 * values have not been {@linkplain #splitRegex() split}, and for map properties values may look like {@code "key=value"}*/
5596 public List<String> originalStringValues() { return Collections.unmodifiableList(originalStringValues); }
5597
5598 /** Sets the {@code originalStringValues} to a new list instance. */
5599 protected void resetOriginalStringValues() { originalStringValues = new ArrayList<String>(); }
5600
5601 /** Returns whether the default for this option or positional parameter should be shown, potentially overriding the specified global setting.
5602 * @param usageHelpShowDefaults whether the command's UsageMessageSpec is configured to show default values. */
5603 protected boolean internalShowDefaultValue(boolean usageHelpShowDefaults) {
5604 if (showDefaultValue() == Help.Visibility.ALWAYS) { return true; } // override global usage help setting
5605 if (showDefaultValue() == Help.Visibility.NEVER) { return false; } // override global usage help setting
5606 if (initialValue == null && defaultValue() == null && defaultValueFromProvider() == null) { return false; } // no default value to show
5607 return usageHelpShowDefaults && !isBoolean(type());
5608 }
5609 /** Returns the Messages for this arg specification, or {@code null}.
5610 * @since 3.6 */
5611 public Messages messages() { return messages; }
5612 /** Sets the Messages for this ArgSpec, and returns this ArgSpec.
5613 * @param msgs the new Messages value, may be {@code null}
5614 * @see Command#resourceBundle()
5615 * @see OptionSpec#description()
5616 * @see PositionalParamSpec#description()
5617 * @since 3.6 */
5618 public ArgSpec messages(Messages msgs) { messages = msgs; return this; }
5619
5620 /** Returns a string respresentation of this option or positional parameter. */
5621 public String toString() { return toString; }
5622
5623 String[] splitValue(String value, ParserSpec parser, Range arity, int consumed) {
5624 if (splitRegex().length() == 0) { return new String[] {value}; }
5625 int limit = parser.limitSplit() ? Math.max(arity.max - consumed, 0) : 0;
5626 if (parser.splitQuotedStrings()) {
5627 return debug(value.split(splitRegex(), limit), "Split (ignoring quotes)", value);
5628 }
5629 return debug(splitRespectingQuotedStrings(value, limit, parser, this, splitRegex()), "Split", value);
5630 }
5631 private String[] debug(String[] result, String msg, String value) {
5632 Tracer t = new Tracer();
5633 if (t.isDebug()) {t.debug("%s with regex '%s' resulted in %s parts: %s%n", msg, splitRegex(), result.length, Arrays.asList(result));}
5634 return result;
5635 }
5636 // @since 3.7
5637 private static String[] splitRespectingQuotedStrings(String value, int limit, ParserSpec parser, ArgSpec argSpec, String splitRegex) {
5638 StringBuilder splittable = new StringBuilder();
5639 StringBuilder temp = new StringBuilder();
5640 StringBuilder current = splittable;
5641 Queue<String> quotedValues = new LinkedList<String>();
5642 boolean escaping = false, inQuote = false;
5643 for (int ch = 0, i = 0; i < value.length(); i += Character.charCount(ch)) {
5644 ch = value.codePointAt(i);
5645 switch (ch) {
5646 case '\\': escaping = !escaping; break;
5647 case '\"':
5648 if (!escaping) {
5649 inQuote = !inQuote;
5650 current = inQuote ? temp : splittable;
5651 if (inQuote) {
5652 splittable.appendCodePoint(ch);
5653 continue;
5654 } else {
5655 quotedValues.add(temp.toString());
5656 temp.setLength(0);
5657 }
5658 }
5659 break;
5660 default: escaping = false; break;
5661 }
5662 current.appendCodePoint(ch);
5663 }
5664 if (temp.length() > 0) {
5665 new Tracer().warn("Unbalanced quotes in [%s] for %s (value=%s)%n", temp, argSpec, value);
5666 quotedValues.add(temp.toString());
5667 temp.setLength(0);
5668 }
5669 String[] result = splittable.toString().split(splitRegex, limit);
5670 for (int i = 0; i < result.length; i++) {
5671 result[i] = restoreQuotedValues(result[i], quotedValues, parser);
5672 }
5673 if (!quotedValues.isEmpty()) {
5674 new Tracer().warn("Unable to respect quotes while splitting value %s for %s (unprocessed remainder: %s)%n", value, argSpec, quotedValues);
5675 return value.split(splitRegex, limit);
5676 }
5677 return result;
5678 }
5679
5680 private static String restoreQuotedValues(String part, Queue<String> quotedValues, ParserSpec parser) {
5681 StringBuilder result = new StringBuilder();
5682 boolean escaping = false, inQuote = false, skip = false;
5683 for (int ch = 0, i = 0; i < part.length(); i += Character.charCount(ch)) {
5684 ch = part.codePointAt(i);
5685 switch (ch) {
5686 case '\\': escaping = !escaping; break;
5687 case '\"':
5688 if (!escaping) {
5689 inQuote = !inQuote;
5690 if (!inQuote) { result.append(quotedValues.remove()); }
5691 skip = parser.trimQuotes();
5692 }
5693 break;
5694 default: escaping = false; break;
5695 }
5696 if (!skip) { result.appendCodePoint(ch); }
5697 skip = false;
5698 }
5699 return result.toString();
5700 }
5701
5702 protected boolean equalsImpl(ArgSpec other) {
5703 boolean result = Assert.equals(this.defaultValue, other.defaultValue)
5704 && Assert.equals(this.arity, other.arity)
5705 && Assert.equals(this.hidden, other.hidden)
5706 && Assert.equals(this.paramLabel, other.paramLabel)
5707 && Assert.equals(this.hideParamSyntax, other.hideParamSyntax)
5708 && Assert.equals(this.required, other.required)
5709 && Assert.equals(this.splitRegex, other.splitRegex)
5710 && Arrays.equals(this.description, other.description)
5711 && Assert.equals(this.descriptionKey, other.descriptionKey)
5712 && this.typeInfo.equals(other.typeInfo)
5713 ;
5714 return result;
5715 }
5716 protected int hashCodeImpl() {
5717 return 17
5718 + 37 * Assert.hashCode(defaultValue)
5719 + 37 * Assert.hashCode(arity)
5720 + 37 * Assert.hashCode(hidden)
5721 + 37 * Assert.hashCode(paramLabel)
5722 + 37 * Assert.hashCode(hideParamSyntax)
5723 + 37 * Assert.hashCode(required)
5724 + 37 * Assert.hashCode(splitRegex)
5725 + 37 * Arrays.hashCode(description)
5726 + 37 * Assert.hashCode(descriptionKey)
5727 + 37 * typeInfo.hashCode()
5728 ;
5729 }
5730
5731 private static String describe(Collection<ArgSpec> args) {
5732 StringBuilder sb = new StringBuilder();
5733 for (ArgSpec arg : args) {
5734 if (sb.length() > 0) { sb.append(", "); }
5735 sb.append(describe(arg, "="));
5736 }
5737 return sb.toString();
5738 }
5739 /** Returns a description of the option or positional arg, e.g. {@code -a=<a>}
5740 * @param separator separator between arg and arg parameter label, usually '=' */
5741 private static String describe(ArgSpec argSpec, String separator) {
5742 return describe(argSpec, separator, argSpec.paramLabel());
5743 }
5744 /** Returns a description of the option or positional arg
5745 * @param separator separator between arg and arg parameter value, usually '='
5746 * @param value the value to append after the separator*/
5747 private static String describe(ArgSpec argSpec, String separator, String value) {
5748 String prefix = (argSpec.isOption())
5749 ? ((OptionSpec) argSpec).longestName()
5750 : "params[" + ((PositionalParamSpec) argSpec).index() + "]";
5751 return argSpec.arity().min > 0 ? prefix + separator + value : prefix;
5752 }
5753 abstract static class Builder<T extends Builder<T>> {
5754 private Object userObject;
5755 private Range arity;
5756 private String[] description;
5757 private String descriptionKey;
5758 private boolean required;
5759 private boolean interactive;
5760 private String paramLabel;
5761 private boolean hideParamSyntax;
5762 private String splitRegex;
5763 private boolean hidden;
5764 private Class<?> type;
5765 private Class<?>[] auxiliaryTypes;
5766 private ITypeInfo typeInfo;
5767 private ITypeConverter<?>[] converters;
5768 private String defaultValue;
5769 private Object initialValue;
5770 private boolean hasInitialValue = true;
5771 private Help.Visibility showDefaultValue;
5772 private Iterable<String> completionCandidates;
5773 private String toString;
5774 private IGetter getter = new ObjectBinding();
5775 private ISetter setter = (ISetter) getter;
5776 private IScope scope = new ObjectScope(null);
5777
5778 Builder() {}
5779 Builder(ArgSpec original) {
5780 userObject = original.userObject;
5781 arity = original.arity;
5782 converters = original.converters;
5783 defaultValue = original.defaultValue;
5784 description = original.description;
5785 getter = original.getter;
5786 setter = original.setter;
5787 hidden = original.hidden;
5788 paramLabel = original.paramLabel;
5789 hideParamSyntax = original.hideParamSyntax;
5790 required = original.required;
5791 interactive = original.interactive;
5792 showDefaultValue = original.showDefaultValue;
5793 completionCandidates = original.completionCandidates;
5794 splitRegex = original.splitRegex;
5795 toString = original.toString;
5796 descriptionKey = original.descriptionKey;
5797 setTypeInfo(original.typeInfo);
5798 }
5799 Builder(IAnnotatedElement source) {
5800 userObject = source.userObject();
5801 setTypeInfo(source.getTypeInfo());
5802 toString = source.getToString();
5803 getter = source.getter();
5804 setter = source.setter();
5805 scope = source.scope();
5806 hasInitialValue = source.hasInitialValue();
5807 try { initialValue = source.getter().get(); } catch (Exception ex) { initialValue = null; hasInitialValue = false; }
5808 }
5809 Builder(Option option, IAnnotatedElement source, IFactory factory) {
5810 this(source);
5811 arity = Range.optionArity(source);
5812 required = option.required();
5813
5814 paramLabel = inferLabel(option.paramLabel(), source.getName(), source.getTypeInfo());
5815
5816 hideParamSyntax = option.hideParamSyntax();
5817 interactive = option.interactive();
5818 description = option.description();
5819 descriptionKey = option.descriptionKey();
5820 splitRegex = option.split();
5821 hidden = option.hidden();
5822 defaultValue = option.defaultValue();
5823 showDefaultValue = option.showDefaultValue();
5824 if (factory != null) {
5825 converters = DefaultFactory.createConverter(factory, option.converter());
5826 if (!NoCompletionCandidates.class.equals(option.completionCandidates())) {
5827 completionCandidates = DefaultFactory.createCompletionCandidates(factory, option.completionCandidates());
5828 }
5829 }
5830 }
5831 Builder(Parameters parameters, IAnnotatedElement source, IFactory factory) {
5832 this(source);
5833 arity = Range.parameterArity(source);
5834 required = arity.min > 0;
5835
5836 // method parameters may be positional parameters without @Parameters annotation
5837 if (parameters == null) {
5838 paramLabel = inferLabel(null, source.getName(), source.getTypeInfo());
5839 } else {
5840 paramLabel = inferLabel(parameters.paramLabel(), source.getName(), source.getTypeInfo());
5841
5842 hideParamSyntax = parameters.hideParamSyntax();
5843 interactive = parameters.interactive();
5844 description = parameters.description();
5845 descriptionKey = parameters.descriptionKey();
5846 splitRegex = parameters.split();
5847 hidden = parameters.hidden();
5848 defaultValue = parameters.defaultValue();
5849 showDefaultValue = parameters.showDefaultValue();
5850 if (factory != null) { // annotation processors will pass a null factory
5851 converters = DefaultFactory.createConverter(factory, parameters.converter());
5852 if (!NoCompletionCandidates.class.equals(parameters.completionCandidates())) {
5853 completionCandidates = DefaultFactory.createCompletionCandidates(factory, parameters.completionCandidates());
5854 }
5855 }
5856 }
5857 }
5858 private static String inferLabel(String label, String fieldName, ITypeInfo typeInfo) {
5859 if (!empty(label)) { return label.trim(); }
5860 String name = fieldName;
5861 if (typeInfo.isMap()) { // #195 better param labels for map fields
5862 List<ITypeInfo> aux = typeInfo.getAuxiliaryTypeInfos();
5863 if (aux.size() < 2 || aux.get(0) == null || aux.get(1) == null) {
5864 name = "String=String";
5865 } else { name = aux.get(0).getClassSimpleName() + "=" + aux.get(1).getClassSimpleName(); }
5866 }
5867 return "<" + name + ">";
5868 }
5869
5870 public abstract ArgSpec build();
5871 protected abstract T self(); // subclasses must override to return "this"
5872 /** Returns whether this is a required option or positional parameter.
5873 * @see Option#required() */
5874 public boolean required() { return required; }
5875 /** Returns whether this option prompts the user to enter a value on the command line.
5876 * @see Option#interactive() */
5877 public boolean interactive() { return interactive; }
5878
5879 /** Returns the description of this option, used when generating the usage documentation.
5880 * @see Option#description() */
5881 public String[] description() { return description; }
5882
5883 /** Returns the description key of this arg spec, used to get the description from a resource bundle.
5884 * @see Option#descriptionKey()
5885 * @see Parameters#descriptionKey()
5886 * @since 3.6 */
5887 public String descriptionKey() { return descriptionKey; }
5888
5889 /** Returns how many arguments this option or positional parameter requires.
5890 * @see Option#arity() */
5891 public Range arity() { return arity; }
5892
5893 /** Returns the name of the option or positional parameter used in the usage help message.
5894 * @see Option#paramLabel() {@link Parameters#paramLabel()} */
5895 public String paramLabel() { return paramLabel; }
5896
5897 /** Returns whether usage syntax decorations around the {@linkplain #paramLabel() paramLabel} should be suppressed.
5898 * The default is {@code false}: by default, the paramLabel is surrounded with {@code '['} and {@code ']'} characters
5899 * if the value is optional and followed by ellipses ("...") when multiple values can be specified.
5900 * @since 3.6.0 */
5901 public boolean hideParamSyntax() { return hideParamSyntax; }
5902
5903 /** Returns auxiliary type information used when the {@link #type()} is a generic {@code Collection}, {@code Map} or an abstract class.
5904 * @see Option#type() */
5905 public Class<?>[] auxiliaryTypes() { return auxiliaryTypes; }
5906
5907 /** Returns one or more {@link CommandLine.ITypeConverter type converters} to use to convert the command line
5908 * argument into a strongly typed value (or key-value pair for map fields). This is useful when a particular
5909 * option or positional parameter should use a custom conversion that is different from the normal conversion for the arg spec's type.
5910 * @see Option#converter() */
5911 public ITypeConverter<?>[] converters() { return converters; }
5912
5913 /** Returns a regular expression to split option parameter values or {@code ""} if the value should not be split.
5914 * @see Option#split() */
5915 public String splitRegex() { return splitRegex; }
5916
5917 /** Returns whether this option should be excluded from the usage message.
5918 * @see Option#hidden() */
5919 public boolean hidden() { return hidden; }
5920
5921 /** Returns the type to convert the option or positional parameter to before {@linkplain #setValue(Object) setting} the value. */
5922 public Class<?> type() { return type; }
5923
5924 /** Returns the type info for this option or positional parameter.
5925 * @return type information that does not require {@code Class} objects and be constructed both at runtime and compile time
5926 * @since 4.0
5927 */
5928 public ITypeInfo typeInfo() { return typeInfo; }
5929
5930 /** Returns the user object associated with this option or positional parameters.
5931 * @return may return the annotated program element, or some other useful object
5932 * @since 4.0 */
5933 public Object userObject() { return userObject; }
5934
5935 /** Returns the default value of this option or positional parameter, before splitting and type conversion.
5936 * A value of {@code null} means this option or positional parameter does not have a default. */
5937 public String defaultValue() { return defaultValue; }
5938 /** Returns the initial value this option or positional parameter. If {@link #hasInitialValue()} is true,
5939 * the option will be reset to the initial value before parsing (regardless of whether a default value exists),
5940 * to clear values that would otherwise remain from parsing previous input. */
5941 public Object initialValue() { return initialValue; }
5942 /** Determines whether the option or positional parameter will be reset to the {@link #initialValue()}
5943 * before parsing new input.*/
5944 public boolean hasInitialValue() { return hasInitialValue; }
5945
5946 /** Returns whether this option or positional parameter's default value should be shown in the usage help. */
5947 public Help.Visibility showDefaultValue() { return showDefaultValue; }
5948
5949 /** Returns the completion candidates for this option or positional parameter, or {@code null}.
5950 * @since 3.2 */
5951 public Iterable<String> completionCandidates() { return completionCandidates; }
5952
5953 /** Returns the {@link IGetter} that is responsible for supplying the value of this argument. */
5954 public IGetter getter() { return getter; }
5955 /** Returns the {@link ISetter} that is responsible for modifying the value of this argument. */
5956 public ISetter setter() { return setter; }
5957 /** Returns the {@link IScope} that determines where the setter sets the value (or the getter gets the value) of this argument. */
5958 public IScope scope() { return scope; }
5959
5960 public String toString() { return toString; }
5961
5962 /** Sets whether this is a required option or positional parameter, and returns this builder. */
5963 public T required(boolean required) { this.required = required; return self(); }
5964
5965 /** Sets whether this option prompts the user to enter a value on the command line, and returns this builder. */
5966 public T interactive(boolean interactive) { this.interactive = interactive; return self(); }
5967
5968 /** Sets the description of this option, used when generating the usage documentation, and returns this builder.
5969 * @see Option#description() */
5970 public T description(String... description) { this.description = Assert.notNull(description, "description").clone(); return self(); }
5971
5972 /** Sets the description key that is used to look up the description in a resource bundle, and returns this builder.
5973 * @see Option#descriptionKey()
5974 * @see Parameters#descriptionKey()
5975 * @since 3.6 */
5976 public T descriptionKey(String descriptionKey) { this.descriptionKey = descriptionKey; return self(); }
5977
5978 /** Sets how many arguments this option or positional parameter requires, and returns this builder. */
5979 public T arity(String range) { return arity(Range.valueOf(range)); }
5980
5981 /** Sets how many arguments this option or positional parameter requires, and returns this builder. */
5982 public T arity(Range arity) { this.arity = Assert.notNull(arity, "arity"); return self(); }
5983
5984 /** Sets the name of the option or positional parameter used in the usage help message, and returns this builder. */
5985 public T paramLabel(String paramLabel) { this.paramLabel = Assert.notNull(paramLabel, "paramLabel"); return self(); }
5986
5987 /** Sets whether usage syntax decorations around the {@linkplain #paramLabel() paramLabel} should be suppressed.
5988 * The default is {@code false}: by default, the paramLabel is surrounded with {@code '['} and {@code ']'} characters
5989 * if the value is optional and followed by ellipses ("...") when multiple values can be specified.
5990 * @since 3.6.0 */
5991 public T hideParamSyntax(boolean hideParamSyntax) { this.hideParamSyntax = hideParamSyntax; return self(); }
5992
5993 /** Sets auxiliary type information, and returns this builder.
5994 * @param types the element type(s) when the {@link #type()} is a generic {@code Collection} or a {@code Map};
5995 * or the concrete type when the {@link #type()} is an abstract class. */
5996 public T auxiliaryTypes(Class<?>... types) { this.auxiliaryTypes = Assert.notNull(types, "types").clone(); return self(); }
5997
5998 /** Sets option/positional param-specific converter (or converters for Maps), and returns this builder. */
5999 public T converters(ITypeConverter<?>... cs) { this.converters = Assert.notNull(cs, "type converters").clone(); return self(); }
6000
6001 /** Sets a regular expression to split option parameter values or {@code ""} if the value should not be split, and returns this builder. */
6002 public T splitRegex(String splitRegex) { this.splitRegex = Assert.notNull(splitRegex, "splitRegex"); return self(); }
6003
6004 /** Sets whether this option or positional parameter's default value should be shown in the usage help, and returns this builder. */
6005 public T showDefaultValue(Help.Visibility visibility) { showDefaultValue = Assert.notNull(visibility, "visibility"); return self(); }
6006
6007 /** Sets the completion candidates for this option or positional parameter, and returns this builder.
6008 * @since 3.2 */
6009 public T completionCandidates(Iterable<String> completionCandidates) { this.completionCandidates = completionCandidates; return self(); }
6010
6011 /** Sets whether this option should be excluded from the usage message, and returns this builder. */
6012 public T hidden(boolean hidden) { this.hidden = hidden; return self(); }
6013
6014 /** Sets the type to convert the option or positional parameter to before {@linkplain #setValue(Object) setting} the value, and returns this builder.
6015 * @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. */
6016 public T type(Class<?> propertyType) { this.type = Assert.notNull(propertyType, "type"); return self(); }
6017
6018 /** Sets the type info for this option or positional parameter, and returns this builder.
6019 * @param typeInfo type information that does not require {@code Class} objects and be constructed both at runtime and compile time
6020 * @since 4.0 */
6021 public T typeInfo(ITypeInfo typeInfo) {
6022 setTypeInfo(Assert.notNull(typeInfo, "typeInfo"));
6023 return self();
6024 }
6025 private void setTypeInfo(ITypeInfo newValue) {
6026 this.typeInfo = newValue;
6027 if (typeInfo != null) {
6028 type = typeInfo.getType();
6029 auxiliaryTypes = typeInfo.getAuxiliaryTypes();
6030 }
6031 }
6032
6033 /** Sets the user object associated with this option or positional parameters, and returns this builder.
6034 * @param userObject may be the annotated program element, or some other useful object
6035 * @since 4.0 */
6036 public T userObject(Object userObject) { this.userObject = Assert.notNull(userObject, "userObject"); return self(); }
6037
6038 /** Sets the default value of this option or positional parameter to the specified value, and returns this builder.
6039 * Before parsing the command line, the result of {@linkplain #splitRegex() splitting} and {@linkplain #converters() type converting}
6040 * this default value is applied to the option or positional parameter. A value of {@code null} or {@code "__no_default_value__"} means no default. */
6041 public T defaultValue(String defaultValue) { this.defaultValue = defaultValue; return self(); }
6042
6043 /** Sets the initial value of this option or positional parameter to the specified value, and returns this builder.
6044 * If {@link #hasInitialValue()} is true, the option will be reset to the initial value before parsing (regardless
6045 * of whether a default value exists), to clear values that would otherwise remain from parsing previous input. */
6046 public T initialValue(Object initialValue) { this.initialValue = initialValue; return self(); }
6047
6048 /** Determines whether the option or positional parameter will be reset to the {@link #initialValue()}
6049 * before parsing new input.*/
6050 public T hasInitialValue(boolean hasInitialValue) { this.hasInitialValue = hasInitialValue; return self(); }
6051
6052 /** Sets the {@link IGetter} that is responsible for getting the value of this argument, and returns this builder. */
6053 public T getter(IGetter getter) { this.getter = getter; return self(); }
6054 /** Sets the {@link ISetter} that is responsible for modifying the value of this argument, and returns this builder. */
6055 public T setter(ISetter setter) { this.setter = setter; return self(); }
6056 /** Sets the {@link IScope} that targets where the setter sets the value, and returns this builder. */
6057 public T scope(IScope scope) { this.scope = scope; return self(); }
6058
6059 /** Sets the string respresentation of this option or positional parameter to the specified value, and returns this builder. */
6060 public T withToString(String toString) { this.toString = toString; return self(); }
6061 }
6062 }
6063 /** The {@code OptionSpec} class models aspects of a <em>named option</em> of a {@linkplain CommandSpec command}, including whether
6064 * it is required or optional, the option parameters supported (or required) by the option,
6065 * and attributes for the usage help message describing the option.
6066 * <p>
6067 * 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.
6068 * Depending on the option's {@link #arity() arity},
6069 * the parser may expect it to have option parameters. The parser will call {@link #setValue(Object) setValue} on
6070 * the matched option for each of the option parameters encountered.
6071 * </p><p>
6072 * For multi-value options, the {@code type} may be an array, a {@code Collection} or a {@code Map}. In this case
6073 * the parser will get the data structure by calling {@link #getValue() getValue} and modify the contents of this data structure.
6074 * (In the case of arrays, the array is replaced with a new instance with additional elements.)
6075 * </p><p>
6076 * Before calling the setter, picocli converts the option parameter value from a String to the option parameter's type.
6077 * </p>
6078 * <ul>
6079 * <li>If a option-specific {@link #converters() converter} is configured, this will be used for type conversion.
6080 * If the option's type is a {@code Map}, the map may have different types for its keys and its values, so
6081 * {@link #converters() converters} should provide two converters: one for the map keys and one for the map values.</li>
6082 * <li>Otherwise, the option's {@link #type() type} is used to look up a converter in the list of
6083 * {@linkplain CommandLine#registerConverter(Class, ITypeConverter) registered converters}.
6084 * For multi-value options,
6085 * the {@code type} may be an array, or a {@code Collection} or a {@code Map}. In that case the elements are converted
6086 * based on the option's {@link #auxiliaryTypes() auxiliaryTypes}. The auxiliaryType is used to look up
6087 * the converter(s) to use to convert the individual parameter values.
6088 * Maps may have different types for its keys and its values, so {@link #auxiliaryTypes() auxiliaryTypes}
6089 * should provide two types: one for the map keys and one for the map values.</li>
6090 * </ul>
6091 * <p>
6092 * {@code OptionSpec} objects are used by the picocli command line interpreter and help message generator.
6093 * Picocli can construct an {@code OptionSpec} automatically from fields and methods with {@link Option @Option}
6094 * annotations. Alternatively an {@code OptionSpec} can be constructed programmatically.
6095 * </p><p>
6096 * When an {@code OptionSpec} is created from an {@link Option @Option} -annotated field or method, it is "bound"
6097 * to that field or method: this field is set (or the method is invoked) when the option is matched and
6098 * {@link #setValue(Object) setValue} is called.
6099 * Programmatically constructed {@code OptionSpec} instances will remember the value passed to the
6100 * {@link #setValue(Object) setValue} method so it can be retrieved with the {@link #getValue() getValue} method.
6101 * This behaviour can be customized by installing a custom {@link IGetter} and {@link ISetter} on the {@code OptionSpec}.
6102 * </p>
6103 * @since 3.0 */
6104 public static class OptionSpec extends ArgSpec implements IOrdered {
6105 static final int DEFAULT_ORDER = -1;
6106 private String[] names;
6107 private boolean help;
6108 private boolean usageHelp;
6109 private boolean versionHelp;
6110 private int order;
6111
6112 public static OptionSpec.Builder builder(String name, String... names) {
6113 String[] copy = new String[Assert.notNull(names, "names").length + 1];
6114 copy[0] = Assert.notNull(name, "name");
6115 System.arraycopy(names, 0, copy, 1, names.length);
6116 return new Builder(copy);
6117 }
6118 public static OptionSpec.Builder builder(String[] names) { return new Builder(names); }
6119 public static OptionSpec.Builder builder(IAnnotatedElement source, IFactory factory) { return new Builder(source, factory); }
6120
6121 /** Ensures all attributes of this {@code OptionSpec} have a valid value; throws an {@link InitializationException} if this cannot be achieved. */
6122 private OptionSpec(Builder builder) {
6123 super(builder);
6124 if (builder.names == null) {
6125 throw new InitializationException("OptionSpec names cannot be null. Specify at least one option name.");
6126 }
6127 names = builder.names.clone();
6128 help = builder.help;
6129 usageHelp = builder.usageHelp;
6130 versionHelp = builder.versionHelp;
6131 order = builder.order;
6132
6133 if (names.length == 0 || Arrays.asList(names).contains("")) {
6134 throw new InitializationException("Invalid names: " + Arrays.toString(names));
6135 }
6136 if (toString() == null) { toString = "option " + longestName(); }
6137
6138// if (arity().max == 0 && !(isBoolean(type()) || (isMultiValue() && isBoolean(auxiliaryTypes()[0])))) {
6139// throw new InitializationException("Option " + longestName() + " is not a boolean so should not be defined with arity=" + arity());
6140// }
6141 }
6142
6143 /** Returns a new Builder initialized with the attributes from this {@code OptionSpec}. Calling {@code build} immediately will return a copy of this {@code OptionSpec}.
6144 * @return a builder that can create a copy of this spec
6145 */
6146 public Builder toBuilder() { return new Builder(this); }
6147 @Override public boolean isOption() { return true; }
6148 @Override public boolean isPositional() { return false; }
6149
6150 protected boolean internalShowDefaultValue(boolean usageMessageShowDefaults) {
6151 return super.internalShowDefaultValue(usageMessageShowDefaults) && !help() && !versionHelp() && !usageHelp();
6152 }
6153
6154 /** Returns the description template of this option, before variables are {@linkplain Option#description() rendered}.
6155 * If a resource bundle has been {@linkplain ArgSpec#messages(Messages) set}, this method will first try to find a value in the resource bundle:
6156 * If the resource bundle has no entry for the {@code fully qualified commandName + "." + descriptionKey} or for the unqualified {@code descriptionKey},
6157 * an attempt is made to find the option description using any of the option names (without leading hyphens) as key,
6158 * first with the {@code fully qualified commandName + "."} prefix, then without.
6159 * @see CommandSpec#qualifiedName(String)
6160 * @see Option#description() */
6161 @Override public String[] description() {
6162 if (messages() == null) { return super.description(); }
6163 String[] newValue = messages().getStringArray(descriptionKey(), null);
6164 if (newValue != null) { return newValue; }
6165 for (String name : names()) {
6166 newValue = messages().getStringArray(CommandSpec.stripPrefix(name), null);
6167 if (newValue != null) { return newValue; }
6168 }
6169 return super.description();
6170 }
6171
6172 /** Returns one or more option names. The returned array will contain at least one option name.
6173 * @see Option#names() */
6174 public String[] names() { return names.clone(); }
6175
6176 /** Returns the longest {@linkplain #names() option name}. */
6177 public String longestName() { return Help.ShortestFirst.longestFirst(names.clone())[0]; }
6178
6179 /** Returns the shortest {@linkplain #names() option name}.
6180 * @since 3.8 */
6181 public String shortestName() { return Help.ShortestFirst.sort(names.clone())[0]; }
6182
6183 /** Returns the position in the options list in the usage help message at which this option should be shown.
6184 * Options with a lower number are shown before options with a higher number.
6185 * This attribute is only honored if {@link UsageMessageSpec#sortOptions()} is {@code false} for this command.
6186 * @see Option#order()
6187 * @since 3.9 */
6188 public int order() { return this.order; }
6189
6190 /** Returns whether this option disables validation of the other arguments.
6191 * @see Option#help()
6192 * @deprecated Use {@link #usageHelp()} and {@link #versionHelp()} instead. */
6193 @Deprecated public boolean help() { return help; }
6194
6195 /** Returns whether this option allows the user to request usage help.
6196 * @see Option#usageHelp() */
6197 public boolean usageHelp() { return usageHelp; }
6198
6199 /** Returns whether this option allows the user to request version information.
6200 * @see Option#versionHelp() */
6201 public boolean versionHelp() { return versionHelp; }
6202 public boolean equals(Object obj) {
6203 if (obj == this) { return true; }
6204 if (!(obj instanceof OptionSpec)) { return false; }
6205 OptionSpec other = (OptionSpec) obj;
6206 boolean result = super.equalsImpl(other)
6207 && help == other.help
6208 && usageHelp == other.usageHelp
6209 && versionHelp == other.versionHelp
6210 && order == other.order
6211 && new HashSet<String>(Arrays.asList(names)).equals(new HashSet<String>(Arrays.asList(other.names)));
6212 return result;
6213 }
6214 public int hashCode() {
6215 return super.hashCodeImpl()
6216 + 37 * Assert.hashCode(help)
6217 + 37 * Assert.hashCode(usageHelp)
6218 + 37 * Assert.hashCode(versionHelp)
6219 + 37 * Arrays.hashCode(names)
6220 + 37 * order;
6221 }
6222
6223 /** Builder responsible for creating valid {@code OptionSpec} objects.
6224 * @since 3.0
6225 */
6226 public static class Builder extends ArgSpec.Builder<Builder> {
6227 private String[] names;
6228 private boolean help;
6229 private boolean usageHelp;
6230 private boolean versionHelp;
6231 private int order = DEFAULT_ORDER;
6232
6233 private Builder(String[] names) { this.names = names; }
6234 private Builder(OptionSpec original) {
6235 super(original);
6236 names = original.names;
6237 help = original.help;
6238 usageHelp = original.usageHelp;
6239 versionHelp = original.versionHelp;
6240 order = original.order;
6241 }
6242 private Builder(IAnnotatedElement member, IFactory factory) {
6243 super(member.getAnnotation(Option.class), member, factory);
6244 Option option = member.getAnnotation(Option.class);
6245 names = option.names();
6246 help = option.help();
6247 usageHelp = option.usageHelp();
6248 versionHelp = option.versionHelp();
6249 order = option.order();
6250 }
6251
6252 /** Returns a valid {@code OptionSpec} instance. */
6253 @Override public OptionSpec build() { return new OptionSpec(this); }
6254 /** Returns this builder. */
6255 @Override protected Builder self() { return this; }
6256
6257 /** Returns one or more option names. At least one option name is required.
6258 * @see Option#names() */
6259 public String[] names() { return names; }
6260
6261 /** Returns whether this option disables validation of the other arguments.
6262 * @see Option#help()
6263 * @deprecated Use {@link #usageHelp()} and {@link #versionHelp()} instead. */
6264 @Deprecated public boolean help() { return help; }
6265
6266 /** Returns whether this option allows the user to request usage help.
6267 * @see Option#usageHelp() */
6268 public boolean usageHelp() { return usageHelp; }
6269
6270 /** Returns whether this option allows the user to request version information.
6271 * @see Option#versionHelp() */
6272 public boolean versionHelp() { return versionHelp; }
6273
6274 /** Returns the position in the options list in the usage help message at which this option should be shown.
6275 * Options with a lower number are shown before options with a higher number.
6276 * This attribute is only honored if {@link UsageMessageSpec#sortOptions()} is {@code false} for this command.
6277 * @see Option#order()
6278 * @since 3.9 */
6279 public int order() { return order; }
6280
6281 /** Replaces the option names with the specified values. At least one option name is required, and returns this builder.
6282 * @return this builder instance to provide a fluent interface */
6283 public Builder names(String... names) { this.names = Assert.notNull(names, "names").clone(); return self(); }
6284
6285 /** Sets whether this option disables validation of the other arguments, and returns this builder. */
6286 public Builder help(boolean help) { this.help = help; return self(); }
6287
6288 /** Sets whether this option allows the user to request usage help, and returns this builder. */
6289 public Builder usageHelp(boolean usageHelp) { this.usageHelp = usageHelp; return self(); }
6290
6291 /** Sets whether this option allows the user to request version information, and returns this builder.*/
6292 public Builder versionHelp(boolean versionHelp) { this.versionHelp = versionHelp; return self(); }
6293
6294 /** Sets the position in the options list in the usage help message at which this option should be shown, and returns this builder.
6295 * @since 3.9 */
6296 public Builder order(int order) { this.order = order; return self(); }
6297 }
6298 }
6299 /** The {@code PositionalParamSpec} class models aspects of a <em>positional parameter</em> of a {@linkplain CommandSpec command}, including whether
6300 * it is required or optional, and attributes for the usage help message describing the positional parameter.
6301 * <p>
6302 * Positional parameters have an {@link #index() index} (or a range of indices). A positional parameter is matched when the parser
6303 * encounters a command line argument at that index. Named options and their parameters do not change the index counter,
6304 * so the command line can contain a mixture of positional parameters and named options.
6305 * </p><p>
6306 * Depending on the positional parameter's {@link #arity() arity}, the parser may consume multiple command line
6307 * arguments starting from the current index. The parser will call {@link #setValue(Object) setValue} on
6308 * the {@code PositionalParamSpec} for each of the parameters encountered.
6309 * For multi-value positional parameters, the {@code type} may be an array, a {@code Collection} or a {@code Map}. In this case
6310 * the parser will get the data structure by calling {@link #getValue() getValue} and modify the contents of this data structure.
6311 * (In the case of arrays, the array is replaced with a new instance with additional elements.)
6312 * </p><p>
6313 * Before calling the setter, picocli converts the positional parameter value from a String to the parameter's type.
6314 * </p>
6315 * <ul>
6316 * <li>If a positional parameter-specific {@link #converters() converter} is configured, this will be used for type conversion.
6317 * If the positional parameter's type is a {@code Map}, the map may have different types for its keys and its values, so
6318 * {@link #converters() converters} should provide two converters: one for the map keys and one for the map values.</li>
6319 * <li>Otherwise, the positional parameter's {@link #type() type} is used to look up a converter in the list of
6320 * {@linkplain CommandLine#registerConverter(Class, ITypeConverter) registered converters}. For multi-value positional parameters,
6321 * the {@code type} may be an array, or a {@code Collection} or a {@code Map}. In that case the elements are converted
6322 * based on the positional parameter's {@link #auxiliaryTypes() auxiliaryTypes}. The auxiliaryType is used to look up
6323 * the converter(s) to use to convert the individual parameter values.
6324 * Maps may have different types for its keys and its values, so {@link #auxiliaryTypes() auxiliaryTypes}
6325 * should provide two types: one for the map keys and one for the map values.</li>
6326 * </ul>
6327 * <p>
6328 * {@code PositionalParamSpec} objects are used by the picocli command line interpreter and help message generator.
6329 * Picocli can construct a {@code PositionalParamSpec} automatically from fields and methods with {@link Parameters @Parameters}
6330 * annotations. Alternatively a {@code PositionalParamSpec} can be constructed programmatically.
6331 * </p><p>
6332 * When a {@code PositionalParamSpec} is created from a {@link Parameters @Parameters} -annotated field or method,
6333 * it is "bound" to that field or method: this field is set (or the method is invoked) when the position is matched
6334 * and {@link #setValue(Object) setValue} is called.
6335 * Programmatically constructed {@code PositionalParamSpec} instances will remember the value passed to the
6336 * {@link #setValue(Object) setValue} method so it can be retrieved with the {@link #getValue() getValue} method.
6337 * This behaviour can be customized by installing a custom {@link IGetter} and {@link ISetter} on the {@code PositionalParamSpec}.
6338 * </p>
6339 * @since 3.0 */
6340 public static class PositionalParamSpec extends ArgSpec {
6341 private Range index;
6342 private Range capacity;
6343
6344 /** Ensures all attributes of this {@code PositionalParamSpec} have a valid value; throws an {@link InitializationException} if this cannot be achieved. */
6345 private PositionalParamSpec(Builder builder) {
6346 super(builder);
6347 index = builder.index == null ? Range.valueOf("*") : builder.index;
6348 capacity = builder.capacity == null ? Range.parameterCapacity(arity(), index) : builder.capacity;
6349 if (toString == null) { toString = "positional parameter[" + index() + "]"; }
6350 }
6351 public static Builder builder() { return new Builder(); }
6352 public static Builder builder(IAnnotatedElement source, IFactory factory) { return new Builder(source, factory); }
6353 /** Returns a new Builder initialized with the attributes from this {@code PositionalParamSpec}. Calling {@code build} immediately will return a copy of this {@code PositionalParamSpec}.
6354 * @return a builder that can create a copy of this spec
6355 */
6356 public Builder toBuilder() { return new Builder(this); }
6357 @Override public boolean isOption() { return false; }
6358 @Override public boolean isPositional() { return true; }
6359
6360 /** Returns the description template of this positional parameter, before variables are {@linkplain Parameters#description() rendered}.
6361 * If a resource bundle has been {@linkplain ArgSpec#messages(Messages) set}, this method will first try to find a value in the resource bundle:
6362 * If the resource bundle has no entry for the {@code fully qualified commandName + "." + descriptionKey} or for the unqualified {@code descriptionKey},
6363 * an attempt is made to find the positional parameter description using {@code paramLabel() + "[" + index() + "]"} as key,
6364 * first with the {@code fully qualified commandName + "."} prefix, then without.
6365 * @see Parameters#description()
6366 * @see CommandSpec#qualifiedName(String)
6367 * @since 3.6 */
6368 @Override public String[] description() {
6369 if (messages() == null) { return super.description(); }
6370 String[] newValue = messages().getStringArray(descriptionKey(), null);
6371 if (newValue != null) { return newValue; }
6372 newValue = messages().getStringArray(paramLabel() + "[" + index() + "]", null);
6373 if (newValue != null) { return newValue; }
6374 return super.description();
6375 }
6376
6377 /** Returns an index or range specifying which of the command line arguments should be assigned to this positional parameter.
6378 * @see Parameters#index() */
6379 public Range index() { return index; }
6380 private Range capacity() { return capacity; }
6381
6382 public int hashCode() {
6383 return super.hashCodeImpl()
6384 + 37 * Assert.hashCode(capacity)
6385 + 37 * Assert.hashCode(index);
6386 }
6387 public boolean equals(Object obj) {
6388 if (obj == this) {
6389 return true;
6390 }
6391 if (!(obj instanceof PositionalParamSpec)) {
6392 return false;
6393 }
6394 PositionalParamSpec other = (PositionalParamSpec) obj;
6395 return super.equalsImpl(other)
6396 && Assert.equals(this.capacity, other.capacity)
6397 && Assert.equals(this.index, other.index);
6398 }
6399
6400 /** Builder responsible for creating valid {@code PositionalParamSpec} objects.
6401 * @since 3.0
6402 */
6403 public static class Builder extends ArgSpec.Builder<Builder> {
6404 private Range capacity;
6405 private Range index;
6406 private Builder() {}
6407 private Builder(PositionalParamSpec original) {
6408 super(original);
6409 index = original.index;
6410 capacity = original.capacity;
6411 }
6412 private Builder(IAnnotatedElement member, IFactory factory) {
6413 super(member.getAnnotation(Parameters.class), member, factory);
6414 index = Range.parameterIndex(member);
6415 capacity = Range.parameterCapacity(member);
6416 }
6417 /** Returns a valid {@code PositionalParamSpec} instance. */
6418 @Override public PositionalParamSpec build() { return new PositionalParamSpec(this); }
6419 /** Returns this builder. */
6420 @Override protected Builder self() { return this; }
6421
6422 /** Returns an index or range specifying which of the command line arguments should be assigned to this positional parameter.
6423 * @see Parameters#index() */
6424 public Range index() { return index; }
6425
6426 /** Sets the index or range specifying which of the command line arguments should be assigned to this positional parameter, and returns this builder. */
6427 public Builder index(String range) { return index(Range.valueOf(range)); }
6428
6429 /** Sets the index or range specifying which of the command line arguments should be assigned to this positional parameter, and returns this builder. */
6430 public Builder index(Range index) { this.index = index; return self(); }
6431
6432 Range capacity() { return capacity; }
6433 Builder capacity(Range capacity) { this.capacity = capacity; return self(); }
6434 }
6435 }
6436
6437 /** Interface for sorting {@link OptionSpec options} and {@link ArgGroupSpec groups} together.
6438 * @since 4.0 */
6439 public interface IOrdered {
6440 /** Returns the position in the options list in the usage help message at which this element should be shown.
6441 * Elements with a lower number are shown before elements with a higher number.
6442 * This attribute is only honored if {@link UsageMessageSpec#sortOptions()} is {@code false} for this command. */
6443 int order();
6444 }
6445
6446 /** The {@code ArgGroupSpec} class models a {@link ArgGroup group} of arguments (options, positional parameters or a mixture of the two).
6447 * @see ArgGroup
6448 * @since 4.0 */
6449 public static class ArgGroupSpec implements IOrdered {
6450 static final int DEFAULT_ORDER = -1;
6451 private static final String NO_HEADING = "__no_heading__";
6452 private static final String NO_HEADING_KEY = "__no_heading_key__";
6453 private final String heading;
6454 private final String headingKey;
6455 private final boolean exclusive;
6456 private final Range multiplicity;
6457 private final boolean validate;
6458 private final int order;
6459 private final IGetter getter;
6460 private final ISetter setter;
6461 private final IScope scope;
6462 private final ITypeInfo typeInfo;
6463 private final List<ArgGroupSpec> subgroups;
6464 private final Set<ArgSpec> args;
6465 private Messages messages;
6466 private ArgGroupSpec parentGroup;
6467 private String id = "1";
6468
6469 ArgGroupSpec(ArgGroupSpec.Builder builder) {
6470 heading = NO_HEADING .equals(builder.heading) ? null : builder.heading;
6471 headingKey = NO_HEADING_KEY.equals(builder.headingKey) ? null : builder.headingKey;
6472 exclusive = builder.exclusive;
6473 multiplicity = builder.multiplicity;
6474 validate = builder.validate;
6475 order = builder.order;
6476 typeInfo = builder.typeInfo;
6477 getter = builder.getter;
6478 setter = builder.setter;
6479 scope = builder.scope;
6480
6481 args = Collections.unmodifiableSet(new LinkedHashSet<ArgSpec>(builder.args()));
6482 subgroups = Collections.unmodifiableList(new ArrayList<ArgGroupSpec>(builder.subgroups()));
6483 if (args.isEmpty() && subgroups.isEmpty()) { throw new InitializationException("ArgGroup has no options or positional parameters, and no subgroups"); }
6484
6485 int i = 1;
6486 for (ArgGroupSpec sub : subgroups) { sub.parentGroup = this; sub.id = id + "." + i++; }
6487 for (ArgSpec arg : args) { arg.group = this; }
6488 }
6489
6490 /** Returns a new {@link Builder}.
6491 * @return a new ArgGroupSpec.Builder instance */
6492 public static Builder builder() { return new Builder(); }
6493
6494 /** Returns a new {@link Builder} associated with the specified annotated element.
6495 * @param annotatedElement the annotated element containing {@code @Option} and {@code @Parameters}
6496 * @return a new ArgGroupSpec.Builder instance */
6497 public static Builder builder(IAnnotatedElement annotatedElement) { return new Builder(Assert.notNull(annotatedElement, "annotatedElement")); }
6498
6499 /** Returns whether this is a mutually exclusive group; {@code true} by default.
6500 * If {@code false}, this is a co-occurring group. Ignored if {@link #validate()} is {@code false}.
6501 * @see ArgGroup#exclusive() */
6502 public boolean exclusive() { return exclusive; }
6503
6504 /** Returns the multiplicity of this group: how many occurrences it may have on the command line; {@code "0..1"} (optional) by default.
6505 * A group can be made required by specifying a multiplicity of {@code "1"}. For a group of mutually exclusive arguments,
6506 * being required means that one of the arguments in the group must appear on the command line, or a MissingParameterException is thrown.
6507 * For a group of co-occurring arguments, being required means that all arguments in the group must appear on the command line.
6508 * Ignored if {@link #validate()} is {@code false}.
6509 * @see ArgGroup#multiplicity() */
6510 public Range multiplicity() { return multiplicity; }
6511
6512 /** Returns whether picocli should validate the rules of this group:
6513 * for a mutually exclusive group this means that no more than one arguments in the group is specified on the command line;
6514 * for a co-ocurring group this means that all arguments in the group are specified on the command line.
6515 * {@code true} by default.
6516 * @see ArgGroup#validate() */
6517 public boolean validate() { return validate; }
6518
6519 /** Returns the position in the options list in the usage help message at which this group should be shown.
6520 * Options with a lower number are shown before options with a higher number.
6521 * This attribute is only honored if {@link UsageMessageSpec#sortOptions()} is {@code false} for this command. */
6522 public int order() { return this.order; }
6523
6524 /** Returns the heading of this group (may be {@code null}), used when generating the usage documentation.
6525 * @see ArgGroup#heading() */
6526 public String heading() {
6527 if (messages() == null) { return heading; }
6528 String newValue = messages().getString(headingKey(), null);
6529 if (newValue != null) { return newValue; }
6530 return heading;
6531 }
6532
6533 /** Returns the heading key of this group (may be {@code null}), used to get the heading from a resource bundle.
6534 * @see ArgGroup#headingKey() */
6535 public String headingKey() { return headingKey; }
6536
6537 /**
6538 * Returns the parent group that this group is part of, or {@code null} if this group is not part of a composite.
6539 */
6540 public ArgGroupSpec parentGroup() { return parentGroup; }
6541
6542 /** Return the subgroups that this group is composed of; may be empty but not {@code null}.
6543 * @return immutable list of subgroups that this group is composed of. */
6544 public List<ArgGroupSpec> subgroups() { return subgroups; }
6545
6546 /**
6547 * Returns {@code true} if this group is a subgroup (or a nested sub-subgroup, to any level of depth)
6548 * of the specified group, {@code false} otherwise.
6549 * @param group the group to check if it contains this group
6550 * @return {@code true} if this group is a subgroup or a nested sub-subgroup of the specified group
6551 */
6552 public boolean isSubgroupOf(ArgGroupSpec group) {
6553 for (ArgGroupSpec sub : group.subgroups) {
6554 if (this == sub) { return true; }
6555 if (isSubgroupOf(sub)) { return true; }
6556 }
6557 return false;
6558 }
6559 /** Returns the type info for the annotated program element associated with this group.
6560 * @return type information that does not require {@code Class} objects and be constructed both at runtime and compile time
6561 */
6562 public ITypeInfo typeInfo() { return typeInfo; }
6563
6564 /** Returns the {@link IGetter} that is responsible for supplying the value of the annotated program element associated with this group. */
6565 public IGetter getter() { return getter; }
6566 /** Returns the {@link ISetter} that is responsible for modifying the value of the annotated program element associated with this group. */
6567 public ISetter setter() { return setter; }
6568 /** 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. */
6569 public IScope scope() { return scope; }
6570
6571 Object userObject() { try { return getter.get(); } catch (Exception ex) { return ex.toString(); } }
6572 String id() { return id; }
6573
6574 /** Returns the options and positional parameters in this group; may be empty but not {@code null}. */
6575 public Set<ArgSpec> args() { return args; }
6576 /** Returns the required options and positional parameters in this group; may be empty but not {@code null}. */
6577 public Set<ArgSpec> requiredArgs() {
6578 Set<ArgSpec> result = new LinkedHashSet<ArgSpec>(args);
6579 for (Iterator<ArgSpec> iter = result.iterator(); iter.hasNext(); ) {
6580 if (!iter.next().required()) { iter.remove(); }
6581 }
6582 return Collections.unmodifiableSet(result);
6583 }
6584
6585 /** Returns the list of positional parameters configured for this group.
6586 * @return an immutable list of positional parameters in this group. */
6587 public List<PositionalParamSpec> positionalParameters() {
6588 List<PositionalParamSpec> result = new ArrayList<PositionalParamSpec>();
6589 for (ArgSpec arg : args()) { if (arg instanceof PositionalParamSpec) { result.add((PositionalParamSpec) arg); } }
6590 return Collections.unmodifiableList(result);
6591 }
6592 /** Returns the list of options configured for this group.
6593 * @return an immutable list of options in this group. */
6594 public List<OptionSpec> options() {
6595 List<OptionSpec> result = new ArrayList<OptionSpec>();
6596 for (ArgSpec arg : args()) { if (arg instanceof OptionSpec) { result.add((OptionSpec) arg); } }
6597 return Collections.unmodifiableList(result);
6598 }
6599
6600 public String synopsis() {
6601 return synopsisText(new Help.ColorScheme(Help.Ansi.OFF)).toString();
6602 }
6603
6604 public Text synopsisText(Help.ColorScheme colorScheme) {
6605 String infix = exclusive() ? " | " : " ";
6606 Text synopsis = colorScheme.ansi().new Text(0);
6607 for (ArgSpec arg : args()) {
6608 if (synopsis.length > 0) { synopsis = synopsis.concat(infix); }
6609 if (arg instanceof OptionSpec) {
6610 synopsis = concatOptionText(synopsis, colorScheme, (OptionSpec) arg);
6611 } else {
6612 synopsis = concatPositionalText(synopsis, colorScheme, (PositionalParamSpec) arg);
6613 }
6614 }
6615 for (ArgGroupSpec subgroup : subgroups()) {
6616 if (synopsis.length > 0) { synopsis = synopsis.concat(infix); }
6617 synopsis = synopsis.concat(subgroup.synopsisText(colorScheme));
6618 }
6619 String prefix = multiplicity().min > 0 ? "(" : "[";
6620 String postfix = multiplicity().min > 0 ? ")" : "]";
6621 Text result = colorScheme.ansi().text(prefix).concat(synopsis).concat(postfix);
6622 int i = 1;
6623 for (; i < multiplicity.min; i++) {
6624 result = result.concat(" (").concat(synopsis).concat(")");
6625 }
6626 if (multiplicity().isVariable) {
6627 result = result.concat("...");
6628 } else {
6629 for (; i < multiplicity.max; i++) {
6630 result = result.concat(" [").concat(synopsis).concat("]");
6631 }
6632 }
6633 return result;
6634 }
6635
6636 private Text concatOptionText(Text text, Help.ColorScheme colorScheme, OptionSpec option) {
6637 if (!option.hidden()) {
6638 Text name = colorScheme.optionText(option.shortestName());
6639 Text param = createLabelRenderer(option.commandSpec).renderParameterLabel(option, colorScheme.ansi(), colorScheme.optionParamStyles);
6640 text = text.concat(open(option)).concat(name).concat(param).concat(close(option));
6641 if (option.isMultiValue()) { // e.g., -x=VAL [-x=VAL]...
6642 text = text.concat(" [").concat(name).concat(param).concat("]...");
6643 }
6644 }
6645 return text;
6646 }
6647
6648 private Text concatPositionalText(Text text, Help.ColorScheme colorScheme, PositionalParamSpec positionalParam) {
6649 if (!positionalParam.hidden()) {
6650 Text label = createLabelRenderer(positionalParam.commandSpec).renderParameterLabel(positionalParam, colorScheme.ansi(), colorScheme.parameterStyles);
6651 text = text.concat(open(positionalParam)).concat(label).concat(close(positionalParam));
6652 }
6653 return text;
6654 }
6655 private String open(ArgSpec argSpec) { return argSpec.required() ? "" : "["; }
6656 private String close(ArgSpec argSpec) { return argSpec.required() ? "" : "]"; }
6657
6658 public Help.IParamLabelRenderer createLabelRenderer(CommandSpec commandSpec) {
6659 return new Help.DefaultParamLabelRenderer(commandSpec == null ? CommandSpec.create() : commandSpec);
6660 }
6661 /** Returns the Messages for this argument group specification, or {@code null}. */
6662 public Messages messages() { return messages; }
6663 /** Sets the Messages for this ArgGroupSpec, and returns this ArgGroupSpec.
6664 * @param msgs the new Messages value, may be {@code null}
6665 * @see Command#resourceBundle()
6666 * @see #headingKey()
6667 */
6668 public ArgGroupSpec messages(Messages msgs) {
6669 messages = msgs;
6670 for (ArgGroupSpec sub : subgroups()) { sub.messages(msgs); }
6671 return this;
6672 }
6673
6674 @Override public boolean equals(Object obj) {
6675 if (obj == this) { return true; }
6676 if (!(obj instanceof ArgGroupSpec)) { return false; }
6677 ArgGroupSpec other = (ArgGroupSpec) obj;
6678 return exclusive == other.exclusive
6679 && Assert.equals(multiplicity, other.multiplicity)
6680 && validate == other.validate
6681 && order == other.order
6682 && Assert.equals(heading, other.heading)
6683 && Assert.equals(headingKey, other.headingKey)
6684 && Assert.equals(subgroups, other.subgroups)
6685 && Assert.equals(args, other.args);
6686 }
6687
6688 @Override public int hashCode() {
6689 int result = 17;
6690 result += 37 * result + Assert.hashCode(exclusive);
6691 result += 37 * result + Assert.hashCode(multiplicity);
6692 result += 37 * result + Assert.hashCode(validate);
6693 result += 37 * result + order;
6694 result += 37 * result + Assert.hashCode(heading);
6695 result += 37 * result + Assert.hashCode(headingKey);
6696 result += 37 * result + Assert.hashCode(subgroups);
6697 result += 37 * result + Assert.hashCode(args);
6698 return result;
6699 }
6700
6701 @Override public String toString() {
6702 List<String> argNames = new ArrayList<String>();
6703 for (ArgSpec arg : args()) {
6704 if (arg instanceof OptionSpec) {
6705 argNames.add(((OptionSpec) arg).shortestName());
6706 } else {
6707 PositionalParamSpec p = (PositionalParamSpec) arg;
6708 argNames.add(p.index() + " (" + p.paramLabel() + ")");
6709 }
6710 }
6711 return "ArgGroup[exclusive=" + exclusive + ", multiplicity=" + multiplicity +
6712 ", validate=" + validate + ", order=" + order + ", args=[" + ArgSpec.describe(args()) +
6713 "], headingKey=" + quote(headingKey) + ", heading=" + quote(heading) +
6714 ", subgroups=" + subgroups + "]";
6715 }
6716 private static String quote(String s) { return s == null ? "null" : "'" + s + "'"; }
6717
6718 void initUserObject(CommandLine commandLine) {
6719 if (commandLine == null) { new Tracer().debug("Could not create user object for %s with null CommandLine%n.", this); }
6720 try {
6721 tryInitUserObject(commandLine);
6722 } catch (PicocliException ex) {
6723 throw ex;
6724 } catch (Exception ex) {
6725 throw new InitializationException("Could not create user object for " + this, ex);
6726 }
6727 }
6728 void tryInitUserObject(CommandLine commandLine) throws Exception {
6729 Tracer tracer = commandLine.tracer;
6730 if (typeInfo() != null) {
6731 tracer.debug("Creating new user object of type %s for group %s%n", typeInfo().getAuxiliaryTypes()[0], synopsis());
6732 Object userObject = DefaultFactory.create(commandLine.factory, typeInfo().getAuxiliaryTypes()[0]);
6733 tracer.debug("Created %s, invoking setter %s with scope %s%n", userObject, setter(), scope());
6734 setUserObject(userObject, commandLine.factory);
6735 for (ArgSpec arg : args()) {
6736 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);
6737 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)
6738 commandLine.interpreter.parseResultBuilder.isInitializingDefaultValues = true;
6739 arg.applyInitialValue(tracer);
6740 commandLine.interpreter.applyDefault(commandLine.getCommandSpec().defaultValueProvider(), arg);
6741 commandLine.interpreter.parseResultBuilder.isInitializingDefaultValues = false;
6742 }
6743 for (ArgGroupSpec subgroup : subgroups()) {
6744 tracer.debug("Setting scope for subgroup %s with setter=%s in group %s to user object %s%n", subgroup.synopsis(), subgroup.setter(), synopsis(), userObject);
6745 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)
6746 }
6747 } else {
6748 tracer.debug("No type information available for group %s: cannot create new user object. Scope for arg setters is not changed.%n", synopsis());
6749 }
6750 tracer.debug("Initialization complete for group %s%n", synopsis());
6751 }
6752
6753 void setUserObject(Object userObject, IFactory factory) throws Exception {
6754 if (typeInfo().isCollection()) {
6755 @SuppressWarnings("unchecked") Collection<Object> c = (Collection<Object>) getter().get();
6756 if (c == null) {
6757 @SuppressWarnings("unchecked")
6758 Collection<Object> c2 = (Collection<Object>) DefaultFactory.create(factory, typeInfo.getType());
6759 setter().set(c = c2);
6760 }
6761 (c).add(userObject);
6762 } else if (typeInfo().isArray()) {
6763 Object old = getter().get();
6764 int oldSize = old == null ? 0 : Array.getLength(old);
6765 Object array = Array.newInstance(typeInfo().getAuxiliaryTypes()[0], oldSize + 1);
6766 for (int i = 0; i < oldSize; i++) {
6767 Array.set(array, i, Array.get(old, i));
6768 }
6769 Array.set(array, oldSize, userObject);
6770 setter().set(array);
6771 } else {
6772 setter().set(userObject);
6773 }
6774 }
6775
6776 enum GroupValidationResult {
6777 SUCCESS_PRESENT, SUCCESS_ABSENT,
6778 FAILURE_PRESENT, FAILURE_ABSENT, FAILURE_PARTIAL;
6779 static boolean containsBlockingFailure(EnumSet<GroupValidationResult> set) {
6780 return set.contains(FAILURE_PRESENT) || set.contains(FAILURE_PARTIAL);
6781 }
6782 /** FAILURE_PRESENT or FAILURE_PARTIAL */
6783 boolean blockingFailure() { return this == FAILURE_PRESENT || this == FAILURE_PARTIAL; }
6784 boolean present() { return this == SUCCESS_PRESENT /*|| this == FAILURE_PRESENT*/; }
6785 boolean success() { return this == SUCCESS_ABSENT || this == SUCCESS_PRESENT; }
6786 }
6787
6788 private ParameterException validationException;
6789 private GroupValidationResult validationResult;
6790
6791 /** Clears temporary validation state for this group and its subgroups. */
6792 void clearValidationResult() {
6793 validationException = null;
6794 validationResult = null;
6795 for (ArgGroupSpec sub : subgroups()) { sub.clearValidationResult(); }
6796 }
6797
6798 /** Throws an exception if the constraints in this group are not met by the specified match. */
6799 void validateConstraints(ParseResult parseResult) {
6800 if (!validate()) { return; }
6801 CommandLine commandLine = parseResult.commandSpec().commandLine();
6802
6803 // first validate args in this group
6804 validationResult = validateArgs(commandLine, parseResult);
6805 if (validationResult.blockingFailure()) {
6806 commandLine.interpreter.maybeThrow(validationException); // composite parent validations cannot succeed anyway
6807 }
6808 // then validate sub groups
6809 EnumSet<GroupValidationResult> validationResults = validateSubgroups(parseResult);
6810 if (GroupValidationResult.containsBlockingFailure(validationResults)) {
6811 commandLine.interpreter.maybeThrow(validationException); // composite parent validations cannot succeed anyway
6812 }
6813 List<MatchedGroup> matchedGroups = parseResult.findMatchedGroup(this);
6814 if (matchedGroups.isEmpty()) {
6815 // TODO can/should we verify minimum multiplicity here?
6816 if (multiplicity().min > 0) {
6817 if (validationResult.success()) {
6818 validationResult = GroupValidationResult.FAILURE_ABSENT;
6819 validationException = new MissingParameterException(commandLine, args(),
6820 "Error: Group: " + synopsis() + " must be specified " + multiplicity().min + " times but was missing");
6821 }
6822 }
6823 }
6824 for (MatchedGroup matchedGroup : matchedGroups) {
6825 int matchCount = matchedGroup.multiples().size();
6826 // note: matchCount == 0 if only subgroup(s) are matched for a group without args (subgroups-only)
6827 boolean checkMinimum = matchCount > 0 || !args().isEmpty();
6828 if (checkMinimum && matchCount < multiplicity().min) {
6829 if (validationResult.success()) {
6830 validationResult = matchCount == 0 ? GroupValidationResult.FAILURE_ABSENT: GroupValidationResult.FAILURE_PARTIAL;
6831 validationException = new MissingParameterException(commandLine, args(),
6832 "Error: Group: " + synopsis() + " must be specified " + multiplicity().min + " times but was matched " + matchCount + " times");
6833 }
6834 } else if (matchCount > multiplicity().max) {
6835 if (!validationResult.blockingFailure()) {
6836 validationResult = GroupValidationResult.FAILURE_PRESENT;
6837 validationException = new MaxValuesExceededException(commandLine,
6838 "Error: Group: " + synopsis() + " can only be specified " + multiplicity().max + " times but was matched " + matchCount + " times.");
6839 }
6840 }
6841 if (validationResult.blockingFailure()) {
6842 commandLine.interpreter.maybeThrow(validationException);
6843 }
6844 }
6845 if (validationException != null && parentGroup == null) {
6846 commandLine.interpreter.maybeThrow(validationException);
6847 }
6848 }
6849
6850 private EnumSet<GroupValidationResult> validateSubgroups(ParseResult parseResult) {
6851 EnumSet<GroupValidationResult> validationResults = EnumSet.of(validationResult);
6852 if (subgroups().isEmpty()) { return validationResults; }
6853 for (ArgGroupSpec subgroup : subgroups()) {
6854 subgroup.validateConstraints(parseResult);
6855 validationResults.add(Assert.notNull(subgroup.validationResult, "subgroup validation result"));
6856 if (subgroup.validationResult.blockingFailure()) { this.validationException = subgroup.validationException; break; }
6857 }
6858 // now do some coarse-grained checking for exclusive subgroups
6859 int elementCount = args().size() + subgroups().size();
6860 int presentCount = validationResult.present() ? 1 : 0;
6861 String exclusiveElements = "";
6862 for (ArgGroupSpec subgroup : subgroups()) {
6863 if (!parseResult.findMatchedGroup(subgroup).isEmpty()) { presentCount++; }
6864 //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
6865 if (exclusiveElements.length() > 0) { exclusiveElements += " and "; }
6866 exclusiveElements += subgroup.synopsis();
6867 }
6868 validationResult = validate(parseResult.commandSpec().commandLine(), presentCount, presentCount < elementCount,
6869 presentCount > 0 && presentCount < elementCount, exclusiveElements, synopsis(), synopsis());
6870 validationResults.add(validationResult);
6871 return validationResults;
6872 }
6873
6874 private GroupValidationResult validateArgs(CommandLine commandLine, ParseResult parseResult) {
6875 if (args().isEmpty()) { return GroupValidationResult.SUCCESS_ABSENT; }
6876 return validateArgs(commandLine, parseResult.findMatchedGroup(this));
6877 }
6878
6879 private GroupValidationResult validateArgs(CommandLine commandLine, List<MatchedGroup> matchedGroups) {
6880 if (matchedGroups.isEmpty()) {
6881 int presentCount = 0;
6882 boolean haveMissing = true;
6883 boolean someButNotAllSpecified = false;
6884 String exclusiveElements = "";
6885 String missingElements = ArgSpec.describe(requiredArgs());
6886 return validate(commandLine, presentCount, haveMissing, someButNotAllSpecified, exclusiveElements, missingElements, missingElements);
6887 }
6888 GroupValidationResult result = GroupValidationResult.SUCCESS_ABSENT;
6889 Map<MatchedGroup, List<MatchedGroup>> byParent = groupByParent(matchedGroups);
6890 for (Map.Entry<MatchedGroup, List<MatchedGroup>> entry : byParent.entrySet()) {
6891 List<MatchedGroup> allForOneParent = entry.getValue();
6892 for (MatchedGroup oneForOneParent : allForOneParent) {
6893 result = validateMultiples(commandLine, oneForOneParent.multiples());
6894 if (result.blockingFailure()) { return result; }
6895 }
6896 }
6897 return result;
6898 }
6899
6900 private Map<MatchedGroup, List<MatchedGroup>> groupByParent(List<MatchedGroup> matchedGroups) {
6901 Map<MatchedGroup, List<MatchedGroup>> result = new HashMap<MatchedGroup, List<MatchedGroup>>();
6902 for (MatchedGroup mg : matchedGroups) {
6903 addValueToListInMap(result, mg.parentMatchedGroup(), mg);
6904 }
6905 return result;
6906 }
6907
6908 private List<ParseResult.MatchedGroupMultiple> flatListMultiples(Collection<MatchedGroup> matchedGroups) {
6909 List<ParseResult.MatchedGroupMultiple> all = new ArrayList<ParseResult.MatchedGroupMultiple>();
6910 for (MatchedGroup matchedGroup : matchedGroups) {
6911 all.addAll(matchedGroup.multiples());
6912 }
6913 return all;
6914 }
6915
6916 private GroupValidationResult validateMultiples(CommandLine commandLine, List<ParseResult.MatchedGroupMultiple> multiples) {
6917 Set<ArgSpec> intersection = new LinkedHashSet<ArgSpec>(this.args());
6918 Set<ArgSpec> missing = new LinkedHashSet<ArgSpec>(this.requiredArgs());
6919 Set<ArgSpec> found = new LinkedHashSet<ArgSpec>();
6920 for (ParseResult.MatchedGroupMultiple multiple : multiples) {
6921 found.addAll(multiple.matchedValues.keySet());
6922 missing.removeAll(multiple.matchedValues.keySet());
6923 }
6924 intersection.retainAll(found);
6925 int presentCount = intersection.size();
6926 boolean haveMissing = !missing.isEmpty();
6927 boolean someButNotAllSpecified = haveMissing && !intersection.isEmpty();
6928 String exclusiveElements = ArgSpec.describe(intersection);
6929 String requiredElements = ArgSpec.describe(requiredArgs());
6930 String missingElements = ArgSpec.describe(missing);
6931
6932 return validate(commandLine, presentCount, haveMissing, someButNotAllSpecified, exclusiveElements, requiredElements, missingElements);
6933 }
6934
6935 private GroupValidationResult validate(CommandLine commandLine, int presentCount, boolean haveMissing, boolean someButNotAllSpecified, String exclusiveElements, String requiredElements, String missingElements) {
6936 if (exclusive()) {
6937 if (presentCount > 1) {
6938 validationException = new MutuallyExclusiveArgsException(commandLine,
6939 "Error: " + exclusiveElements + " are mutually exclusive (specify only one)");
6940 return GroupValidationResult.FAILURE_PRESENT;
6941 }
6942 // check that exactly one member was matched
6943 if (multiplicity().min > 0 && presentCount < 1) {
6944 validationException = new MissingParameterException(commandLine, args(),
6945 "Error: Missing required argument (specify one of these): " + requiredElements);
6946 return GroupValidationResult.FAILURE_ABSENT;
6947 }
6948 return GroupValidationResult.SUCCESS_PRESENT;
6949 } else { // co-occurring group
6950 if (someButNotAllSpecified) {
6951 validationException = new MissingParameterException(commandLine, args(),
6952 "Error: Missing required argument(s): " + missingElements);
6953 return GroupValidationResult.FAILURE_PARTIAL;
6954 }
6955 if ((multiplicity().min > 0 && haveMissing)) {
6956 validationException = new MissingParameterException(commandLine, args(),
6957 "Error: Missing required argument(s): " + missingElements);
6958 return GroupValidationResult.FAILURE_ABSENT;
6959 }
6960 return haveMissing ? GroupValidationResult.SUCCESS_ABSENT : GroupValidationResult.SUCCESS_PRESENT;
6961 }
6962 }
6963
6964 /** Builder responsible for creating valid {@code ArgGroupSpec} objects.
6965 * @since 4.0 */
6966 public static class Builder {
6967 private IGetter getter;
6968 private ISetter setter;
6969 private IScope scope;
6970 private ITypeInfo typeInfo;
6971 private String heading;
6972 private String headingKey;
6973 private boolean exclusive = true;
6974 private Range multiplicity = Range.valueOf("0..1");
6975 private boolean validate = true;
6976 private int order = DEFAULT_ORDER;
6977 private List<ArgSpec> args = new ArrayList<ArgSpec>();
6978 private List<ArgGroupSpec> subgroups = new ArrayList<ArgGroupSpec>();
6979
6980 // for topological sorting; private only
6981 private Boolean topologicalSortDone;
6982 private List<Builder> compositesReferencingMe = new ArrayList<Builder>();
6983
6984 Builder() { }
6985 Builder(IAnnotatedElement source) {
6986 typeInfo = source.getTypeInfo();
6987 getter = source.getter();
6988 setter = source.setter();
6989 scope = source.scope();
6990 }
6991
6992 /** Updates this builder from the specified annotation values.
6993 * @param group annotation values
6994 * @return this builder for method chaining */
6995 public Builder updateArgGroupAttributes(ArgGroup group) {
6996 return this
6997 .heading(group.heading())
6998 .headingKey(group.headingKey())
6999 .exclusive(group.exclusive())
7000 .multiplicity(group.multiplicity())
7001 .validate(group.validate())
7002 .order(group.order());
7003 }
7004
7005 /** Returns a valid {@code ArgGroupSpec} instance. */
7006 public ArgGroupSpec build() { return new ArgGroupSpec(this); }
7007
7008 /** Returns whether this is a mutually exclusive group; {@code true} by default.
7009 * If {@code false}, this is a co-occurring group. Ignored if {@link #validate()} is {@code false}.
7010 * @see ArgGroup#exclusive() */
7011 public boolean exclusive() { return exclusive; }
7012 /** Sets whether this is a mutually exclusive group; {@code true} by default.
7013 * If {@code false}, this is a co-occurring group. Ignored if {@link #validate()} is {@code false}.
7014 * @see ArgGroup#exclusive() */
7015 public Builder exclusive(boolean newValue) { exclusive = newValue; return this; }
7016
7017 /** Returns the multiplicity of this group: how many occurrences it may have on the command line; {@code "0..1"} (optional) by default.
7018 * A group can be made required by specifying a multiplicity of {@code "1"}. For a group of mutually exclusive arguments,
7019 * being required means that one of the arguments in the group must appear on the command line, or a MissingParameterException is thrown.
7020 * For a group of co-occurring arguments, being required means that all arguments in the group must appear on the command line.
7021 * Ignored if {@link #validate()} is {@code false}.
7022 * @see ArgGroup#multiplicity() */
7023 public Range multiplicity() { return multiplicity; }
7024 /** Sets the multiplicity of this group: how many occurrences it may have on the command line; {@code "0..1"} (optional) by default.
7025 * A group can be made required by specifying a multiplicity of {@code "1"}. For a group of mutually exclusive arguments,
7026 * being required means that one of the arguments in the group must appear on the command line, or a MissingParameterException is thrown.
7027 * For a group of co-occurring arguments, being required means that all arguments in the group must appear on the command line.
7028 * Ignored if {@link #validate()} is {@code false}.
7029 * @see ArgGroup#multiplicity() */
7030 public Builder multiplicity(String newValue) { return multiplicity(Range.valueOf(newValue)); }
7031 /** Sets the multiplicity of this group: how many occurrences it may have on the command line; {@code "0..1"} (optional) by default.
7032 * A group can be made required by specifying a multiplicity of {@code "1"}. For a group of mutually exclusive arguments,
7033 * being required means that one of the arguments in the group must appear on the command line, or a MissingParameterException is thrown.
7034 * For a group of co-occurring arguments, being required means that all arguments in the group must appear on the command line.
7035 * Ignored if {@link #validate()} is {@code false}.
7036 * @see ArgGroup#multiplicity() */
7037 public Builder multiplicity(Range newValue) { multiplicity = newValue; return this; }
7038
7039 /** Returns whether picocli should validate the rules of this group:
7040 * for a mutually exclusive group this means that no more than one arguments in the group is specified on the command line;
7041 * for a co-ocurring group this means that all arguments in the group are specified on the command line.
7042 * {@code true} by default.
7043 * @see ArgGroup#validate() */
7044 public boolean validate() { return validate; }
7045 /** Sets whether picocli should validate the rules of this group:
7046 * for a mutually exclusive group this means that no more than one arguments in the group is specified on the command line;
7047 * for a co-ocurring group this means that all arguments in the group are specified on the command line.
7048 * {@code true} by default.
7049 * @see ArgGroup#validate() */
7050 public Builder validate(boolean newValue) { validate = newValue; return this; }
7051
7052 /** Returns the position in the options list in the usage help message at which this group should be shown.
7053 * Options with a lower number are shown before options with a higher number.
7054 * This attribute is only honored if {@link UsageMessageSpec#sortOptions()} is {@code false} for this command.*/
7055 public int order() { return order; }
7056
7057 /** Sets the position in the options list in the usage help message at which this group should be shown, and returns this builder. */
7058 public Builder order(int order) { this.order = order; return this; }
7059
7060 /** Returns the heading of this group, used when generating the usage documentation.
7061 * @see ArgGroup#heading() */
7062 public String heading() { return heading; }
7063
7064 /** Sets the heading of this group (may be {@code null}), used when generating the usage documentation.
7065 * @see ArgGroup#heading() */
7066 public Builder heading(String newValue) { this.heading = newValue; return this; }
7067
7068 /** Returns the heading key of this group, used to get the heading from a resource bundle.
7069 * @see ArgGroup#headingKey() */
7070 public String headingKey() { return headingKey; }
7071 /** Sets the heading key of this group, used to get the heading from a resource bundle.
7072 * @see ArgGroup#headingKey() */
7073 public Builder headingKey(String newValue) { this.headingKey = newValue; return this; }
7074
7075 /** Returns the type info for the annotated program element associated with this group.
7076 * @return type information that does not require {@code Class} objects and be constructed both at runtime and compile time
7077 */
7078 public ITypeInfo typeInfo() { return typeInfo; }
7079 /** Sets the type info for the annotated program element associated with this group, and returns this builder.
7080 * @param newValue type information that does not require {@code Class} objects and be constructed both at runtime and compile time
7081 */
7082 public Builder typeInfo(ITypeInfo newValue) { this.typeInfo = newValue; return this; }
7083
7084 /** Returns the {@link IGetter} that is responsible for supplying the value of the annotated program element associated with this group. */
7085 public IGetter getter() { return getter; }
7086 /** Sets the {@link IGetter} that is responsible for getting the value of the annotated program element associated with this group, and returns this builder. */
7087 public Builder getter(IGetter getter) { this.getter = getter; return this; }
7088
7089 /** Returns the {@link ISetter} that is responsible for modifying the value of the annotated program element associated with this group. */
7090 public ISetter setter() { return setter; }
7091 /** Sets the {@link ISetter} that is responsible for modifying the value of the annotated program element associated with this group, and returns this builder. */
7092 public Builder setter(ISetter setter) { this.setter = setter; return this; }
7093
7094 /** 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. */
7095 public IScope scope() { return scope; }
7096 /** 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. */
7097 public Builder scope(IScope scope) { this.scope = scope; return this; }
7098
7099 /** Adds the specified argument to the list of options and positional parameters that depend on this group. */
7100 public Builder addArg(ArgSpec arg) { args.add(arg); return this; }
7101
7102 /** Returns the list of options and positional parameters that depend on this group.*/
7103 public List<ArgSpec> args() { return args; }
7104
7105 /** Adds the specified group to the list of subgroups that this group is composed of. */
7106 public Builder addSubgroup(ArgGroupSpec group) { subgroups.add(group); return this; }
7107
7108 /** Returns the list of subgroups that this group is composed of.*/
7109 public List<ArgGroupSpec> subgroups() { return subgroups; }
7110 }
7111 }
7112
7113 /** This class allows applications to specify a custom binding that will be invoked for unmatched arguments.
7114 * A binding can be created with a {@code ISetter} that consumes the unmatched arguments {@code String[]}, or with a
7115 * {@code IGetter} that produces a {@code Collection<String>} that the unmatched arguments can be added to.
7116 * @since 3.0 */
7117 public static class UnmatchedArgsBinding {
7118 private final IGetter getter;
7119 private final ISetter setter;
7120
7121 /** Creates a {@code UnmatchedArgsBinding} for a setter that consumes {@code String[]} objects.
7122 * @param setter consumes the String[] array with unmatched arguments. */
7123 public static UnmatchedArgsBinding forStringArrayConsumer(ISetter setter) { return new UnmatchedArgsBinding(null, setter); }
7124
7125 /** Creates a {@code UnmatchedArgsBinding} for a getter that produces a {@code Collection<String>} that the unmatched arguments can be added to.
7126 * @param getter supplies a {@code Collection<String>} that the unmatched arguments can be added to. */
7127 public static UnmatchedArgsBinding forStringCollectionSupplier(IGetter getter) { return new UnmatchedArgsBinding(getter, null); }
7128
7129 private UnmatchedArgsBinding(IGetter getter, ISetter setter) {
7130 if (getter == null && setter == null) { throw new IllegalArgumentException("Getter and setter cannot both be null"); }
7131 this.setter = setter;
7132 this.getter = getter;
7133 }
7134 /** Returns the getter responsible for producing a {@code Collection} that the unmatched arguments can be added to. */
7135 public IGetter getter() { return getter; }
7136 /** Returns the setter responsible for consuming the unmatched arguments. */
7137 public ISetter setter() { return setter; }
7138 void addAll(String[] unmatched) {
7139 if (setter != null) {
7140 try {
7141 setter.set(unmatched);
7142 } catch (Exception ex) {
7143 throw new PicocliException(String.format("Could not invoke setter (%s) with unmatched argument array '%s': %s", setter, Arrays.toString(unmatched), ex), ex);
7144 }
7145 }
7146 if (getter != null) {
7147 try {
7148 Collection<String> collection = getter.get();
7149 Assert.notNull(collection, "getter returned null Collection");
7150 collection.addAll(Arrays.asList(unmatched));
7151 } catch (Exception ex) {
7152 throw new PicocliException(String.format("Could not add unmatched argument array '%s' to collection returned by getter (%s): %s",
7153 Arrays.toString(unmatched), getter, ex), ex);
7154 }
7155 }
7156 }
7157 }
7158 /** Command method parameter, similar to java.lang.reflect.Parameter (not available before Java 8).
7159 * @since 4.0 */
7160 public static class MethodParam extends AccessibleObject {
7161 final Method method;
7162 final int paramIndex;
7163 final String name;
7164 int position;
7165
7166 public MethodParam(Method method, int paramIndex) {
7167 this.method = method;
7168 this.paramIndex = paramIndex;
7169 String tmp = "arg" + paramIndex;
7170 try {
7171 Method getParameters = Method.class.getMethod("getParameters");
7172 Object parameters = getParameters.invoke(method);
7173 Object parameter = Array.get(parameters, paramIndex);
7174 tmp = (String) Class.forName("java.lang.reflect.Parameter").getDeclaredMethod("getName").invoke(parameter);
7175 } catch (Exception ignored) {}
7176 this.name = tmp;
7177 }
7178 public Type getParameterizedType() { return method.getGenericParameterTypes()[paramIndex]; }
7179 public String getName() { return name; }
7180 public Class<?> getType() { return method.getParameterTypes()[paramIndex]; }
7181 public Method getDeclaringExecutable() { return method; }
7182 @Override public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
7183 for (Annotation annotation : getDeclaredAnnotations()) {
7184 if (annotationClass.isAssignableFrom(annotation.getClass())) { return annotationClass.cast(annotation); }
7185 }
7186 return null;
7187 }
7188 @Override public Annotation[] getDeclaredAnnotations() { return method.getParameterAnnotations()[paramIndex]; }
7189 @Override public void setAccessible(boolean flag) throws SecurityException { method.setAccessible(flag); }
7190 @Override public boolean isAccessible() throws SecurityException { return method.isAccessible(); }
7191 @Override public String toString() { return method.toString() + ":" + getName(); }
7192 }
7193
7194 /** Encapculates type information for an option or parameter to make this information available both at runtime
7195 * and at compile time (when {@code Class} values are not available).
7196 * Most of the methods in this interface (but not all!) are safe to use by annotation processors.
7197 * @since 4.0
7198 */
7199 public interface ITypeInfo {
7200 /** Returns {@code true} if {@link #getType()} is {@code boolean} or {@code java.lang.Boolean}. */
7201 boolean isBoolean();
7202 /** Returns {@code true} if {@link #getType()} is an array, map or collection. */
7203 boolean isMultiValue();
7204 boolean isArray();
7205 boolean isCollection();
7206 boolean isMap();
7207 /** Returns {@code true} if {@link #getType()} is an enum. */
7208 boolean isEnum();
7209 List<String> getEnumConstantNames();
7210 String getClassName();
7211 String getClassSimpleName();
7212 /** Returns type information of components or elements of a {@link #isMultiValue() multivalue} type. */
7213 List<ITypeInfo> getAuxiliaryTypeInfos();
7214 /** 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>}. */
7215 List<String> getActualGenericTypeArguments();
7216
7217 /** Returns the class that the option or parameter value should be converted to when matched on the command
7218 * line. This method is <em>not</em> safe for annotation processors to use.
7219 * @return the class that the option or parameter value should be converted to
7220 */
7221 Class<?> getType();
7222 /** Returns the component class of an array, or the parameter type of a generic Collection, or the parameter
7223 * types of the key and the value of a generic Map.
7224 * This method is <em>not</em> safe for annotation processors to use.
7225 * @return the component type or types of an array, Collection or Map type
7226 */
7227 Class<?>[] getAuxiliaryTypes();
7228 }
7229 static class RuntimeTypeInfo implements ITypeInfo {
7230 private final Class<?> type;
7231 private final Class<?>[] auxiliaryTypes;
7232 private final List<String> actualGenericTypeArguments;
7233
7234 RuntimeTypeInfo(Class<?> type, Class<?>[] auxiliaryTypes, List<String> actualGenericTypeArguments) {
7235 this.type = Assert.notNull(type, "type");
7236 this.auxiliaryTypes = Assert.notNull(auxiliaryTypes, "auxiliaryTypes").clone();
7237 this.actualGenericTypeArguments = actualGenericTypeArguments == null ? Collections.<String>emptyList() : Collections.unmodifiableList(new ArrayList<String>(actualGenericTypeArguments));
7238 }
7239
7240 static ITypeInfo createForAuxType(Class<?> type) {
7241 return create(type, new Class[0], (Type) null, Range.valueOf("1"), String.class);
7242 }
7243 public static ITypeInfo create(Class<?> type,
7244 Class<?>[] annotationTypes,
7245 Type genericType,
7246 Range arity,
7247 Class<?> defaultType) {
7248 Class<?>[] auxiliaryTypes = RuntimeTypeInfo.inferTypes(type, annotationTypes, genericType);
7249 List<String> actualGenericTypeArguments = new ArrayList<String>();
7250 if (genericType instanceof ParameterizedType) {
7251 Class[] declaredTypeParameters = extractTypeParameters((ParameterizedType) genericType);
7252 for (Class<?> c : declaredTypeParameters) { actualGenericTypeArguments.add(c.getName()); }
7253 }
7254 return create(type, auxiliaryTypes, actualGenericTypeArguments, arity, defaultType);
7255 }
7256
7257 public static ITypeInfo create(Class<?> type, Class<?>[] auxiliaryTypes, List<String> actualGenericTypeArguments, Range arity, Class<?> defaultType) {
7258 if (type == null) {
7259 if (auxiliaryTypes == null || auxiliaryTypes.length == 0) {
7260 if (arity.isVariable || arity.max > 1) {
7261 type = String[].class;
7262 } else if (arity.max == 1) {
7263 type = String.class;
7264 } else {
7265 type = defaultType;
7266 }
7267 } else {
7268 type = auxiliaryTypes[0];
7269 }
7270 }
7271 if (auxiliaryTypes == null || auxiliaryTypes.length == 0) {
7272 if (type.isArray()) {
7273 auxiliaryTypes = new Class<?>[] {type.getComponentType()};
7274 } else if (Collection.class.isAssignableFrom(type)) { // type is a collection but element type is unspecified
7275 auxiliaryTypes = new Class<?>[] {String.class}; // use String elements
7276 } else if (Map.class.isAssignableFrom(type)) { // type is a map but element type is unspecified
7277 auxiliaryTypes = new Class<?>[] {String.class, String.class}; // use String keys and String values
7278 } else {
7279 auxiliaryTypes = new Class<?>[] {type};
7280 }
7281 }
7282 return new RuntimeTypeInfo(type, auxiliaryTypes, actualGenericTypeArguments);
7283 }
7284 static Class<?>[] inferTypes(Class<?> propertyType, Class<?>[] annotationTypes, Type genericType) {
7285 if (annotationTypes != null && annotationTypes.length > 0) { return annotationTypes; }
7286 if (propertyType.isArray()) { return new Class<?>[] { propertyType.getComponentType() }; }
7287 if (CommandLine.isMultiValue(propertyType)) {
7288 if (genericType instanceof ParameterizedType) {// e.g. Map<Long, ? extends Number>
7289 return extractTypeParameters((ParameterizedType) genericType);
7290 }
7291 return new Class<?>[] {String.class, String.class}; // field is multi-value but not ParameterizedType
7292 }
7293 return new Class<?>[] {propertyType}; // not a multi-value field
7294 }
7295
7296 static Class<?>[] extractTypeParameters(ParameterizedType genericType) {
7297 ParameterizedType parameterizedType = genericType;
7298 Type[] paramTypes = parameterizedType.getActualTypeArguments(); // e.g. ? extends Number
7299 Class<?>[] result = new Class<?>[paramTypes.length];
7300 for (int i = 0; i < paramTypes.length; i++) {
7301 if (paramTypes[i] instanceof Class) { result[i] = (Class<?>) paramTypes[i]; continue; } // e.g. Long
7302 if (paramTypes[i] instanceof WildcardType) { // e.g. ? extends Number
7303 WildcardType wildcardType = (WildcardType) paramTypes[i];
7304 Type[] lower = wildcardType.getLowerBounds(); // e.g. []
7305 if (lower.length > 0 && lower[0] instanceof Class) { result[i] = (Class<?>) lower[0]; continue; }
7306 Type[] upper = wildcardType.getUpperBounds(); // e.g. Number
7307 if (upper.length > 0 && upper[0] instanceof Class) { result[i] = (Class<?>) upper[0]; continue; }
7308 }
7309 Arrays.fill(result, String.class); return result; // too convoluted generic type, giving up
7310 }
7311 return result; // we inferred all types from ParameterizedType
7312 }
7313
7314 public boolean isBoolean() { return auxiliaryTypes[0] == boolean.class || auxiliaryTypes[0] == Boolean.class; }
7315 public boolean isMultiValue() { return CommandLine.isMultiValue(type); }
7316 public boolean isArray() { return type.isArray(); }
7317 public boolean isCollection() { return Collection.class.isAssignableFrom(type); }
7318 public boolean isMap() { return Map.class.isAssignableFrom(type); }
7319 public boolean isEnum() { return auxiliaryTypes[0].isEnum(); }
7320 public String getClassName() { return type.getName(); }
7321 public String getClassSimpleName() { return type.getSimpleName(); }
7322 public Class<?> getType() { return type; }
7323 public Class<?>[] getAuxiliaryTypes() { return auxiliaryTypes; }
7324 public List<String> getActualGenericTypeArguments() { return actualGenericTypeArguments; }
7325
7326 public List<ITypeInfo> getAuxiliaryTypeInfos() {
7327 List<ITypeInfo> result = new ArrayList<ITypeInfo>();
7328 for (Class<?> c : auxiliaryTypes) { result.add(createForAuxType(c)); }
7329 return result;
7330 }
7331 public List<String> getEnumConstantNames() {
7332 if (!isEnum()) { return Collections.emptyList(); }
7333 List<String> result = new ArrayList<String>();
7334 for (Object c : auxiliaryTypes[0].getEnumConstants()) { result.add(c.toString()); }
7335 return result;
7336 }
7337
7338 public boolean equals(Object obj) {
7339 if (obj == this) { return true; }
7340 if (!(obj instanceof RuntimeTypeInfo)) { return false; }
7341 RuntimeTypeInfo other = (RuntimeTypeInfo) obj;
7342 return Arrays.equals(other.auxiliaryTypes, auxiliaryTypes) && type.equals(other.type);
7343 }
7344 public int hashCode() {
7345 return Arrays.hashCode(auxiliaryTypes) + 37 * Assert.hashCode(type);
7346 }
7347 public String toString() {
7348 return String.format("RuntimeTypeInfo(%s, aux=%s, collection=%s, map=%s)",
7349 type.getCanonicalName(), Arrays.toString(auxiliaryTypes), isCollection(), isMap());
7350 }
7351 }
7352 /** Internal interface to allow annotation processors to construct a command model at compile time.
7353 * @since 4.0 */
7354 public interface IAnnotatedElement {
7355 Object userObject();
7356 boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
7357 <T extends Annotation> T getAnnotation(Class<T> annotationClass);
7358 String getName();
7359 String getMixinName();
7360 boolean isArgSpec();
7361 boolean isOption();
7362 boolean isParameter();
7363 boolean isArgGroup();
7364 boolean isMixin();
7365 boolean isUnmatched();
7366 boolean isInjectSpec();
7367 boolean isMultiValue();
7368 boolean hasInitialValue();
7369 boolean isMethodParameter();
7370 int getMethodParamPosition();
7371 CommandLine.Model.IScope scope();
7372 CommandLine.Model.IGetter getter();
7373 CommandLine.Model.ISetter setter();
7374 ITypeInfo getTypeInfo();
7375 String getToString();
7376 }
7377
7378 static class TypedMember implements IAnnotatedElement {
7379 final AccessibleObject accessible;
7380 final String name;
7381 final ITypeInfo typeInfo;
7382 boolean hasInitialValue;
7383 private IScope scope;
7384 private IGetter getter;
7385 private ISetter setter;
7386 static TypedMember createIfAnnotated(Field field, IScope scope) {
7387 return isAnnotated(field) ? new TypedMember(field, scope) : null;
7388 }
7389 static boolean isAnnotated(AnnotatedElement e) {
7390 return false
7391 || e.isAnnotationPresent(Option.class)
7392 || e.isAnnotationPresent(Parameters.class)
7393 || e.isAnnotationPresent(ArgGroup.class)
7394 || e.isAnnotationPresent(Unmatched.class)
7395 || e.isAnnotationPresent(Mixin.class)
7396 || e.isAnnotationPresent(Spec.class)
7397 || e.isAnnotationPresent(ParentCommand.class);
7398 }
7399 TypedMember(Field field) {
7400 accessible = Assert.notNull(field, "field");
7401 accessible.setAccessible(true);
7402 name = field.getName();
7403 typeInfo = createTypeInfo(field.getType(), field.getGenericType());
7404 hasInitialValue = true;
7405 }
7406 private TypedMember(Field field, IScope scope) {
7407 this(field);
7408 Object obj = ObjectScope.tryGet(scope);
7409 if (obj != null && Proxy.isProxyClass(obj.getClass())) {
7410 throw new InitializationException("Invalid picocli annotation on interface field");
7411 }
7412 FieldBinding binding = new FieldBinding(scope, field);
7413 getter = binding; setter = binding;
7414 this.scope = scope;
7415 hasInitialValue &= obj != null ;
7416 }
7417 static TypedMember createIfAnnotated(Method method, IScope scope, CommandSpec spec) {
7418 return isAnnotated(method) ? new TypedMember(method, scope, spec) : null;
7419 }
7420 private TypedMember(Method method, IScope scope, CommandSpec spec) {
7421 accessible = Assert.notNull(method, "method");
7422 accessible.setAccessible(true);
7423 name = propertyName(method.getName());
7424 Class<?>[] parameterTypes = method.getParameterTypes();
7425 boolean isGetter = parameterTypes.length == 0 && method.getReturnType() != Void.TYPE && method.getReturnType() != Void.class;
7426 boolean isSetter = parameterTypes.length > 0;
7427 if (isSetter == isGetter) { throw new InitializationException("Invalid method, must be either getter or setter: " + method); }
7428 if (isGetter) {
7429 hasInitialValue = true;
7430 typeInfo = createTypeInfo(method.getReturnType(), method.getGenericReturnType());
7431 Object proxy = ObjectScope.tryGet(scope);
7432 if (Proxy.isProxyClass(proxy.getClass())) {
7433 PicocliInvocationHandler handler = (PicocliInvocationHandler) Proxy.getInvocationHandler(proxy);
7434 PicocliInvocationHandler.ProxyBinding binding = handler.new ProxyBinding(method);
7435 getter = binding; setter = binding;
7436 initializeInitialValue(method);
7437 } else {
7438 //throw new IllegalArgumentException("Getter method but not a proxy: " + scope + ": " + method);
7439 MethodBinding binding = new MethodBinding(scope, method, spec);
7440 getter = binding; setter = binding;
7441 }
7442 } else {
7443 hasInitialValue = false;
7444 typeInfo = createTypeInfo(parameterTypes[0], method.getGenericParameterTypes()[0]);
7445 MethodBinding binding = new MethodBinding(scope, method, spec);
7446 getter = binding; setter = binding;
7447 }
7448 }
7449 TypedMember(MethodParam param, IScope scope) {
7450 accessible = Assert.notNull(param, "command method parameter");
7451 accessible.setAccessible(true);
7452 name = param.getName();
7453 typeInfo = createTypeInfo(param.getType(), param.getParameterizedType());
7454 // bind parameter
7455 ObjectBinding binding = new ObjectBinding();
7456 getter = binding; setter = binding;
7457 initializeInitialValue(param);
7458 hasInitialValue = true;
7459 }
7460
7461 private ITypeInfo createTypeInfo(Class<?> type, Type genericType) {
7462 Range arity = null;
7463 if (isOption()) { arity = Range.valueOf(getAnnotation(Option.class).arity()); }
7464 if (isParameter()) { arity = Range.valueOf(getAnnotation(Parameters.class).arity()); }
7465 if (arity == null || arity.isUnspecified) {
7466 if (isOption()) {
7467 arity = (type == null || isBoolean(type)) ? Range.valueOf("0") : Range.valueOf("1");
7468 } else {
7469 arity = Range.valueOf("1");
7470 }
7471 arity = arity.unspecified(true);
7472 }
7473 return RuntimeTypeInfo.create(type, annotationTypes(), genericType, arity, (isOption() ? boolean.class : String.class));
7474 }
7475
7476 private void initializeInitialValue(Object arg) {
7477 Class<?> type = typeInfo.getType();
7478 try {
7479 if (type == Boolean.TYPE ) { setter.set(false); }
7480 else if (type == Byte.TYPE ) { setter.set(Byte.valueOf((byte) 0)); }
7481 else if (type == Character.TYPE) { setter.set(Character.valueOf((char) 0)); }
7482 else if (type == Short.TYPE ) { setter.set(Short.valueOf((short) 0)); }
7483 else if (type == Integer.TYPE ) { setter.set(Integer.valueOf(0)); }
7484 else if (type == Long.TYPE ) { setter.set(Long.valueOf(0L)); }
7485 else if (type == Float.TYPE ) { setter.set(Float.valueOf(0f)); }
7486 else if (type == Double.TYPE ) { setter.set(Double.valueOf(0d)); }
7487 else { setter.set(null); }
7488 } catch (Exception ex) {
7489 throw new InitializationException("Could not set initial value for " + arg + ": " + ex.toString(), ex);
7490 }
7491 }
7492 public Object userObject() { return accessible; }
7493 public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) { return accessible.isAnnotationPresent(annotationClass); }
7494 public <T extends Annotation> T getAnnotation(Class<T> annotationClass) { return accessible.getAnnotation(annotationClass); }
7495 public String getName() { return name; }
7496 public boolean isArgSpec() { return isOption() || isParameter() || (isMethodParameter() && !isMixin()); }
7497 public boolean isOption() { return isAnnotationPresent(Option.class); }
7498 public boolean isParameter() { return isAnnotationPresent(Parameters.class); }
7499 public boolean isArgGroup() { return isAnnotationPresent(ArgGroup.class); }
7500 public boolean isMixin() { return isAnnotationPresent(Mixin.class); }
7501 public boolean isUnmatched() { return isAnnotationPresent(Unmatched.class); }
7502 public boolean isInjectSpec() { return isAnnotationPresent(Spec.class); }
7503 public boolean isMultiValue() { return CommandLine.isMultiValue(getType()); }
7504 public IScope scope() { return scope; }
7505 public IGetter getter() { return getter; }
7506 public ISetter setter() { return setter; }
7507 public ITypeInfo getTypeInfo() { return typeInfo; }
7508 public Class<?> getType() { return typeInfo.getType(); }
7509 public Class<?>[] getAuxiliaryTypes() { return typeInfo.getAuxiliaryTypes(); }
7510 private Class<?>[] annotationTypes() {
7511 if (isOption()) { return getAnnotation(Option.class).type(); }
7512 if (isParameter()) { return getAnnotation(Parameters.class).type(); }
7513 return new Class[0];
7514 }
7515 public String toString() { return accessible.toString(); }
7516 public String getToString() {
7517 if (isMixin()) { return abbreviate("mixin from member " + toGenericString()); }
7518 return (accessible instanceof Field ? "field " : accessible instanceof Method ? "method " : accessible.getClass().getSimpleName() + " ") + abbreviate(toGenericString());
7519 }
7520 public String toGenericString() { return accessible instanceof Field ? ((Field) accessible).toGenericString() : accessible instanceof Method ? ((Method) accessible).toGenericString() : ((MethodParam)accessible).toString(); }
7521 public boolean hasInitialValue() { return hasInitialValue; }
7522 public boolean isMethodParameter() { return accessible instanceof MethodParam; }
7523 public int getMethodParamPosition() { return isMethodParameter() ? ((MethodParam) accessible).position : -1; }
7524 public String getMixinName() {
7525 String annotationName = getAnnotation(Mixin.class).name();
7526 return empty(annotationName) ? getName() : annotationName;
7527 }
7528 static String propertyName(String methodName) {
7529 if (methodName.length() > 3 && (methodName.startsWith("get") || methodName.startsWith("set"))) { return decapitalize(methodName.substring(3)); }
7530 return decapitalize(methodName);
7531 }
7532 private static String decapitalize(String name) {
7533 if (name == null || name.length() == 0) { return name; }
7534 char[] chars = name.toCharArray();
7535 chars[0] = Character.toLowerCase(chars[0]);
7536 return new String(chars);
7537 }
7538 static String abbreviate(String text) {
7539 return text.replace("private ", "")
7540 .replace("protected ", "")
7541 .replace("public ", "")
7542 .replace("java.lang.", "");
7543 }
7544 }
7545
7546 /** Utility class for getting resource bundle strings.
7547 * Enhances the standard <a href="https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html">ResourceBundle</a>
7548 * with support for String arrays and qualified keys: keys that may or may not be prefixed with the fully qualified command name.
7549 * <p>Example properties resource bundle:</p><pre>
7550 * # Usage Help Message Sections
7551 * # ---------------------------
7552 * # Numbered resource keys can be used to create multi-line sections.
7553 * usage.headerHeading = This is my app. There are other apps like it but this one is mine.%n
7554 * usage.header = header first line
7555 * usage.header.0 = header second line
7556 * usage.descriptionHeading = Description:%n
7557 * usage.description.0 = first line
7558 * usage.description.1 = second line
7559 * usage.description.2 = third line
7560 * usage.synopsisHeading = Usage:\u0020
7561 * # Leading whitespace is removed by default. Start with \u0020 to keep the leading whitespace.
7562 * usage.customSynopsis.0 = Usage: ln [OPTION]... [-T] TARGET LINK_NAME (1st form)
7563 * usage.customSynopsis.1 = \u0020 or: ln [OPTION]... TARGET (2nd form)
7564 * usage.customSynopsis.2 = \u0020 or: ln [OPTION]... TARGET... DIRECTORY (3rd form)
7565 * # Headings can contain the %n character to create multi-line values.
7566 * usage.parameterListHeading = %nPositional parameters:%n
7567 * usage.optionListHeading = %nOptions:%n
7568 * usage.commandListHeading = %nCommands:%n
7569 * usage.footerHeading = Powered by picocli%n
7570 * usage.footer = footer
7571 *
7572 * # Option Descriptions
7573 * # -------------------
7574 * # Use numbered keys to create multi-line descriptions.
7575 * help = Show this help message and exit.
7576 * version = Print version information and exit.
7577 * </pre>
7578 * <p>Resources for multiple commands can be specified in a single ResourceBundle. Keys and their value can be
7579 * shared by multiple commands (so you don't need to repeat them for every command), but keys can be prefixed with
7580 * {@code fully qualified command name + "."} to specify different values for different commands.
7581 * The most specific key wins. For example: </p>
7582 * <pre>
7583 * jfrog.rt.usage.header = Artifactory commands
7584 * jfrog.rt.config.usage.header = Configure Artifactory details.
7585 * jfrog.rt.upload.usage.header = Upload files.
7586 *
7587 * jfrog.bt.usage.header = Bintray commands
7588 * jfrog.bt.config.usage.header = Configure Bintray details.
7589 * jfrog.bt.upload.usage.header = Upload files.
7590 *
7591 * # shared between all commands
7592 * usage.footerHeading = Environment Variables:
7593 * usage.footer.0 = footer line 0
7594 * usage.footer.1 = footer line 1
7595 * </pre>
7596 * @see Command#resourceBundle()
7597 * @see Option#descriptionKey()
7598 * @see OptionSpec#description()
7599 * @see PositionalParamSpec#description()
7600 * @see CommandSpec#qualifiedName(String)
7601 * @since 3.6 */
7602 public static class Messages {
7603 private final CommandSpec spec;
7604 private final String bundleBaseName;
7605 private final ResourceBundle rb;
7606 private final Set<String> keys;
7607 public Messages(CommandSpec spec, String baseName) {
7608 this(spec, baseName, createBundle(baseName));
7609 }
7610 public Messages(CommandSpec spec, ResourceBundle rb) {
7611 this(spec, extractName(rb), rb);
7612 }
7613 public Messages(CommandSpec spec, String baseName, ResourceBundle rb) {
7614 this.spec = Assert.notNull(spec, "CommandSpec");
7615 this.bundleBaseName = baseName;
7616 this.rb = rb;
7617 this.keys = keys(rb);
7618 }
7619 private static ResourceBundle createBundle(String baseName) {
7620 return ResourceBundle.getBundle(baseName);
7621 }
7622 private static String extractName(ResourceBundle rb) {
7623 try { // ResourceBundle.getBaseBundleName was introduced in Java 8
7624 return (String) ResourceBundle.class.getDeclaredMethod("getBaseBundleName").invoke(rb);
7625 } catch (Exception ignored) { return ""; }
7626 }
7627 private static Set<String> keys(ResourceBundle rb) {
7628 if (rb == null) { return Collections.emptySet(); }
7629 Set<String> keys = new LinkedHashSet<String>();
7630 for (Enumeration<String> k = rb.getKeys(); k.hasMoreElements(); keys.add(k.nextElement()));
7631 return keys;
7632 }
7633
7634 /** Returns a copy of the specified Messages object with the CommandSpec replaced by the specified one.
7635 * @param spec the CommandSpec of the returned Messages
7636 * @param original the Messages object whose ResourceBundle to reference
7637 * @return a Messages object with the specified CommandSpec and the ResourceBundle of the specified Messages object
7638 */
7639 public static Messages copy(CommandSpec spec, Messages original) {
7640 return original == null ? null : new Messages(spec, original.bundleBaseName, original.rb);
7641 }
7642 /** Returns {@code true} if the specified {@code Messages} is {@code null} or has a {@code null ResourceBundle}. */
7643 public static boolean empty(Messages messages) { return messages == null || messages.rb == null; }
7644
7645 /** Returns the String value found in the resource bundle for the specified key, or the specified default value if not found.
7646 * @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,
7647 * and if not found, it will try with the unqualified key.
7648 * @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
7649 * @return the String value found in the resource bundle for the specified key, or the specified default value
7650 */
7651 public String getString(String key, String defaultValue) {
7652 if (isEmpty()) { return defaultValue; }
7653 String cmd = spec.qualifiedName(".");
7654 if (keys.contains(cmd + "." + key)) { return rb.getString(cmd + "." + key); }
7655 if (keys.contains(key)) { return rb.getString(key); }
7656 return defaultValue;
7657 }
7658
7659 boolean isEmpty() { return rb == null || keys.isEmpty(); }
7660
7661 /** Returns the String array value found in the resource bundle for the specified key, or the specified default value if not found.
7662 * Multi-line strings can be specified in the resource bundle with {@code key.0}, {@code key.1}, {@code key.2}, etc.
7663 * @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,
7664 * and if not found, it will try with the unqualified key.
7665 * @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
7666 * @return the String array value found in the resource bundle for the specified key, or the specified default value
7667 */
7668 public String[] getStringArray(String key, String[] defaultValues) {
7669 if (isEmpty()) { return defaultValues; }
7670 String cmd = spec.qualifiedName(".");
7671 List<String> result = addAllWithPrefix(rb, cmd + "." + key, keys, new ArrayList<String>());
7672 if (!result.isEmpty()) { return result.toArray(new String[0]); }
7673 addAllWithPrefix(rb, key, keys, result);
7674 return result.isEmpty() ? defaultValues : result.toArray(new String[0]);
7675 }
7676 private static List<String> addAllWithPrefix(ResourceBundle rb, String key, Set<String> keys, List<String> result) {
7677 if (keys.contains(key)) { result.add(rb.getString(key)); }
7678 for (int i = 0; true; i++) {
7679 String elementKey = key + "." + i;
7680 if (keys.contains(elementKey)) {
7681 result.add(rb.getString(elementKey));
7682 } else {
7683 return result;
7684 }
7685 }
7686 }
7687 /** Returns the ResourceBundle of the specified Messages object or {@code null} if the specified Messages object is {@code null}.
7688 * @since 4.0 */
7689 public static String resourceBundleBaseName(Messages messages) { return messages == null ? null : messages.resourceBundleBaseName(); }
7690 /** Returns the ResourceBundle of the specified Messages object or {@code null} if the specified Messages object is {@code null}. */
7691 public static ResourceBundle resourceBundle(Messages messages) { return messages == null ? null : messages.resourceBundle(); }
7692 /** Returns the base name of the ResourceBundle of this object or {@code null}.
7693 * @since 4.0 */
7694 public String resourceBundleBaseName() { return bundleBaseName; }
7695 /** Returns the ResourceBundle of this object or {@code null}. */
7696 public ResourceBundle resourceBundle() { return rb; }
7697 /** Returns the CommandSpec of this object, never {@code null}. */
7698 public CommandSpec commandSpec() { return spec; }
7699 }
7700 private static class CommandReflection {
7701 static ArgGroupSpec extractArgGroupSpec(IAnnotatedElement member, IFactory factory, CommandSpec commandSpec, boolean annotationsAreMandatory) throws Exception {
7702 Object instance = null;
7703 try { instance = member.getter().get(); } catch (Exception ignored) {}
7704 Class<?> cls = instance == null ? member.getTypeInfo().getType() : instance.getClass();
7705 Tracer t = new Tracer();
7706
7707 if (member.isMultiValue()) {
7708 cls = member.getTypeInfo().getAuxiliaryTypes()[0];
7709 }
7710 IScope scope = new ObjectScope(instance);
7711 ArgGroupSpec.Builder builder = ArgGroupSpec.builder(member);
7712 builder.updateArgGroupAttributes(member.getAnnotation(ArgGroup.class));
7713 if (member.isOption() || member.isParameter()) {
7714 if (member instanceof TypedMember) { validateArgSpecMember((TypedMember) member); }
7715 builder.addArg(buildArgForMember(member, factory));
7716 }
7717
7718 Stack<Class<?>> hierarchy = new Stack<Class<?>>();
7719 while (cls != null) { hierarchy.add(cls); cls = cls.getSuperclass(); }
7720 boolean hasArgAnnotation = false;
7721 while (!hierarchy.isEmpty()) {
7722 cls = hierarchy.pop();
7723 hasArgAnnotation |= initFromAnnotatedFields(scope, cls, commandSpec, builder, factory);
7724 }
7725 ArgGroupSpec result = builder.build();
7726 if (annotationsAreMandatory) {validateArgGroupSpec(result, hasArgAnnotation, cls.getName()); }
7727 return result;
7728 }
7729 static CommandSpec extractCommandSpec(Object command, IFactory factory, boolean annotationsAreMandatory) {
7730 Class<?> cls = command.getClass();
7731 Tracer t = new Tracer();
7732 t.debug("Creating CommandSpec for object of class %s with factory %s%n", cls.getName(), factory.getClass().getName());
7733 if (command instanceof CommandSpec) { return (CommandSpec) command; }
7734
7735 Object[] tmp = getOrCreateInstance(cls, command, factory, t);
7736 cls = (Class<?>) tmp[0];
7737 Object instance = tmp[1];
7738 String commandClassName = (String) tmp[2];
7739
7740 CommandSpec result = CommandSpec.wrapWithoutInspection(Assert.notNull(instance, "command"));
7741 ObjectScope scope = new ObjectScope(instance);
7742
7743 Stack<Class<?>> hierarchy = new Stack<Class<?>>();
7744 while (cls != null) { hierarchy.add(cls); cls = cls.getSuperclass(); }
7745 boolean hasCommandAnnotation = false;
7746 boolean mixinStandardHelpOptions = false;
7747 while (!hierarchy.isEmpty()) {
7748 cls = hierarchy.pop();
7749 Command cmd = cls.getAnnotation(Command.class);
7750 if (cmd != null) {
7751 result.updateCommandAttributes(cmd, factory);
7752 initSubcommands(cmd, cls, result, factory);
7753 // addGroups(cmd, groupBuilders); // TODO delete
7754 hasCommandAnnotation = true;
7755 }
7756 hasCommandAnnotation |= initFromAnnotatedFields(scope, cls, result, null, factory);
7757 if (cls.isAnnotationPresent(Command.class)) {
7758 mixinStandardHelpOptions |= cls.getAnnotation(Command.class).mixinStandardHelpOptions();
7759 }
7760 }
7761 result.mixinStandardHelpOptions(mixinStandardHelpOptions); //#377 Standard help options should be added last
7762 if (command instanceof Method) {
7763 Method method = (Method) command;
7764 t.debug("Using method %s as command %n", method);
7765 commandClassName = method.toString();
7766 Command cmd = method.getAnnotation(Command.class);
7767 result.updateCommandAttributes(cmd, factory);
7768 result.setAddMethodSubcommands(false); // method commands don't have method subcommands
7769 initSubcommands(cmd, null, result, factory);
7770 hasCommandAnnotation = true;
7771 result.mixinStandardHelpOptions(method.getAnnotation(Command.class).mixinStandardHelpOptions());
7772 initFromMethodParameters(scope, method, result, null, factory);
7773 // set command name to method name, unless @Command#name is set
7774 result.initName(((Method)command).getName());
7775 }
7776 result.updateArgSpecMessages();
7777
7778 if (annotationsAreMandatory) {validateCommandSpec(result, hasCommandAnnotation, commandClassName); }
7779 result.withToString(commandClassName).validate();
7780 return result;
7781 }
7782
7783 private static Object[] getOrCreateInstance(Class<?> cls, Object command, IFactory factory, Tracer t) {
7784 Object instance = command;
7785 String commandClassName = cls.getName();
7786 if (command instanceof Class) {
7787 cls = (Class) command;
7788 commandClassName = cls.getName();
7789 try {
7790 t.debug("Getting a %s instance from the factory%n", cls.getName());
7791 instance = DefaultFactory.create(factory, cls);
7792 cls = instance.getClass();
7793 commandClassName = cls.getName();
7794 t.debug("Factory returned a %s instance%n", commandClassName);
7795 } catch (InitializationException ex) {
7796 if (cls.isInterface()) {
7797 t.debug("%s. Creating Proxy for interface %s%n", ex.getCause(), cls.getName());
7798 instance = Proxy.newProxyInstance(cls.getClassLoader(), new Class<?>[]{cls}, new PicocliInvocationHandler());
7799 } else {
7800 throw ex;
7801 }
7802 }
7803 } else if (command instanceof Method) {
7804 cls = null; // don't mix in options/positional params from outer class @Command
7805 } else if (instance == null) {
7806 t.debug("Getting a %s instance from the factory%n", cls.getName());
7807 instance = DefaultFactory.create(factory, cls);
7808 t.debug("Factory returned a %s instance%n", instance.getClass().getName());
7809 }
7810 return new Object[] { cls, instance, commandClassName };
7811 }
7812 private static void initSubcommands(Command cmd, Class<?> cls, CommandSpec parent, IFactory factory) {
7813 for (Class<?> sub : cmd.subcommands()) {
7814 try {
7815 if (Help.class == sub) { throw new InitializationException(Help.class.getName() + " is not a valid subcommand. Did you mean " + HelpCommand.class.getName() + "?"); }
7816 CommandLine subcommandLine = toCommandLine(factory.create(sub), factory);
7817 parent.addSubcommand(subcommandName(sub), subcommandLine);
7818 initParentCommand(subcommandLine.getCommandSpec().userObject(), parent.userObject());
7819 }
7820 catch (InitializationException ex) { throw ex; }
7821 catch (NoSuchMethodException ex) { throw new InitializationException("Cannot instantiate subcommand " +
7822 sub.getName() + ": the class has no constructor", ex); }
7823 catch (Exception ex) {
7824 throw new InitializationException("Could not instantiate and add subcommand " +
7825 sub.getName() + ": " + ex, ex);
7826 }
7827 }
7828 if (cmd.addMethodSubcommands() && cls != null) {
7829 for (CommandLine sub : CommandSpec.createMethodSubcommands(cls, factory)) {
7830 parent.addSubcommand(sub.getCommandName(), sub);
7831 }
7832 }
7833 }
7834 static void initParentCommand(Object subcommand, Object parent) {
7835 if (subcommand == null) { return; }
7836 try {
7837 Class<?> cls = subcommand.getClass();
7838 while (cls != null) {
7839 for (Field f : cls.getDeclaredFields()) {
7840 if (f.isAnnotationPresent(ParentCommand.class)) {
7841 f.setAccessible(true);
7842 f.set(subcommand, parent);
7843 }
7844 }
7845 cls = cls.getSuperclass();
7846 }
7847 } catch (Exception ex) {
7848 throw new InitializationException("Unable to initialize @ParentCommand field: " + ex, ex);
7849 }
7850 }
7851 private static String subcommandName(Class<?> sub) {
7852 Command subCommand = sub.getAnnotation(Command.class);
7853 if (subCommand == null || Help.DEFAULT_COMMAND_NAME.equals(subCommand.name())) {
7854 throw new InitializationException("Subcommand " + sub.getName() +
7855 " is missing the mandatory @Command annotation with a 'name' attribute");
7856 }
7857 return subCommand.name();
7858 }
7859 private static boolean initFromAnnotatedFields(IScope scope, Class<?> cls, CommandSpec receiver, ArgGroupSpec.Builder groupBuilder, IFactory factory) {
7860 boolean result = false;
7861 for (Field field : cls.getDeclaredFields()) {
7862 result |= initFromAnnotatedTypedMembers(TypedMember.createIfAnnotated(field, scope), receiver, groupBuilder, factory);
7863 }
7864 for (Method method : cls.getDeclaredMethods()) {
7865 result |= initFromAnnotatedTypedMembers(TypedMember.createIfAnnotated(method, scope, receiver), receiver, groupBuilder, factory);
7866 }
7867 return result;
7868 }
7869 @SuppressWarnings("unchecked")
7870 private static boolean initFromAnnotatedTypedMembers(TypedMember member,
7871 CommandSpec commandSpec,
7872 ArgGroupSpec.Builder groupBuilder,
7873 IFactory factory) {
7874 boolean result = false;
7875 if (member == null) { return result; }
7876 if (member.isMixin()) {
7877 assertNoDuplicateAnnotations(member, Mixin.class, Option.class, Parameters.class, Unmatched.class, Spec.class, ArgGroup.class);
7878 if (groupBuilder != null) {
7879 throw new InitializationException("@Mixins are not supported on @ArgGroups");
7880 // TODO groupBuilder.addMixin(member.getMixinName(), buildMixinForMember(member, factory));
7881 } else {
7882 commandSpec.addMixin(member.getMixinName(), buildMixinForMember(member, factory));
7883 }
7884 result = true;
7885 }
7886 if (member.isArgGroup()) {
7887 assertNoDuplicateAnnotations(member, ArgGroup.class, Spec.class, Parameters.class, Option.class, Unmatched.class, Mixin.class);
7888 if (groupBuilder != null) {
7889 groupBuilder.addSubgroup(buildArgGroupForMember(member, factory, commandSpec));
7890 } else {
7891 commandSpec.addArgGroup(buildArgGroupForMember(member, factory, commandSpec));
7892 }
7893 return true;
7894 }
7895 if (member.isUnmatched()) {
7896 assertNoDuplicateAnnotations(member, Unmatched.class, Mixin.class, Option.class, Parameters.class, Spec.class, ArgGroup.class);
7897 if (groupBuilder != null) {
7898 // we don't support @Unmatched on @ArgGroup class members...
7899 throw new InitializationException("@Unmatched are not supported on @ArgGroups");
7900 } else {
7901 commandSpec.addUnmatchedArgsBinding(buildUnmatchedForMember(member));
7902 }
7903 }
7904 if (member.isArgSpec()) {
7905 validateArgSpecMember(member);
7906 if (groupBuilder != null) {
7907 groupBuilder.addArg(buildArgForMember(member, factory));
7908 } else {
7909 commandSpec.add(buildArgForMember(member, factory));
7910 }
7911 result = true;
7912 }
7913 if (member.isInjectSpec()) {
7914 validateInjectSpec(member);
7915 try { member.setter().set(commandSpec); } catch (Exception ex) { throw new InitializationException("Could not inject spec", ex); }
7916 }
7917 return result;
7918 }
7919 private static boolean initFromMethodParameters(IScope scope, Method method, CommandSpec receiver, ArgGroupSpec.Builder groupBuilder, IFactory factory) {
7920 boolean result = false;
7921 int optionCount = 0;
7922 for (int i = 0, count = method.getParameterTypes().length; i < count; i++) {
7923 MethodParam param = new MethodParam(method, i);
7924 if (param.isAnnotationPresent(Option.class) || param.isAnnotationPresent(Mixin.class)) {
7925 optionCount++;
7926 } else {
7927 param.position = i - optionCount;
7928 }
7929 result |= initFromAnnotatedTypedMembers(new TypedMember(param, scope), receiver, groupBuilder, factory);
7930 }
7931 return result;
7932 }
7933 @SuppressWarnings("unchecked")
7934 private static void validateArgSpecMember(TypedMember member) {
7935 if (!member.isArgSpec()) { throw new IllegalStateException("Bug: validateArgSpecMember() should only be called with an @Option or @Parameters member"); }
7936 if (member.isOption()) {
7937 assertNoDuplicateAnnotations(member, Option.class, Unmatched.class, Mixin.class, Parameters.class, Spec.class, ArgGroup.class);
7938 } else {
7939 assertNoDuplicateAnnotations(member, Parameters.class, Option.class, Unmatched.class, Mixin.class, Spec.class, ArgGroup.class);
7940 }
7941 if (!(member.accessible instanceof Field)) { return; }
7942 Field field = (Field) member.accessible;
7943 if (Modifier.isFinal(field.getModifiers()) && (field.getType().isPrimitive() || String.class.isAssignableFrom(field.getType()))) {
7944 throw new InitializationException("Constant (final) primitive and String fields like " + field + " cannot be used as " +
7945 (member.isOption() ? "an @Option" : "a @Parameter") + ": compile-time constant inlining may hide new values written to it.");
7946 }
7947 }
7948 private static void validateCommandSpec(CommandSpec result, boolean hasCommandAnnotation, String commandClassName) {
7949 if (!hasCommandAnnotation && result.positionalParameters.isEmpty() && result.optionsByNameMap.isEmpty() && result.unmatchedArgs.isEmpty()) {
7950 throw new InitializationException(commandClassName + " is not a command: it has no @Command, @Option, @Parameters or @Unmatched annotations");
7951 }
7952 }
7953 private static void validateArgGroupSpec(ArgGroupSpec result, boolean hasArgAnnotation, String className) {
7954 if (!hasArgAnnotation && result.args().isEmpty()) {
7955 throw new InitializationException(className + " is not a group: it has no @Option or @Parameters annotations");
7956 }
7957 }
7958 @SuppressWarnings("unchecked")
7959 private static void validateInjectSpec(TypedMember member) {
7960 if (!member.isInjectSpec()) { throw new IllegalStateException("Bug: validateInjectSpec() should only be called with @Spec members"); }
7961 assertNoDuplicateAnnotations(member, Spec.class, Parameters.class, Option.class, Unmatched.class, Mixin.class, ArgGroup.class);
7962 if (!CommandSpec.class.getName().equals(member.getTypeInfo().getClassName())) {
7963 throw new InitializationException("@picocli.CommandLine.Spec annotation is only supported on fields of type " + CommandSpec.class.getName());
7964 }
7965 }
7966 private static void assertNoDuplicateAnnotations(TypedMember member, Class<? extends Annotation> myAnnotation, Class<? extends Annotation>... forbidden) {
7967 for (Class<? extends Annotation> annotation : forbidden) {
7968 if (member.isAnnotationPresent(annotation)) {
7969 throw new DuplicateOptionAnnotationsException("A member cannot have both @" + myAnnotation.getSimpleName() + " and @" + annotation.getSimpleName() + " annotations, but '" + member + "' has both.");
7970 }
7971 }
7972 }
7973 private static CommandSpec buildMixinForMember(IAnnotatedElement member, IFactory factory) {
7974 try {
7975 Object userObject = member.getter().get();
7976 if (userObject == null) {
7977 userObject = factory.create(member.getTypeInfo().getType());
7978 member.setter().set(userObject);
7979 }
7980 CommandSpec result = CommandSpec.forAnnotatedObject(userObject, factory);
7981 return result.withToString(member.getToString());
7982 } catch (InitializationException ex) {
7983 throw ex;
7984 } catch (Exception ex) {
7985 throw new InitializationException("Could not access or modify mixin member " + member + ": " + ex, ex);
7986 }
7987 }
7988 private static ArgSpec buildArgForMember(IAnnotatedElement member, IFactory factory) {
7989 if (member.isOption()) { return OptionSpec.builder(member, factory).build(); }
7990 else if (member.isParameter()) { return PositionalParamSpec.builder(member, factory).build(); }
7991 else { return PositionalParamSpec.builder(member, factory).build(); }
7992 }
7993 private static ArgGroupSpec buildArgGroupForMember(IAnnotatedElement member, IFactory factory, CommandSpec commandSpec) {
7994 try {
7995 return extractArgGroupSpec(member, factory, commandSpec, true);
7996 } catch (InitializationException ex) {
7997 throw ex;
7998 } catch (Exception ex) {
7999 throw new InitializationException("Could not access or modify ArgGroup member " + member + ": " + ex, ex);
8000 }
8001 }
8002 private static UnmatchedArgsBinding buildUnmatchedForMember(final IAnnotatedElement member) {
8003 ITypeInfo info = member.getTypeInfo();
8004 if (!(info.getClassName().equals(String[].class.getName()) ||
8005 (info.isCollection() && info.getActualGenericTypeArguments().equals(Arrays.asList(String.class.getName()))))) {
8006 throw new InitializationException("Invalid type for " + member + ": must be either String[] or List<String>");
8007 }
8008 if (info.getClassName().equals(String[].class.getName())) {
8009 return UnmatchedArgsBinding.forStringArrayConsumer(member.setter());
8010 } else {
8011 return UnmatchedArgsBinding.forStringCollectionSupplier(new IGetter() {
8012 @SuppressWarnings("unchecked") public <T> T get() throws Exception {
8013 List<String> result = (List<String>) member.getter().get();
8014 if (result == null) {
8015 result = new ArrayList<String>();
8016 member.setter().set(result);
8017 }
8018 return (T) result;
8019 }
8020 });
8021 }
8022 }
8023 }
8024
8025 static class FieldBinding implements IGetter, ISetter {
8026 private final IScope scope;
8027 private final Field field;
8028 private static IScope asScope(Object scope) { return scope instanceof IScope ? ((IScope) scope) : new ObjectScope(scope); }
8029 FieldBinding(Object scope, Field field) { this(asScope(scope), field); }
8030 FieldBinding(IScope scope, Field field) { this.scope = scope; this.field = field; }
8031 public <T> T get() throws PicocliException {
8032 Object obj = null;
8033 try { obj = scope.get(); }
8034 catch (Exception ex) { throw new PicocliException("Could not get scope for field " + field, ex); }
8035 try {
8036 @SuppressWarnings("unchecked") T result = (T) field.get(obj);
8037 return result;
8038 } catch (Exception ex) {
8039 throw new PicocliException("Could not get value for field " + field, ex);
8040 }
8041 }
8042 public <T> T set(T value) throws PicocliException {
8043 Object obj = null;
8044 try { obj = scope.get(); }
8045 catch (Exception ex) { throw new PicocliException("Could not get scope for field " + field, ex); }
8046 try {
8047 @SuppressWarnings("unchecked") T result = (T) field.get(obj);
8048 field.set(obj, value);
8049 return result;
8050 } catch (Exception ex) {
8051 throw new PicocliException("Could not set value for field " + field + " to " + value, ex);
8052 }
8053 }
8054 public String toString() {
8055 return String.format("%s(%s %s.%s)", getClass().getSimpleName(), field.getType().getName(),
8056 field.getDeclaringClass().getName(), field.getName());
8057 }
8058 }
8059 static class MethodBinding implements IGetter, ISetter {
8060 private final IScope scope;
8061 private final Method method;
8062 private final CommandSpec spec;
8063 private Object currentValue;
8064 MethodBinding(IScope scope, Method method, CommandSpec spec) {
8065 this.scope = scope;
8066 this.method = method;
8067 this.spec = spec;
8068 }
8069 @SuppressWarnings("unchecked") public <T> T get() { return (T) currentValue; }
8070 public <T> T set(T value) throws PicocliException {
8071 Object obj = null;
8072 try { obj = scope.get(); }
8073 catch (Exception ex) { throw new PicocliException("Could not get scope for method " + method, ex); }
8074 try {
8075 @SuppressWarnings("unchecked") T result = (T) currentValue;
8076 method.invoke(obj, value);
8077 currentValue = value;
8078 return result;
8079 } catch (InvocationTargetException ex) {
8080 if (ex.getCause() instanceof PicocliException) { throw (PicocliException) ex.getCause(); }
8081 throw createParameterException(value, ex.getCause());
8082 } catch (Exception ex) {
8083 throw createParameterException(value, ex);
8084 }
8085 }
8086 private ParameterException createParameterException(Object value, Throwable t) {
8087 CommandLine cmd = spec.commandLine() == null ? new CommandLine(spec) : spec.commandLine();
8088 return new ParameterException(cmd, "Could not invoke " + method + " with " + value, t);
8089 }
8090 public String toString() {
8091 return String.format("%s(%s)", getClass().getSimpleName(), method);
8092 }
8093 }
8094 private static class PicocliInvocationHandler implements InvocationHandler {
8095 final Map<String, Object> map = new HashMap<String, Object>();
8096 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
8097 return map.get(method.getName());
8098 }
8099 class ProxyBinding implements IGetter, ISetter {
8100 private final Method method;
8101 ProxyBinding(Method method) { this.method = Assert.notNull(method, "method"); }
8102 @SuppressWarnings("unchecked") public <T> T get() { return (T) map.get(method.getName()); }
8103 public <T> T set(T value) {
8104 T result = get();
8105 map.put(method.getName(), value);
8106 return result;
8107 }
8108 }
8109 }
8110 private static class ObjectBinding implements IGetter, ISetter {
8111 private Object value;
8112 @SuppressWarnings("unchecked") public <T> T get() { return (T) value; }
8113 public <T> T set(T value) {
8114 @SuppressWarnings("unchecked") T result = value;
8115 this.value = value;
8116 return result;
8117 }
8118 public String toString() {
8119 return String.format("%s(value=%s)", getClass().getSimpleName(), value);
8120 }
8121 }
8122 static class ObjectScope implements IScope {
8123 private Object value;
8124 public ObjectScope(Object value) { this.value = value; }
8125 @SuppressWarnings("unchecked") public <T> T get() { return (T) value; }
8126 @SuppressWarnings("unchecked") public <T> T set(T value) { T old = (T) this.value; this.value = value; return old; }
8127 public static Object tryGet(IScope scope) {
8128 try {
8129 return scope.get();
8130 } catch (Exception e) {
8131 throw new InitializationException("Could not get scope value", e);
8132 }
8133 }
8134 public String toString() { return String.format("Scope(value=%s)", value); }
8135 }
8136 }
8137
8138 /** Encapsulates the result of parsing an array of command line arguments.
8139 * @since 3.0 */
8140 public static class ParseResult {
8141 private final CommandSpec commandSpec;
8142 private final List<OptionSpec> matchedOptions;
8143 private final List<PositionalParamSpec> matchedUniquePositionals;
8144 private final List<String> originalArgs;
8145 private final List<String> unmatched;
8146 private final List<List<PositionalParamSpec>> matchedPositionalParams;
8147 private final List<Exception> errors;
8148 private final MatchedGroup matchedGroup;
8149 final List<Object> tentativeMatch;
8150
8151 private final ParseResult subcommand;
8152 private final boolean usageHelpRequested;
8153 private final boolean versionHelpRequested;
8154
8155 private ParseResult(ParseResult.Builder builder) {
8156 commandSpec = builder.commandSpec;
8157 subcommand = builder.subcommand;
8158 matchedOptions = new ArrayList<OptionSpec>(builder.options);
8159 unmatched = new ArrayList<String>(builder.unmatched);
8160 originalArgs = new ArrayList<String>(builder.originalArgList);
8161 matchedUniquePositionals = new ArrayList<PositionalParamSpec>(builder.positionals);
8162 matchedPositionalParams = new ArrayList<List<PositionalParamSpec>>(builder.positionalParams);
8163 errors = new ArrayList<Exception>(builder.errors);
8164 usageHelpRequested = builder.usageHelpRequested;
8165 versionHelpRequested = builder.versionHelpRequested;
8166 tentativeMatch = builder.nowProcessing;
8167 matchedGroup = builder.matchedGroup.trim();
8168 }
8169 /** Creates and returns a new {@code ParseResult.Builder} for the specified command spec. */
8170 public static Builder builder(CommandSpec commandSpec) { return new Builder(commandSpec); }
8171
8172 /**
8173 * Returns the matches for the specified argument group.
8174 * @since 4.0 */
8175 public List<MatchedGroup> findMatchedGroup(ArgGroupSpec group) {
8176 return matchedGroup.findMatchedGroup(group, new ArrayList<MatchedGroup>());
8177 }
8178
8179 /**
8180 * Returns the top-level container for the {@link ArgGroupSpec ArgGroupSpec} match or matches found.
8181 * <p>
8182 * If the user input was a valid combination of group arguments, the returned list should contain a single
8183 * {@linkplain MatchedGroupMultiple multiple}. Details of the {@linkplain MatchedGroup matched groups} encountered
8184 * on the command line can be obtained via its {@link MatchedGroupMultiple#matchedSubgroups() matchedSubgroups()} method.
8185 * The top-level multiple returned by this method contains no {@linkplain MatchedGroupMultiple#matchedValues(ArgSpec) matched arguments}.
8186 * </p><p>
8187 * If the returned list contains more than one {@linkplain MatchedGroupMultiple multiple}, the user input was invalid:
8188 * the maximum {@linkplain ArgGroup#multiplicity() multiplicity} of a group was exceeded, and the parser created an extra
8189 * {@code multiple} to capture the values. Usually this results in a {@link ParameterException ParameterException}
8190 * being thrown by the {@code parse} method, unless the parser is configured to {@linkplain ParserSpec#collectErrors() collect errors}.
8191 * </p>
8192 * @since 4.0 */
8193 public List<MatchedGroupMultiple> getMatchedGroupMultiples() {
8194 return matchedGroup.multiples();
8195 }
8196 /** Returns the option with the specified short name, or {@code null} if no option with that name was matched
8197 * on the command line.
8198 * <p>Use {@link OptionSpec#getValue() getValue} on the returned {@code OptionSpec} to get the matched value (or values),
8199 * converted to the type of the option. Alternatively, use {@link OptionSpec#stringValues() stringValues}
8200 * to get the matched String values after they were {@linkplain OptionSpec#splitRegex() split} into parts, or
8201 * {@link OptionSpec#originalStringValues() originalStringValues} to get the original String values that were
8202 * matched on the command line, before any processing.
8203 * </p><p>To get the {@linkplain OptionSpec#defaultValue() default value} of an option that was
8204 * {@linkplain #hasMatchedOption(char) <em>not</em> matched} on the command line, use
8205 * {@code parseResult.commandSpec().findOption(shortName).getValue()}. </p>
8206 * @see CommandSpec#findOption(char) */
8207 public OptionSpec matchedOption(char shortName) { return CommandSpec.findOption(shortName, matchedOptions); }
8208
8209 /** Returns the option with the specified name, or {@code null} if no option with that name was matched on the command line.
8210 * <p>Use {@link OptionSpec#getValue() getValue} on the returned {@code OptionSpec} to get the matched value (or values),
8211 * converted to the type of the option. Alternatively, use {@link OptionSpec#stringValues() stringValues}
8212 * to get the matched String values after they were {@linkplain OptionSpec#splitRegex() split} into parts, or
8213 * {@link OptionSpec#originalStringValues() originalStringValues} to get the original String values that were
8214 * matched on the command line, before any processing.
8215 * </p><p>To get the {@linkplain OptionSpec#defaultValue() default value} of an option that was
8216 * {@linkplain #hasMatchedOption(String) <em>not</em> matched} on the command line, use
8217 * {@code parseResult.commandSpec().findOption(String).getValue()}. </p>
8218 * @see CommandSpec#findOption(String)
8219 * @param name used to search the matched options. May be an alias of the option name that was actually specified on the command line.
8220 * The specified name may include option name prefix characters or not. */
8221 public OptionSpec matchedOption(String name) { return CommandSpec.findOption(name, matchedOptions); }
8222
8223 /** 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. */
8224 public PositionalParamSpec matchedPositional(int position) {
8225 if (matchedPositionalParams.size() <= position || matchedPositionalParams.get(position).isEmpty()) { return null; }
8226 return matchedPositionalParams.get(position).get(0);
8227 }
8228
8229 /** 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. */
8230 public List<PositionalParamSpec> matchedPositionals(int position) {
8231 if (matchedPositionalParams.size() <= position) { return Collections.emptyList(); }
8232 return matchedPositionalParams.get(position) == null ? Collections.<PositionalParamSpec>emptyList() : matchedPositionalParams.get(position);
8233 }
8234 /** Returns the {@code CommandSpec} for the matched command. */
8235 public CommandSpec commandSpec() { return commandSpec; }
8236
8237 /** Returns whether an option whose aliases include the specified short name was matched on the command line.
8238 * @param shortName used to search the matched options. May be an alias of the option name that was actually specified on the command line. */
8239 public boolean hasMatchedOption(char shortName) { return matchedOption(shortName) != null; }
8240 /** Returns whether an option whose aliases include the specified name was matched on the command line.
8241 * @param name used to search the matched options. May be an alias of the option name that was actually specified on the command line.
8242 * The specified name may include option name prefix characters or not. */
8243 public boolean hasMatchedOption(String name) { return matchedOption(name) != null; }
8244 /** Returns whether the specified option was matched on the command line. */
8245 public boolean hasMatchedOption(OptionSpec option) { return matchedOptions.contains(option); }
8246
8247 /** Returns whether a positional parameter was matched at the specified position. */
8248 public boolean hasMatchedPositional(int position) { return matchedPositional(position) != null; }
8249 /** Returns whether the specified positional parameter was matched on the command line. */
8250 public boolean hasMatchedPositional(PositionalParamSpec positional) { return matchedUniquePositionals.contains(positional); }
8251
8252 /** Returns a list of matched options, in the order they were found on the command line. */
8253 public List<OptionSpec> matchedOptions() { return Collections.unmodifiableList(matchedOptions); }
8254
8255 /** Returns a list of matched positional parameters. */
8256 public List<PositionalParamSpec> matchedPositionals() { return Collections.unmodifiableList(matchedUniquePositionals); }
8257
8258 /** Returns a list of command line arguments that did not match any options or positional parameters. */
8259 public List<String> unmatched() { return Collections.unmodifiableList(unmatched); }
8260
8261 /** Returns the command line arguments that were parsed. */
8262 public List<String> originalArgs() { return Collections.unmodifiableList(originalArgs); }
8263
8264 /** If {@link ParserSpec#collectErrors} is {@code true}, returns the list of exceptions that were encountered during parsing, otherwise, returns an empty list.
8265 * @since 3.2 */
8266 public List<Exception> errors() { return Collections.unmodifiableList(errors); }
8267
8268 /** 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. */
8269 public <T> T matchedOptionValue(char shortName, T defaultValue) { return matchedOptionValue(matchedOption(shortName), defaultValue); }
8270 /** 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. */
8271 public <T> T matchedOptionValue(String name, T defaultValue) { return matchedOptionValue(matchedOption(name), defaultValue); }
8272 /** 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}. */
8273 @SuppressWarnings("unchecked")
8274 private <T> T matchedOptionValue(OptionSpec option, T defaultValue) { return option == null ? defaultValue : (T) option.getValue(); }
8275
8276 /** 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. */
8277 public <T> T matchedPositionalValue(int position, T defaultValue) { return matchedPositionalValue(matchedPositional(position), defaultValue); }
8278 /** 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}. */
8279 @SuppressWarnings("unchecked")
8280 private <T> T matchedPositionalValue(PositionalParamSpec positional, T defaultValue) { return positional == null ? defaultValue : (T) positional.getValue(); }
8281
8282 /** Returns {@code true} if a subcommand was matched on the command line, {@code false} otherwise. */
8283 public boolean hasSubcommand() { return subcommand != null; }
8284
8285 /** 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. */
8286 public ParseResult subcommand() { return subcommand; }
8287
8288 /** Returns {@code true} if one of the options that was matched on the command line is a {@link OptionSpec#usageHelp() usageHelp} option. */
8289 public boolean isUsageHelpRequested() { return usageHelpRequested; }
8290
8291 /** Returns {@code true} if one of the options that was matched on the command line is a {@link OptionSpec#versionHelp() versionHelp} option. */
8292 public boolean isVersionHelpRequested() { return versionHelpRequested; }
8293
8294 /** Returns this {@code ParseResult} as a list of {@code CommandLine} objects, one for each matched command/subcommand.
8295 * For backwards compatibility with pre-3.0 methods. */
8296 public List<CommandLine> asCommandLineList() {
8297 List<CommandLine> result = new ArrayList<CommandLine>();
8298 ParseResult pr = this;
8299 while (pr != null) { result.add(pr.commandSpec().commandLine()); pr = pr.hasSubcommand() ? pr.subcommand() : null; }
8300 return result;
8301 }
8302
8303 /** Builds immutable {@code ParseResult} instances. */
8304 public static class Builder {
8305 private final CommandSpec commandSpec;
8306 private final Set<OptionSpec> options = new LinkedHashSet<OptionSpec>();
8307 private final Set<PositionalParamSpec> positionals = new LinkedHashSet<PositionalParamSpec>();
8308 private final List<String> unmatched = new ArrayList<String>();
8309 private final List<String> originalArgList = new ArrayList<String>();
8310 private final List<List<PositionalParamSpec>> positionalParams = new ArrayList<List<PositionalParamSpec>>();
8311 private ParseResult subcommand;
8312 private boolean usageHelpRequested;
8313 private boolean versionHelpRequested;
8314 boolean isInitializingDefaultValues;
8315 private List<Exception> errors = new ArrayList<Exception>(1);
8316 private List<Object> nowProcessing;
8317 private MatchedGroup matchedGroup = new MatchedGroup(null, null);
8318
8319 private Builder(CommandSpec spec) { commandSpec = Assert.notNull(spec, "commandSpec"); }
8320 /** Creates and returns a new {@code ParseResult} instance for this builder's configuration. */
8321 public ParseResult build() {
8322 return new ParseResult(this);
8323 }
8324
8325 private void nowProcessing(ArgSpec spec, Object value) {
8326 if (nowProcessing != null && !isInitializingDefaultValues) {
8327 nowProcessing.add(spec.isPositional() ? spec : value);
8328 }
8329 }
8330
8331 /** Adds the specified {@code OptionSpec} or {@code PositionalParamSpec} to the list of options and parameters
8332 * that were matched on the command line.
8333 * @param arg the matched {@code OptionSpec} or {@code PositionalParamSpec}
8334 * @param position the command line position at which the {@code PositionalParamSpec} was matched. Ignored for {@code OptionSpec}s.
8335 * @return this builder for method chaining */
8336 public Builder add(ArgSpec arg, int position) {
8337 if (arg.isOption()) {
8338 addOption((OptionSpec) arg);
8339 } else {
8340 addPositionalParam((PositionalParamSpec) arg, position);
8341 }
8342 afterMatchingGroupElement(arg, position);
8343 return this;
8344 }
8345
8346 /** Adds the specified {@code OptionSpec} to the list of options that were matched on the command line. */
8347 public Builder addOption(OptionSpec option) { if (!isInitializingDefaultValues) {options.add(option);} return this; }
8348 /** Adds the specified {@code PositionalParamSpec} to the list of parameters that were matched on the command line.
8349 * @param positionalParam the matched {@code PositionalParamSpec}
8350 * @param position the command line position at which the {@code PositionalParamSpec} was matched.
8351 * @return this builder for method chaining */
8352 public Builder addPositionalParam(PositionalParamSpec positionalParam, int position) {
8353 if (isInitializingDefaultValues) { return this; }
8354 positionals.add(positionalParam);
8355 while (positionalParams.size() <= position) { positionalParams.add(new ArrayList<PositionalParamSpec>()); }
8356 positionalParams.get(position).add(positionalParam);
8357 return this;
8358 }
8359 /** Adds the specified command line argument to the list of unmatched command line arguments. */
8360 public Builder addUnmatched(String arg) { unmatched.add(arg); return this; }
8361 /** Adds all elements of the specified command line arguments stack to the list of unmatched command line arguments. */
8362 public Builder addUnmatched(Stack<String> args) { while (!args.isEmpty()) { addUnmatched(args.pop()); } return this; }
8363 /** Sets the specified {@code ParseResult} for a subcommand that was matched on the command line. */
8364 public Builder subcommand(ParseResult subcommand) { this.subcommand = subcommand; return this; }
8365 /** Sets the specified command line arguments that were parsed. */
8366 public Builder originalArgs(String[] originalArgs) { originalArgList.addAll(Arrays.asList(originalArgs)); return this;}
8367
8368 void addStringValue (ArgSpec argSpec, String value) { if (!isInitializingDefaultValues) { argSpec.stringValues.add(value);} }
8369 void addOriginalStringValue(ArgSpec argSpec, String value) {
8370 if (!isInitializingDefaultValues) {
8371 argSpec.originalStringValues.add(value);
8372 if (argSpec.group() != null) {
8373 MatchedGroup matchedGroup = this.matchedGroup.findOrCreateMatchingGroup(argSpec, commandSpec.commandLine);
8374 matchedGroup.multiple().addOriginalStringValue(argSpec, value);
8375 }
8376 }
8377 }
8378
8379 void addTypedValues(ArgSpec argSpec, int position, Object typedValue) {
8380 if (!isInitializingDefaultValues) {
8381 argSpec.typedValues.add(typedValue);
8382 if (argSpec.group() == null) {
8383 argSpec.typedValueAtPosition.put(position, typedValue);
8384 } else {
8385 MatchedGroup matchedGroup = this.matchedGroup.findOrCreateMatchingGroup(argSpec, commandSpec.commandLine);
8386 matchedGroup.multiple().addMatchedValue(argSpec, position, typedValue, commandSpec.commandLine.tracer);
8387 }
8388 }
8389 }
8390
8391 public void addError(PicocliException ex) {
8392 errors.add(Assert.notNull(ex, "exception"));
8393 }
8394
8395 void beforeMatchingGroupElement(ArgSpec argSpec) throws Exception {
8396 ArgGroupSpec group = argSpec.group();
8397 if (group == null || isInitializingDefaultValues) { return; }
8398 MatchedGroup foundMatchedGroup = this.matchedGroup.findOrCreateMatchingGroup(argSpec, commandSpec.commandLine);
8399 if (foundMatchedGroup.multiple().matchedMinElements() && argSpec.required()) {
8400 // we need to create a new multiple; if maxMultiplicity has been reached, we need to add a new MatchedGroup.
8401 String elementDescription = ArgSpec.describe(argSpec, "=");
8402 Tracer tracer = commandSpec.commandLine.tracer;
8403 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);
8404 foundMatchedGroup.addMultiple(commandSpec.commandLine);
8405 }
8406 }
8407
8408 private void afterMatchingGroupElement(ArgSpec argSpec, int position) {
8409// ArgGroupSpec group = argSpec.group();
8410// if (group == null || isInitializingDefaultValues) { return; }
8411// MatchedGroup matchedGroup = this.matchedGroup.findOrCreateMatchingGroup(argSpec, commandSpec.commandLine);
8412// promotePartiallyMatchedGroupToMatched(group, matchedGroup, true);
8413 }
8414
8415 private void promotePartiallyMatchedGroupToMatched(ArgGroupSpec group, MatchedGroup matchedGroup, boolean allRequired) {
8416 if (!matchedGroup.matchedFully(allRequired)) { return; }
8417
8418 // FIXME: before promoting the child group, check to see if the parent is matched, given the child group
8419
8420 Tracer tracer = commandSpec.commandLine.tracer;
8421 if (matchedGroup.matchedMaxElements()) {
8422 tracer.info("Marking matched group %s as complete: max elements reached. User object: %s%n", matchedGroup, matchedGroup.group.userObject());
8423 matchedGroup.complete(commandSpec.commandLine());
8424 }
8425 }
8426 }
8427
8428 /** Provides information about an {@link ArgGroup} that was matched on the command line.
8429 * <p>
8430 * The {@code ParseResult} may have more than one {@code MatchedGroup} for an {@code ArgGroupSpec}, when the
8431 * group was matched more often than its maximum {@linkplain ArgGroup#multiplicity() multiplicity}.
8432 * This is not necessarily a problem: the parser will add a multiple to the {@linkplain MatchedGroup#parentMatchedGroup() parent matched group}
8433 * 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.
8434 * </p><p>
8435 * Ultimately, as long as the {@link ParseResult#getMatchedGroupMultiples()} method does not return more than one multiple, the maximum number of elements is not exceeded.
8436 * </p>
8437 * @since 4.0 */
8438 public static class MatchedGroup {
8439 private final ArgGroupSpec group;
8440 private MatchedGroup parentMatchedGroup;
8441 private List<MatchedGroupMultiple> multiples = new ArrayList<MatchedGroupMultiple>();
8442
8443 MatchedGroup(ArgGroupSpec group, CommandLine cmd) { this.group = group; addMultiple(cmd);}
8444
8445 /** Returns the {@code ArgGroupSpec} whose matches are captured in this {@code MatchedGroup}. */
8446 public ArgGroupSpec group() { return group; }
8447 /** Returns the {@code MatchedGroup} of the parent {@code ArgGroupSpec}, or {@code null} if this group has no parent. */
8448 public MatchedGroup parentMatchedGroup() { return parentMatchedGroup; }
8449 /** Returns the list of {@code MatchedGroupMultiple} instances: {@code ArgGroupSpec}s with a multiplicity greater than one may be matched multiple times. */
8450 public List<MatchedGroupMultiple> multiples() { return Collections.unmodifiableList(multiples); }
8451
8452 void addMultiple(CommandLine commandLine) {
8453 Tracer tracer = commandLine == null ? new Tracer() : commandLine.tracer;
8454 if (group != null && isMaxMultiplicityReached()) {
8455 tracer.info("Completing MatchedGroup %s: max multiplicity is reached.%n", this);
8456 complete(commandLine);
8457 } else {
8458 if (group != null) {
8459 tracer.info("Adding multiple to MatchedGroup %s (group=%s %s).%n", this, group == null ? "?" : group.id(), group == null ? "ROOT" : group.synopsis());
8460 }
8461 multiples.add(new MatchedGroupMultiple(this));
8462 if (group == null) { return; }
8463 }
8464 group.initUserObject(commandLine);
8465 }
8466 void complete(CommandLine commandLine) {
8467 if (parentMatchedGroup == null) {
8468 addMultiple(commandLine); // we have no choice but to potentially exceed the max multiplicity of this group...
8469 } else {
8470 parentMatchedGroup.addMultiple(commandLine);
8471 }
8472 }
8473 /** Returns the "active" multiple of this MatchedGroup. */
8474 MatchedGroupMultiple multiple() { return multiples.get(multiples.size() - 1); }
8475 /** 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.*/
8476 boolean isMaxMultiplicityReached() { return multiples.size() >= group.multiplicity.max; }
8477 /** 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. */
8478 boolean isMinMultiplicityReached() { return multiples.size() >= group.multiplicity.min; }
8479
8480 /** Returns {@code true} if the minimum number of multiples has been matched for the multiplicity of this group,
8481 * and each multiple has matched at least the {@linkplain MatchedGroupMultiple#matchedMinElements() minimum number of elements}.*/
8482 boolean matchedMinElements() { return matchedFully(false); }
8483 /** Returns {@code true} if the maximum number of multiples has been matched for the multiplicity of this group,
8484 * and the last multiple has {@linkplain MatchedGroupMultiple#matchedMaxElements() matched the maximum number of elements},
8485 * while all other multiples have matched at least the {@linkplain MatchedGroupMultiple#matchedMinElements() minimum number of elements}.*/
8486 boolean matchedMaxElements() { return matchedFully(true); }
8487 private boolean matchedFully(boolean allRequired) {
8488 for (MatchedGroupMultiple multiple : multiples) {
8489 boolean actuallyAllRequired = allRequired && multiple == multiple();
8490 if (!multiple.matchedFully(actuallyAllRequired)) { return false; }
8491 }
8492 return allRequired ? isMaxMultiplicityReached() : isMinMultiplicityReached();
8493 }
8494
8495 private MatchedGroup findOrCreateMatchingGroup(ArgSpec argSpec, CommandLine commandLine) {
8496 ArgGroupSpec searchGroup = Assert.notNull(argSpec.group(), "group for " + argSpec);
8497 MatchedGroup match = this;
8498 if (searchGroup == match.group()) { return match; }
8499 List<ArgGroupSpec> keys = new ArrayList<ArgGroupSpec>();
8500 while (searchGroup != null) {
8501 keys.add(searchGroup);
8502 searchGroup = searchGroup.parentGroup();
8503 }
8504 Collections.reverse(keys);
8505 for (ArgGroupSpec key : keys) {
8506 MatchedGroup sub = match.multiple().matchedSubgroups().get(key);
8507 if (sub == null) {
8508 sub = createMatchedGroup(key, match, commandLine);
8509 }
8510 match = sub;
8511 }
8512 return match;
8513 }
8514 private MatchedGroup createMatchedGroup(ArgGroupSpec group, MatchedGroup parent, CommandLine commandLine) {
8515 MatchedGroup result = new MatchedGroup(group, commandLine);
8516 result.parentMatchedGroup = parent;
8517 parent.multiple().matchedSubgroups.put(group, result);
8518 return result;
8519 }
8520 MatchedGroup trim() {
8521 for (Iterator<MatchedGroupMultiple> iter = multiples.iterator(); iter.hasNext(); ) {
8522 MatchedGroupMultiple multiple = iter.next();
8523 if (multiple.isEmpty()) { iter.remove(); }
8524 for (MatchedGroup sub : multiple.matchedSubgroups.values()) { sub.trim(); }
8525 }
8526 return this;
8527 }
8528
8529 List<MatchedGroup> findMatchedGroup(ArgGroupSpec group, List<MatchedGroup> result) {
8530 if (this.group == group) { result.add(this); return result; }
8531 for (MatchedGroupMultiple multiple : multiples()) {
8532 for (MatchedGroup mg : multiple.matchedSubgroups.values()) {
8533 mg.findMatchedGroup(group, result);
8534 }
8535 }
8536 return result;
8537 }
8538
8539 @Override public String toString() {
8540 return toString(new StringBuilder()).toString();
8541 }
8542
8543 private StringBuilder toString(StringBuilder result) {
8544 String prefix = result.length() == 0 ? "={" : "";
8545 String suffix = result.length() == 0 ? "}" : "";
8546 if (group != null && result.length() == 0) {
8547 result.append(group.synopsis());
8548 }
8549 result.append(prefix);
8550 String infix = "";
8551 for (MatchedGroupMultiple occurrence : multiples) {
8552 result.append(infix);
8553 occurrence.toString(result);
8554 infix = " ";
8555 }
8556 return result.append(suffix);
8557
8558 }
8559 }
8560
8561 /** A group's {@linkplain ArgGroup#multiplicity() multiplicity} specifies how many multiples of a group can/must
8562 * appear on the command line before a group is fully matched. This class models a single "multiple".
8563 * For example, this group: {@code (-a -b) (-a -b)} requires two multiples of its arguments to fully match.
8564 * @since 4.0
8565 */
8566 public static class MatchedGroupMultiple {
8567 int position;
8568 final MatchedGroup container;
8569
8570 Map<ArgGroupSpec, MatchedGroup> matchedSubgroups = new LinkedHashMap<ArgGroupSpec, MatchedGroup>(2); // preserve order: used in toString()
8571 Map<ArgSpec, List<Object>> matchedValues = new IdentityHashMap<ArgSpec, List<Object>>(); // identity map for performance
8572 Map<ArgSpec, List<String>> originalStringValues = new LinkedHashMap<ArgSpec, List<String>>(); // preserve order: used in toString()
8573 Map<ArgSpec, Map<Integer, List<Object>>> matchedValuesAtPosition = new IdentityHashMap<ArgSpec, Map<Integer, List<Object>>>();
8574
8575 MatchedGroupMultiple(MatchedGroup container) { this.container = container; }
8576
8577 /** Returns {@code true} if this multiple has no matched arguments and no matched subgroups. */
8578 public boolean isEmpty() { return originalStringValues.isEmpty() && matchedSubgroups.isEmpty(); }
8579 /** Returns the {@code ArgGroupSpec} of the container {@code MatchedGroup} of this multiple. */
8580 public ArgGroupSpec group() { return container.group; }
8581 /** Returns the container {@code MatchedGroup} of this multiple. */
8582 public MatchedGroup container() { return container; }
8583 /** Returns matches for the subgroups, if any. */
8584 public Map<ArgGroupSpec, MatchedGroup> matchedSubgroups() { return Collections.unmodifiableMap(matchedSubgroups); }
8585 int matchCount(ArgSpec argSpec) { return matchedValues.get(argSpec) == null ? 0 : matchedValues.get(argSpec).size(); }
8586 /** Returns the values matched for the specified argument, converted to the type of the argument. */
8587 public List<Object> matchedValues(ArgSpec argSpec) { return matchedValues.get(argSpec) == null ? Collections.emptyList() : Collections.unmodifiableList(matchedValues.get(argSpec)); }
8588 void addOriginalStringValue(ArgSpec argSpec, String value) {
8589 addValueToListInMap(originalStringValues, argSpec, value);
8590 }
8591 void addMatchedValue(ArgSpec argSpec, int matchPosition, Object stronglyTypedValue, Tracer tracer) {
8592 addValueToListInMap(matchedValues, argSpec, stronglyTypedValue);
8593
8594 Map<Integer, List<Object>> positionalValues = matchedValuesAtPosition.get(argSpec);
8595 if (positionalValues == null) {
8596 positionalValues = new TreeMap<Integer, List<Object>>();
8597 matchedValuesAtPosition.put(argSpec, positionalValues);
8598 }
8599 addValueToListInMap(positionalValues, matchPosition, stronglyTypedValue);
8600 }
8601 boolean hasMatchedValueAtPosition(ArgSpec arg, int position) { Map<Integer, List<Object>> atPos = matchedValuesAtPosition.get(arg); return atPos != null && atPos.containsKey(position); }
8602
8603 /** Returns {@code true} if the minimum number of elements have been matched for this multiple:
8604 * all required arguments have been matched, and for each subgroup,
8605 * the {@linkplain MatchedGroup#matchedMinElements() minimum number of elements have been matched}.*/
8606 boolean matchedMinElements() { return matchedFully(false); }
8607 /** Returns {@code true} if the maximum number of multiples has been matched for this multiple:
8608 * all arguments (required or not) have been matched, and for each subgroup,
8609 * the {@linkplain MatchedGroup#matchedMaxElements() maximum number of elements have been matched}.*/
8610 boolean matchedMaxElements() { return matchedFully(true); }
8611 private boolean matchedFully(boolean allRequired) {
8612 if (group().exclusive()) { return !matchedValues.isEmpty() || hasFullyMatchedSubgroup(allRequired); }
8613 for (ArgSpec arg : group().args()) {
8614 if (matchedValues.get(arg) == null && (arg.required() || allRequired)) { return false; }
8615 }
8616 for (ArgGroupSpec subgroup : group().subgroups()) {
8617 MatchedGroup matchedGroup = matchedSubgroups.get(subgroup);
8618 if (matchedGroup != null) {
8619 if (!matchedGroup.matchedFully(allRequired)) { return false; }
8620 } else {
8621 if (allRequired || subgroup.multiplicity().min > 0) { return false; }
8622 }
8623 }
8624 return true;
8625 }
8626 private boolean hasFullyMatchedSubgroup(boolean allRequired) {
8627 for (MatchedGroup sub : matchedSubgroups.values()) { if (sub.matchedFully(allRequired)) { return true; } }
8628 return false;
8629 }
8630 @Override public String toString() {
8631 return toString(new StringBuilder()).toString();
8632 }
8633
8634 private StringBuilder toString(StringBuilder result) {
8635 int originalLength = result.length();
8636 for (ArgSpec arg : originalStringValues.keySet()) {
8637 List<String> values = originalStringValues.get(arg);
8638 for (String value : values) {
8639 if (result.length() != originalLength) { result.append(" "); }
8640 result.append(ArgSpec.describe(arg, "=", value));
8641 }
8642 }
8643 for (MatchedGroup sub : matchedSubgroups.values()) {
8644 if (result.length() != originalLength) { result.append(" "); }
8645 if (originalLength == 0) {
8646 result.append(sub.toString()); // include synopsis
8647 } else {
8648 sub.toString(result); // without synopsis
8649 }
8650 }
8651 return result;
8652 }
8653 }
8654 }
8655 static <K, T> void addValueToListInMap(Map<K, List<T>> map, K key, T value) {
8656 List<T> values = map.get(key);
8657 if (values == null) { values = new ArrayList<T>(); map.put(key, values); }
8658 values.add(value);
8659 }
8660 static <T> List<T> flatList(Collection<? extends Collection<T>> collection) {
8661 List<T> result = new ArrayList<T>();
8662 for (Collection<T> sub : collection) { result.addAll(sub); }
8663 return result;
8664 }
8665 private enum LookBehind { SEPARATE, ATTACHED, ATTACHED_WITH_SEPARATOR;
8666 public boolean isAttached() { return this != LookBehind.SEPARATE; }
8667 }
8668 /**
8669 * Helper class responsible for processing command line arguments.
8670 */
8671 private class Interpreter {
8672 private final Map<Class<?>, ITypeConverter<?>> converterRegistry = new HashMap<Class<?>, ITypeConverter<?>>();
8673 private boolean isHelpRequested;
8674 private int position;
8675 private boolean endOfOptions;
8676 private ParseResult.Builder parseResultBuilder;
8677
8678 Interpreter() { registerBuiltInConverters(); }
8679
8680 private void registerBuiltInConverters() {
8681 converterRegistry.put(Object.class, new BuiltIn.StringConverter());
8682 converterRegistry.put(String.class, new BuiltIn.StringConverter());
8683 converterRegistry.put(StringBuilder.class, new BuiltIn.StringBuilderConverter());
8684 converterRegistry.put(CharSequence.class, new BuiltIn.CharSequenceConverter());
8685 converterRegistry.put(Byte.class, new BuiltIn.ByteConverter());
8686 converterRegistry.put(Byte.TYPE, new BuiltIn.ByteConverter());
8687 converterRegistry.put(Boolean.class, new BuiltIn.BooleanConverter());
8688 converterRegistry.put(Boolean.TYPE, new BuiltIn.BooleanConverter());
8689 converterRegistry.put(Character.class, new BuiltIn.CharacterConverter());
8690 converterRegistry.put(Character.TYPE, new BuiltIn.CharacterConverter());
8691 converterRegistry.put(Short.class, new BuiltIn.ShortConverter());
8692 converterRegistry.put(Short.TYPE, new BuiltIn.ShortConverter());
8693 converterRegistry.put(Integer.class, new BuiltIn.IntegerConverter());
8694 converterRegistry.put(Integer.TYPE, new BuiltIn.IntegerConverter());
8695 converterRegistry.put(Long.class, new BuiltIn.LongConverter());
8696 converterRegistry.put(Long.TYPE, new BuiltIn.LongConverter());
8697 converterRegistry.put(Float.class, new BuiltIn.FloatConverter());
8698 converterRegistry.put(Float.TYPE, new BuiltIn.FloatConverter());
8699 converterRegistry.put(Double.class, new BuiltIn.DoubleConverter());
8700 converterRegistry.put(Double.TYPE, new BuiltIn.DoubleConverter());
8701 converterRegistry.put(File.class, new BuiltIn.FileConverter());
8702 converterRegistry.put(URI.class, new BuiltIn.URIConverter());
8703 converterRegistry.put(URL.class, new BuiltIn.URLConverter());
8704 converterRegistry.put(Date.class, new BuiltIn.ISO8601DateConverter());
8705 converterRegistry.put(BigDecimal.class, new BuiltIn.BigDecimalConverter());
8706 converterRegistry.put(BigInteger.class, new BuiltIn.BigIntegerConverter());
8707 converterRegistry.put(Charset.class, new BuiltIn.CharsetConverter());
8708 converterRegistry.put(InetAddress.class, new BuiltIn.InetAddressConverter());
8709 converterRegistry.put(Pattern.class, new BuiltIn.PatternConverter());
8710 converterRegistry.put(UUID.class, new BuiltIn.UUIDConverter());
8711 converterRegistry.put(Currency.class, new BuiltIn.CurrencyConverter());
8712 converterRegistry.put(TimeZone.class, new BuiltIn.TimeZoneConverter());
8713 converterRegistry.put(ByteOrder.class, new BuiltIn.ByteOrderConverter());
8714 converterRegistry.put(Class.class, new BuiltIn.ClassConverter());
8715 converterRegistry.put(NetworkInterface.class, new BuiltIn.NetworkInterfaceConverter());
8716
8717 BuiltIn.ISO8601TimeConverter.registerIfAvailable(converterRegistry, tracer);
8718 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.sql.Connection", "java.sql.DriverManager","getConnection", String.class);
8719 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.sql.Driver", "java.sql.DriverManager","getDriver", String.class);
8720 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.sql.Timestamp", "java.sql.Timestamp","valueOf", String.class);
8721
8722 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.Duration", "parse", CharSequence.class);
8723 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.Instant", "parse", CharSequence.class);
8724 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.LocalDate", "parse", CharSequence.class);
8725 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.LocalDateTime", "parse", CharSequence.class);
8726 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.LocalTime", "parse", CharSequence.class);
8727 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.MonthDay", "parse", CharSequence.class);
8728 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.OffsetDateTime", "parse", CharSequence.class);
8729 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.OffsetTime", "parse", CharSequence.class);
8730 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.Period", "parse", CharSequence.class);
8731 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.Year", "parse", CharSequence.class);
8732 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.YearMonth", "parse", CharSequence.class);
8733 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.ZonedDateTime", "parse", CharSequence.class);
8734 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.ZoneId", "of", String.class);
8735 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.time.ZoneOffset", "of", String.class);
8736
8737 BuiltIn.registerIfAvailable(converterRegistry, tracer, "java.nio.file.Path", "java.nio.file.Paths", "get", String.class, String[].class);
8738 }
8739 private ParserSpec config() { return commandSpec.parser(); }
8740 /**
8741 * Entry point into parsing command line arguments.
8742 * @param args the command line arguments
8743 * @return a list with all commands and subcommands initialized by this method
8744 * @throws ParameterException if the specified command line arguments are invalid
8745 */
8746 List<CommandLine> parse(String... args) {
8747 Assert.notNull(args, "argument array");
8748 if (tracer.isInfo()) {tracer.info("Picocli version: %s%n", versionString());}
8749 if (tracer.isInfo()) {tracer.info("Parsing %d command line args %s%n", args.length, Arrays.toString(args));}
8750 if (tracer.isDebug()){tracer.debug("Parser configuration: %s%n", config());}
8751 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",
8752 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"));}
8753 List<String> expanded = new ArrayList<String>();
8754 for (String arg : args) { addOrExpand(arg, expanded, new LinkedHashSet<String>()); }
8755 Stack<String> arguments = new Stack<String>();
8756 arguments.addAll(reverseList(expanded));
8757 List<CommandLine> result = new ArrayList<CommandLine>();
8758 parse(result, arguments, args, new ArrayList<Object>());
8759 return result;
8760 }
8761
8762 private void addOrExpand(String arg, List<String> arguments, Set<String> visited) {
8763 if (config().expandAtFiles() && !arg.equals("@") && arg.startsWith("@")) {
8764 arg = arg.substring(1);
8765 if (arg.startsWith("@")) {
8766 if (tracer.isInfo()) { tracer.info("Not expanding @-escaped argument %s (trimmed leading '@' char)%n", arg); }
8767 } else {
8768 if (tracer.isInfo()) { tracer.info("Expanding argument file @%s%n", arg); }
8769 expandArgumentFile(arg, arguments, visited);
8770 return;
8771 }
8772 }
8773 arguments.add(arg);
8774 }
8775 private void expandArgumentFile(String fileName, List<String> arguments, Set<String> visited) {
8776 File file = new File(fileName);
8777 if (!file.canRead()) {
8778 if (tracer.isInfo()) {tracer.info("File %s does not exist or cannot be read; treating argument literally%n", fileName);}
8779 arguments.add("@" + fileName);
8780 } else if (visited.contains(file.getAbsolutePath())) {
8781 if (tracer.isInfo()) {tracer.info("Already visited file %s; ignoring...%n", file.getAbsolutePath());}
8782 } else {
8783 expandValidArgumentFile(fileName, file, arguments, visited);
8784 }
8785 }
8786 private void expandValidArgumentFile(String fileName, File file, List<String> arguments, Set<String> visited) {
8787 List<String> result = new ArrayList<String>();
8788 LineNumberReader reader = null;
8789 try {
8790 visited.add(file.getAbsolutePath());
8791 reader = new LineNumberReader(new FileReader(file));
8792 if (commandSpec.parser().useSimplifiedAtFiles()) {
8793 String token;
8794 while ((token = reader.readLine()) != null) {
8795 if (token.length() > 0 && !token.trim().startsWith(String.valueOf(commandSpec.parser().atFileCommentChar()))) {
8796 addOrExpand(token, result, visited);
8797 }
8798 }
8799 } else {
8800 StreamTokenizer tok = new StreamTokenizer(reader);
8801 tok.resetSyntax();
8802 tok.wordChars(' ', 255);
8803 tok.whitespaceChars(0, ' ');
8804 tok.quoteChar('"');
8805 tok.quoteChar('\'');
8806 if (commandSpec.parser().atFileCommentChar() != null) {
8807 tok.commentChar(commandSpec.parser().atFileCommentChar());
8808 }
8809 while (tok.nextToken() != StreamTokenizer.TT_EOF) {
8810 addOrExpand(tok.sval, result, visited);
8811 }
8812 }
8813 } catch (Exception ex) {
8814 throw new InitializationException("Could not read argument file @" + fileName, ex);
8815 } finally {
8816 if (reader != null) { try {reader.close();} catch (Exception ignored) {} }
8817 }
8818 if (tracer.isInfo()) {tracer.info("Expanded file @%s to arguments %s%n", fileName, result);}
8819 arguments.addAll(result);
8820 }
8821 private void clear() {
8822 position = 0;
8823 endOfOptions = false;
8824 isHelpRequested = false;
8825 parseResultBuilder = ParseResult.builder(getCommandSpec());
8826 for (OptionSpec option : getCommandSpec().options()) { clear(option); }
8827 for (PositionalParamSpec positional : getCommandSpec().positionalParameters()) { clear(positional); }
8828 }
8829 private void clear(ArgSpec argSpec) {
8830 argSpec.resetStringValues();
8831 argSpec.resetOriginalStringValues();
8832 argSpec.typedValues.clear();
8833 argSpec.typedValueAtPosition.clear();
8834 if (argSpec.group() == null) { argSpec.applyInitialValue(tracer); } // groups do their own initialization
8835 }
8836
8837 private void maybeThrow(PicocliException ex) throws PicocliException {
8838 if (commandSpec.parser().collectErrors) {
8839 parseResultBuilder.addError(ex);
8840 } else {
8841 throw ex;
8842 }
8843 }
8844
8845 private void parse(List<CommandLine> parsedCommands, Stack<String> argumentStack, String[] originalArgs, List<Object> nowProcessing) {
8846 clear(); // first reset any state in case this CommandLine instance is being reused
8847 if (tracer.isDebug()) {
8848 tracer.debug("Initializing %s: %d options, %d positional parameters, %d required, %d groups, %d subcommands.%n",
8849 commandSpec.toString(), new HashSet<ArgSpec>(commandSpec.optionsMap().values()).size(),
8850 commandSpec.positionalParameters().size(), commandSpec.requiredArgs().size(),
8851 commandSpec.argGroups().size(), commandSpec.subcommands().size());
8852 }
8853 parsedCommands.add(CommandLine.this);
8854 List<ArgSpec> required = new ArrayList<ArgSpec>(commandSpec.requiredArgs());
8855 Set<ArgSpec> initialized = new LinkedHashSet<ArgSpec>();
8856 Collections.sort(required, new PositionalParametersSorter());
8857 boolean continueOnError = commandSpec.parser().collectErrors();
8858 do {
8859 int stackSize = argumentStack.size();
8860 try {
8861 applyDefaultValues(required);
8862 processArguments(parsedCommands, argumentStack, required, initialized, originalArgs, nowProcessing);
8863 } catch (ParameterException ex) {
8864 maybeThrow(ex);
8865 } catch (Exception ex) {
8866 int offendingArgIndex = originalArgs.length - argumentStack.size() - 1;
8867 String arg = offendingArgIndex >= 0 && offendingArgIndex < originalArgs.length ? originalArgs[offendingArgIndex] : "?";
8868 maybeThrow(ParameterException.create(CommandLine.this, ex, arg, offendingArgIndex, originalArgs));
8869 }
8870 if (continueOnError && stackSize == argumentStack.size() && stackSize > 0) {
8871 parseResultBuilder.unmatched.add(argumentStack.pop());
8872 }
8873 } while (!argumentStack.isEmpty() && continueOnError);
8874
8875 if (!isAnyHelpRequested()) {
8876 validateConstraints(argumentStack, required, initialized);
8877 }
8878 }
8879
8880 private void validateConstraints(Stack<String> argumentStack, List<ArgSpec> required, Set<ArgSpec> matched) {
8881 if (!required.isEmpty()) {
8882 for (ArgSpec missing : required) {
8883 Assert.assertTrue(missing.group() == null, "Arguments in a group are not necessarily required for the command");
8884 if (missing.isOption()) {
8885 maybeThrow(MissingParameterException.create(CommandLine.this, required, config().separator()));
8886 } else {
8887 assertNoMissingParameters(missing, missing.arity(), argumentStack);
8888 }
8889 }
8890 }
8891 if (!parseResultBuilder.unmatched.isEmpty()) {
8892 String[] unmatched = parseResultBuilder.unmatched.toArray(new String[0]);
8893 for (UnmatchedArgsBinding unmatchedArgsBinding : getCommandSpec().unmatchedArgsBindings()) {
8894 unmatchedArgsBinding.addAll(unmatched.clone());
8895 }
8896 if (!isUnmatchedArgumentsAllowed()) { maybeThrow(new UnmatchedArgumentException(CommandLine.this, Collections.unmodifiableList(parseResultBuilder.unmatched))); }
8897 if (tracer.isInfo()) { tracer.info("Unmatched arguments: %s%n", parseResultBuilder.unmatched); }
8898 }
8899 for (ArgGroupSpec group : commandSpec.argGroups()) {
8900 group.clearValidationResult();
8901 }
8902 ParseResult pr = parseResultBuilder.build();
8903 for (ArgGroupSpec group : commandSpec.argGroups()) {
8904 group.validateConstraints(pr);
8905 }
8906 List<ParseResult.MatchedGroupMultiple> matchedGroupMultiples = pr.getMatchedGroupMultiples();
8907 if (matchedGroupMultiples.size() > 1) {
8908 failGroupMultiplicityExceeded(matchedGroupMultiples);
8909 }
8910 }
8911
8912 private void failGroupMultiplicityExceeded(List<ParseResult.MatchedGroupMultiple> matchedGroupMultiples) {
8913 Map<ArgGroupSpec, List<List<ParseResult.MatchedGroupMultiple>>> multiplesPerGroup = new IdentityHashMap<ArgGroupSpec, List<List<ParseResult.MatchedGroupMultiple>>>();
8914 String msg = "";
8915 for (ParseResult.MatchedGroupMultiple multiple : matchedGroupMultiples) {
8916 if (msg.length() > 0) { msg += " and "; }
8917 msg += multiple.toString();
8918 Map<ArgGroupSpec, MatchedGroup> subgroups = multiple.matchedSubgroups();
8919 for (ArgGroupSpec group : subgroups.keySet()) {
8920 addValueToListInMap(multiplesPerGroup, group, subgroups.get(group).multiples());
8921 }
8922 }
8923 if (!simplifyErrorMessageForSingleGroup(multiplesPerGroup)) {
8924 maybeThrow(new MaxValuesExceededException(CommandLine.this, "Error: expected only one match but got " + msg));
8925 }
8926 }
8927
8928 private boolean simplifyErrorMessageForSingleGroup(Map<ArgGroupSpec, List<List<ParseResult.MatchedGroupMultiple>>> multiplesPerGroup) {
8929 if (multiplesPerGroup.size() == 1) { // all multiples were matches for a single group
8930 ArgGroupSpec group = multiplesPerGroup.keySet().iterator().next();
8931 List<ParseResult.MatchedGroupMultiple> flat = flatList(multiplesPerGroup.get(group));
8932 for (ParseResult.MatchedGroupMultiple multiple : flat) {
8933 if (!multiple.matchedSubgroups().isEmpty()) { return false; }
8934 }
8935 group.validationException = null;
8936 group.validateMultiples(CommandLine.this, flat);
8937 if (group.validationException != null) {
8938 maybeThrow(group.validationException);
8939 return true;
8940 }
8941 }
8942 return false;
8943 }
8944
8945 private void applyDefaultValues(List<ArgSpec> required) throws Exception {
8946 parseResultBuilder.isInitializingDefaultValues = true;
8947 for (ArgSpec arg : commandSpec.args()) {
8948 if (arg.group() == null) {
8949 if (applyDefault(commandSpec.defaultValueProvider(), arg)) { required.remove(arg); }
8950 }
8951 }
8952 parseResultBuilder.isInitializingDefaultValues = false;
8953 }
8954
8955 private boolean applyDefault(IDefaultValueProvider defaultValueProvider, ArgSpec arg) throws Exception {
8956
8957 // Default value provider return value is only used if provider exists and if value
8958 // is not null otherwise the original default or initial value are used
8959 String fromProvider = defaultValueProvider == null ? null : defaultValueProvider.defaultValue(arg);
8960 String defaultValue = fromProvider == null ? arg.defaultValue() : fromProvider;
8961
8962 if (defaultValue != null) {
8963 if (tracer.isDebug()) {tracer.debug("Applying defaultValue (%s) to %s%n", defaultValue, arg);}
8964 Range arity = arg.arity().min(Math.max(1, arg.arity().min));
8965 applyOption(arg, LookBehind.SEPARATE, arity, stack(defaultValue), new HashSet<ArgSpec>(), arg.toString);
8966 }
8967 return defaultValue != null;
8968 }
8969
8970 private Stack<String> stack(String value) {Stack<String> result = new Stack<String>(); result.push(value); return result;}
8971
8972 private void processArguments(List<CommandLine> parsedCommands,
8973 Stack<String> args,
8974 Collection<ArgSpec> required,
8975 Set<ArgSpec> initialized,
8976 String[] originalArgs,
8977 List<Object> nowProcessing) throws Exception {
8978 // arg must be one of:
8979 // 1. the "--" double dash separating options from positional arguments
8980 // 1. a stand-alone flag, like "-v" or "--verbose": no value required, must map to boolean or Boolean field
8981 // 2. a short option followed by an argument, like "-f file" or "-ffile": may map to any type of field
8982 // 3. a long option followed by an argument, like "-file out.txt" or "-file=out.txt"
8983 // 3. one or more remaining arguments without any associated options. Must be the last in the list.
8984 // 4. a combination of stand-alone options, like "-vxr". Equivalent to "-v -x -r", "-v true -x true -r true"
8985 // 5. a combination of stand-alone options and one option with an argument, like "-vxrffile"
8986
8987 parseResultBuilder.originalArgs(originalArgs);
8988 parseResultBuilder.nowProcessing = nowProcessing;
8989 String separator = config().separator();
8990 while (!args.isEmpty()) {
8991 if (endOfOptions) {
8992 processRemainderAsPositionalParameters(required, initialized, args);
8993 return;
8994 }
8995 String arg = args.pop();
8996 if (tracer.isDebug()) {tracer.debug("Processing argument '%s'. Remainder=%s%n", arg, reverse(copy(args)));}
8997
8998 // Double-dash separates options from positional arguments.
8999 // If found, then interpret the remaining args as positional parameters.
9000 if (commandSpec.parser.endOfOptionsDelimiter().equals(arg)) {
9001 tracer.info("Found end-of-options delimiter '--'. Treating remainder as positional parameters.%n");
9002 endOfOptions = true;
9003 processRemainderAsPositionalParameters(required, initialized, args);
9004 return; // we are done
9005 }
9006
9007 // if we find another command, we are done with the current command
9008 if (commandSpec.subcommands().containsKey(arg)) {
9009 CommandLine subcommand = commandSpec.subcommands().get(arg);
9010 nowProcessing.add(subcommand.commandSpec);
9011 updateHelpRequested(subcommand.commandSpec);
9012 if (!isAnyHelpRequested() && !required.isEmpty()) { // ensure current command portion is valid
9013 throw MissingParameterException.create(CommandLine.this, required, separator);
9014 }
9015 if (tracer.isDebug()) {tracer.debug("Found subcommand '%s' (%s)%n", arg, subcommand.commandSpec.toString());}
9016 subcommand.interpreter.parse(parsedCommands, args, originalArgs, nowProcessing);
9017 parseResultBuilder.subcommand(subcommand.interpreter.parseResultBuilder.build());
9018 return; // remainder done by the command
9019 }
9020
9021 // First try to interpret the argument as a single option (as opposed to a compact group of options).
9022 // A single option may be without option parameters, like "-v" or "--verbose" (a boolean value),
9023 // or an option may have one or more option parameters.
9024 // A parameter may be attached to the option.
9025 boolean paramAttachedToOption = false;
9026 int separatorIndex = arg.indexOf(separator);
9027 if (separatorIndex > 0) {
9028 String key = arg.substring(0, separatorIndex);
9029 // be greedy. Consume the whole arg as an option if possible.
9030 if (commandSpec.optionsMap().containsKey(key) && commandSpec.optionsMap().containsKey(arg)) {
9031 tracer.warn("Both '%s' and '%s' are valid option names in %s. Using '%s'...%n", arg, key, getCommandName(), arg);
9032 } else if (commandSpec.optionsMap().containsKey(key)) {
9033 paramAttachedToOption = true;
9034 String optionParam = arg.substring(separatorIndex + separator.length());
9035 args.push(optionParam);
9036 arg = key;
9037 if (tracer.isDebug()) {tracer.debug("Separated '%s' option from '%s' option parameter%n", key, optionParam);}
9038 } else {
9039 if (tracer.isDebug()) {tracer.debug("'%s' contains separator '%s' but '%s' is not a known option%n", arg, separator, key);}
9040 }
9041 } else {
9042 if (tracer.isDebug()) {tracer.debug("'%s' cannot be separated into <option>%s<option-parameter>%n", arg, separator);}
9043 }
9044 if (isStandaloneOption(arg)) {
9045 processStandaloneOption(required, initialized, arg, args, paramAttachedToOption);
9046 }
9047 // Compact (single-letter) options can be grouped with other options or with an argument.
9048 // only single-letter options can be combined with other options or with an argument
9049 else if (config().posixClusteredShortOptionsAllowed() && arg.length() > 2 && arg.startsWith("-")) {
9050 if (tracer.isDebug()) {tracer.debug("Trying to process '%s' as clustered short options%n", arg, args);}
9051 processClusteredShortOptions(required, initialized, arg, args);
9052 }
9053 // The argument could not be interpreted as an option: process it as a positional argument
9054 else {
9055 args.push(arg);
9056 if (tracer.isDebug()) {tracer.debug("Could not find option '%s', deciding whether to treat as unmatched option or positional parameter...%n", arg);}
9057 if (commandSpec.resemblesOption(arg, tracer)) { handleUnmatchedArgument(args); continue; } // #149
9058 if (tracer.isDebug()) {tracer.debug("No option named '%s' found. Processing as positional parameter%n", arg);}
9059 processPositionalParameter(required, initialized, args);
9060 }
9061 }
9062 }
9063
9064 private boolean isStandaloneOption(String arg) {
9065 return commandSpec.optionsMap().containsKey(arg);
9066 }
9067 private void handleUnmatchedArgument(Stack<String> args) throws Exception {
9068 if (!args.isEmpty()) { handleUnmatchedArgument(args.pop()); }
9069 if (config().stopAtUnmatched()) {
9070 // addAll would give args in reverse order
9071 while (!args.isEmpty()) { handleUnmatchedArgument(args.pop()); }
9072 }
9073 }
9074 private void handleUnmatchedArgument(String arg) {
9075 parseResultBuilder.unmatched.add(arg);
9076 }
9077
9078 private void processRemainderAsPositionalParameters(Collection<ArgSpec> required, Set<ArgSpec> initialized, Stack<String> args) throws Exception {
9079 while (!args.empty()) {
9080 processPositionalParameter(required, initialized, args);
9081 }
9082 }
9083 private void processPositionalParameter(Collection<ArgSpec> required, Set<ArgSpec> initialized, Stack<String> args) throws Exception {
9084 if (tracer.isDebug()) {tracer.debug("Processing next arg as a positional parameter. Command-local position=%d. Remainder=%s%n", position, reverse(copy(args)));}
9085 if (config().stopAtPositional()) {
9086 if (!endOfOptions && tracer.isDebug()) {tracer.debug("Parser was configured with stopAtPositional=true, treating remaining arguments as positional parameters.%n");}
9087 endOfOptions = true;
9088 }
9089 int consumedByGroup = 0;
9090 int argsConsumed = 0;
9091 int interactiveConsumed = 0;
9092 int originalNowProcessingSize = parseResultBuilder.nowProcessing.size();
9093 Map<PositionalParamSpec, Integer> newPositions = new IdentityHashMap<PositionalParamSpec, Integer>();
9094 for (PositionalParamSpec positionalParam : commandSpec.positionalParameters()) {
9095 Range indexRange = positionalParam.index();
9096 int localPosition = getPosition(positionalParam);
9097 if (positionalParam.group() != null) { // does the positionalParam's index range contain the current position in the currently matching group
9098 MatchedGroup matchedGroup = parseResultBuilder.matchedGroup.findOrCreateMatchingGroup(positionalParam, commandSpec.commandLine());
9099 if (!indexRange.contains(localPosition) || (matchedGroup != null && matchedGroup.multiple().hasMatchedValueAtPosition(positionalParam, localPosition))) {
9100 continue;
9101 }
9102 } else {
9103 if (!indexRange.contains(localPosition) || positionalParam.typedValueAtPosition.get(localPosition) != null) {
9104 continue;
9105 }
9106 }
9107 Stack<String> argsCopy = copy(args);
9108 Range arity = positionalParam.arity();
9109 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);}
9110 if (!assertNoMissingParameters(positionalParam, arity, argsCopy)) { break; } // #389 collectErrors parsing
9111 int originalSize = argsCopy.size();
9112 int actuallyConsumed = applyOption(positionalParam, LookBehind.SEPARATE, arity, argsCopy, initialized, "args[" + indexRange + "] at position " + localPosition);
9113 int count = originalSize - argsCopy.size();
9114 if (count > 0 || actuallyConsumed > 0) {
9115 required.remove(positionalParam);
9116 if (positionalParam.interactive()) { interactiveConsumed++; }
9117 }
9118 if (positionalParam.group() == null) { // don't update the command-level position for group args
9119 argsConsumed = Math.max(argsConsumed, count);
9120 } else {
9121 newPositions.put(positionalParam, localPosition + count);
9122 consumedByGroup = Math.max(consumedByGroup, count);
9123 }
9124 while (parseResultBuilder.nowProcessing.size() > originalNowProcessingSize + count) {
9125 parseResultBuilder.nowProcessing.remove(parseResultBuilder.nowProcessing.size() - 1);
9126 }
9127 }
9128 // remove processed args from the stack
9129 int maxConsumed = Math.max(consumedByGroup, argsConsumed);
9130 for (int i = 0; i < maxConsumed; i++) { args.pop(); }
9131 position += argsConsumed + interactiveConsumed;
9132 if (tracer.isDebug()) {tracer.debug("Consumed %d arguments and %d interactive values, moving command-local position to index %d.%n", argsConsumed, interactiveConsumed, position);}
9133 for (PositionalParamSpec positional : newPositions.keySet()) {
9134 MatchedGroup inProgress = parseResultBuilder.matchedGroup.findOrCreateMatchingGroup(positional, commandSpec.commandLine());
9135 if (inProgress != null) {
9136 inProgress.multiple().position = newPositions.get(positional);
9137 if (tracer.isDebug()) {tracer.debug("Updated group position to %s for group %s.%n", inProgress.multiple().position, inProgress);}
9138 }
9139 }
9140 if (consumedByGroup == 0 && argsConsumed == 0 && interactiveConsumed == 0 && !args.isEmpty()) {
9141 handleUnmatchedArgument(args);
9142 }
9143 }
9144
9145 private void processStandaloneOption(Collection<ArgSpec> required,
9146 Set<ArgSpec> initialized,
9147 String arg,
9148 Stack<String> args,
9149 boolean paramAttachedToKey) throws Exception {
9150 ArgSpec argSpec = commandSpec.optionsMap().get(arg);
9151 required.remove(argSpec);
9152 Range arity = argSpec.arity();
9153 if (paramAttachedToKey) {
9154 arity = arity.min(Math.max(1, arity.min)); // if key=value, minimum arity is at least 1
9155 }
9156 LookBehind lookBehind = paramAttachedToKey ? LookBehind.ATTACHED_WITH_SEPARATOR : LookBehind.SEPARATE;
9157 if (tracer.isDebug()) {tracer.debug("Found option named '%s': %s, arity=%s%n", arg, argSpec, arity);}
9158 parseResultBuilder.nowProcessing.add(argSpec);
9159 applyOption(argSpec, lookBehind, arity, args, initialized, "option " + arg);
9160 }
9161
9162 private void processClusteredShortOptions(Collection<ArgSpec> required,
9163 Set<ArgSpec> initialized,
9164 String arg,
9165 Stack<String> args) throws Exception {
9166 String prefix = arg.substring(0, 1);
9167 String cluster = arg.substring(1);
9168 boolean paramAttachedToOption = true;
9169 boolean first = true;
9170 do {
9171 if (cluster.length() > 0 && commandSpec.posixOptionsMap().containsKey(cluster.charAt(0))) {
9172 ArgSpec argSpec = commandSpec.posixOptionsMap().get(cluster.charAt(0));
9173 Range arity = argSpec.arity();
9174 String argDescription = "option " + prefix + cluster.charAt(0);
9175 if (tracer.isDebug()) {tracer.debug("Found option '%s%s' in %s: %s, arity=%s%n", prefix, cluster.charAt(0), arg,
9176 argSpec, arity);}
9177 required.remove(argSpec);
9178 cluster = cluster.substring(1);
9179 paramAttachedToOption = cluster.length() > 0;
9180 LookBehind lookBehind = paramAttachedToOption ? LookBehind.ATTACHED : LookBehind.SEPARATE;
9181 if (cluster.startsWith(config().separator())) {// attached with separator, like -f=FILE or -v=true
9182 lookBehind = LookBehind.ATTACHED_WITH_SEPARATOR;
9183 cluster = cluster.substring(config().separator().length());
9184 arity = arity.min(Math.max(1, arity.min)); // if key=value, minimum arity is at least 1
9185 }
9186 if (arity.min > 0 && !empty(cluster)) {
9187 if (tracer.isDebug()) {tracer.debug("Trying to process '%s' as option parameter%n", cluster);}
9188 }
9189 // arity may be >= 1, or
9190 // arity <= 0 && !cluster.startsWith(separator)
9191 // e.g., boolean @Option("-v", arity=0, varargs=true); arg "-rvTRUE", remainder cluster="TRUE"
9192 if (!empty(cluster)) {
9193 args.push(cluster); // interpret remainder as option parameter (CAUTION: may be empty string!)
9194 }
9195 if (first) {
9196 parseResultBuilder.nowProcessing.add(argSpec);
9197 first = false;
9198 } else {
9199 parseResultBuilder.nowProcessing.set(parseResultBuilder.nowProcessing.size() - 1, argSpec); // replace
9200 }
9201 int argCount = args.size();
9202 int consumed = applyOption(argSpec, lookBehind, arity, args, initialized, argDescription);
9203 // 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
9204 if (empty(cluster) || args.isEmpty() || args.size() < argCount) {
9205 return;
9206 }
9207 cluster = args.pop();
9208 } else { // cluster is empty || cluster.charAt(0) is not a short option key
9209 if (cluster.length() == 0) { // we finished parsing a group of short options like -rxv
9210 return; // return normally and parse the next arg
9211 }
9212 // We get here when the remainder of the cluster group is neither an option,
9213 // nor a parameter that the last option could consume.
9214 if (arg.endsWith(cluster)) {
9215 args.push(paramAttachedToOption ? prefix + cluster : cluster);
9216 if (args.peek().equals(arg)) { // #149 be consistent between unmatched short and long options
9217 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);}
9218 if (commandSpec.resemblesOption(arg, tracer)) { handleUnmatchedArgument(args); return; } // #149
9219 processPositionalParameter(required, initialized, args);
9220 return;
9221 }
9222 // remainder was part of a clustered group that could not be completely parsed
9223 if (tracer.isDebug()) {tracer.debug("No option found for %s in %s%n", cluster, arg);}
9224 String tmp = args.pop();
9225 tmp = tmp + " (while processing option: '" + arg + "')";
9226 args.push(tmp);
9227 handleUnmatchedArgument(args);
9228 } else {
9229 args.push(cluster);
9230 if (tracer.isDebug()) {tracer.debug("%s is not an option parameter for %s%n", cluster, arg);}
9231 processPositionalParameter(required, initialized, args);
9232 }
9233 return;
9234 }
9235 } while (true);
9236 }
9237
9238 private int applyOption(ArgSpec argSpec,
9239 LookBehind lookBehind,
9240 Range arity,
9241 Stack<String> args,
9242 Set<ArgSpec> initialized,
9243 String argDescription) throws Exception {
9244 updateHelpRequested(argSpec);
9245 boolean consumeOnlyOne = commandSpec.parser().aritySatisfiedByAttachedOptionParam() && lookBehind.isAttached();
9246 Stack<String> workingStack = args;
9247 if (consumeOnlyOne) {
9248 workingStack = args.isEmpty() ? args : stack(args.pop());
9249 } else {
9250 if (!assertNoMissingParameters(argSpec, arity, args)) { return 0; } // #389 collectErrors parsing
9251 }
9252
9253 if (argSpec.interactive()) {
9254 String name = argSpec.isOption() ? ((OptionSpec) argSpec).longestName() : "position " + position;
9255 String prompt = String.format("Enter value for %s (%s): ", name, str(argSpec.renderedDescription(), 0));
9256 if (tracer.isDebug()) {tracer.debug("Reading value for %s from console...%n", name);}
9257 char[] value = readPassword(prompt);
9258 if (tracer.isDebug()) {tracer.debug("User entered '%s' for %s.%n", value, name);}
9259 workingStack.push(new String(value));
9260 }
9261
9262 parseResultBuilder.beforeMatchingGroupElement(argSpec);
9263
9264 int result;
9265 if (argSpec.type().isArray()) {
9266 result = applyValuesToArrayField(argSpec, lookBehind, arity, workingStack, initialized, argDescription);
9267 } else if (Collection.class.isAssignableFrom(argSpec.type())) {
9268 result = applyValuesToCollectionField(argSpec, lookBehind, arity, workingStack, initialized, argDescription);
9269 } else if (Map.class.isAssignableFrom(argSpec.type())) {
9270 result = applyValuesToMapField(argSpec, lookBehind, arity, workingStack, initialized, argDescription);
9271 } else {
9272 result = applyValueToSingleValuedField(argSpec, lookBehind, arity, workingStack, initialized, argDescription);
9273 }
9274 if (workingStack != args && !workingStack.isEmpty()) {
9275 args.push(workingStack.pop());
9276 Assert.assertTrue(workingStack.isEmpty(), "Working stack should be empty but was " + new ArrayList<String>(workingStack));
9277 }
9278 return result;
9279 }
9280
9281 private int applyValueToSingleValuedField(ArgSpec argSpec,
9282 LookBehind lookBehind,
9283 Range derivedArity,
9284 Stack<String> args,
9285 Set<ArgSpec> initialized,
9286 String argDescription) throws Exception {
9287 boolean noMoreValues = args.isEmpty();
9288 String value = args.isEmpty() ? null : trim(args.pop()); // unquote the value
9289 Range arity = argSpec.arity().isUnspecified ? derivedArity : argSpec.arity(); // #509
9290 if (arity.max == 0 && !arity.isUnspecified && lookBehind == LookBehind.ATTACHED_WITH_SEPARATOR) { // #509
9291 throw new MaxValuesExceededException(CommandLine.this, optionDescription("", argSpec, 0) +
9292 " should be specified without '" + value + "' parameter");
9293 }
9294 int result = arity.min; // the number or args we need to consume
9295
9296 Class<?> cls = argSpec.auxiliaryTypes()[0]; // field may be interface/abstract type, use annotation to get concrete type
9297 if (arity.min <= 0) { // value may be optional
9298
9299 // special logic for booleans: BooleanConverter accepts only "true" or "false".
9300 if (cls == Boolean.class || cls == Boolean.TYPE) {
9301
9302 // boolean option with arity = 0..1 or 0..*: value MAY be a param
9303 if (arity.max > 0 && ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value))) {
9304 result = 1; // if it is a varargs we only consume 1 argument if it is a boolean value
9305 if (!lookBehind.isAttached()) { parseResultBuilder.nowProcessing(argSpec, value); }
9306 } else if (lookBehind != LookBehind.ATTACHED_WITH_SEPARATOR) { // if attached, try converting the value to boolean (and fail if invalid value)
9307 // it's okay to ignore value if not attached to option
9308 if (value != null) {
9309 args.push(value); // we don't consume the value
9310 }
9311 if (commandSpec.parser().toggleBooleanFlags()) {
9312 Boolean currentValue = (Boolean) argSpec.getValue();
9313 value = String.valueOf(currentValue == null || !currentValue); // #147 toggle existing boolean value
9314 } else {
9315 value = "true";
9316 }
9317 }
9318 } else { // non-boolean option with optional value #325, #279
9319 if (isOption(value)) {
9320 args.push(value); // we don't consume the value
9321 value = "";
9322 } else if (value == null) {
9323 value = "";
9324 } else {
9325 if (!lookBehind.isAttached()) { parseResultBuilder.nowProcessing(argSpec, value); }
9326 }
9327 }
9328 } else {
9329 if (!lookBehind.isAttached()) { parseResultBuilder.nowProcessing(argSpec, value); }
9330 }
9331 if (noMoreValues && value == null) {
9332 return 0;
9333 }
9334 ITypeConverter<?> converter = getTypeConverter(cls, argSpec, 0);
9335 Object newValue = tryConvert(argSpec, -1, converter, value, cls);
9336 Object oldValue = argSpec.getValue();
9337 String traceMessage = "Setting %s to '%3$s' (was '%2$s') for %4$s%n";
9338 if (argSpec.group() == null && initialized.contains(argSpec)) {
9339 if (!isOverwrittenOptionsAllowed()) {
9340 throw new OverwrittenOptionException(CommandLine.this, argSpec, optionDescription("", argSpec, 0) + " should be specified only once");
9341 }
9342 traceMessage = "Overwriting %s value '%s' with '%s' for %s%n";
9343 }
9344 initialized.add(argSpec);
9345
9346 if (tracer.isInfo()) { tracer.info(traceMessage, argSpec.toString(), String.valueOf(oldValue), String.valueOf(newValue), argDescription); }
9347 int pos = getPosition(argSpec);
9348 argSpec.setValue(newValue);
9349 parseResultBuilder.addOriginalStringValue(argSpec, value);// #279 track empty string value if no command line argument was consumed
9350 parseResultBuilder.addStringValue(argSpec, value);
9351 parseResultBuilder.addTypedValues(argSpec, pos, newValue);
9352 parseResultBuilder.add(argSpec, pos);
9353 return result;
9354 }
9355 private int applyValuesToMapField(ArgSpec argSpec,
9356 LookBehind lookBehind,
9357 Range arity,
9358 Stack<String> args,
9359 Set<ArgSpec> initialized,
9360 String argDescription) throws Exception {
9361 Class<?>[] classes = argSpec.auxiliaryTypes();
9362 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); }
9363 ITypeConverter<?> keyConverter = getTypeConverter(classes[0], argSpec, 0);
9364 ITypeConverter<?> valueConverter = getTypeConverter(classes[1], argSpec, 1);
9365 @SuppressWarnings("unchecked") Map<Object, Object> map = (Map<Object, Object>) argSpec.getValue();
9366 if (map == null || (!map.isEmpty() && !initialized.contains(argSpec))) {
9367 tracer.debug("Initializing binding for %s with empty %s%n", optionDescription("", argSpec, 0), argSpec.type().getSimpleName());
9368 map = createMap(argSpec.type()); // map class
9369 argSpec.setValue(map);
9370 }
9371 initialized.add(argSpec);
9372 int originalSize = map.size();
9373 int pos = getPosition(argSpec);
9374 consumeMapArguments(argSpec, lookBehind, arity, args, classes, keyConverter, valueConverter, map, argDescription);
9375 parseResultBuilder.add(argSpec, pos);
9376 argSpec.setValue(map);
9377 return map.size() - originalSize;
9378 }
9379
9380 private void consumeMapArguments(ArgSpec argSpec,
9381 LookBehind lookBehind,
9382 Range arity,
9383 Stack<String> args,
9384 Class<?>[] classes,
9385 ITypeConverter<?> keyConverter,
9386 ITypeConverter<?> valueConverter,
9387 Map<Object, Object> result,
9388 String argDescription) throws Exception {
9389
9390 // don't modify Interpreter.position: same position may be consumed by multiple ArgSpec objects
9391 int currentPosition = getPosition(argSpec);
9392
9393 // first do the arity.min mandatory parameters
9394 int initialSize = argSpec.stringValues().size();
9395 int consumed = consumedCountMap(0, initialSize, argSpec);
9396 for (int i = 0; consumed < arity.min && !args.isEmpty(); i++) {
9397 Map<Object, Object> typedValuesAtPosition = new LinkedHashMap<Object, Object>();
9398 parseResultBuilder.addTypedValues(argSpec, currentPosition++, typedValuesAtPosition);
9399 assertNoMissingMandatoryParameter(argSpec, args, i, arity);
9400 consumeOneMapArgument(argSpec, lookBehind, arity, consumed, args.pop(), classes, keyConverter, valueConverter, typedValuesAtPosition, i, argDescription);
9401 result.putAll(typedValuesAtPosition);
9402 consumed = consumedCountMap(i + 1, initialSize, argSpec);
9403 lookBehind = LookBehind.SEPARATE;
9404 }
9405 // now process the varargs if any
9406 for (int i = consumed; consumed < arity.max && !args.isEmpty(); i++) {
9407 if (!varargCanConsumeNextValue(argSpec, args.peek())) { break; }
9408
9409 Map<Object, Object> typedValuesAtPosition = new LinkedHashMap<Object, Object>();
9410 parseResultBuilder.addTypedValues(argSpec, currentPosition++, typedValuesAtPosition);
9411 if (!canConsumeOneMapArgument(argSpec, arity, consumed, args.peek(), classes, keyConverter, valueConverter, argDescription)) {
9412 break; // leave empty map at argSpec.typedValueAtPosition[currentPosition] so we won't try to consume that position again
9413 }
9414 consumeOneMapArgument(argSpec, lookBehind, arity, consumed, args.pop(), classes, keyConverter, valueConverter, typedValuesAtPosition, i, argDescription);
9415 result.putAll(typedValuesAtPosition);
9416 consumed = consumedCountMap(i + 1, initialSize, argSpec);
9417 lookBehind = LookBehind.SEPARATE;
9418 }
9419 }
9420
9421 private void consumeOneMapArgument(ArgSpec argSpec,
9422 LookBehind lookBehind,
9423 Range arity, int consumed,
9424 String arg,
9425 Class<?>[] classes,
9426 ITypeConverter<?> keyConverter, ITypeConverter<?> valueConverter,
9427 Map<Object, Object> result,
9428 int index,
9429 String argDescription) throws Exception {
9430 if (!lookBehind.isAttached()) { parseResultBuilder.nowProcessing(argSpec, arg); }
9431 String raw = trim(arg);
9432 String[] values = argSpec.splitValue(raw, commandSpec.parser(), arity, consumed);
9433 for (String value : values) {
9434 String[] keyValue = splitKeyValue(argSpec, value);
9435 Object mapKey = tryConvert(argSpec, index, keyConverter, keyValue[0], classes[0]);
9436 Object mapValue = tryConvert(argSpec, index, valueConverter, keyValue[1], classes[1]);
9437 result.put(mapKey, mapValue);
9438 if (tracer.isInfo()) { tracer.info("Putting [%s : %s] in %s<%s, %s> %s for %s%n", String.valueOf(mapKey), String.valueOf(mapValue),
9439 result.getClass().getSimpleName(), classes[0].getSimpleName(), classes[1].getSimpleName(), argSpec.toString(), argDescription); }
9440 parseResultBuilder.addStringValue(argSpec, keyValue[0]);
9441 parseResultBuilder.addStringValue(argSpec, keyValue[1]);
9442 }
9443 parseResultBuilder.addOriginalStringValue(argSpec, raw);
9444 }
9445
9446 private boolean canConsumeOneMapArgument(ArgSpec argSpec, Range arity, int consumed,
9447 String raw, Class<?>[] classes,
9448 ITypeConverter<?> keyConverter, ITypeConverter<?> valueConverter,
9449 String argDescription) {
9450 String[] values = argSpec.splitValue(raw, commandSpec.parser(), arity, consumed);
9451 try {
9452 for (String value : values) {
9453 String[] keyValue = splitKeyValue(argSpec, value);
9454 tryConvert(argSpec, -1, keyConverter, keyValue[0], classes[0]);
9455 tryConvert(argSpec, -1, valueConverter, keyValue[1], classes[1]);
9456 }
9457 return true;
9458 } catch (PicocliException ex) {
9459 tracer.debug("$s cannot be assigned to %s: type conversion fails: %s.%n", raw, argDescription, ex.getMessage());
9460 return false;
9461 }
9462 }
9463
9464 private String[] splitKeyValue(ArgSpec argSpec, String value) {
9465 String[] keyValue = ArgSpec.splitRespectingQuotedStrings(value, 2, config(), argSpec, "=");
9466
9467 if (keyValue.length < 2) {
9468 String splitRegex = argSpec.splitRegex();
9469 if (splitRegex.length() == 0) {
9470 throw new ParameterException(CommandLine.this, "Value for option " + optionDescription("",
9471 argSpec, 0) + " should be in KEY=VALUE format but was " + value, argSpec, value);
9472 } else {
9473 throw new ParameterException(CommandLine.this, "Value for option " + optionDescription("",
9474 argSpec, 0) + " should be in KEY=VALUE[" + splitRegex + "KEY=VALUE]... format but was " + value, argSpec, value);
9475 }
9476 }
9477 return keyValue;
9478 }
9479
9480 private void assertNoMissingMandatoryParameter(ArgSpec argSpec, Stack<String> args, int i, Range arity) {
9481 if (!varargCanConsumeNextValue(argSpec, args.peek())) {
9482 String desc = arity.min > 1 ? (i + 1) + " (of " + arity.min + " mandatory parameters) " : "";
9483 throw new MissingParameterException(CommandLine.this, argSpec, "Expected parameter " + desc + "for " + optionDescription("", argSpec, -1) + " but found '" + args.peek() + "'");
9484 }
9485 }
9486 private int applyValuesToArrayField(ArgSpec argSpec,
9487 LookBehind lookBehind,
9488 Range arity,
9489 Stack<String> args,
9490 Set<ArgSpec> initialized,
9491 String argDescription) throws Exception {
9492 Object existing = argSpec.getValue();
9493 int length = existing == null ? 0 : Array.getLength(existing);
9494 Class<?> type = argSpec.auxiliaryTypes()[0];
9495 int pos = getPosition(argSpec);
9496 List<Object> converted = consumeArguments(argSpec, lookBehind, arity, args, type, argDescription);
9497 List<Object> newValues = new ArrayList<Object>();
9498 if (initialized.contains(argSpec)) { // existing values are default values if initialized does NOT contain argsSpec
9499 for (int i = 0; i < length; i++) {
9500 newValues.add(Array.get(existing, i)); // keep non-default values
9501 }
9502 }
9503 initialized.add(argSpec);
9504 for (Object obj : converted) {
9505 if (obj instanceof Collection<?>) {
9506 newValues.addAll((Collection<?>) obj);
9507 } else {
9508 newValues.add(obj);
9509 }
9510 }
9511 Object array = Array.newInstance(type, newValues.size());
9512 for (int i = 0; i < newValues.size(); i++) {
9513 Array.set(array, i, newValues.get(i));
9514 }
9515 argSpec.setValue(array);
9516 parseResultBuilder.add(argSpec, pos);
9517 return converted.size(); // return how many args were consumed
9518 }
9519
9520 @SuppressWarnings("unchecked")
9521 private int applyValuesToCollectionField(ArgSpec argSpec,
9522 LookBehind lookBehind,
9523 Range arity,
9524 Stack<String> args,
9525 Set<ArgSpec> initialized,
9526 String argDescription) throws Exception {
9527 Collection<Object> collection = (Collection<Object>) argSpec.getValue();
9528 Class<?> type = argSpec.auxiliaryTypes()[0];
9529 int pos = getPosition(argSpec);
9530 List<Object> converted = consumeArguments(argSpec, lookBehind, arity, args, type, argDescription);
9531 if (collection == null || (!collection.isEmpty() && !initialized.contains(argSpec))) {
9532 tracer.debug("Initializing binding for %s with empty %s%n", optionDescription("", argSpec, 0), argSpec.type().getSimpleName());
9533 collection = createCollection(argSpec.type(), type); // collection type, element type
9534 argSpec.setValue(collection);
9535 }
9536 initialized.add(argSpec);
9537 for (Object element : converted) {
9538 if (element instanceof Collection<?>) {
9539 collection.addAll((Collection<?>) element);
9540 } else {
9541 collection.add(element);
9542 }
9543 }
9544 parseResultBuilder.add(argSpec, pos);
9545 argSpec.setValue(collection);
9546 return converted.size();
9547 }
9548
9549 private List<Object> consumeArguments(ArgSpec argSpec,
9550 LookBehind lookBehind,
9551 Range arity,
9552 Stack<String> args,
9553 Class<?> type,
9554 String argDescription) throws Exception {
9555 List<Object> result = new ArrayList<Object>();
9556
9557 // don't modify Interpreter.position: same position may be consumed by multiple ArgSpec objects
9558 int currentPosition = getPosition(argSpec);
9559
9560 // first do the arity.min mandatory parameters
9561 int initialSize = argSpec.stringValues().size();
9562 int consumed = consumedCount(0, initialSize, argSpec);
9563 for (int i = 0; consumed < arity.min && !args.isEmpty(); i++) {
9564 List<Object> typedValuesAtPosition = new ArrayList<Object>();
9565 parseResultBuilder.addTypedValues(argSpec, currentPosition++, typedValuesAtPosition);
9566 assertNoMissingMandatoryParameter(argSpec, args, i, arity);
9567 consumeOneArgument(argSpec, lookBehind, arity, consumed, args.pop(), type, typedValuesAtPosition, i, argDescription);
9568 result.addAll(typedValuesAtPosition);
9569 consumed = consumedCount(i + 1, initialSize, argSpec);
9570 lookBehind = LookBehind.SEPARATE;
9571 }
9572 // now process the varargs if any
9573 for (int i = consumed; consumed < arity.max && !args.isEmpty(); i++) {
9574 if (!varargCanConsumeNextValue(argSpec, args.peek())) { break; }
9575
9576 List<Object> typedValuesAtPosition = new ArrayList<Object>();
9577 parseResultBuilder.addTypedValues(argSpec, currentPosition++, typedValuesAtPosition);
9578 if (!canConsumeOneArgument(argSpec, arity, consumed, args.peek(), type, argDescription)) {
9579 break; // leave empty list at argSpec.typedValueAtPosition[currentPosition] so we won't try to consume that position again
9580 }
9581 consumeOneArgument(argSpec, lookBehind, arity, consumed, args.pop(), type, typedValuesAtPosition, i, argDescription);
9582 result.addAll(typedValuesAtPosition);
9583 consumed = consumedCount(i + 1, initialSize, argSpec);
9584 lookBehind = LookBehind.SEPARATE;
9585 }
9586 if (result.isEmpty() && arity.min == 0 && arity.max <= 1 && isBoolean(type)) {
9587 return Arrays.asList((Object) Boolean.TRUE);
9588 }
9589 return result;
9590 }
9591
9592 private int consumedCount(int i, int initialSize, ArgSpec arg) {
9593 return commandSpec.parser().splitFirst() ? arg.stringValues().size() - initialSize : i;
9594 }
9595
9596 private int consumedCountMap(int i, int initialSize, ArgSpec arg) {
9597 return commandSpec.parser().splitFirst() ? (arg.stringValues().size() - initialSize) / 2 : i;
9598 }
9599
9600 private int consumeOneArgument(ArgSpec argSpec,
9601 LookBehind lookBehind,
9602 Range arity,
9603 int consumed,
9604 String arg,
9605 Class<?> type,
9606 List<Object> result,
9607 int index,
9608 String argDescription) {
9609 if (!lookBehind.isAttached()) { parseResultBuilder.nowProcessing(argSpec, arg); }
9610 String raw = trim(arg);
9611 String[] values = argSpec.splitValue(raw, commandSpec.parser(), arity, consumed);
9612 ITypeConverter<?> converter = getTypeConverter(type, argSpec, 0);
9613 for (int j = 0; j < values.length; j++) {
9614 Object stronglyTypedValue = tryConvert(argSpec, index, converter, values[j], type);
9615 result.add(stronglyTypedValue);
9616 if (tracer.isInfo()) {
9617 tracer.info("Adding [%s] to %s for %s%n", String.valueOf(result.get(result.size() - 1)), argSpec.toString(), argDescription);
9618 }
9619 parseResultBuilder.addStringValue(argSpec, values[j]);
9620 }
9621 parseResultBuilder.addOriginalStringValue(argSpec, raw);
9622 return ++index;
9623 }
9624 private boolean canConsumeOneArgument(ArgSpec argSpec, Range arity, int consumed, String arg, Class<?> type, String argDescription) {
9625 ITypeConverter<?> converter = getTypeConverter(type, argSpec, 0);
9626 try {
9627 String[] values = argSpec.splitValue(trim(arg), commandSpec.parser(), arity, consumed);
9628// if (!argSpec.acceptsValues(values.length, commandSpec.parser())) {
9629// tracer.debug("$s would split into %s values but %s cannot accept that many values.%n", arg, values.length, argDescription);
9630// return false;
9631// }
9632 for (String value : values) {
9633 tryConvert(argSpec, -1, converter, value, type);
9634 }
9635 return true;
9636 } catch (PicocliException ex) {
9637 tracer.debug("$s cannot be assigned to %s: type conversion fails: %s.%n", arg, argDescription, ex.getMessage());
9638 return false;
9639 }
9640 }
9641
9642 /** Returns whether the next argument can be assigned to a vararg option/positional parameter.
9643 * <p>
9644 * Usually, we stop if we encounter '--', a command, or another option.
9645 * However, if end-of-options has been reached, positional parameters may consume all remaining arguments. </p>*/
9646 private boolean varargCanConsumeNextValue(ArgSpec argSpec, String nextValue) {
9647 if (endOfOptions && argSpec.isPositional()) { return true; }
9648 boolean isCommand = commandSpec.subcommands().containsKey(nextValue);
9649 return !isCommand && !isOption(nextValue);
9650 }
9651
9652 /**
9653 * Called when parsing varargs parameters for a multi-value option.
9654 * When an option is encountered, the remainder should not be interpreted as vararg elements.
9655 * @param arg the string to determine whether it is an option or not
9656 * @return true if it is an option, false otherwise
9657 */
9658 private boolean isOption(String arg) {
9659 if (arg == null) { return false; }
9660 if ("--".equals(arg)) { return true; }
9661
9662 // not just arg prefix: we may be in the middle of parsing -xrvfFILE
9663 if (commandSpec.optionsMap().containsKey(arg)) { // -v or -f or --file (not attached to param or other option)
9664 return true;
9665 }
9666 int separatorIndex = arg.indexOf(config().separator());
9667 if (separatorIndex > 0) { // -f=FILE or --file==FILE (attached to param via separator)
9668 if (commandSpec.optionsMap().containsKey(arg.substring(0, separatorIndex))) {
9669 return true;
9670 }
9671 }
9672 return (arg.length() > 2 && arg.startsWith("-") && commandSpec.posixOptionsMap().containsKey(arg.charAt(1)));
9673 }
9674 private Object tryConvert(ArgSpec argSpec, int index, ITypeConverter<?> converter, String value, Class<?> type)
9675 throws ParameterException {
9676 try {
9677 return converter.convert(value);
9678 } catch (TypeConversionException ex) {
9679 String msg = String.format("Invalid value for %s: %s", optionDescription("", argSpec, index), ex.getMessage());
9680 throw new ParameterException(CommandLine.this, msg, argSpec, value);
9681 } catch (Exception other) {
9682 String desc = optionDescription("", argSpec, index);
9683 String msg = String.format("Invalid value for %s: cannot convert '%s' to %s (%s)", desc, value, type.getSimpleName(), other);
9684 throw new ParameterException(CommandLine.this, msg, other, argSpec, value);
9685 }
9686 }
9687
9688 private String optionDescription(String prefix, ArgSpec argSpec, int index) {
9689 String desc = "";
9690 if (argSpec.isOption()) {
9691 desc = prefix + "option '" + ((OptionSpec) argSpec).longestName() + "'";
9692 if (index >= 0) {
9693 if (argSpec.arity().max > 1) {
9694 desc += " at index " + index;
9695 }
9696 desc += " (" + argSpec.paramLabel() + ")";
9697 }
9698 } else {
9699 desc = prefix + "positional parameter at index " + ((PositionalParamSpec) argSpec).index() + " (" + argSpec.paramLabel() + ")";
9700 }
9701 return desc;
9702 }
9703
9704 private boolean isAnyHelpRequested() { return isHelpRequested || parseResultBuilder.versionHelpRequested || parseResultBuilder.usageHelpRequested; }
9705
9706 private void updateHelpRequested(CommandSpec command) {
9707 isHelpRequested |= command.helpCommand();
9708 }
9709 private void updateHelpRequested(ArgSpec argSpec) {
9710 if (!parseResultBuilder.isInitializingDefaultValues && argSpec.isOption()) {
9711 OptionSpec option = (OptionSpec) argSpec;
9712 isHelpRequested |= is(argSpec, "help", option.help());
9713 parseResultBuilder.versionHelpRequested |= is(argSpec, "versionHelp", option.versionHelp());
9714 parseResultBuilder.usageHelpRequested |= is(argSpec, "usageHelp", option.usageHelp());
9715 }
9716 }
9717 private boolean is(ArgSpec p, String attribute, boolean value) {
9718 if (value) { if (tracer.isInfo()) {tracer.info("%s has '%s' annotation: not validating required fields%n", p.toString(), attribute); }}
9719 return value;
9720 }
9721 @SuppressWarnings("unchecked")
9722 private Collection<Object> createCollection(Class<?> collectionClass, Class<?> elementType) throws Exception {
9723 if (EnumSet.class.isAssignableFrom(collectionClass) && Enum.class.isAssignableFrom(elementType)) {
9724 Object enumSet = EnumSet.noneOf((Class<Enum>) elementType);
9725 return (Collection<Object>) enumSet;
9726 }
9727 // custom Collection implementation class must have default constructor
9728 return (Collection<Object>) factory.create(collectionClass);
9729 }
9730 @SuppressWarnings("unchecked") private Map<Object, Object> createMap(Class<?> mapClass) throws Exception {
9731 return (Map<Object, Object>) factory.create(mapClass);
9732 }
9733 private ITypeConverter<?> getTypeConverter(final Class<?> type, ArgSpec argSpec, int index) {
9734 if (argSpec.converters().length > index) { return argSpec.converters()[index]; }
9735 if (converterRegistry.containsKey(type)) { return converterRegistry.get(type); }
9736 if (type.isEnum()) {
9737 return new ITypeConverter<Object>() {
9738 @SuppressWarnings("unchecked")
9739 public Object convert(String value) throws Exception {
9740 String sensitivity = "case-sensitive";
9741 if (commandSpec.parser().caseInsensitiveEnumValuesAllowed()) {
9742 String upper = value.toUpperCase();
9743 for (Object enumConstant : type.getEnumConstants()) {
9744 if (upper.equals(String.valueOf(enumConstant).toUpperCase())) { return enumConstant; }
9745 }
9746 sensitivity = "case-insensitive";
9747 }
9748 try { return Enum.valueOf((Class<Enum>) type, value); }
9749 catch (Exception ex) {
9750 Enum<?>[] constants = ((Class<Enum<?>>) type).getEnumConstants();
9751 String[] names = new String[constants.length];
9752 for (int i = 0; i < names.length; i++) { names[i] = constants[i].name(); }
9753 throw new TypeConversionException(
9754 String.format("expected one of %s (%s) but was '%s'", Arrays.asList(names), sensitivity, value)); }
9755 }
9756 };
9757 }
9758 throw new MissingTypeConverterException(CommandLine.this, "No TypeConverter registered for " + type.getName() + " of " + argSpec);
9759 }
9760
9761 private boolean assertNoMissingParameters(ArgSpec argSpec, Range arity, Stack<String> args) {
9762 if (argSpec.interactive()) { return true; }
9763 int available = args.size();
9764 if (available > 0 && commandSpec.parser().splitFirst() && argSpec.splitRegex().length() > 0) {
9765 available += argSpec.splitValue(args.peek(), commandSpec.parser(), arity, 0).length - 1;
9766 }
9767 if (arity.min > available) {
9768 if (arity.min == 1) {
9769 if (argSpec.isOption()) {
9770 maybeThrow(new MissingParameterException(CommandLine.this, argSpec, "Missing required parameter for " +
9771 optionDescription("", argSpec, 0)));
9772 return false;
9773 }
9774 Range indexRange = ((PositionalParamSpec) argSpec).index();
9775 String sep = "";
9776 String names = ": ";
9777 int count = 0;
9778 List<PositionalParamSpec> positionalParameters = commandSpec.positionalParameters();
9779 for (int i = indexRange.min; i < positionalParameters.size(); i++) {
9780 if (positionalParameters.get(i).arity().min > 0) {
9781 names += sep + positionalParameters.get(i).paramLabel();
9782 sep = ", ";
9783 count++;
9784 }
9785 }
9786 String msg = "Missing required parameter";
9787 Range paramArity = argSpec.arity();
9788 if (count > 1 || arity.min - available > 1) {
9789 msg += "s";
9790 }
9791 maybeThrow(new MissingParameterException(CommandLine.this, argSpec, msg + names));
9792 } else if (args.isEmpty()) {
9793 maybeThrow(new MissingParameterException(CommandLine.this, argSpec, optionDescription("", argSpec, 0) +
9794 " requires at least " + arity.min + " values, but none were specified."));
9795 } else {
9796 maybeThrow(new MissingParameterException(CommandLine.this, argSpec, optionDescription("", argSpec, 0) +
9797 " requires at least " + arity.min + " values, but only " + available + " were specified: " + reverse(args)));
9798 }
9799 return false;
9800 }
9801 return true;
9802 }
9803 private String trim(String value) {
9804 return unquote(value);
9805 }
9806
9807 private String unquote(String value) {
9808 if (!commandSpec.parser().trimQuotes()) { return value; }
9809 return value == null
9810 ? null
9811 : (value.length() > 1 && value.startsWith("\"") && value.endsWith("\""))
9812 ? value.substring(1, value.length() - 1)
9813 : value;
9814 }
9815
9816 char[] readPassword(String prompt) {
9817 try {
9818 Object console = System.class.getDeclaredMethod("console").invoke(null);
9819 Method method = Class.forName("java.io.Console").getDeclaredMethod("readPassword", String.class, Object[].class);
9820 return (char[]) method.invoke(console, prompt, new Object[0]);
9821 } catch (Exception e) {
9822 System.out.print(prompt);
9823 InputStreamReader isr = new InputStreamReader(System.in);
9824 BufferedReader in = new BufferedReader(isr);
9825 try {
9826 return in.readLine().toCharArray();
9827 } catch (IOException ex2) {
9828 throw new IllegalStateException(ex2);
9829 }
9830 }
9831 }
9832 int getPosition(ArgSpec arg) {
9833 if (arg.group() == null) { return position; }
9834 MatchedGroup matchedGroup = parseResultBuilder.matchedGroup.findOrCreateMatchingGroup(arg, commandSpec.commandLine());
9835 return matchedGroup == null ? 0 : matchedGroup.multiple().position;
9836 }
9837 String positionDesc(ArgSpec arg) {
9838 int pos = getPosition(arg);
9839 return (arg.group() == null) ? pos + " (command-local)" : pos + " (in group " + arg.group().synopsis() + ")";
9840 }
9841 }
9842 private static class PositionalParametersSorter implements Comparator<ArgSpec> {
9843 private static final Range OPTION_INDEX = new Range(0, 0, false, true, "0");
9844 public int compare(ArgSpec p1, ArgSpec p2) {
9845 int result = index(p1).compareTo(index(p2));
9846 return (result == 0) ? p1.arity().compareTo(p2.arity()) : result;
9847 }
9848 private Range index(ArgSpec arg) { return arg.isOption() ? OPTION_INDEX : ((PositionalParamSpec) arg).index(); }
9849 }
9850 /**
9851 * Inner class to group the built-in {@link ITypeConverter} implementations.
9852 */
9853 private static class BuiltIn {
9854 static class StringConverter implements ITypeConverter<String> {
9855 public String convert(String value) { return value; }
9856 }
9857 static class StringBuilderConverter implements ITypeConverter<StringBuilder> {
9858 public StringBuilder convert(String value) { return new StringBuilder(value); }
9859 }
9860 static class CharSequenceConverter implements ITypeConverter<CharSequence> {
9861 public String convert(String value) { return value; }
9862 }
9863 /** Converts {@code "true"} or {@code "false"} to a {@code Boolean}. Other values result in a ParameterException.*/
9864 static class BooleanConverter implements ITypeConverter<Boolean> {
9865 public Boolean convert(String value) {
9866 if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) {
9867 return Boolean.parseBoolean(value);
9868 } else {
9869 throw new TypeConversionException("'" + value + "' is not a boolean");
9870 }
9871 }
9872 }
9873 static class CharacterConverter implements ITypeConverter<Character> {
9874 public Character convert(String value) {
9875 if (value.length() > 1) {
9876 throw new TypeConversionException("'" + value + "' is not a single character");
9877 }
9878 return value.charAt(0);
9879 }
9880 }
9881 private static TypeConversionException fail(String value, Class<?> c) { return fail(value, c, "'%s' is not a %s"); }
9882 private static TypeConversionException fail(String value, Class<?> c, String template) {
9883 return new TypeConversionException(String.format(template, value, c.getSimpleName()));
9884 }
9885 /** Converts text to a {@code Byte} by delegating to {@link Byte#valueOf(String)}.*/
9886 static class ByteConverter implements ITypeConverter<Byte> {
9887 public Byte convert(String value) { try {return Byte.valueOf(value);} catch (Exception ex) {throw fail(value, Byte.TYPE);} }
9888 }
9889 /** Converts text to a {@code Short} by delegating to {@link Short#valueOf(String)}.*/
9890 static class ShortConverter implements ITypeConverter<Short> {
9891 public Short convert(String value) { try {return Short.valueOf(value);} catch (Exception ex) {throw fail(value, Short.TYPE);} }
9892 }
9893 /** Converts text to an {@code Integer} by delegating to {@link Integer#valueOf(String)}.*/
9894 static class IntegerConverter implements ITypeConverter<Integer> {
9895 public Integer convert(String value) { try {return Integer.valueOf(value);} catch (Exception ex) {throw fail(value, Integer.TYPE, "'%s' is not an %s");} }
9896 }
9897 /** Converts text to a {@code Long} by delegating to {@link Long#valueOf(String)}.*/
9898 static class LongConverter implements ITypeConverter<Long> {
9899 public Long convert(String value) { try {return Long.valueOf(value);} catch (Exception ex) {throw fail(value, Long.TYPE);} }
9900 }
9901 static class FloatConverter implements ITypeConverter<Float> {
9902 public Float convert(String value) { try {return Float.valueOf(value);} catch (Exception ex) {throw fail(value, Float.TYPE);} }
9903 }
9904 static class DoubleConverter implements ITypeConverter<Double> {
9905 public Double convert(String value) { try {return Double.valueOf(value);} catch (Exception ex) {throw fail(value, Double.TYPE);} }
9906 }
9907 static class FileConverter implements ITypeConverter<File> {
9908 public File convert(String value) { return new File(value); }
9909 }
9910 static class URLConverter implements ITypeConverter<URL> {
9911 public URL convert(String value) throws MalformedURLException { return new URL(value); }
9912 }
9913 static class URIConverter implements ITypeConverter<URI> {
9914 public URI convert(String value) throws URISyntaxException { return new URI(value); }
9915 }
9916 /** Converts text in {@code yyyy-mm-dd} format to a {@code java.util.Date}. ParameterException on failure. */
9917 static class ISO8601DateConverter implements ITypeConverter<Date> {
9918 public Date convert(String value) {
9919 try {
9920 return new SimpleDateFormat("yyyy-MM-dd").parse(value);
9921 } catch (ParseException e) {
9922 throw new TypeConversionException("'" + value + "' is not a yyyy-MM-dd date");
9923 }
9924 }
9925 }
9926 /** Converts text in any of the following formats to a {@code java.sql.Time}: {@code HH:mm}, {@code HH:mm:ss},
9927 * {@code HH:mm:ss.SSS}, {@code HH:mm:ss,SSS}. Other formats result in a ParameterException. */
9928 static class ISO8601TimeConverter implements ITypeConverter<Object> {
9929 // Implementation note: use reflection so that picocli only requires the java.base module in Java 9.
9930 private static /*final*/ String FQCN = "java.sql.Time"; // non-final for testing
9931 public Object convert(String value) {
9932 try {
9933 if (value.length() <= 5) {
9934 return createTime(new SimpleDateFormat("HH:mm").parse(value).getTime());
9935 } else if (value.length() <= 8) {
9936 return createTime(new SimpleDateFormat("HH:mm:ss").parse(value).getTime());
9937 } else if (value.length() <= 12) {
9938 try {
9939 return createTime(new SimpleDateFormat("HH:mm:ss.SSS").parse(value).getTime());
9940 } catch (ParseException e2) {
9941 return createTime(new SimpleDateFormat("HH:mm:ss,SSS").parse(value).getTime());
9942 }
9943 }
9944 } catch (ParseException ignored) {
9945 // ignored because we throw a ParameterException below
9946 }
9947 throw new TypeConversionException("'" + value + "' is not a HH:mm[:ss[.SSS]] time");
9948 }
9949
9950 private Object createTime(long epochMillis) {
9951 try {
9952 Class<?> timeClass = Class.forName(FQCN);
9953 Constructor<?> constructor = timeClass.getDeclaredConstructor(long.class);
9954 return constructor.newInstance(epochMillis);
9955 } catch (Exception e) {
9956 throw new TypeConversionException("Unable to create new java.sql.Time with long value " + epochMillis + ": " + e.getMessage());
9957 }
9958 }
9959
9960 public static void registerIfAvailable(Map<Class<?>, ITypeConverter<?>> registry, Tracer tracer) {
9961 if (excluded(FQCN, tracer)) { return; }
9962 try {
9963 registry.put(Class.forName(FQCN), new ISO8601TimeConverter());
9964 } catch (Exception e) {
9965 if (!traced.contains(FQCN)) {
9966 tracer.debug("Could not register converter for %s: %s%n", FQCN, e.toString());
9967 }
9968 traced.add(FQCN);
9969 }
9970 }
9971 }
9972 static class BigDecimalConverter implements ITypeConverter<BigDecimal> {
9973 public BigDecimal convert(String value) { return new BigDecimal(value); }
9974 }
9975 static class BigIntegerConverter implements ITypeConverter<BigInteger> {
9976 public BigInteger convert(String value) { return new BigInteger(value); }
9977 }
9978 static class CharsetConverter implements ITypeConverter<Charset> {
9979 public Charset convert(String s) { return Charset.forName(s); }
9980 }
9981 /** Converts text to a {@code InetAddress} by delegating to {@link InetAddress#getByName(String)}. */
9982 static class InetAddressConverter implements ITypeConverter<InetAddress> {
9983 public InetAddress convert(String s) throws Exception { return InetAddress.getByName(s); }
9984 }
9985 static class PatternConverter implements ITypeConverter<Pattern> {
9986 public Pattern convert(String s) { return Pattern.compile(s); }
9987 }
9988 static class UUIDConverter implements ITypeConverter<UUID> {
9989 public UUID convert(String s) throws Exception { return UUID.fromString(s); }
9990 }
9991 static class CurrencyConverter implements ITypeConverter<Currency> {
9992 public Currency convert(String s) throws Exception { return Currency.getInstance(s); }
9993 }
9994 static class TimeZoneConverter implements ITypeConverter<TimeZone> {
9995 public TimeZone convert(String s) throws Exception { return TimeZone.getTimeZone(s); }
9996 }
9997 static class ByteOrderConverter implements ITypeConverter<ByteOrder> {
9998 public ByteOrder convert(String s) throws Exception {
9999 if (s.equalsIgnoreCase(ByteOrder.BIG_ENDIAN.toString())) { return ByteOrder.BIG_ENDIAN; }
10000 if (s.equalsIgnoreCase(ByteOrder.LITTLE_ENDIAN.toString())) { return ByteOrder.LITTLE_ENDIAN; }
10001 throw new TypeConversionException("'" + s + "' is not a valid ByteOrder");
10002 }
10003 }
10004 static class ClassConverter implements ITypeConverter<Class<?>> {
10005 public Class<?> convert(String s) throws Exception { return Class.forName(s); }
10006 }
10007 static class NetworkInterfaceConverter implements ITypeConverter<NetworkInterface> {
10008 public NetworkInterface convert(String s) throws Exception {
10009 try {
10010 InetAddress addr = new InetAddressConverter().convert(s);
10011 return NetworkInterface.getByInetAddress(addr);
10012 } catch (Exception ex) {
10013 try { return NetworkInterface.getByName(s);
10014 } catch (Exception ex2) {
10015 throw new TypeConversionException("'" + s + "' is not an InetAddress or NetworkInterface name");
10016 }
10017 }
10018 }
10019 }
10020 static void registerIfAvailable(Map<Class<?>, ITypeConverter<?>> registry, Tracer tracer, String fqcn, String factoryMethodName, Class<?>... paramTypes) {
10021 registerIfAvailable(registry, tracer, fqcn, fqcn, factoryMethodName, paramTypes);
10022 }
10023 static void registerIfAvailable(Map<Class<?>, ITypeConverter<?>> registry, Tracer tracer, String fqcn, String factoryClass, String factoryMethodName, Class<?>... paramTypes) {
10024 if (excluded(fqcn, tracer)) { return; }
10025 try {
10026 Class<?> cls = Class.forName(fqcn);
10027 Class<?> factory = Class.forName(factoryClass);
10028 Method method = factory.getDeclaredMethod(factoryMethodName, paramTypes);
10029 registry.put(cls, new ReflectionConverter(method, paramTypes));
10030 } catch (Exception e) {
10031 if (!traced.contains(fqcn)) {
10032 tracer.debug("Could not register converter for %s: %s%n", fqcn, e.toString());
10033 }
10034 traced.add(fqcn);
10035 }
10036 }
10037 static boolean excluded(String fqcn, Tracer tracer) {
10038 String[] excludes = System.getProperty("picocli.converters.excludes", "").split(",");
10039 for (String regex : excludes) {
10040 if (fqcn.matches(regex)) {
10041 tracer.debug("BuiltIn type converter for %s is not loaded: (picocli.converters.excludes=%s)%n", fqcn, System.getProperty("picocli.converters.excludes"));
10042 return true;
10043 }
10044 }
10045 return false;
10046 }
10047 static Set<String> traced = new HashSet<String>();
10048 static class ReflectionConverter implements ITypeConverter<Object> {
10049 private final Method method;
10050 private Class<?>[] paramTypes;
10051
10052 public ReflectionConverter(Method method, Class<?>... paramTypes) {
10053 this.method = Assert.notNull(method, "method");
10054 this.paramTypes = Assert.notNull(paramTypes, "paramTypes");
10055 }
10056
10057 public Object convert(String s) {
10058 try {
10059 if (paramTypes.length > 1) {
10060 return method.invoke(null, s, new String[0]);
10061 } else {
10062 return method.invoke(null, s);
10063 }
10064 } catch (InvocationTargetException e) {
10065 throw new TypeConversionException(String.format("cannot convert '%s' to %s (%s)", s, method.getReturnType(), e.getTargetException()));
10066 } catch (Exception e) {
10067 throw new TypeConversionException(String.format("Internal error converting '%s' to %s (%s)", s, method.getReturnType(), e));
10068 }
10069 }
10070 }
10071 private BuiltIn() {} // private constructor: never instantiate
10072 }
10073
10074 static class AutoHelpMixin {
10075 private static final String KEY = "mixinStandardHelpOptions";
10076
10077 @Option(names = {"-h", "--help"}, usageHelp = true, descriptionKey = "mixinStandardHelpOptions.help",
10078 description = "Show this help message and exit.")
10079 private boolean helpRequested;
10080
10081 @Option(names = {"-V", "--version"}, versionHelp = true, descriptionKey = "mixinStandardHelpOptions.version",
10082 description = "Print version information and exit.")
10083 private boolean versionRequested;
10084 }
10085
10086 /** Help command that can be installed as a subcommand on all application commands. When invoked with a subcommand
10087 * argument, it prints usage help for the specified subcommand. For example:<pre>
10088 *
10089 * // print help for subcommand
10090 * command help subcommand
10091 * </pre><p>
10092 * When invoked without additional parameters, it prints usage help for the parent command. For example:
10093 * </p><pre>
10094 *
10095 * // print help for command
10096 * command help
10097 * </pre>
10098 * For {@linkplain Messages internationalization}: this command has a {@code --help} option with {@code descriptionKey = "helpCommand.help"},
10099 * and a {@code COMMAND} positional parameter with {@code descriptionKey = "helpCommand.command"}.
10100 * @since 3.0
10101 */
10102 @Command(name = "help", header = "Displays help information about the specified command",
10103 synopsisHeading = "%nUsage: ", helpCommand = true,
10104 description = {"%nWhen no COMMAND is given, the usage help for the main command is displayed.",
10105 "If a COMMAND is specified, the help for that command is shown.%n"})
10106 public static final class HelpCommand implements IHelpCommandInitializable, Runnable {
10107
10108 @Option(names = {"-h", "--help"}, usageHelp = true, descriptionKey = "helpCommand.help",
10109 description = "Show usage help for the help command and exit.")
10110 private boolean helpRequested;
10111
10112 @Parameters(paramLabel = "COMMAND", descriptionKey = "helpCommand.command",
10113 description = "The COMMAND to display the usage help message for.")
10114 private String[] commands = new String[0];
10115
10116 private CommandLine self;
10117 private PrintStream out;
10118 private PrintStream err;
10119 private Help.Ansi ansi;
10120
10121 /** Invokes {@link #usage(PrintStream, Help.Ansi) usage} for the specified command, or for the parent command. */
10122 public void run() {
10123 CommandLine parent = self == null ? null : self.getParent();
10124 if (parent == null) { return; }
10125 if (commands.length > 0) {
10126 CommandLine subcommand = parent.getSubcommands().get(commands[0]);
10127 if (subcommand != null) {
10128 subcommand.usage(out, ansi);
10129 } else {
10130 throw new ParameterException(parent, "Unknown subcommand '" + commands[0] + "'.", null, commands[0]);
10131 }
10132 } else {
10133 parent.usage(out, ansi);
10134 }
10135 }
10136 /** {@inheritDoc} */
10137 public void init(CommandLine helpCommandLine, Help.Ansi ansi, PrintStream out, PrintStream err) {
10138 this.self = Assert.notNull(helpCommandLine, "helpCommandLine");
10139 this.ansi = Assert.notNull(ansi, "ansi");
10140 this.out = Assert.notNull(out, "out");
10141 this.err = Assert.notNull(err, "err");
10142 }
10143 }
10144
10145 /** Help commands that provide usage help for other commands can implement this interface to be initialized with the information they need.
10146 * <p>The {@link #printHelpIfRequested(List, PrintStream, PrintStream, Help.Ansi) CommandLine::printHelpIfRequested} method calls the
10147 * {@link #init(CommandLine, picocli.CommandLine.Help.Ansi, PrintStream, PrintStream) init} method on commands marked as {@link Command#helpCommand() helpCommand}
10148 * before the help command's {@code run} or {@code call} method is called.</p>
10149 * <p><b>Implementation note:</b></p><p>
10150 * If an error occurs in the {@code run} or {@code call} method while processing the help request, it is recommended custom Help
10151 * commands throw a {@link ParameterException ParameterException} with a reference to the parent command. The {@link DefaultExceptionHandler DefaultExceptionHandler} will print
10152 * 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.
10153 * </p>
10154 * @since 3.0 */
10155 public static interface IHelpCommandInitializable {
10156 /** Initializes this object with the information needed to implement a help command that provides usage help for other commands.
10157 * @param helpCommandLine the {@code CommandLine} object associated with this help command. Implementors can use
10158 * this to walk the command hierarchy and get access to the help command's parent and sibling commands.
10159 * @param ansi whether to use Ansi colors or not
10160 * @param out the stream to print the usage help message to
10161 * @param err the error stream to print any diagnostic messages to, in addition to the output from the exception handler
10162 */
10163 void init(CommandLine helpCommandLine, Help.Ansi ansi, PrintStream out, PrintStream err);
10164 }
10165
10166 /**
10167 * Renders a section of the usage help message. The usage help message can be customized:
10168 * use the {@link #setHelpSectionKeys(List)} and {@link #setHelpSectionMap(Map)} to change the order of sections,
10169 * delete standard sections, add custom sections or replace the renderer of a standard sections with a custom one.
10170 * <p>
10171 * This gives complete freedom on how a usage help message section is rendered, but it also means that the section renderer
10172 * is responsible for all aspects of rendering the section, including layout and emitting ANSI escape codes.
10173 * 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.
10174 * </p>
10175 * @see UsageMessageSpec
10176 * @since 3.9
10177 */
10178 public interface IHelpSectionRenderer {
10179 /**
10180 * Renders a section of the usage help, like header heading, header, synopsis heading,
10181 * synopsis, description heading, description, etc.
10182 * @param help the {@code Help} instance for which to render a section
10183 * @return the text for this section; may contain {@linkplain Help.Ansi ANSI} escape codes
10184 * @since 3.9
10185 */
10186 String render(Help help);
10187 }
10188
10189 /**
10190 * A collection of methods and inner classes that provide fine-grained control over the contents and layout of
10191 * the usage help message to display to end users when help is requested or invalid input values were specified.
10192 * <h2>Class Diagram of the CommandLine.Help API</h2>
10193 * <p>
10194 * <img src="doc-files/class-diagram-help-api.png" alt="Class Diagram of the CommandLine.Help API">
10195 * </p>
10196 * <h2>Layered API</h2>
10197 * <p>The {@link Command} annotation and the {@link UsageMessageSpec} programmatic API equivalent
10198 * provide the easiest way to configure the usage help message. See
10199 * the <a href="https://remkop.github.io/picocli/index.html#_usage_help">Manual</a> for details.</p>
10200 * <p>This Help class provides high-level functions to create sections of the usage help message and headings
10201 * for these sections. Instead of calling the {@link CommandLine#usage(PrintStream, CommandLine.Help.ColorScheme)}
10202 * method, application authors may want to create a custom usage help message by reorganizing sections in a
10203 * different order and/or adding custom sections.</p>
10204 * <p>Finally, the Help class contains inner classes and interfaces that can be used to create custom help messages.</p>
10205 * <h3>IOptionRenderer and IParameterRenderer</h3>
10206 * <p>Renders a field annotated with {@link Option} or {@link Parameters} to an array of {@link Text} values.
10207 * By default, these values are</p><ul>
10208 * <li>mandatory marker character (if the option/parameter is {@link Option#required() required})</li>
10209 * <li>short option name (empty for parameters)</li>
10210 * <li>comma or empty (empty for parameters)</li>
10211 * <li>long option names (the parameter {@link IParamLabelRenderer label} for parameters)</li>
10212 * <li>description</li>
10213 * </ul>
10214 * <p>Other components rely on this ordering.</p>
10215 * <h3>Layout</h3>
10216 * <p>Delegates to the renderers to create {@link Text} values for the annotated fields, and uses a
10217 * {@link TextTable} to display these values in tabular format. Layout is responsible for deciding which values
10218 * to display where in the table. By default, Layout shows one option or parameter per table row.</p>
10219 * <h3>TextTable</h3>
10220 * <p>Responsible for spacing out {@link Text} values according to the {@link Column} definitions the table was
10221 * created with. Columns have a width, indentation, and an overflow policy that decides what to do if a value is
10222 * longer than the column's width.</p>
10223 * <h3>Text</h3>
10224 * <p>Encapsulates rich text with styles and colors in a way that other components like {@link TextTable} are
10225 * unaware of the embedded ANSI escape codes.</p>
10226 */
10227 public static class Help {
10228
10229 /** Constant String holding the default program name, value defined in {@link CommandSpec#DEFAULT_COMMAND_NAME}. */
10230 protected static final String DEFAULT_COMMAND_NAME = CommandSpec.DEFAULT_COMMAND_NAME;
10231
10232 /** Constant String holding the default string that separates options from option parameters, value defined in {@link ParserSpec#DEFAULT_SEPARATOR}. */
10233 protected static final String DEFAULT_SEPARATOR = ParserSpec.DEFAULT_SEPARATOR;
10234
10235 private final static int defaultOptionsColumnWidth = 24;
10236 private final CommandSpec commandSpec;
10237 private final ColorScheme colorScheme;
10238 private final Map<String, Help> commands = new LinkedHashMap<String, Help>();
10239 private List<String> aliases = Collections.emptyList();
10240
10241 private IParamLabelRenderer parameterLabelRenderer;
10242
10243 /** Constructs a new {@code Help} instance with a default color scheme, initialized from annotatations
10244 * on the specified class and superclasses.
10245 * @param command the annotated object to create usage help for */
10246 public Help(Object command) {
10247 this(command, Ansi.AUTO);
10248 }
10249
10250 /** Constructs a new {@code Help} instance with a default color scheme, initialized from annotatations
10251 * on the specified class and superclasses.
10252 * @param command the annotated object to create usage help for
10253 * @param ansi whether to emit ANSI escape codes or not */
10254 public Help(Object command, Ansi ansi) {
10255 this(command, defaultColorScheme(ansi));
10256 }
10257 /** Constructs a new {@code Help} instance with the specified color scheme, initialized from annotatations
10258 * on the specified class and superclasses.
10259 * @param command the annotated object to create usage help for
10260 * @param colorScheme the color scheme to use
10261 * @deprecated use {@link picocli.CommandLine.Help#Help(picocli.CommandLine.Model.CommandSpec, picocli.CommandLine.Help.ColorScheme)} */
10262 @Deprecated public Help(Object command, ColorScheme colorScheme) {
10263 this(CommandSpec.forAnnotatedObject(command, new DefaultFactory()), colorScheme);
10264 }
10265 /** Constructs a new {@code Help} instance with the specified color scheme, initialized from annotatations
10266 * on the specified class and superclasses.
10267 * @param commandSpec the command model to create usage help for
10268 * @param colorScheme the color scheme to use */
10269 public Help(CommandSpec commandSpec, ColorScheme colorScheme) {
10270 this.commandSpec = Assert.notNull(commandSpec, "commandSpec");
10271 this.aliases = new ArrayList<String>(Arrays.asList(commandSpec.aliases()));
10272 this.aliases.add(0, commandSpec.name());
10273 this.colorScheme = Assert.notNull(colorScheme, "colorScheme").applySystemProperties();
10274 parameterLabelRenderer = createDefaultParamLabelRenderer(); // uses help separator
10275
10276 this.addAllSubcommands(commandSpec.subcommands());
10277 }
10278
10279 Help withCommandNames(List<String> aliases) { this.aliases = aliases; return this; }
10280
10281 /** Returns the {@code CommandSpec} model that this Help was constructed with.
10282 * @since 3.9 */
10283 public CommandSpec commandSpec() { return commandSpec; }
10284
10285 /** Returns the {@code ColorScheme} model that this Help was constructed with.
10286 * @since 3.0 */
10287 public ColorScheme colorScheme() { return colorScheme; }
10288
10289 /** Returns the {@code IHelpFactory} that this Help was constructed with.
10290 * @since 3.9 */
10291 private IHelpFactory getHelpFactory() { return commandSpec.usageMessage().helpFactory(); }
10292
10293 /** Returns the map of subcommand {@code Help} instances for this command Help.
10294 * @since 3.9 */
10295 protected Map<String, Help> subcommands() { return Collections.unmodifiableMap(commands); }
10296
10297 /** Returns the list of aliases for the command in this Help.
10298 * @since 3.9 */
10299 protected List<String> aliases() { return Collections.unmodifiableList(aliases); }
10300
10301 /** Option and positional parameter value label renderer used for the synopsis line(s) and the option list.
10302 * By default initialized to the result of {@link #createDefaultParamLabelRenderer()}, which takes a snapshot
10303 * of the {@link ParserSpec#separator()} at construction time. If the separator is modified after Help construction, you
10304 * may need to re-initialize this field by calling {@link #createDefaultParamLabelRenderer()} again. */
10305 public IParamLabelRenderer parameterLabelRenderer() {return parameterLabelRenderer;}
10306
10307 /** Registers all specified subcommands with this Help.
10308 * @param commands maps the command names to the associated CommandLine object
10309 * @return this Help instance (for method chaining)
10310 * @see CommandLine#getSubcommands()
10311 */
10312 public Help addAllSubcommands(Map<String, CommandLine> commands) {
10313 if (commands != null) {
10314 // first collect aliases
10315 Map<CommandLine, List<String>> done = new IdentityHashMap<CommandLine, List<String>>();
10316 for (CommandLine cmd : commands.values()) {
10317 if (!done.containsKey(cmd)) {
10318 done.put(cmd, new ArrayList<String>(Arrays.asList(cmd.commandSpec.aliases())));
10319 }
10320 }
10321 // 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)
10322 for (Map.Entry<String, CommandLine> entry : commands.entrySet()) {
10323 List<String> aliases = done.get(entry.getValue());
10324 if (!aliases.contains(entry.getKey())) { aliases.add(0, entry.getKey()); }
10325 }
10326 // The aliases list for each command now has at least one entry, with the main name at the front.
10327 // Now we loop over the commands in the order that they were registered on their parent command.
10328 for (Map.Entry<String, CommandLine> entry : commands.entrySet()) {
10329 // not registering hidden commands is easier than suppressing display in Help.commandList():
10330 // if all subcommands are hidden, help should not show command list header
10331 if (!entry.getValue().getCommandSpec().usageMessage().hidden()) {
10332 List<String> aliases = done.remove(entry.getValue());
10333 if (aliases != null) { // otherwise we already processed this command by another alias
10334 addSubcommand(aliases, entry.getValue());
10335 }
10336 }
10337 }
10338 }
10339 return this;
10340 }
10341
10342 /** Registers the specified subcommand with this Help.
10343 * @param commandNames the name and aliases of the subcommand to display in the usage message
10344 * @param commandLine the {@code CommandLine} object to get more information from
10345 * @return this Help instance (for method chaining) */
10346 Help addSubcommand(List<String> commandNames, CommandLine commandLine) {
10347 String all = commandNames.toString();
10348 commands.put(all.substring(1, all.length() - 1), getHelpFactory().create(commandLine.commandSpec, colorScheme).withCommandNames(commandNames));
10349 return this;
10350 }
10351
10352 /** Registers the specified subcommand with this Help.
10353 * @param commandName the name of the subcommand to display in the usage message
10354 * @param command the {@code CommandSpec} or {@code @Command} annotated object to get more information from
10355 * @return this Help instance (for method chaining)
10356 * @deprecated
10357 */
10358 @Deprecated public Help addSubcommand(String commandName, Object command) {
10359 commands.put(commandName,
10360 getHelpFactory().create(CommandSpec.forAnnotatedObject(command, commandSpec.commandLine().factory), defaultColorScheme(Ansi.AUTO)));
10361 return this;
10362 }
10363
10364 List<OptionSpec> options() { return commandSpec.options(); }
10365 List<PositionalParamSpec> positionalParameters() { return commandSpec.positionalParameters(); }
10366 String commandName() { return commandSpec.name(); }
10367
10368 /** Returns a synopsis for the command without reserving space for the synopsis heading.
10369 * @return a synopsis
10370 * @see #abbreviatedSynopsis()
10371 * @see #detailedSynopsis(Comparator, boolean)
10372 * @deprecated use {@link #synopsis(int)} instead
10373 */
10374 @Deprecated public String synopsis() { return synopsis(0); }
10375
10376 /**
10377 * Returns a synopsis for the command, reserving the specified space for the synopsis heading.
10378 * @param synopsisHeadingLength the length of the synopsis heading that will be displayed on the same line
10379 * @return a synopsis
10380 * @see #abbreviatedSynopsis()
10381 * @see #detailedSynopsis(Comparator, boolean)
10382 * @see #synopsisHeading
10383 */
10384 public String synopsis(int synopsisHeadingLength) {
10385 if (!empty(commandSpec.usageMessage().customSynopsis())) { return customSynopsis(); }
10386 return commandSpec.usageMessage().abbreviateSynopsis() ? abbreviatedSynopsis()
10387 : detailedSynopsis(synopsisHeadingLength, createShortOptionArityAndNameComparator(), true);
10388 }
10389
10390 /** Generates a generic synopsis like {@code <command name> [OPTIONS] [PARAM1 [PARAM2]...]}, omitting parts
10391 * that don't apply to the command (e.g., does not show [OPTIONS] if the command has no options).
10392 * @return a generic synopsis */
10393 public String abbreviatedSynopsis() {
10394 StringBuilder sb = new StringBuilder();
10395 if (!commandSpec.optionsMap().isEmpty()) { // only show if annotated object actually has options
10396 sb.append(" [OPTIONS]");
10397 }
10398 // sb.append(" [--] "); // implied
10399 for (PositionalParamSpec positionalParam : commandSpec.positionalParameters()) {
10400 if (!positionalParam.hidden()) {
10401 sb.append(' ').append(parameterLabelRenderer().renderParameterLabel(positionalParam, ansi(), colorScheme.parameterStyles));
10402 }
10403 }
10404
10405 // only show if object has subcommands
10406 if (!commandSpec.subcommands().isEmpty()) {
10407 sb.append(" [COMMAND]");
10408 }
10409
10410 return colorScheme.commandText(commandSpec.qualifiedName()).toString()
10411 + (sb.toString()) + System.getProperty("line.separator");
10412 }
10413 /** Generates a detailed synopsis message showing all options and parameters. Follows the unix convention of
10414 * showing optional options and parameters in square brackets ({@code [ ]}).
10415 * @param optionSort comparator to sort options or {@code null} if options should not be sorted
10416 * @param clusterBooleanOptions {@code true} if boolean short options should be clustered into a single string
10417 * @return a detailed synopsis
10418 * @deprecated use {@link #detailedSynopsis(int, Comparator, boolean)} instead. */
10419 @Deprecated public String detailedSynopsis(Comparator<OptionSpec> optionSort, boolean clusterBooleanOptions) {
10420 return detailedSynopsis(0, optionSort, clusterBooleanOptions);
10421 }
10422
10423 /** Generates a detailed synopsis message showing all options and parameters. Follows the unix convention of
10424 * showing optional options and parameters in square brackets ({@code [ ]}).
10425 * @param synopsisHeadingLength the length of the synopsis heading that will be displayed on the same line
10426 * @param optionSort comparator to sort options or {@code null} if options should not be sorted
10427 * @param clusterBooleanOptions {@code true} if boolean short options should be clustered into a single string
10428 * @return a detailed synopsis
10429 * @since 3.0 */
10430 public String detailedSynopsis(int synopsisHeadingLength, Comparator<OptionSpec> optionSort, boolean clusterBooleanOptions) {
10431 Set<ArgSpec> argsInGroups = new HashSet<ArgSpec>();
10432 Text groupsText = createDetailedSynopsisGroupsText(argsInGroups);
10433 Text optionText = createDetailedSynopsisOptionsText(argsInGroups, optionSort, clusterBooleanOptions);
10434 Text positionalParamText = createDetailedSynopsisPositionalsText(argsInGroups);
10435 Text commandText = createDetailedSynopsisCommandText();
10436
10437 Text text = groupsText.concat(optionText).concat(positionalParamText).concat(commandText);
10438
10439 return insertSynopsisCommandName(synopsisHeadingLength, text);
10440 }
10441
10442 /** Returns a Text object containing a partial detailed synopsis showing only the options and positional parameters in
10443 * the specified {@linkplain ArgGroup#validate() validating} {@linkplain ArgGroup groups}, starting with a {@code " "} space.
10444 * @param outparam_groupArgs all options and positional parameters in the groups this method generates a synopsis for;
10445 * these options and positional parameters should be excluded from appearing elsewhere in the synopsis
10446 * @return the formatted groups synopsis elements, starting with a {@code " "} space, or an empty Text if this command has no validating groups
10447 * @since 4.0 */
10448 protected Text createDetailedSynopsisGroupsText(Set<ArgSpec> outparam_groupArgs) {
10449 Set<ArgGroupSpec> remove = new HashSet<ArgGroupSpec>();
10450 List<ArgGroupSpec> groups = new ArrayList<ArgGroupSpec>(commandSpec().argGroups());
10451 for (ArgGroupSpec group : groups) {
10452 if (group.validate()) {
10453 // remove subgroups
10454 remove.addAll(group.subgroups());
10455
10456 // exclude options and positional parameters in this group
10457 outparam_groupArgs.addAll(group.args());
10458
10459 // exclude options and positional parameters in the subgroups
10460 for (ArgGroupSpec subgroup : group.subgroups()) {
10461 outparam_groupArgs.addAll(subgroup.args());
10462 }
10463 } else {
10464 remove.add(group); // non-validating groups should not impact synopsis
10465 }
10466 }
10467 groups.removeAll(remove);
10468 Text groupText = ansi().new Text(0);
10469 for (ArgGroupSpec group : groups) {
10470 groupText = groupText.concat(" ").concat(group.synopsisText(colorScheme()));
10471 }
10472 return groupText;
10473 }
10474 /** Returns a Text object containing a partial detailed synopsis showing only the options, starting with a {@code " "} space.
10475 * Follows the unix convention of showing optional options and parameters in square brackets ({@code [ ]}).
10476 * @param done the list of options and positional parameters for which a synopsis was already generated. Options in this set should be excluded.
10477 * @param optionSort comparator to sort options or {@code null} if options should not be sorted
10478 * @param clusterBooleanOptions {@code true} if boolean short options should be clustered into a single string
10479 * @return the formatted options, starting with a {@code " "} space, or an empty Text if this command has no named options
10480 * @since 3.9 */
10481 protected Text createDetailedSynopsisOptionsText(Collection<ArgSpec> done, Comparator<OptionSpec> optionSort, boolean clusterBooleanOptions) {
10482 Text optionText = ansi().new Text(0);
10483 List<OptionSpec> options = new ArrayList<OptionSpec>(commandSpec.options()); // iterate in declaration order
10484 if (optionSort != null) {
10485 Collections.sort(options, optionSort);// iterate in specified sort order
10486 }
10487 options.removeAll(done);
10488 if (clusterBooleanOptions) { // cluster all short boolean options into a single string
10489 List<OptionSpec> booleanOptions = new ArrayList<OptionSpec>();
10490 StringBuilder clusteredRequired = new StringBuilder("-");
10491 StringBuilder clusteredOptional = new StringBuilder("-");
10492 for (OptionSpec option : options) {
10493 if (option.hidden()) { continue; }
10494 boolean isFlagOption = option.typeInfo().isBoolean();
10495 if (isFlagOption && option.arity().max <= 0) { // #612 consider arity: boolean options may require a parameter
10496 String shortestName = option.shortestName();
10497 if (shortestName.length() == 2 && shortestName.startsWith("-")) {
10498 booleanOptions.add(option);
10499 if (option.required()) {
10500 clusteredRequired.append(shortestName.substring(1));
10501 } else {
10502 clusteredOptional.append(shortestName.substring(1));
10503 }
10504 }
10505 }
10506 }
10507 options.removeAll(booleanOptions);
10508 if (clusteredRequired.length() > 1) { // initial length was 1
10509 optionText = optionText.concat(" ").concat(colorScheme.optionText(clusteredRequired.toString()));
10510 }
10511 if (clusteredOptional.length() > 1) { // initial length was 1
10512 optionText = optionText.concat(" [").concat(colorScheme.optionText(clusteredOptional.toString())).concat("]");
10513 }
10514 }
10515 for (OptionSpec option : options) {
10516 if (!option.hidden()) {
10517 Text name = colorScheme.optionText(option.shortestName());
10518 Text param = parameterLabelRenderer().renderParameterLabel(option, colorScheme.ansi(), colorScheme.optionParamStyles);
10519 if (option.required()) { // e.g., -x=VAL
10520 optionText = optionText.concat(" ").concat(name).concat(param).concat("");
10521 if (option.isMultiValue()) { // e.g., -x=VAL [-x=VAL]...
10522 optionText = optionText.concat(" [").concat(name).concat(param).concat("]...");
10523 }
10524 } else {
10525 optionText = optionText.concat(" [").concat(name).concat(param).concat("]");
10526 if (option.isMultiValue()) { // add ellipsis to show option is repeatable
10527 optionText = optionText.concat("...");
10528 }
10529 }
10530 }
10531 }
10532 return optionText;
10533 }
10534
10535 /** Returns a Text object containing a partial detailed synopsis showing only the positional parameters, starting with a {@code " "} space.
10536 * Follows the unix convention of showing optional options and parameters in square brackets ({@code [ ]}).
10537 * @param done the list of options and positional parameters for which a synopsis was already generated. Positional parameters in this set should be excluded.
10538 * @return the formatted positional parameters, starting with a {@code " "} space, or an empty Text if this command has no positional parameters
10539 * @since 3.9 */
10540 protected Text createDetailedSynopsisPositionalsText(Collection<ArgSpec> done) {
10541 Text positionalParamText = ansi().new Text(0);
10542 List<PositionalParamSpec> positionals = new ArrayList<PositionalParamSpec>(commandSpec.positionalParameters()); // iterate in declaration order
10543 positionals.removeAll(done);
10544 for (PositionalParamSpec positionalParam : positionals) {
10545 if (!positionalParam.hidden()) {
10546 positionalParamText = positionalParamText.concat(" ");
10547 Text label = parameterLabelRenderer().renderParameterLabel(positionalParam, colorScheme.ansi(), colorScheme.parameterStyles);
10548 positionalParamText = positionalParamText.concat(label);
10549 }
10550 }
10551 return positionalParamText;
10552 }
10553
10554 /** Returns a Text object containing a partial detailed synopsis showing only the subcommands, starting with a {@code " "} space.
10555 * Follows the unix convention of showing optional elements in square brackets ({@code [ ]}).
10556 * @return this implementation returns a hard-coded string {@code " [COMMAND]"} if this command has subcommands, an empty Text otherwise
10557 * @since 3.9 */
10558 protected Text createDetailedSynopsisCommandText() {
10559 Text commandText = ansi().new Text(0);
10560 if (!commandSpec.subcommands().isEmpty()){
10561 commandText = commandText.concat(" [")
10562 .concat("COMMAND")
10563 .concat("]");
10564 }
10565 return commandText;
10566 }
10567
10568 /**
10569 * Returns the detailed synopsis text by inserting the command name before the specified text with options and positional parameters details.
10570 * @param synopsisHeadingLength length of the synopsis heading string to be displayed on the same line as the first synopsis line.
10571 * For example, if the synopsis heading is {@code "Usage: "}, this value is 7.
10572 * @param optionsAndPositionalsAndCommandsDetails formatted string with options, positional parameters and subcommands.
10573 * Follows the unix convention of showing optional options and parameters in square brackets ({@code [ ]}).
10574 * @return the detailed synopsis text, in multiple lines if the length exceeds the usage width
10575 */
10576 protected String insertSynopsisCommandName(int synopsisHeadingLength, Text optionsAndPositionalsAndCommandsDetails) {
10577 // Fix for #142: first line of synopsis overshoots max. characters
10578 String commandName = commandSpec.qualifiedName();
10579 int firstColumnLength = commandName.length() + synopsisHeadingLength;
10580
10581 // synopsis heading ("Usage: ") may be on the same line, so adjust column width
10582 TextTable textTable = TextTable.forColumnWidths(ansi(), firstColumnLength, width() - firstColumnLength);
10583 textTable.indentWrappedLines = 1; // don't worry about first line: options (2nd column) always start with a space
10584
10585 // right-adjust the command name by length of synopsis heading
10586 Text PADDING = Ansi.OFF.new Text(stringOf('X', synopsisHeadingLength));
10587 textTable.addRowValues(PADDING.concat(colorScheme.commandText(commandName)), optionsAndPositionalsAndCommandsDetails);
10588 return textTable.toString().substring(synopsisHeadingLength); // cut off leading synopsis heading spaces
10589 }
10590
10591 /** Returns the number of characters the synopsis heading will take on the same line as the synopsis.
10592 * @return the number of characters the synopsis heading will take on the same line as the synopsis.
10593 * @see #detailedSynopsis(int, Comparator, boolean)
10594 */
10595 public int synopsisHeadingLength() {
10596 String[] lines = Ansi.OFF.new Text(commandSpec.usageMessage().synopsisHeading()).toString().split("\\r?\\n|\\r|%n", -1);
10597 return lines[lines.length - 1].length();
10598 }
10599 /**
10600 * <p>Returns a description of the {@linkplain Option options} supported by the application.
10601 * This implementation {@linkplain #createShortOptionNameComparator() sorts options alphabetically}, and shows
10602 * only the {@linkplain Option#hidden() non-hidden} options in a {@linkplain TextTable tabular format}
10603 * using the {@linkplain #createDefaultOptionRenderer() default renderer} and {@linkplain Layout default layout}.</p>
10604 * @return the fully formatted option list
10605 * @see #optionList(Layout, Comparator, IParamLabelRenderer)
10606 */
10607 public String optionList() {
10608 Comparator<OptionSpec> sortOrder = commandSpec.usageMessage().sortOptions()
10609 ? createShortOptionNameComparator()
10610 : createOrderComparatorIfNecessary(commandSpec.options());
10611
10612 return optionList(createLayout(calcLongOptionColumnWidth()), sortOrder, parameterLabelRenderer());
10613 }
10614
10615 private static Comparator<OptionSpec> createOrderComparatorIfNecessary(List<OptionSpec> options) {
10616 for (OptionSpec option : options) { if (option.order() != OptionSpec.DEFAULT_ORDER) { return createOrderComparator(); } }
10617 return null;
10618 }
10619
10620 private int calcLongOptionColumnWidth() {
10621 int max = 0;
10622 IOptionRenderer optionRenderer = new DefaultOptionRenderer(false, " ");
10623 for (OptionSpec option : commandSpec.options()) {
10624 Text[][] values = optionRenderer.render(option, parameterLabelRenderer(), colorScheme);
10625 int len = values[0][3].length;
10626 if (len < Help.defaultOptionsColumnWidth - 3) { max = Math.max(max, len); }
10627 }
10628 IParameterRenderer paramRenderer = new DefaultParameterRenderer(false, " ");
10629 for (PositionalParamSpec positional : commandSpec.positionalParameters()) {
10630 Text[][] values = paramRenderer.render(positional, parameterLabelRenderer(), colorScheme);
10631 int len = values[0][3].length;
10632 if (len < Help.defaultOptionsColumnWidth - 3) { max = Math.max(max, len); }
10633 }
10634 return max + 3;
10635 }
10636
10637 /** Sorts all {@code Options} with the specified {@code comparator} (if the comparator is non-{@code null}),
10638 * then {@linkplain Layout#addOption(CommandLine.Model.OptionSpec, CommandLine.Help.IParamLabelRenderer) adds} all non-hidden options to the
10639 * specified TextTable and returns the result of TextTable.toString().
10640 * @param layout responsible for rendering the option list
10641 * @param valueLabelRenderer used for options with a parameter
10642 * @return the fully formatted option list
10643 * @since 3.0 */
10644 public String optionList(Layout layout, Comparator<OptionSpec> optionSort, IParamLabelRenderer valueLabelRenderer) {
10645 List<OptionSpec> options = new ArrayList<OptionSpec>(commandSpec.options()); // options are stored in order of declaration
10646 if (optionSort != null) {
10647 Collections.sort(options, optionSort); // default: sort options ABC
10648 }
10649 List<ArgGroupSpec> groups = optionListGroups();
10650 for (ArgGroupSpec group : groups) { options.removeAll(group.options()); }
10651
10652 StringBuilder sb = new StringBuilder();
10653 layout.addOptions(options, valueLabelRenderer);
10654 sb.append(layout.toString());
10655
10656 int longOptionColumnWidth = calcLongOptionColumnWidth();
10657 Collections.sort(groups, new SortByOrder<ArgGroupSpec>());
10658 for (ArgGroupSpec group : groups) {
10659 sb.append(heading(ansi(), width(), group.heading()));
10660
10661 Layout groupLayout = createLayout(longOptionColumnWidth);
10662 groupLayout.addPositionalParameters(group.positionalParameters(), valueLabelRenderer);
10663 List<OptionSpec> groupOptions = new ArrayList<OptionSpec>(group.options());
10664 if (optionSort != null) {
10665 Collections.sort(groupOptions, optionSort);
10666 }
10667 groupLayout.addOptions(groupOptions, valueLabelRenderer);
10668 sb.append(groupLayout);
10669 }
10670 return sb.toString();
10671 }
10672
10673 /** Returns the list of {@code ArgGroupSpec}s with a non-{@code null} heading. */
10674 private List<ArgGroupSpec> optionListGroups() {
10675 List<ArgGroupSpec> result = new ArrayList<ArgGroupSpec>();
10676 optionListGroups(commandSpec.argGroups(), result);
10677 return result;
10678 }
10679 private static void optionListGroups(List<ArgGroupSpec> groups, List<ArgGroupSpec> result) {
10680 for (ArgGroupSpec group : groups) {
10681 optionListGroups(group.subgroups(), result);
10682 if (group.heading() != null) { result.add(group); }
10683 }
10684 }
10685
10686 /**
10687 * Returns the section of the usage help message that lists the parameters with their descriptions.
10688 * @return the section of the usage help message that lists the parameters
10689 */
10690 public String parameterList() {
10691 return parameterList(createLayout(calcLongOptionColumnWidth()), parameterLabelRenderer());
10692 }
10693 /**
10694 * Returns the section of the usage help message that lists the parameters with their descriptions.
10695 * @param layout the layout to use
10696 * @param paramLabelRenderer for rendering parameter names
10697 * @return the section of the usage help message that lists the parameters
10698 */
10699 public String parameterList(Layout layout, IParamLabelRenderer paramLabelRenderer) {
10700 List<PositionalParamSpec> positionals = new ArrayList<PositionalParamSpec>(commandSpec.positionalParameters());
10701 List<ArgGroupSpec> groups = optionListGroups();
10702 for (ArgGroupSpec group : groups) { positionals.removeAll(group.positionalParameters()); }
10703
10704 layout.addPositionalParameters(positionals, paramLabelRenderer);
10705 return layout.toString();
10706 }
10707
10708 private static String heading(Ansi ansi, int usageWidth, String values, Object... params) {
10709 StringBuilder sb = join(ansi, usageWidth, new String[] {values}, new StringBuilder(), params);
10710 return trimLineSeparator(sb.toString()) + new String(spaces(countTrailingSpaces(values)));
10711 }
10712 static String trimLineSeparator(String result) {
10713 return result.endsWith(System.getProperty("line.separator"))
10714 ? result.substring(0, result.length() - System.getProperty("line.separator").length()) : result;
10715 }
10716
10717 private static char[] spaces(int length) { char[] result = new char[length]; Arrays.fill(result, ' '); return result; }
10718 private static int countTrailingSpaces(String str) {
10719 if (str == null) {return 0;}
10720 int trailingSpaces = 0;
10721 for (int i = str.length() - 1; i >= 0 && str.charAt(i) == ' '; i--) { trailingSpaces++; }
10722 return trailingSpaces;
10723 }
10724
10725 /** Formats each of the specified values and appends it to the specified StringBuilder.
10726 * @param ansi whether the result should contain ANSI escape codes or not
10727 * @param usageHelpWidth the width of the usage help message
10728 * @param values the values to format and append to the StringBuilder
10729 * @param sb the StringBuilder to collect the formatted strings
10730 * @param params the parameters to pass to the format method when formatting each value
10731 * @return the specified StringBuilder */
10732 public static StringBuilder join(Ansi ansi, int usageHelpWidth, String[] values, StringBuilder sb, Object... params) {
10733 if (values != null) {
10734 TextTable table = TextTable.forColumnWidths(ansi, usageHelpWidth);
10735 table.indentWrappedLines = 0;
10736 for (String summaryLine : values) {
10737 Text[] lines = ansi.new Text(format(summaryLine, params)).splitLines();
10738 for (Text line : lines) { table.addRowValues(line); }
10739 }
10740 table.toString(sb);
10741 }
10742 return sb;
10743 }
10744 private int width() { return commandSpec.usageMessage().width(); }
10745 /** Returns command custom synopsis as a string. A custom synopsis can be zero or more lines, and can be
10746 * specified declaratively with the {@link Command#customSynopsis()} annotation attribute or programmatically
10747 * by setting the Help instance's {@link Help#customSynopsis} field.
10748 * @param params Arguments referenced by the format specifiers in the synopsis strings
10749 * @return the custom synopsis lines combined into a single String (which may be empty)
10750 */
10751 public String customSynopsis(Object... params) {
10752 return join(ansi(), width(), commandSpec.usageMessage().customSynopsis(), new StringBuilder(), params).toString();
10753 }
10754 /** Returns command description text as a string. Description text can be zero or more lines, and can be specified
10755 * declaratively with the {@link Command#description()} annotation attribute or programmatically by
10756 * setting the Help instance's {@link Help#description} field.
10757 * @param params Arguments referenced by the format specifiers in the description strings
10758 * @return the description lines combined into a single String (which may be empty)
10759 */
10760 public String description(Object... params) {
10761 return join(ansi(), width(), commandSpec.usageMessage().description(), new StringBuilder(), params).toString();
10762 }
10763 /** Returns the command header text as a string. Header text can be zero or more lines, and can be specified
10764 * declaratively with the {@link Command#header()} annotation attribute or programmatically by
10765 * setting the Help instance's {@link Help#header} field.
10766 * @param params Arguments referenced by the format specifiers in the header strings
10767 * @return the header lines combined into a single String (which may be empty)
10768 */
10769 public String header(Object... params) {
10770 return join(ansi(), width(), commandSpec.usageMessage().header(), new StringBuilder(), params).toString();
10771 }
10772 /** Returns command footer text as a string. Footer text can be zero or more lines, and can be specified
10773 * declaratively with the {@link Command#footer()} annotation attribute or programmatically by
10774 * setting the Help instance's {@link Help#footer} field.
10775 * @param params Arguments referenced by the format specifiers in the footer strings
10776 * @return the footer lines combined into a single String (which may be empty)
10777 */
10778 public String footer(Object... params) {
10779 return join(ansi(), width(), commandSpec.usageMessage().footer(), new StringBuilder(), params).toString();
10780 }
10781
10782 /** Returns the text displayed before the header text; the result of {@code String.format(headerHeading, params)}.
10783 * @param params the parameters to use to format the header heading
10784 * @return the formatted header heading */
10785 public String headerHeading(Object... params) {
10786 return heading(ansi(), width(), commandSpec.usageMessage().headerHeading(), params);
10787 }
10788
10789 /** Returns the text displayed before the synopsis text; the result of {@code String.format(synopsisHeading, params)}.
10790 * @param params the parameters to use to format the synopsis heading
10791 * @return the formatted synopsis heading */
10792 public String synopsisHeading(Object... params) {
10793 return heading(ansi(), width(), commandSpec.usageMessage().synopsisHeading(), params);
10794 }
10795
10796 /** Returns the text displayed before the description text; an empty string if there is no description,
10797 * otherwise the result of {@code String.format(descriptionHeading, params)}.
10798 * @param params the parameters to use to format the description heading
10799 * @return the formatted description heading */
10800 public String descriptionHeading(Object... params) {
10801 return empty(commandSpec.usageMessage().descriptionHeading()) ? "" : heading(ansi(), width(), commandSpec.usageMessage().descriptionHeading(), params);
10802 }
10803
10804 /** Returns the text displayed before the positional parameter list; an empty string if there are no positional
10805 * parameters, otherwise the result of {@code String.format(parameterListHeading, params)}.
10806 * @param params the parameters to use to format the parameter list heading
10807 * @return the formatted parameter list heading */
10808 public String parameterListHeading(Object... params) {
10809 return commandSpec.positionalParameters().isEmpty() ? "" : heading(ansi(), width(), commandSpec.usageMessage().parameterListHeading(), params);
10810 }
10811
10812 /** Returns the text displayed before the option list; an empty string if there are no options,
10813 * otherwise the result of {@code String.format(optionListHeading, params)}.
10814 * @param params the parameters to use to format the option list heading
10815 * @return the formatted option list heading */
10816 public String optionListHeading(Object... params) {
10817 return commandSpec.optionsMap().isEmpty() ? "" : heading(ansi(), width(), commandSpec.usageMessage().optionListHeading(), params);
10818 }
10819
10820 /** Returns the text displayed before the command list; an empty string if there are no commands,
10821 * otherwise the result of {@code String.format(commandListHeading, params)}.
10822 * @param params the parameters to use to format the command list heading
10823 * @return the formatted command list heading */
10824 public String commandListHeading(Object... params) {
10825 return commands.isEmpty() ? "" : heading(ansi(), width(), commandSpec.usageMessage().commandListHeading(), params);
10826 }
10827
10828 /** Returns the text displayed before the footer text; the result of {@code String.format(footerHeading, params)}.
10829 * @param params the parameters to use to format the footer heading
10830 * @return the formatted footer heading */
10831 public String footerHeading(Object... params) {
10832 return heading(ansi(), width(), commandSpec.usageMessage().footerHeading(), params);
10833 }
10834 /** Returns a 2-column list with command names and the first line of their header or (if absent) description.
10835 * @return a usage help section describing the added commands */
10836 public String commandList() {
10837 if (subcommands().isEmpty()) { return ""; }
10838 int commandLength = maxLength(subcommands().keySet());
10839 Help.TextTable textTable = Help.TextTable.forColumns(ansi(),
10840 new Help.Column(commandLength + 2, 2, Help.Column.Overflow.SPAN),
10841 new Help.Column(width() - (commandLength + 2), 2, Help.Column.Overflow.WRAP));
10842
10843 for (Map.Entry<String, Help> entry : subcommands().entrySet()) {
10844 Help help = entry.getValue();
10845 UsageMessageSpec usage = help.commandSpec().usageMessage();
10846 String header = !empty(usage.header())
10847 ? usage.header()[0]
10848 : (!empty(usage.description()) ? usage.description()[0] : "");
10849 Text[] lines = ansi().text(format(header)).splitLines();
10850 for (int i = 0; i < lines.length; i++) {
10851 textTable.addRowValues(i == 0 ? help.commandNamesText(", ") : Ansi.EMPTY_TEXT, lines[i]);
10852 }
10853 }
10854 return textTable.toString();
10855 }
10856 private static int maxLength(Collection<String> any) {
10857 List<String> strings = new ArrayList<String>(any);
10858 Collections.sort(strings, Collections.reverseOrder(Help.shortestFirst()));
10859 return strings.get(0).length();
10860 }
10861
10862 /** Returns a {@code Text} object containing the command name and all aliases, separated with the specified separator.
10863 * Command names will use the {@link ColorScheme#commandText(String) command style} for the color scheme of this Help.
10864 * @since 3.9 */
10865 public Text commandNamesText(String separator) {
10866 Text result = colorScheme().commandText(aliases().get(0));
10867 for (int i = 1; i < aliases().size(); i++) {
10868 result = result.concat(separator).concat(colorScheme().commandText(aliases().get(i)));
10869 }
10870 return result;
10871 }
10872 private static String join(String[] names, int offset, int length, String separator) {
10873 if (names == null) { return ""; }
10874 StringBuilder result = new StringBuilder();
10875 for (int i = offset; i < offset + length; i++) {
10876 result.append((i > offset) ? separator : "").append(names[i]);
10877 }
10878 return result.toString();
10879 }
10880 private static String stringOf(char chr, int length) {
10881 char[] buff = new char[length];
10882 Arrays.fill(buff, chr);
10883 return new String(buff);
10884 }
10885
10886 /** Returns a {@code Layout} instance configured with the user preferences captured in this Help instance.
10887 * @return a Layout */
10888 public Layout createDefaultLayout() {
10889 return createLayout(Help.defaultOptionsColumnWidth);
10890 }
10891
10892 private Layout createLayout(int longOptionsColumnWidth) {
10893 return new Layout(colorScheme, TextTable.forDefaultColumns(colorScheme.ansi(), longOptionsColumnWidth, width()), createDefaultOptionRenderer(), createDefaultParameterRenderer());
10894 }
10895
10896 /** Returns a new default OptionRenderer which converts {@link OptionSpec Options} to five columns of text to match
10897 * the default {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
10898 * <ol>
10899 * <li>the required option marker</li>
10900 * <li>2-character short option name (or empty string if no short option exists)</li>
10901 * <li>comma separator (only if both short option and long option exist, empty string otherwise)</li>
10902 * <li>comma-separated string with long option name(s)</li>
10903 * <li>first element of the {@link OptionSpec#description()} array</li>
10904 * </ol>
10905 * <p>Following this, there will be one row for each of the remaining elements of the {@link
10906 * OptionSpec#description()} array, and these rows look like {@code {"", "", "", "", option.description()[i]}}.</p>
10907 * <p>If configured, this option renderer adds an additional row to display the default field value.</p>
10908 * @return a new default OptionRenderer
10909 */
10910 public IOptionRenderer createDefaultOptionRenderer() {
10911 return new DefaultOptionRenderer(commandSpec.usageMessage.showDefaultValues(), "" +commandSpec.usageMessage().requiredOptionMarker());
10912 }
10913 /** Returns a new minimal OptionRenderer which converts {@link OptionSpec Options} to a single row with two columns
10914 * of text: an option name and a description. If multiple names or descriptions exist, the first value is used.
10915 * @return a new minimal OptionRenderer */
10916 public static IOptionRenderer createMinimalOptionRenderer() {
10917 return new MinimalOptionRenderer();
10918 }
10919
10920 /** Returns a new default ParameterRenderer which converts {@linkplain PositionalParamSpec positional parameters} to four columns of
10921 * text to match the default {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
10922 * <ol>
10923 * <li>empty string </li>
10924 * <li>empty string </li>
10925 * <li>parameter(s) label as rendered by the {@link IParamLabelRenderer}</li>
10926 * <li>first element of the {@link PositionalParamSpec#description()} array</li>
10927 * </ol>
10928 * <p>Following this, there will be one row for each of the remaining elements of the {@link
10929 * PositionalParamSpec#description()} array, and these rows look like {@code {"", "", "", param.description()[i]}}.</p>
10930 * <p>If configured, this parameter renderer adds an additional row to display the default field value.</p>
10931 * @return a new default ParameterRenderer
10932 */
10933 public IParameterRenderer createDefaultParameterRenderer() {
10934 return new DefaultParameterRenderer(commandSpec.usageMessage.showDefaultValues(), "" + commandSpec.usageMessage().requiredOptionMarker());
10935 }
10936 /** Returns a new minimal ParameterRenderer which converts {@linkplain PositionalParamSpec positional parameters}
10937 * to a single row with two columns of text: an option name and a description. If multiple descriptions exist, the first value is used.
10938 * @return a new minimal ParameterRenderer */
10939 public static IParameterRenderer createMinimalParameterRenderer() {
10940 return new MinimalParameterRenderer();
10941 }
10942
10943 /** Returns a value renderer that returns the {@code paramLabel} if defined or the field name otherwise.
10944 * @return a new minimal ParamLabelRenderer */
10945 public static IParamLabelRenderer createMinimalParamLabelRenderer() {
10946 return new IParamLabelRenderer() {
10947 public Text renderParameterLabel(ArgSpec argSpec, Ansi ansi, List<IStyle> styles) {
10948 return ansi.apply(argSpec.paramLabel(), styles);
10949 }
10950 public String separator() { return ""; }
10951 };
10952 }
10953 /** Returns a new default param label renderer that separates option parameters from their option name
10954 * with the specified separator string, and, unless {@link ArgSpec#hideParamSyntax()} is true,
10955 * surrounds optional parameters with {@code '['} and {@code ']'}
10956 * characters and uses ellipses ("...") to indicate that any number of a parameter are allowed.
10957 * @return a new default ParamLabelRenderer
10958 */
10959 public IParamLabelRenderer createDefaultParamLabelRenderer() {
10960 return new DefaultParamLabelRenderer(commandSpec);
10961 }
10962 /** Sorts {@link OptionSpec OptionSpecs} by their option name in case-insensitive alphabetic order. If an
10963 * option has multiple names, the shortest name is used for the sorting. Help options follow non-help options.
10964 * @return a comparator that sorts OptionSpecs by their option name in case-insensitive alphabetic order */
10965 public static Comparator<OptionSpec> createShortOptionNameComparator() {
10966 return new SortByShortestOptionNameAlphabetically();
10967 }
10968 /** Sorts {@link OptionSpec OptionSpecs} by their option {@linkplain Range#max max arity} first, by
10969 * {@linkplain Range#min min arity} next, and by {@linkplain #createShortOptionNameComparator() option name} last.
10970 * @return a comparator that sorts OptionSpecs by arity first, then their option name */
10971 public static Comparator<OptionSpec> createShortOptionArityAndNameComparator() {
10972 return new SortByOptionArityAndNameAlphabetically();
10973 }
10974 /** Sorts short strings before longer strings.
10975 * @return a comparators that sorts short strings before longer strings */
10976 public static Comparator<String> shortestFirst() { return new ShortestFirst(); }
10977 /** Sorts {@link OptionSpec options} by their option {@linkplain IOrdered#order() order}, lowest first, highest last.
10978 * @return a comparator that sorts OptionSpecs by their order
10979 * @since 3.9*/
10980 static Comparator<OptionSpec> createOrderComparator() {
10981 return new SortByOrder<OptionSpec>();
10982 }
10983
10984 /** Returns whether ANSI escape codes are enabled or not.
10985 * @return whether ANSI escape codes are enabled or not
10986 */
10987 public Ansi ansi() { return colorScheme.ansi; }
10988
10989 /** Controls the visibility of certain aspects of the usage help message. */
10990 public enum Visibility { ALWAYS, NEVER, ON_DEMAND }
10991
10992 /** When customizing online help for {@link OptionSpec Option} details, a custom {@code IOptionRenderer} can be
10993 * used to create textual representation of an Option in a tabular format: one or more rows, each containing
10994 * one or more columns. The {@link Layout Layout} is responsible for placing these text values in the
10995 * {@link TextTable TextTable}. */
10996 public interface IOptionRenderer {
10997 /**
10998 * Returns a text representation of the specified option and its parameter(s) if any.
10999 * @param option the command line option to show online usage help for
11000 * @param parameterLabelRenderer responsible for rendering option parameters to text
11001 * @param scheme color scheme for applying ansi color styles to options and option parameters
11002 * @return a 2-dimensional array of text values: one or more rows, each containing one or more columns
11003 * @since 3.0
11004 */
11005 Text[][] render(OptionSpec option, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme);
11006 }
11007 /** The DefaultOptionRenderer converts {@link OptionSpec Options} to five columns of text to match the default
11008 * {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
11009 * <ol>
11010 * <li>the required option marker (if the option is required)</li>
11011 * <li>2-character short option name (or empty string if no short option exists)</li>
11012 * <li>comma separator (only if both short option and long option exist, empty string otherwise)</li>
11013 * <li>comma-separated string with long option name(s)</li>
11014 * <li>first element of the {@link OptionSpec#description()} array</li>
11015 * </ol>
11016 * <p>Following this, there will be one row for each of the remaining elements of the {@link
11017 * OptionSpec#description()} array, and these rows look like {@code {"", "", "", option.description()[i]}}.</p>
11018 */
11019 static class DefaultOptionRenderer implements IOptionRenderer {
11020 private String requiredMarker = " ";
11021 private boolean showDefaultValues;
11022 private String sep;
11023 public DefaultOptionRenderer(boolean showDefaultValues, String requiredMarker) {
11024 this.showDefaultValues = showDefaultValues;
11025 this.requiredMarker = Assert.notNull(requiredMarker, "requiredMarker");
11026 }
11027 public Text[][] render(OptionSpec option, IParamLabelRenderer paramLabelRenderer, ColorScheme scheme) {
11028 String[] names = ShortestFirst.sort(option.names());
11029 int shortOptionCount = names[0].length() == 2 ? 1 : 0;
11030 String shortOption = shortOptionCount > 0 ? names[0] : "";
11031 sep = shortOptionCount > 0 && names.length > 1 ? "," : "";
11032
11033 String longOption = join(names, shortOptionCount, names.length - shortOptionCount, ", ");
11034 Text longOptionText = createLongOptionText(option, paramLabelRenderer, scheme, longOption);
11035
11036 String requiredOption = option.required() ? requiredMarker : "";
11037 return renderDescriptionLines(option, scheme, requiredOption, shortOption, longOptionText);
11038 }
11039
11040 private Text createLongOptionText(OptionSpec option, IParamLabelRenderer renderer, ColorScheme scheme, String longOption) {
11041 Text paramLabelText = renderer.renderParameterLabel(option, scheme.ansi(), scheme.optionParamStyles);
11042
11043 // if no long option, fill in the space between the short option name and the param label value
11044 if (paramLabelText.length > 0 && longOption.length() == 0) {
11045 sep = renderer.separator();
11046 // #181 paramLabelText may be =LABEL or [=LABEL...]
11047 int sepStart = paramLabelText.plainString().indexOf(sep);
11048 Text prefix = paramLabelText.substring(0, sepStart);
11049 paramLabelText = prefix.concat(paramLabelText.substring(sepStart + sep.length()));
11050 }
11051 Text longOptionText = scheme.optionText(longOption);
11052 longOptionText = longOptionText.concat(paramLabelText);
11053 return longOptionText;
11054 }
11055
11056 private Text[][] renderDescriptionLines(OptionSpec option,
11057 ColorScheme scheme,
11058 String requiredOption,
11059 String shortOption,
11060 Text longOptionText) {
11061 Text EMPTY = Ansi.EMPTY_TEXT;
11062 boolean[] showDefault = {option.internalShowDefaultValue(showDefaultValues)};
11063 List<Text[]> result = new ArrayList<Text[]>();
11064 String[] description = option.renderedDescription();
11065 Text[] descriptionFirstLines = createDescriptionFirstLines(scheme, option, description, showDefault);
11066 result.add(new Text[] { scheme.optionText(requiredOption), scheme.optionText(shortOption),
11067 scheme.ansi().new Text(sep), longOptionText, descriptionFirstLines[0] });
11068 for (int i = 1; i < descriptionFirstLines.length; i++) {
11069 result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, descriptionFirstLines[i] });
11070 }
11071 for (int i = 1; i < description.length; i++) {
11072 Text[] descriptionNextLines = scheme.ansi().new Text(description[i]).splitLines();
11073 for (Text line : descriptionNextLines) {
11074 result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, line });
11075 }
11076 }
11077 if (showDefault[0]) { addTrailingDefaultLine(result, option, scheme); }
11078 return result.toArray(new Text[result.size()][]);
11079 }
11080 }
11081 /** The MinimalOptionRenderer converts {@link OptionSpec Options} to a single row with two columns of text: an
11082 * option name and a description. If multiple names or description lines exist, the first value is used. */
11083 static class MinimalOptionRenderer implements IOptionRenderer {
11084 public Text[][] render(OptionSpec option, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme) {
11085 Text optionText = scheme.optionText(option.names()[0]);
11086 Text paramLabelText = parameterLabelRenderer.renderParameterLabel(option, scheme.ansi(), scheme.optionParamStyles);
11087 optionText = optionText.concat(paramLabelText);
11088 return new Text[][] {{ optionText,
11089 scheme.ansi().new Text(option.description().length == 0 ? "" : option.description()[0]) }};
11090 }
11091 }
11092 /** The MinimalParameterRenderer converts {@linkplain PositionalParamSpec positional parameters} to a single row with two columns of
11093 * text: the parameters label and a description. If multiple description lines exist, the first value is used. */
11094 static class MinimalParameterRenderer implements IParameterRenderer {
11095 public Text[][] render(PositionalParamSpec param, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme) {
11096 return new Text[][] {{ parameterLabelRenderer.renderParameterLabel(param, scheme.ansi(), scheme.parameterStyles),
11097 scheme.ansi().new Text(param.description().length == 0 ? "" : param.description()[0]) }};
11098 }
11099 }
11100 /** When customizing online help for {@linkplain PositionalParamSpec positional parameters} details, a custom {@code IParameterRenderer}
11101 * can be used to create textual representation of a Parameters field in a tabular format: one or more rows,
11102 * each containing one or more columns. The {@link Layout Layout} is responsible for placing these text
11103 * values in the {@link TextTable TextTable}. */
11104 public interface IParameterRenderer {
11105 /**
11106 * Returns a text representation of the specified positional parameter.
11107 * @param param the positional parameter to show online usage help for
11108 * @param parameterLabelRenderer responsible for rendering parameter labels to text
11109 * @param scheme color scheme for applying ansi color styles to positional parameters
11110 * @return a 2-dimensional array of text values: one or more rows, each containing one or more columns
11111 * @since 3.0
11112 */
11113 Text[][] render(PositionalParamSpec param, IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme);
11114 }
11115 /** The DefaultParameterRenderer converts {@linkplain PositionalParamSpec positional parameters} to five columns of text to match the
11116 * default {@linkplain TextTable TextTable} column layout. The first row of values looks like this:
11117 * <ol>
11118 * <li>the required option marker (if the parameter's arity is to have at least one value)</li>
11119 * <li>empty string </li>
11120 * <li>empty string </li>
11121 * <li>parameter(s) label as rendered by the {@link IParamLabelRenderer}</li>
11122 * <li>first element of the {@link PositionalParamSpec#description()} array</li>
11123 * </ol>
11124 * <p>Following this, there will be one row for each of the remaining elements of the {@link
11125 * PositionalParamSpec#description()} array, and these rows look like {@code {"", "", "", param.description()[i]}}.</p>
11126 */
11127 static class DefaultParameterRenderer implements IParameterRenderer {
11128 private String requiredMarker = " ";
11129 private boolean showDefaultValues;
11130 public DefaultParameterRenderer(boolean showDefaultValues, String requiredMarker) {
11131 this.showDefaultValues = showDefaultValues;
11132 this.requiredMarker = Assert.notNull(requiredMarker, "requiredMarker");
11133 }
11134 public Text[][] render(PositionalParamSpec param, IParamLabelRenderer paramLabelRenderer, ColorScheme scheme) {
11135 Text label = paramLabelRenderer.renderParameterLabel(param, scheme.ansi(), scheme.parameterStyles);
11136 Text requiredParameter = scheme.parameterText(param.arity().min > 0 ? requiredMarker : "");
11137
11138 Text EMPTY = Ansi.EMPTY_TEXT;
11139 boolean[] showDefault = {param.internalShowDefaultValue(showDefaultValues)};
11140 List<Text[]> result = new ArrayList<Text[]>();
11141 String[] description = param.renderedDescription();
11142 Text[] descriptionFirstLines = createDescriptionFirstLines(scheme, param, description, showDefault);
11143 result.add(new Text[] { requiredParameter, EMPTY, EMPTY, label, descriptionFirstLines[0] });
11144 for (int i = 1; i < descriptionFirstLines.length; i++) {
11145 result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, descriptionFirstLines[i] });
11146 }
11147 for (int i = 1; i < description.length; i++) {
11148 Text[] descriptionNextLines = scheme.ansi().new Text(description[i]).splitLines();
11149 for (Text line : descriptionNextLines) {
11150 result.add(new Text[] { EMPTY, EMPTY, EMPTY, EMPTY, line });
11151 }
11152 }
11153 if (showDefault[0]) { addTrailingDefaultLine(result, param, scheme); }
11154 return result.toArray(new Text[result.size()][]);
11155 }
11156 }
11157
11158 private static void addTrailingDefaultLine(List<Text[]> result, ArgSpec arg, ColorScheme scheme) {
11159 Text EMPTY = Ansi.EMPTY_TEXT;
11160 result.add(new Text[]{EMPTY, EMPTY, EMPTY, EMPTY, scheme.ansi().new Text(" Default: " + arg.defaultValueString())});
11161 }
11162
11163 private static Text[] createDescriptionFirstLines(ColorScheme scheme, ArgSpec arg, String[] description, boolean[] showDefault) {
11164 Text[] result = scheme.ansi().new Text(str(description, 0)).splitLines();
11165 if (result.length == 0 || (result.length == 1 && result[0].plain.length() == 0)) {
11166 if (showDefault[0]) {
11167 result = new Text[]{scheme.ansi().new Text(" Default: " + arg.defaultValueString())};
11168 showDefault[0] = false; // don't show the default value twice
11169 } else {
11170 result = new Text[]{ Ansi.EMPTY_TEXT };
11171 }
11172 }
11173 return result;
11174 }
11175
11176 /** When customizing online usage help for an option parameter or a positional parameter, a custom
11177 * {@code IParamLabelRenderer} can be used to render the parameter name or label to a String. */
11178 public interface IParamLabelRenderer {
11179
11180 /** Returns a text rendering of the option parameter or positional parameter; returns an empty string
11181 * {@code ""} if the option is a boolean and does not take a parameter.
11182 * @param argSpec the named or positional parameter with a parameter label
11183 * @param ansi determines whether ANSI escape codes should be emitted or not
11184 * @param styles the styles to apply to the parameter label
11185 * @return a text rendering of the Option parameter or positional parameter
11186 * @since 3.0 */
11187 Text renderParameterLabel(ArgSpec argSpec, Ansi ansi, List<IStyle> styles);
11188
11189 /** Returns the separator between option name and param label.
11190 * @return the separator between option name and param label */
11191 String separator();
11192 }
11193 /**
11194 * DefaultParamLabelRenderer separates option parameters from their {@linkplain OptionSpec option names} with a
11195 * {@linkplain CommandLine.Model.ParserSpec#separator() separator} string, and, unless
11196 * {@link ArgSpec#hideParamSyntax()} is true, surrounds optional values with {@code '['} and {@code ']'} characters
11197 * and uses ellipses ("...") to indicate that any number of values is allowed for options or parameters with variable arity.
11198 */
11199 static class DefaultParamLabelRenderer implements IParamLabelRenderer {
11200 private final CommandSpec commandSpec;
11201 /** Constructs a new DefaultParamLabelRenderer with the specified separator string. */
11202 public DefaultParamLabelRenderer(CommandSpec commandSpec) {
11203 this.commandSpec = Assert.notNull(commandSpec, "commandSpec");
11204 }
11205 public String separator() { return commandSpec.parser().separator(); }
11206 public Text renderParameterLabel(ArgSpec argSpec, Ansi ansi, List<IStyle> styles) {
11207 Range capacity = argSpec.isOption() ? argSpec.arity() : ((PositionalParamSpec)argSpec).capacity();
11208 if (capacity.max == 0) { return ansi.new Text(""); }
11209 if (argSpec.hideParamSyntax()) { return ansi.apply((argSpec.isOption() ? separator() : "") + argSpec.paramLabel(), styles); }
11210
11211 Text paramName = ansi.apply(argSpec.paramLabel(), styles);
11212 String split = argSpec.splitRegex();
11213 String mandatorySep = empty(split) ? " " : split;
11214 String optionalSep = empty(split) ? " [" : "[" + split;
11215
11216 boolean unlimitedSplit = !empty(split) && !commandSpec.parser().limitSplit();
11217 boolean limitedSplit = !empty(split) && commandSpec.parser().limitSplit();
11218 Text repeating = paramName;
11219 int paramCount = 1;
11220 if (unlimitedSplit) {
11221 repeating = paramName.concat("[" + split).concat(paramName).concat("...]");
11222 paramCount++;
11223 mandatorySep = " ";
11224 optionalSep = " [";
11225 }
11226 Text result = repeating;
11227
11228 int done = 1;
11229 for (; done < capacity.min; done++) {
11230 result = result.concat(mandatorySep).concat(repeating); // " PARAM" or ",PARAM"
11231 paramCount += paramCount;
11232 }
11233 if (!capacity.isVariable) {
11234 for (int i = done; i < capacity.max; i++) {
11235 result = result.concat(optionalSep).concat(paramName); // " [PARAM" or "[,PARAM"
11236 paramCount++;
11237 }
11238 for (int i = done; i < capacity.max; i++) {
11239 result = result.concat("]");
11240 }
11241 }
11242 // show an extra trailing "[,PARAM]" if split and either max=* or splitting is not restricted to max
11243 boolean effectivelyVariable = capacity.isVariable || (limitedSplit && paramCount == 1);
11244 if (limitedSplit && effectivelyVariable && paramCount == 1) {
11245 result = result.concat(optionalSep).concat(repeating).concat("]"); // PARAM[,PARAM]...
11246 }
11247 if (effectivelyVariable) {
11248 if (!argSpec.arity().isVariable && argSpec.arity().min > 1) {
11249 result = ansi.new Text("(").concat(result).concat(")"); // repeating group
11250 }
11251 result = result.concat("..."); // PARAM...
11252 }
11253 String optionSeparator = argSpec.isOption() ? separator() : "";
11254 if (capacity.min == 0) { // optional
11255 String sep2 = empty(optionSeparator.trim()) ? optionSeparator + "[" : "[" + optionSeparator;
11256 result = ansi.new Text(sep2).concat(result).concat("]");
11257 } else {
11258 result = ansi.new Text(optionSeparator).concat(result);
11259 }
11260 return result;
11261 }
11262 }
11263 /** Use a Layout to format usage help text for options and parameters in tabular format.
11264 * <p>Delegates to the renderers to create {@link Text} values for the annotated fields, and uses a
11265 * {@link TextTable} to display these values in tabular format. Layout is responsible for deciding which values
11266 * to display where in the table. By default, Layout shows one option or parameter per table row.</p>
11267 * <p>Customize by overriding the {@link #layout(CommandLine.Model.ArgSpec, CommandLine.Help.Ansi.Text[][])} method.</p>
11268 * @see IOptionRenderer rendering options to text
11269 * @see IParameterRenderer rendering parameters to text
11270 * @see TextTable showing values in a tabular format
11271 */
11272 public static class Layout {
11273 protected final ColorScheme colorScheme;
11274 protected final TextTable table;
11275 protected IOptionRenderer optionRenderer;
11276 protected IParameterRenderer parameterRenderer;
11277
11278 /** Constructs a Layout with the specified color scheme, a new default TextTable, the
11279 * {@linkplain Help#createDefaultOptionRenderer() default option renderer}, and the
11280 * {@linkplain Help#createDefaultParameterRenderer() default parameter renderer}.
11281 * @param colorScheme the color scheme to use for common, auto-generated parts of the usage help message */
11282 public Layout(ColorScheme colorScheme, int tableWidth) { this(colorScheme, TextTable.forDefaultColumns(colorScheme.ansi(), tableWidth)); }
11283
11284 /** Constructs a Layout with the specified color scheme, the specified TextTable, the
11285 * {@linkplain Help#createDefaultOptionRenderer() default option renderer}, and the
11286 * {@linkplain Help#createDefaultParameterRenderer() default parameter renderer}.
11287 * @param colorScheme the color scheme to use for common, auto-generated parts of the usage help message
11288 * @param textTable the TextTable to lay out parts of the usage help message in tabular format */
11289 public Layout(ColorScheme colorScheme, TextTable textTable) {
11290 this(colorScheme, textTable, new DefaultOptionRenderer(false, " "), new DefaultParameterRenderer(false, " "));
11291 }
11292 /** Constructs a Layout with the specified color scheme, the specified TextTable, the
11293 * specified option renderer and the specified parameter renderer.
11294 * @param colorScheme the color scheme to use for common, auto-generated parts of the usage help message
11295 * @param optionRenderer the object responsible for rendering Options to Text
11296 * @param parameterRenderer the object responsible for rendering Parameters to Text
11297 * @param textTable the TextTable to lay out parts of the usage help message in tabular format */
11298 public Layout(ColorScheme colorScheme, TextTable textTable, IOptionRenderer optionRenderer, IParameterRenderer parameterRenderer) {
11299 this.colorScheme = Assert.notNull(colorScheme, "colorScheme");
11300 this.table = Assert.notNull(textTable, "textTable");
11301 this.optionRenderer = Assert.notNull(optionRenderer, "optionRenderer");
11302 this.parameterRenderer = Assert.notNull(parameterRenderer, "parameterRenderer");
11303 }
11304 /**
11305 * Copies the specified text values into the correct cells in the {@link TextTable}. This implementation
11306 * delegates to {@link TextTable#addRowValues(CommandLine.Help.Ansi.Text...)} for each row of values.
11307 * <p>Subclasses may override.</p>
11308 * @param argSpec the Option or Parameters
11309 * @param cellValues the text values representing the Option/Parameters, to be displayed in tabular form
11310 * @since 3.0 */
11311 public void layout(ArgSpec argSpec, Text[][] cellValues) {
11312 for (Text[] oneRow : cellValues) {
11313 table.addRowValues(oneRow);
11314 }
11315 }
11316 /** Calls {@link #addOption(CommandLine.Model.OptionSpec, CommandLine.Help.IParamLabelRenderer)} for all non-hidden Options in the list.
11317 * @param options options to add usage descriptions for
11318 * @param paramLabelRenderer object that knows how to render option parameters
11319 * @since 3.0 */
11320 public void addOptions(List<OptionSpec> options, IParamLabelRenderer paramLabelRenderer) {
11321 for (OptionSpec option : options) {
11322 if (!option.hidden()) {
11323 addOption(option, paramLabelRenderer);
11324 }
11325 }
11326 }
11327 /**
11328 * Delegates to the {@link #optionRenderer option renderer} of this layout to obtain
11329 * text values for the specified {@link OptionSpec}, and then calls the {@link #layout(CommandLine.Model.ArgSpec, CommandLine.Help.Ansi.Text[][])}
11330 * method to write these text values into the correct cells in the TextTable.
11331 * @param option the option argument
11332 * @param paramLabelRenderer knows how to render option parameters
11333 * @since 3.0 */
11334 public void addOption(OptionSpec option, IParamLabelRenderer paramLabelRenderer) {
11335 Text[][] values = optionRenderer.render(option, paramLabelRenderer, colorScheme);
11336 layout(option, values);
11337 }
11338 /** Calls {@link #addPositionalParameter(CommandLine.Model.PositionalParamSpec, CommandLine.Help.IParamLabelRenderer)} for all non-hidden Parameters in the list.
11339 * @param params positional parameters to add usage descriptions for
11340 * @param paramLabelRenderer knows how to render option parameters
11341 * @since 3.0 */
11342 public void addPositionalParameters(List<PositionalParamSpec> params, IParamLabelRenderer paramLabelRenderer) {
11343 for (PositionalParamSpec param : params) {
11344 if (!param.hidden()) {
11345 addPositionalParameter(param, paramLabelRenderer);
11346 }
11347 }
11348 }
11349 /**
11350 * Delegates to the {@link #parameterRenderer parameter renderer} of this layout
11351 * to obtain text values for the specified {@linkplain PositionalParamSpec positional parameter}, and then calls
11352 * {@link #layout(CommandLine.Model.ArgSpec, CommandLine.Help.Ansi.Text[][])} to write these text values into the correct cells in the TextTable.
11353 * @param param the positional parameter
11354 * @param paramLabelRenderer knows how to render option parameters
11355 * @since 3.0 */
11356 public void addPositionalParameter(PositionalParamSpec param, IParamLabelRenderer paramLabelRenderer) {
11357 Text[][] values = parameterRenderer.render(param, paramLabelRenderer, colorScheme);
11358 layout(param, values);
11359 }
11360 /** Returns the section of the usage help message accumulated in the TextTable owned by this layout. */
11361 @Override public String toString() { return table.toString(); }
11362 }
11363 /** Sorts short strings before longer strings. */
11364 static class ShortestFirst implements Comparator<String> {
11365 public int compare(String o1, String o2) {
11366 return o1.length() - o2.length();
11367 }
11368 /** Sorts the specified array of Strings shortest-first and returns it. */
11369 public static String[] sort(String[] names) {
11370 Arrays.sort(names, new ShortestFirst());
11371 return names;
11372 }
11373 /** Sorts the specified array of Strings longest-first and returns it. */
11374 public static String[] longestFirst(String[] names) {
11375 Arrays.sort(names, Collections.reverseOrder(new ShortestFirst()));
11376 return names;
11377 }
11378 }
11379 /** Sorts {@code OptionSpec} instances by their name in case-insensitive alphabetic order. If an option has
11380 * multiple names, the shortest name is used for the sorting. Help options follow non-help options. */
11381 static class SortByShortestOptionNameAlphabetically implements Comparator<OptionSpec> {
11382 public int compare(OptionSpec o1, OptionSpec o2) {
11383 if (o1 == null) { return 1; } else if (o2 == null) { return -1; } // options before params
11384 String[] names1 = ShortestFirst.sort(o1.names());
11385 String[] names2 = ShortestFirst.sort(o2.names());
11386 int result = names1[0].toUpperCase().compareTo(names2[0].toUpperCase()); // case insensitive sort
11387 result = result == 0 ? -names1[0].compareTo(names2[0]) : result; // lower case before upper case
11388 return o1.help() == o2.help() ? result : o2.help() ? -1 : 1; // help options come last
11389 }
11390 }
11391 /** Sorts {@code OptionSpec} instances by their max arity first, then their min arity, then delegates to super class. */
11392 static class SortByOptionArityAndNameAlphabetically extends SortByShortestOptionNameAlphabetically {
11393 public int compare(OptionSpec o1, OptionSpec o2) {
11394 Range arity1 = o1.arity();
11395 Range arity2 = o2.arity();
11396 int result = arity1.max - arity2.max;
11397 if (result == 0) {
11398 result = arity1.min - arity2.min;
11399 }
11400 if (result == 0) { // arity is same
11401 if (o1.isMultiValue() && !o2.isMultiValue()) { result = 1; } // f1 > f2
11402 if (!o1.isMultiValue() && o2.isMultiValue()) { result = -1; } // f1 < f2
11403 }
11404 return result == 0 ? super.compare(o1, o2) : result;
11405 }
11406 }
11407 static class SortByOrder<T extends IOrdered> implements Comparator<T> {
11408 public int compare(T o1, T o2) {
11409 return Integer.signum(o1.order() - o2.order());
11410 }
11411 }
11412 /**
11413 * <p>Responsible for spacing out {@link Text} values according to the {@link Column} definitions the table was
11414 * created with. Columns have a width, indentation, and an overflow policy that decides what to do if a value is
11415 * longer than the column's width.</p>
11416 */
11417 public static class TextTable {
11418 /**
11419 * Helper class to index positions in a {@code Help.TextTable}.
11420 * @since 2.0
11421 */
11422 public static class Cell {
11423 /** Table column index (zero based). */
11424 public final int column;
11425 /** Table row index (zero based). */
11426 public final int row;
11427 /** Constructs a new Cell with the specified coordinates in the table.
11428 * @param column the zero-based table column
11429 * @param row the zero-based table row */
11430 public Cell(int column, int row) { this.column = column; this.row = row; }
11431 }
11432
11433 private static final int OPTION_SEPARATOR_COLUMN = 2;
11434 private static final int LONG_OPTION_COLUMN = 3;
11435
11436 /** The column definitions of this table. */
11437 private final Column[] columns;
11438
11439 /** The {@code char[]} slots of the {@code TextTable} to copy text values into. */
11440 protected final List<Text> columnValues = new ArrayList<Text>();
11441
11442 /** By default, indent wrapped lines by 2 spaces. */
11443 public int indentWrappedLines = 2;
11444
11445 private final Ansi ansi;
11446 private final int tableWidth;
11447
11448 /** Constructs a TextTable with five columns as follows:
11449 * <ol>
11450 * <li>required option/parameter marker (width: 2, indent: 0, TRUNCATE on overflow)</li>
11451 * <li>short option name (width: 2, indent: 0, TRUNCATE on overflow)</li>
11452 * <li>comma separator (width: 1, indent: 0, TRUNCATE on overflow)</li>
11453 * <li>long option name(s) (width: 24, indent: 1, SPAN multiple columns on overflow)</li>
11454 * <li>description line(s) (width: 51, indent: 1, WRAP to next row on overflow)</li>
11455 * </ol>
11456 * @param ansi whether to emit ANSI escape codes or not
11457 * @param usageHelpWidth the total width of the columns combined
11458 */
11459 public static TextTable forDefaultColumns(Ansi ansi, int usageHelpWidth) {
11460 return forDefaultColumns(ansi, defaultOptionsColumnWidth, usageHelpWidth);
11461 }
11462
11463 /** Constructs a TextTable with five columns as follows:
11464 * <ol>
11465 * <li>required option/parameter marker (width: 2, indent: 0, TRUNCATE on overflow)</li>
11466 * <li>short option name (width: 2, indent: 0, TRUNCATE on overflow)</li>
11467 * <li>comma separator (width: 1, indent: 0, TRUNCATE on overflow)</li>
11468 * <li>long option name(s) (width: 24, indent: 1, SPAN multiple columns on overflow)</li>
11469 * <li>description line(s) (width: 51, indent: 1, WRAP to next row on overflow)</li>
11470 * </ol>
11471 * @param ansi whether to emit ANSI escape codes or not
11472 * @param longOptionsColumnWidth the width of the long options column
11473 * @param usageHelpWidth the total width of the columns combined
11474 */
11475 public static TextTable forDefaultColumns(Ansi ansi, int longOptionsColumnWidth, int usageHelpWidth) {
11476 // "* -c, --create Creates a ...."
11477 return forColumns(ansi,
11478 new Column(2, 0, TRUNCATE), // "*"
11479 new Column(2, 0, TRUNCATE), // "-c"
11480 new Column(1, 0, TRUNCATE), // ","
11481 new Column(longOptionsColumnWidth, 1, SPAN), // " --create"
11482 new Column(usageHelpWidth - longOptionsColumnWidth, 1, WRAP)); // " Creates a ..."
11483 }
11484
11485 /** Constructs a new TextTable with columns with the specified width, all SPANning multiple columns on
11486 * overflow except the last column which WRAPS to the next row.
11487 * @param ansi whether to emit ANSI escape codes or not
11488 * @param columnWidths the width of each table column (all columns have zero indent)
11489 */
11490 public static TextTable forColumnWidths(Ansi ansi, int... columnWidths) {
11491 Column[] columns = new Column[columnWidths.length];
11492 for (int i = 0; i < columnWidths.length; i++) {
11493 columns[i] = new Column(columnWidths[i], 0, i == columnWidths.length - 1 ? WRAP : SPAN);
11494 }
11495 return new TextTable(ansi, columns);
11496 }
11497 /** Constructs a {@code TextTable} with the specified columns.
11498 * @param ansi whether to emit ANSI escape codes or not
11499 * @param columns columns to construct this TextTable with */
11500 public static TextTable forColumns(Ansi ansi, Column... columns) { return new TextTable(ansi, columns); }
11501 protected TextTable(Ansi ansi, Column[] columns) {
11502 this.ansi = Assert.notNull(ansi, "ansi");
11503 this.columns = Assert.notNull(columns, "columns").clone();
11504 if (columns.length == 0) { throw new IllegalArgumentException("At least one column is required"); }
11505 int totalWidth = 0;
11506 for (Column col : columns) { totalWidth += col.width; }
11507 tableWidth = totalWidth;
11508 }
11509 /** The column definitions of this table. */
11510 public Column[] columns() { return columns.clone(); }
11511 /** Returns the {@code Text} slot at the specified row and column to write a text value into.
11512 * @param row the row of the cell whose Text to return
11513 * @param col the column of the cell whose Text to return
11514 * @return the Text object at the specified row and column
11515 * @since 2.0 */
11516 public Text textAt(int row, int col) { return columnValues.get(col + (row * columns.length)); }
11517
11518 /** Returns the {@code Text} slot at the specified row and column to write a text value into.
11519 * @param row the row of the cell whose Text to return
11520 * @param col the column of the cell whose Text to return
11521 * @return the Text object at the specified row and column
11522 * @deprecated use {@link #textAt(int, int)} instead */
11523 @Deprecated public Text cellAt(int row, int col) { return textAt(row, col); }
11524
11525 /** Returns the current number of rows of this {@code TextTable}.
11526 * @return the current number of rows in this TextTable */
11527 public int rowCount() { return columnValues.size() / columns.length; }
11528
11529 /** Adds the required {@code char[]} slots for a new row to the {@link #columnValues} field. */
11530 public void addEmptyRow() {
11531 for (int i = 0; i < columns.length; i++) {
11532 columnValues.add(ansi.new Text(columns[i].width));
11533 }
11534 }
11535
11536 /** Delegates to {@link #addRowValues(CommandLine.Help.Ansi.Text...)}.
11537 * @param values the text values to display in each column of the current row */
11538 public void addRowValues(String... values) {
11539 Text[] array = new Text[values.length];
11540 for (int i = 0; i < array.length; i++) {
11541 array[i] = values[i] == null ? Ansi.EMPTY_TEXT : ansi.new Text(values[i]);
11542 }
11543 addRowValues(array);
11544 }
11545 /**
11546 * Adds a new {@linkplain TextTable#addEmptyRow() empty row}, then calls {@link
11547 * TextTable#putValue(int, int, CommandLine.Help.Ansi.Text) putValue} for each of the specified values, adding more empty rows
11548 * if the return value indicates that the value spanned multiple columns or was wrapped to multiple rows.
11549 * @param values the values to write into a new row in this TextTable
11550 * @throws IllegalArgumentException if the number of values exceeds the number of Columns in this table
11551 */
11552 public void addRowValues(Text... values) {
11553 if (values.length > columns.length) {
11554 throw new IllegalArgumentException(values.length + " values don't fit in " +
11555 columns.length + " columns");
11556 }
11557 addEmptyRow();
11558 int oldIndent = unindent(values);
11559 for (int col = 0; col < values.length; col++) {
11560 int row = rowCount() - 1;// write to last row: previous value may have wrapped to next row
11561 Cell cell = putValue(row, col, values[col]);
11562
11563 // add row if a value spanned/wrapped and there are still remaining values
11564 if ((cell.row != row || cell.column != col) && col != values.length - 1) {
11565 addEmptyRow();
11566 }
11567 }
11568 reindent(oldIndent);
11569 }
11570 private int unindent(Text[] values) {
11571 if (columns.length <= LONG_OPTION_COLUMN) { return 0; }
11572 int oldIndent = columns[LONG_OPTION_COLUMN].indent;
11573 if ("=".equals(values[OPTION_SEPARATOR_COLUMN].toString())) {
11574 columns[LONG_OPTION_COLUMN].indent = 0;
11575 }
11576 return oldIndent;
11577 }
11578 private void reindent(int oldIndent) {
11579 if (columns.length <= LONG_OPTION_COLUMN) { return; }
11580 columns[LONG_OPTION_COLUMN].indent = oldIndent;
11581 }
11582
11583 /**
11584 * Writes the specified value into the cell at the specified row and column and returns the last row and
11585 * column written to. Depending on the Column's {@link Column#overflow Overflow} policy, the value may span
11586 * multiple columns or wrap to multiple rows when larger than the column width.
11587 * @param row the target row in the table
11588 * @param col the target column in the table to write to
11589 * @param value the value to write
11590 * @return a Cell indicating the position in the table that was last written to (since 2.0)
11591 * @throws IllegalArgumentException if the specified row exceeds the table's {@linkplain
11592 * TextTable#rowCount() row count}
11593 * @since 2.0 (previous versions returned a {@code java.awt.Point} object)
11594 */
11595 public Cell putValue(int row, int col, Text value) {
11596 if (row > rowCount() - 1) {
11597 throw new IllegalArgumentException("Cannot write to row " + row + ": rowCount=" + rowCount());
11598 }
11599 if (value == null || value.plain.length() == 0) { return new Cell(col, row); }
11600 Column column = columns[col];
11601 int indent = column.indent;
11602 switch (column.overflow) {
11603 case TRUNCATE:
11604 copy(value, textAt(row, col), indent);
11605 return new Cell(col, row);
11606 case SPAN:
11607 int startColumn = col;
11608 do {
11609 boolean lastColumn = col == columns.length - 1;
11610 int charsWritten = lastColumn
11611 ? copy(BreakIterator.getLineInstance(), value, textAt(row, col), indent)
11612 : copy(value, textAt(row, col), indent);
11613 value = value.substring(charsWritten);
11614 indent = 0;
11615 if (value.length > 0) { // value did not fit in column
11616 ++col; // write remainder of value in next column
11617 }
11618 if (value.length > 0 && col >= columns.length) { // we filled up all columns on this row
11619 addEmptyRow();
11620 row++;
11621 col = startColumn;
11622 indent = column.indent + indentWrappedLines;
11623 }
11624 } while (value.length > 0);
11625 return new Cell(col, row);
11626 case WRAP:
11627 BreakIterator lineBreakIterator = BreakIterator.getLineInstance();
11628 do {
11629 int charsWritten = copy(lineBreakIterator, value, textAt(row, col), indent);
11630 value = value.substring(charsWritten);
11631 indent = column.indent + indentWrappedLines;
11632 if (value.length > 0) { // value did not fit in column
11633 ++row; // write remainder of value in next row
11634 addEmptyRow();
11635 }
11636 } while (value.length > 0);
11637 return new Cell(col, row);
11638 }
11639 throw new IllegalStateException(column.overflow.toString());
11640 }
11641 private static int length(Text str) {
11642 return str.length; // TODO count some characters as double length
11643 }
11644
11645 private int copy(BreakIterator line, Text text, Text columnValue, int offset) {
11646 // Deceive the BreakIterator to ensure no line breaks after '-' character
11647 line.setText(text.plainString().replace("-", "\u00ff"));
11648 int done = 0;
11649 for (int start = line.first(), end = line.next(); end != BreakIterator.DONE; start = end, end = line.next()) {
11650 Text word = text.substring(start, end); //.replace("\u00ff", "-"); // not needed
11651 if (columnValue.maxLength >= offset + done + length(word)) {
11652 done += copy(word, columnValue, offset + done); // TODO messages length
11653 } else {
11654 break;
11655 }
11656 }
11657 if (done == 0 && length(text) + offset > columnValue.maxLength) {
11658 // The value is a single word that is too big to be written to the column. Write as much as we can.
11659 done = copy(text, columnValue, offset);
11660 }
11661 return done;
11662 }
11663 private static int copy(Text value, Text destination, int offset) {
11664 int length = Math.min(value.length, destination.maxLength - offset);
11665 value.getStyledChars(value.from, length, destination, offset);
11666 return length;
11667 }
11668
11669 /** Copies the text representation that we built up from the options into the specified StringBuilder.
11670 * @param text the StringBuilder to write into
11671 * @return the specified StringBuilder object (to allow method chaining and a more fluid API) */
11672 public StringBuilder toString(StringBuilder text) {
11673 int columnCount = this.columns.length;
11674 StringBuilder row = new StringBuilder(tableWidth);
11675 for (int i = 0; i < columnValues.size(); i++) {
11676 Text column = columnValues.get(i);
11677 row.append(column.toString());
11678 row.append(new String(spaces(columns[i % columnCount].width - column.length)));
11679 if (i % columnCount == columnCount - 1) {
11680 int lastChar = row.length() - 1;
11681 while (lastChar >= 0 && row.charAt(lastChar) == ' ') {lastChar--;} // rtrim
11682 row.setLength(lastChar + 1);
11683 text.append(row.toString()).append(System.getProperty("line.separator"));
11684 row.setLength(0);
11685 }
11686 }
11687 return text;
11688 }
11689 public String toString() { return toString(new StringBuilder()).toString(); }
11690 }
11691 /** Columns define the width, indent (leading number of spaces in a column before the value) and
11692 * {@linkplain Overflow Overflow} policy of a column in a {@linkplain TextTable TextTable}. */
11693 public static class Column {
11694
11695 /** Policy for handling text that is longer than the column width:
11696 * span multiple columns, wrap to the next row, or simply truncate the portion that doesn't fit. */
11697 public enum Overflow { TRUNCATE, SPAN, WRAP }
11698
11699 /** Column width in characters */
11700 public final int width;
11701
11702 /** Indent (number of empty spaces at the start of the column preceding the text value) */
11703 public int indent;
11704
11705 /** Policy that determines how to handle values larger than the column width. */
11706 public final Overflow overflow;
11707 public Column(int width, int indent, Overflow overflow) {
11708 this.width = width;
11709 this.indent = indent;
11710 this.overflow = Assert.notNull(overflow, "overflow");
11711 }
11712 }
11713
11714 /** All usage help message are generated with a color scheme that assigns certain styles and colors to common
11715 * parts of a usage message: the command name, options, positional parameters and option parameters.
11716 * Users may customize these styles by creating Help with a custom color scheme.
11717 * <p>Note that these options and styles may not be rendered if ANSI escape codes are not
11718 * {@linkplain Ansi#enabled() enabled}.</p>
11719 * @see Help#defaultColorScheme(Ansi)
11720 */
11721 public static class ColorScheme {
11722 public final List<IStyle> commandStyles = new ArrayList<IStyle>();
11723 public final List<IStyle> optionStyles = new ArrayList<IStyle>();
11724 public final List<IStyle> parameterStyles = new ArrayList<IStyle>();
11725 public final List<IStyle> optionParamStyles = new ArrayList<IStyle>();
11726 private final Ansi ansi;
11727
11728 /** Constructs a new empty ColorScheme with {@link Help.Ansi#AUTO}. */
11729 public ColorScheme() { this(Ansi.AUTO); }
11730
11731 /** Constructs a new empty ColorScheme with the specified Ansi enabled mode.
11732 * @see Help#defaultColorScheme(Ansi)
11733 * @param ansi whether to emit ANSI escape codes or not
11734 */
11735 public ColorScheme(Ansi ansi) {this.ansi = Assert.notNull(ansi, "ansi"); }
11736
11737 /** Adds the specified styles to the registered styles for commands in this color scheme and returns this color scheme.
11738 * @param styles the styles to add to the registered styles for commands in this color scheme
11739 * @return this color scheme to enable method chaining for a more fluent API */
11740 public ColorScheme commands(IStyle... styles) { return addAll(commandStyles, styles); }
11741 /** Adds the specified styles to the registered styles for options in this color scheme and returns this color scheme.
11742 * @param styles the styles to add to registered the styles for options in this color scheme
11743 * @return this color scheme to enable method chaining for a more fluent API */
11744 public ColorScheme options(IStyle... styles) { return addAll(optionStyles, styles);}
11745 /** Adds the specified styles to the registered styles for positional parameters in this color scheme and returns this color scheme.
11746 * @param styles the styles to add to registered the styles for parameters in this color scheme
11747 * @return this color scheme to enable method chaining for a more fluent API */
11748 public ColorScheme parameters(IStyle... styles) { return addAll(parameterStyles, styles);}
11749 /** Adds the specified styles to the registered styles for option parameters in this color scheme and returns this color scheme.
11750 * @param styles the styles to add to the registered styles for option parameters in this color scheme
11751 * @return this color scheme to enable method chaining for a more fluent API */
11752 public ColorScheme optionParams(IStyle... styles) { return addAll(optionParamStyles, styles);}
11753 /** Returns a Text with all command styles applied to the specified command string.
11754 * @param command the command string to apply the registered command styles to
11755 * @return a Text with all command styles applied to the specified command string */
11756 public Ansi.Text commandText(String command) { return ansi().apply(command, commandStyles); }
11757 /** Returns a Text with all option styles applied to the specified option string.
11758 * @param option the option string to apply the registered option styles to
11759 * @return a Text with all option styles applied to the specified option string */
11760 public Ansi.Text optionText(String option) { return ansi().apply(option, optionStyles); }
11761 /** Returns a Text with all parameter styles applied to the specified parameter string.
11762 * @param parameter the parameter string to apply the registered parameter styles to
11763 * @return a Text with all parameter styles applied to the specified parameter string */
11764 public Ansi.Text parameterText(String parameter) { return ansi().apply(parameter, parameterStyles); }
11765 /** Returns a Text with all optionParam styles applied to the specified optionParam string.
11766 * @param optionParam the option parameter string to apply the registered option parameter styles to
11767 * @return a Text with all option parameter styles applied to the specified option parameter string */
11768 public Ansi.Text optionParamText(String optionParam) { return ansi().apply(optionParam, optionParamStyles); }
11769
11770 /** Replaces colors and styles in this scheme with ones specified in system properties, and returns this scheme.
11771 * Supported property names:<ul>
11772 * <li>{@code picocli.color.commands}</li>
11773 * <li>{@code picocli.color.options}</li>
11774 * <li>{@code picocli.color.parameters}</li>
11775 * <li>{@code picocli.color.optionParams}</li>
11776 * </ul><p>Property values can be anything that {@link Help.Ansi.Style#parse(String)} can handle.</p>
11777 * @return this ColorScheme
11778 */
11779 public ColorScheme applySystemProperties() {
11780 replace(commandStyles, System.getProperty("picocli.color.commands"));
11781 replace(optionStyles, System.getProperty("picocli.color.options"));
11782 replace(parameterStyles, System.getProperty("picocli.color.parameters"));
11783 replace(optionParamStyles, System.getProperty("picocli.color.optionParams"));
11784 return this;
11785 }
11786 private void replace(List<IStyle> styles, String property) {
11787 if (property != null) {
11788 styles.clear();
11789 addAll(styles, Style.parse(property));
11790 }
11791 }
11792 private ColorScheme addAll(List<IStyle> styles, IStyle... add) {
11793 styles.addAll(Arrays.asList(add));
11794 return this;
11795 }
11796
11797 public Ansi ansi() { return ansi; }
11798 }
11799
11800 /** Creates and returns a new {@link ColorScheme} initialized with picocli default values: commands are bold,
11801 * options and parameters use a yellow foreground, and option parameters use italic.
11802 * @param ansi whether the usage help message should contain ANSI escape codes or not
11803 * @return a new default color scheme
11804 */
11805 public static ColorScheme defaultColorScheme(Ansi ansi) {
11806 return new ColorScheme(ansi)
11807 .commands(Style.bold)
11808 .options(Style.fg_yellow)
11809 .parameters(Style.fg_yellow)
11810 .optionParams(Style.italic);
11811 }
11812
11813 /** Provides methods and inner classes to support using ANSI escape codes in usage help messages. */
11814 public enum Ansi {
11815 /** Only emit ANSI escape codes if the platform supports it and system property {@code "picocli.ansi"}
11816 * is not set to any value other than {@code "true"} (case insensitive). */
11817 AUTO,
11818 /** Forced ON: always emit ANSI escape code regardless of the platform. */
11819 ON,
11820 /** Forced OFF: never emit ANSI escape code regardless of the platform. */
11821 OFF;
11822 static Text EMPTY_TEXT = OFF.new Text(0);
11823
11824 static Boolean tty;
11825 static boolean isTTY() {
11826 if (tty == null) { tty = calcTTY(); }
11827 return tty;
11828 }
11829 static final boolean isWindows() { return System.getProperty("os.name").startsWith("Windows"); }
11830 static final boolean isXterm() { return System.getenv("TERM") != null && System.getenv("TERM").startsWith("xterm"); }
11831 // null on Windows unless on Cygwin or MSYS
11832 static final boolean hasOsType() { return System.getenv("OSTYPE") != null; }
11833
11834 // see Jan Niklas Hasse's https://bixense.com/clicolors/ proposal
11835 // https://conemu.github.io/en/AnsiEscapeCodes.html#Environment_variable
11836 static final boolean hintDisabled() { return "0".equals(System.getenv("CLICOLOR"))
11837 || "OFF".equals(System.getenv("ConEmuANSI")); }
11838
11839 /** https://github.com/adoxa/ansicon/blob/master/readme.txt,
11840 * Jan Niklas Hasse's https://bixense.com/clicolors/ proposal,
11841 * https://conemu.github.io/en/AnsiEscapeCodes.html#Environment_variable */
11842 static final boolean hintEnabled() { return System.getenv("ANSICON") != null
11843 || "1".equals(System.getenv("CLICOLOR"))
11844 || "ON".equals(System.getenv("ConEmuANSI")); }
11845 /** https://no-color.org/ */
11846 static final boolean forceDisabled() { return System.getenv("NO_COLOR") != null; }
11847
11848 /** Jan Niklas Hasse's https://bixense.com/clicolors/ proposal */
11849 static final boolean forceEnabled() { return System.getenv("CLICOLOR_FORCE") != null
11850 && !"0".equals(System.getenv("CLICOLOR_FORCE"));}
11851 /** http://stackoverflow.com/questions/1403772/how-can-i-check-if-a-java-programs-input-output-streams-are-connected-to-a-term */
11852 static boolean calcTTY() {
11853 try { return System.class.getDeclaredMethod("console").invoke(null) != null; }
11854 catch (Throwable reflectionFailed) { return true; }
11855 }
11856 /** Cygwin and MSYS use pseudo-tty and console is always null... */
11857 static boolean isPseudoTTY() { return isWindows() && (isXterm() || hasOsType()); }
11858
11859 static boolean ansiPossible() {
11860 if (forceDisabled()) { return false; }
11861 if (forceEnabled()) { return true; }
11862 if (isWindows() && isJansiConsoleInstalled()) { return true; } // #630 JVM crash loading jansi.AnsiConsole on Linux
11863 if (hintDisabled()) { return false; }
11864 if (!isTTY() && !isPseudoTTY()) { return false; }
11865 return hintEnabled() || !isWindows() || isXterm() || hasOsType();
11866 }
11867 static boolean isJansiConsoleInstalled() {
11868 try {
11869 Class<?> ansiConsole = Class.forName("org.fusesource.jansi.AnsiConsole");
11870 Field out = ansiConsole.getField("out");
11871 return out.get(null) == System.out;
11872 } catch (Exception reflectionFailed) {
11873 return false;
11874 }
11875 }
11876
11877 /** Returns {@code true} if ANSI escape codes should be emitted, {@code false} otherwise.
11878 * @return ON: {@code true}, OFF: {@code false}, AUTO: if system property {@code "picocli.ansi"} is
11879 * defined then return its boolean value, otherwise return whether the platform supports ANSI escape codes */
11880 public boolean enabled() {
11881 if (this == ON) { return true; }
11882 if (this == OFF) { return false; }
11883 String ansi = System.getProperty("picocli.ansi");
11884 boolean auto = ansi == null || "AUTO".equalsIgnoreCase(ansi);
11885 return auto ? ansiPossible() : Boolean.getBoolean("picocli.ansi");
11886 }
11887 /**
11888 * Returns a new Text object for this Ansi mode, encapsulating the specified string
11889 * which may contain markup like {@code @|bg(red),white,underline some text|@}.
11890 * <p>
11891 * Calling {@code toString()} on the returned Text will either include ANSI escape codes
11892 * (if this Ansi mode is ON), or suppress ANSI escape codes (if this Ansi mode is OFF).
11893 * <p>
11894 * Equivalent to {@code this.new Text(stringWithMarkup)}.
11895 * @since 3.4 */
11896 public Text text(String stringWithMarkup) { return this.new Text(stringWithMarkup); }
11897
11898 /**
11899 * Returns a String where any markup like
11900 * {@code @|bg(red),white,underline some text|@} is converted to ANSI escape codes
11901 * if this Ansi is ON, or suppressed if this Ansi is OFF.
11902 * <p>
11903 * Equivalent to {@code this.new Text(stringWithMarkup).toString()}.
11904 * @since 3.4 */
11905 public String string(String stringWithMarkup) { return this.new Text(stringWithMarkup).toString(); }
11906
11907 /** Returns Ansi.ON if the specified {@code enabled} flag is true, Ansi.OFF otherwise.
11908 * @since 3.4 */
11909 public static Ansi valueOf(boolean enabled) {return enabled ? ON : OFF; }
11910
11911 /** Defines the interface for an ANSI escape sequence. */
11912 public interface IStyle {
11913
11914 /** The Control Sequence Introducer (CSI) escape sequence {@value}. */
11915 String CSI = "\u001B[";
11916
11917 /** Returns the ANSI escape code for turning this style on.
11918 * @return the ANSI escape code for turning this style on */
11919 String on();
11920
11921 /** Returns the ANSI escape code for turning this style off.
11922 * @return the ANSI escape code for turning this style off */
11923 String off();
11924 }
11925
11926 /**
11927 * A set of pre-defined ANSI escape code styles and colors, and a set of convenience methods for parsing
11928 * text with embedded markup style names, as well as convenience methods for converting
11929 * styles to strings with embedded escape codes.
11930 */
11931 public enum Style implements IStyle {
11932 reset(0, 0), bold(1, 21), faint(2, 22), italic(3, 23), underline(4, 24), blink(5, 25), reverse(7, 27),
11933 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),
11934 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),
11935 ;
11936 private final int startCode;
11937 private final int endCode;
11938
11939 Style(int startCode, int endCode) {this.startCode = startCode; this.endCode = endCode; }
11940 public String on() { return CSI + startCode + "m"; }
11941 public String off() { return CSI + endCode + "m"; }
11942
11943 /** Returns the concatenated ANSI escape codes for turning all specified styles on.
11944 * @param styles the styles to generate ANSI escape codes for
11945 * @return the concatenated ANSI escape codes for turning all specified styles on */
11946 public static String on(IStyle... styles) {
11947 StringBuilder result = new StringBuilder();
11948 for (IStyle style : styles) {
11949 result.append(style.on());
11950 }
11951 return result.toString();
11952 }
11953 /** Returns the concatenated ANSI escape codes for turning all specified styles off.
11954 * @param styles the styles to generate ANSI escape codes for
11955 * @return the concatenated ANSI escape codes for turning all specified styles off */
11956 public static String off(IStyle... styles) {
11957 StringBuilder result = new StringBuilder();
11958 for (IStyle style : styles) {
11959 result.append(style.off());
11960 }
11961 return result.toString();
11962 }
11963 /** Parses the specified style markup and returns the associated style.
11964 * The markup may be one of the Style enum value names, or it may be one of the Style enum value
11965 * names when {@code "fg_"} is prepended, or it may be one of the indexed colors in the 256 color palette.
11966 * @param str the case-insensitive style markup to convert, e.g. {@code "blue"} or {@code "fg_blue"},
11967 * or {@code "46"} (indexed color) or {@code "0;5;0"} (RGB components of an indexed color)
11968 * @return the IStyle for the specified converter
11969 */
11970 public static IStyle fg(String str) {
11971 try { return Style.valueOf(str.toLowerCase(ENGLISH)); } catch (Exception ignored) {}
11972 try { return Style.valueOf("fg_" + str.toLowerCase(ENGLISH)); } catch (Exception ignored) {}
11973 return new Palette256Color(true, str);
11974 }
11975 /** Parses the specified style markup and returns the associated style.
11976 * The markup may be one of the Style enum value names, or it may be one of the Style enum value
11977 * names when {@code "bg_"} is prepended, or it may be one of the indexed colors in the 256 color palette.
11978 * @param str the case-insensitive style markup to convert, e.g. {@code "blue"} or {@code "bg_blue"},
11979 * or {@code "46"} (indexed color) or {@code "0;5;0"} (RGB components of an indexed color)
11980 * @return the IStyle for the specified converter
11981 */
11982 public static IStyle bg(String str) {
11983 try { return Style.valueOf(str.toLowerCase(ENGLISH)); } catch (Exception ignored) {}
11984 try { return Style.valueOf("bg_" + str.toLowerCase(ENGLISH)); } catch (Exception ignored) {}
11985 return new Palette256Color(false, str);
11986 }
11987 /** Parses the specified comma-separated sequence of style descriptors and returns the associated
11988 * styles. For each markup, strings starting with {@code "bg("} are delegated to
11989 * {@link #bg(String)}, others are delegated to {@link #bg(String)}.
11990 * @param commaSeparatedCodes one or more descriptors, e.g. {@code "bg(blue),underline,red"}
11991 * @return an array with all styles for the specified descriptors
11992 */
11993 public static IStyle[] parse(String commaSeparatedCodes) {
11994 String[] codes = commaSeparatedCodes.split(",");
11995 IStyle[] styles = new IStyle[codes.length];
11996 for(int i = 0; i < codes.length; ++i) {
11997 if (codes[i].toLowerCase(ENGLISH).startsWith("fg(")) {
11998 int end = codes[i].indexOf(')');
11999 styles[i] = Style.fg(codes[i].substring(3, end < 0 ? codes[i].length() : end));
12000 } else if (codes[i].toLowerCase(ENGLISH).startsWith("bg(")) {
12001 int end = codes[i].indexOf(')');
12002 styles[i] = Style.bg(codes[i].substring(3, end < 0 ? codes[i].length() : end));
12003 } else {
12004 styles[i] = Style.fg(codes[i]);
12005 }
12006 }
12007 return styles;
12008 }
12009 }
12010
12011 /** Defines a palette map of 216 colors: 6 * 6 * 6 cube (216 colors):
12012 * 16 + 36 * r + 6 * g + b (0 <= r, g, b <= 5). */
12013 static class Palette256Color implements IStyle {
12014 private final int fgbg;
12015 private final int color;
12016
12017 Palette256Color(boolean foreground, String color) {
12018 this.fgbg = foreground ? 38 : 48;
12019 String[] rgb = color.split(";");
12020 if (rgb.length == 3) {
12021 this.color = 16 + 36 * Integer.decode(rgb[0]) + 6 * Integer.decode(rgb[1]) + Integer.decode(rgb[2]);
12022 } else {
12023 this.color = Integer.decode(color);
12024 }
12025 }
12026 public String on() { return String.format(CSI + "%d;5;%dm", fgbg, color); }
12027 public String off() { return CSI + (fgbg + 1) + "m"; }
12028 }
12029 private static class StyledSection {
12030 int startIndex, length;
12031 String startStyles, endStyles;
12032 StyledSection(int start, int len, String style1, String style2) {
12033 startIndex = start; length = len; startStyles = style1; endStyles = style2;
12034 }
12035 StyledSection withStartIndex(int newStart) {
12036 return new StyledSection(newStart, length, startStyles, endStyles);
12037 }
12038 }
12039
12040 /**
12041 * Returns a new Text object where all the specified styles are applied to the full length of the
12042 * specified plain text.
12043 * @param plainText the string to apply all styles to. Must not contain markup!
12044 * @param styles the styles to apply to the full plain text
12045 * @return a new Text object
12046 */
12047 public Text apply(String plainText, List<IStyle> styles) {
12048 if (plainText.length() == 0) { return new Text(0); }
12049 Text result = new Text(plainText.length());
12050 IStyle[] all = styles.toArray(new IStyle[styles.size()]);
12051 result.sections.add(new StyledSection(
12052 0, plainText.length(), Style.on(all), Style.off(reverse(all)) + Style.reset.off()));
12053 result.plain.append(plainText);
12054 result.length = result.plain.length();
12055 return result;
12056 }
12057
12058 private static <T> T[] reverse(T[] all) {
12059 for (int i = 0; i < all.length / 2; i++) {
12060 T temp = all[i];
12061 all[i] = all[all.length - i - 1];
12062 all[all.length - i - 1] = temp;
12063 }
12064 return all;
12065 }
12066 /** Encapsulates rich text with styles and colors. Text objects may be constructed with Strings containing
12067 * markup like {@code @|bg(red),white,underline some text|@}, and this class converts the markup to ANSI
12068 * escape codes.
12069 * <p>
12070 * Internally keeps both an enriched and a plain text representation to allow layout components to calculate
12071 * text width while remaining unaware of the embedded ANSI escape codes.</p> */
12072 public class Text implements Cloneable {
12073 private final int maxLength;
12074 private int from;
12075 private int length;
12076 private StringBuilder plain = new StringBuilder();
12077 private List<StyledSection> sections = new ArrayList<StyledSection>();
12078
12079 /** Constructs a Text with the specified max length (for use in a TextTable Column).
12080 * @param maxLength max length of this text */
12081 public Text(int maxLength) { this.maxLength = maxLength; }
12082
12083 /** Copy constructor.
12084 * @since 3.9 */
12085 public Text(Text other) {
12086 this.maxLength = other.maxLength;
12087 this.from = other.from;
12088 this.length = other.length;
12089 this.plain = new StringBuilder(other.plain);
12090 this.sections = new ArrayList<StyledSection>(other.sections);
12091 }
12092 /**
12093 * Constructs a Text with the specified String, which may contain markup like
12094 * {@code @|bg(red),white,underline some text|@}.
12095 * @param input the string with markup to parse
12096 */
12097 public Text(String input) {
12098 maxLength = -1;
12099 plain.setLength(0);
12100 int i = 0;
12101
12102 while (true) {
12103 int j = input.indexOf("@|", i);
12104 if (j == -1) {
12105 if (i == 0) {
12106 plain.append(input);
12107 length = plain.length();
12108 return;
12109 }
12110 plain.append(input.substring(i, input.length()));
12111 length = plain.length();
12112 return;
12113 }
12114 plain.append(input.substring(i, j));
12115 int k = input.indexOf("|@", j);
12116 if (k == -1) {
12117 plain.append(input);
12118 length = plain.length();
12119 return;
12120 }
12121
12122 j += 2;
12123 String spec = input.substring(j, k);
12124 String[] items = spec.split(" ", 2);
12125 if (items.length == 1) {
12126 plain.append(input);
12127 length = plain.length();
12128 return;
12129 }
12130
12131 IStyle[] styles = Style.parse(items[0]);
12132 addStyledSection(plain.length(), items[1].length(),
12133 Style.on(styles), Style.off(reverse(styles)) + Style.reset.off());
12134 plain.append(items[1]);
12135 i = k + 2;
12136 }
12137 }
12138 private void addStyledSection(int start, int length, String startStyle, String endStyle) {
12139 sections.add(new StyledSection(start, length, startStyle, endStyle));
12140 }
12141 public Object clone() { return new Text(this); }
12142
12143 public Text[] splitLines() {
12144 List<Text> result = new ArrayList<Text>();
12145 int start = 0, end = 0;
12146 for (int i = 0; i < plain.length(); i++, end = i) {
12147 char c = plain.charAt(i);
12148 boolean eol = c == '\n';
12149 if (c == '\r' && i + 1 < plain.length() && plain.charAt(i + 1) == '\n') { eol = true; i++; } // \r\n
12150 eol |= c == '\r';
12151 if (eol) {
12152 result.add(this.substring(start, end));
12153 start = i + 1;
12154 }
12155 }
12156 // add remainder (may be empty string)
12157 result.add(this.substring(start, plain.length()));
12158 return result.toArray(new Text[result.size()]);
12159 }
12160
12161 /** Returns a new {@code Text} instance that is a substring of this Text. Does not modify this instance!
12162 * @param start index in the plain text where to start the substring
12163 * @return a new Text instance that is a substring of this Text */
12164 public Text substring(int start) {
12165 return substring(start, length);
12166 }
12167
12168 /** Returns a new {@code Text} instance that is a substring of this Text. Does not modify this instance!
12169 * @param start index in the plain text where to start the substring
12170 * @param end index in the plain text where to end the substring
12171 * @return a new Text instance that is a substring of this Text */
12172 public Text substring(int start, int end) {
12173 Text result = (Text) clone();
12174 result.from = from + start;
12175 result.length = end - start;
12176 return result;
12177 }
12178 /** @deprecated use {@link #concat(String)} instead */
12179 @Deprecated public Text append(String string) { return concat(string); }
12180 /** @deprecated use {@link #concat(Text)} instead */
12181 @Deprecated public Text append(Text text) { return concat(text); }
12182
12183 /** Returns a copy of this {@code Text} instance with the specified text concatenated to the end. Does not modify this instance!
12184 * @param string the text to concatenate to the end of this Text
12185 * @return a new Text instance
12186 * @since 3.0 */
12187 public Text concat(String string) { return concat(new Text(string)); }
12188
12189 /** Returns a copy of this {@code Text} instance with the specified text concatenated to the end. Does not modify this instance!
12190 * @param other the text to concatenate to the end of this Text
12191 * @return a new Text instance
12192 * @since 3.0 */
12193 public Text concat(Text other) {
12194 Text result = (Text) clone();
12195 result.plain = new StringBuilder(plain.toString().substring(from, from + length));
12196 result.from = 0;
12197 result.sections = new ArrayList<StyledSection>();
12198 for (StyledSection section : sections) {
12199 result.sections.add(section.withStartIndex(section.startIndex - from));
12200 }
12201 result.plain.append(other.plain.toString().substring(other.from, other.from + other.length));
12202 for (StyledSection section : other.sections) {
12203 int index = result.length + section.startIndex - other.from;
12204 result.sections.add(section.withStartIndex(index));
12205 }
12206 result.length = result.plain.length();
12207 return result;
12208 }
12209
12210 /**
12211 * Copies the specified substring of this Text into the specified destination, preserving the markup.
12212 * @param from start of the substring
12213 * @param length length of the substring
12214 * @param destination destination Text to modify
12215 * @param offset indentation (padding)
12216 */
12217 public void getStyledChars(int from, int length, Text destination, int offset) {
12218 if (destination.length < offset) {
12219 for (int i = destination.length; i < offset; i++) {
12220 destination.plain.append(' ');
12221 }
12222 destination.length = offset;
12223 }
12224 for (StyledSection section : sections) {
12225 destination.sections.add(section.withStartIndex(section.startIndex - from + destination.length));
12226 }
12227 destination.plain.append(plain.toString().substring(from, from + length));
12228 destination.length = destination.plain.length();
12229 }
12230 /** Returns the plain text without any formatting.
12231 * @return the plain text without any formatting */
12232 public String plainString() { return plain.toString().substring(from, from + length); }
12233
12234 public boolean equals(Object obj) { return toString().equals(String.valueOf(obj)); }
12235 public int hashCode() { return toString().hashCode(); }
12236
12237 /** Returns a String representation of the text with ANSI escape codes embedded, unless ANSI is
12238 * {@linkplain Ansi#enabled()} not enabled}, in which case the plain text is returned.
12239 * @return a String representation of the text with ANSI escape codes embedded (if enabled) */
12240 public String toString() {
12241 if (!Ansi.this.enabled()) {
12242 return plain.toString().substring(from, from + length);
12243 }
12244 if (length == 0) { return ""; }
12245 StringBuilder sb = new StringBuilder(plain.length() + 20 * sections.size());
12246 StyledSection current = null;
12247 int end = Math.min(from + length, plain.length());
12248 for (int i = from; i < end; i++) {
12249 StyledSection section = findSectionContaining(i);
12250 if (section != current) {
12251 if (current != null) { sb.append(current.endStyles); }
12252 if (section != null) { sb.append(section.startStyles); }
12253 current = section;
12254 }
12255 sb.append(plain.charAt(i));
12256 }
12257 if (current != null) { sb.append(current.endStyles); }
12258 return sb.toString();
12259 }
12260
12261 private StyledSection findSectionContaining(int index) {
12262 for (StyledSection section : sections) {
12263 if (index >= section.startIndex && index < section.startIndex + section.length) {
12264 return section;
12265 }
12266 }
12267 return null;
12268 }
12269 }
12270 }
12271 }
12272
12273 /**
12274 * Utility class providing some defensive coding convenience methods.
12275 */
12276 private static final class Assert {
12277 /**
12278 * Throws a NullPointerException if the specified object is null.
12279 * @param object the object to verify
12280 * @param description error message
12281 * @param <T> type of the object to check
12282 * @return the verified object
12283 */
12284 static <T> T notNull(T object, String description) {
12285 if (object == null) {
12286 throw new NullPointerException(description);
12287 }
12288 return object;
12289 }
12290 static boolean equals(Object obj1, Object obj2) { return obj1 == null ? obj2 == null : obj1.equals(obj2); }
12291 static int hashCode(Object obj) {return obj == null ? 0 : obj.hashCode(); }
12292 static int hashCode(boolean bool) {return bool ? 1 : 0; }
12293 static void assertTrue(boolean condition, String message) {
12294 if (!condition) throw new IllegalStateException(message);
12295 }
12296 private Assert() {} // private constructor: never instantiate
12297 }
12298 private enum TraceLevel { OFF, WARN, INFO, DEBUG;
12299 public boolean isEnabled(TraceLevel other) { return ordinal() >= other.ordinal(); }
12300 private void print(Tracer tracer, String msg, Object... params) {
12301 if (tracer.level.isEnabled(this)) { tracer.stream.printf(prefix(msg), params); }
12302 }
12303 private String prefix(String msg) { return "[picocli " + this + "] " + msg; }
12304 static TraceLevel lookup(String key) { return key == null ? WARN : empty(key) || "true".equalsIgnoreCase(key) ? INFO : valueOf(key); }
12305 }
12306 static class Tracer {
12307 TraceLevel level = TraceLevel.lookup(System.getProperty("picocli.trace"));
12308 PrintStream stream = System.err;
12309 void warn (String msg, Object... params) { TraceLevel.WARN.print(this, msg, params); }
12310 void info (String msg, Object... params) { TraceLevel.INFO.print(this, msg, params); }
12311 void debug(String msg, Object... params) { TraceLevel.DEBUG.print(this, msg, params); }
12312 boolean isWarn() { return level.isEnabled(TraceLevel.WARN); }
12313 boolean isInfo() { return level.isEnabled(TraceLevel.INFO); }
12314 boolean isDebug() { return level.isEnabled(TraceLevel.DEBUG); }
12315 }
12316 /**
12317 * Uses cosine similarity to find matches from a candidate set for a specified input.
12318 * Based on code from http://www.nearinfinity.com/blogs/seth_schroeder/groovy_cosine_similarity_in_grails.html
12319 *
12320 * @author Burt Beckwith
12321 */
12322 private static class CosineSimilarity {
12323 static List<String> mostSimilar(String pattern, Iterable<String> candidates) { return mostSimilar(pattern, candidates, 0); }
12324 static List<String> mostSimilar(String pattern, Iterable<String> candidates, double threshold) {
12325 pattern = pattern.toLowerCase();
12326 SortedMap<Double, String> sorted = new TreeMap<Double, String>();
12327 for (String candidate : candidates) {
12328 double score = similarity(pattern, candidate.toLowerCase(), 2);
12329 if (score > threshold) { sorted.put(score, candidate); }
12330 }
12331 return reverseList(new ArrayList<String>(sorted.values()));
12332 }
12333
12334 private static double similarity(String sequence1, String sequence2, int degree) {
12335 Map<String, Integer> m1 = countNgramFrequency(sequence1, degree);
12336 Map<String, Integer> m2 = countNgramFrequency(sequence2, degree);
12337 return dotProduct(m1, m2) / Math.sqrt(dotProduct(m1, m1) * dotProduct(m2, m2));
12338 }
12339
12340 private static Map<String, Integer> countNgramFrequency(String sequence, int degree) {
12341 Map<String, Integer> m = new HashMap<String, Integer>();
12342 for (int i = 0; i + degree <= sequence.length(); i++) {
12343 String gram = sequence.substring(i, i + degree);
12344 m.put(gram, 1 + (m.containsKey(gram) ? m.get(gram) : 0));
12345 }
12346 return m;
12347 }
12348
12349 private static double dotProduct(Map<String, Integer> m1, Map<String, Integer> m2) {
12350 double result = 0;
12351 for (String key : m1.keySet()) { result += m1.get(key) * (m2.containsKey(key) ? m2.get(key) : 0); }
12352 return result;
12353 }
12354 }
12355 /** Base class of all exceptions thrown by {@code picocli.CommandLine}.
12356 * <h2>Class Diagram of the Picocli Exceptions</h2>
12357 * <p>
12358 * <img src="doc-files/class-diagram-exceptions.png" alt="Class Diagram of the Picocli Exceptions">
12359 * </p>
12360 * @since 2.0 */
12361 public static class PicocliException extends RuntimeException {
12362 private static final long serialVersionUID = -2574128880125050818L;
12363 public PicocliException(String msg) { super(msg); }
12364 public PicocliException(String msg, Throwable t) { super(msg, t); }
12365 }
12366 /** Exception indicating a problem during {@code CommandLine} initialization.
12367 * @since 2.0 */
12368 public static class InitializationException extends PicocliException {
12369 private static final long serialVersionUID = 8423014001666638895L;
12370 public InitializationException(String msg) { super(msg); }
12371 public InitializationException(String msg, Exception ex) { super(msg, ex); }
12372 }
12373 /** Exception indicating a problem while invoking a command or subcommand.
12374 * @since 2.0 */
12375 public static class ExecutionException extends PicocliException {
12376 private static final long serialVersionUID = 7764539594267007998L;
12377 private final CommandLine commandLine;
12378 public ExecutionException(CommandLine commandLine, String msg) {
12379 super(msg);
12380 this.commandLine = Assert.notNull(commandLine, "commandLine");
12381 }
12382 public ExecutionException(CommandLine commandLine, String msg, Throwable t) {
12383 super(msg, t);
12384 this.commandLine = Assert.notNull(commandLine, "commandLine");
12385 }
12386 /** Returns the {@code CommandLine} object for the (sub)command that could not be invoked.
12387 * @return the {@code CommandLine} object for the (sub)command where invocation failed.
12388 */
12389 public CommandLine getCommandLine() { return commandLine; }
12390 }
12391
12392 /** Exception thrown by {@link ITypeConverter} implementations to indicate a String could not be converted. */
12393 public static class TypeConversionException extends PicocliException {
12394 private static final long serialVersionUID = 4251973913816346114L;
12395 public TypeConversionException(String msg) { super(msg); }
12396 }
12397 /** Exception indicating something went wrong while parsing command line options. */
12398 public static class ParameterException extends PicocliException {
12399 private static final long serialVersionUID = 1477112829129763139L;
12400 private final CommandLine commandLine;
12401 private ArgSpec argSpec = null;
12402 private String value = null;
12403
12404 /** Constructs a new ParameterException with the specified CommandLine and error message.
12405 * @param commandLine the command or subcommand whose input was invalid
12406 * @param msg describes the problem
12407 * @since 2.0 */
12408 public ParameterException(CommandLine commandLine, String msg) {
12409 super(msg);
12410 this.commandLine = Assert.notNull(commandLine, "commandLine");
12411 }
12412
12413 /** Constructs a new ParameterException with the specified CommandLine and error message.
12414 * @param commandLine the command or subcommand whose input was invalid
12415 * @param msg describes the problem
12416 * @param t the throwable that caused this ParameterException
12417 * @since 2.0 */
12418 public ParameterException(CommandLine commandLine, String msg, Throwable t) {
12419 super(msg, t);
12420 this.commandLine = Assert.notNull(commandLine, "commandLine");
12421 }
12422
12423 /** Constructs a new ParameterException with the specified CommandLine and error message.
12424 * @param commandLine the command or subcommand whose input was invalid
12425 * @param msg describes the problem
12426 * @param t the throwable that caused this ParameterException
12427 * @param argSpec the argSpec that caused this ParameterException
12428 * @param value the value that caused this ParameterException
12429 * @since 3.2 */
12430 public ParameterException(CommandLine commandLine, String msg, Throwable t, ArgSpec argSpec, String value) {
12431 super(msg, t);
12432 this.commandLine = Assert.notNull(commandLine, "commandLine");
12433 if (argSpec == null && value == null) { throw new IllegalArgumentException("ArgSpec and value cannot both be null"); }
12434 this.argSpec = argSpec;
12435 this.value = value;
12436 }
12437
12438 /** Constructs a new ParameterException with the specified CommandLine and error message.
12439 * @param commandLine the command or subcommand whose input was invalid
12440 * @param msg describes the problem
12441 * @param argSpec the argSpec that caused this ParameterException
12442 * @param value the value that caused this ParameterException
12443 * @since 3.2 */
12444 public ParameterException(CommandLine commandLine, String msg, ArgSpec argSpec, String value) {
12445 super(msg);
12446 this.commandLine = Assert.notNull(commandLine, "commandLine");
12447 if (argSpec == null && value == null) { throw new IllegalArgumentException("ArgSpec and value cannot both be null"); }
12448 this.argSpec = argSpec;
12449 this.value = value;
12450 }
12451
12452
12453 /** Returns the {@code CommandLine} object for the (sub)command whose input could not be parsed.
12454 * @return the {@code CommandLine} object for the (sub)command where parsing failed.
12455 * @since 2.0
12456 */
12457 public CommandLine getCommandLine() { return commandLine; }
12458
12459 /** Returns the {@code ArgSpec} object for the (sub)command whose input could not be parsed.
12460 * @return the {@code ArgSpec} object for the (sub)command where parsing failed.
12461 * @since 3.2
12462 */
12463 public ArgSpec getArgSpec() { return argSpec; }
12464
12465 /** Returns the {@code String} value for the (sub)command whose input could not be parsed.
12466 * @return the {@code String} value for the (sub)command where parsing failed.
12467 * @since 3.2
12468 */
12469 public String getValue() { return value; }
12470
12471 private static ParameterException create(CommandLine cmd, Exception ex, String arg, int i, String[] args) {
12472 String msg = ex.getClass().getSimpleName() + ": " + ex.getLocalizedMessage()
12473 + " while processing argument at or before arg[" + i + "] '" + arg + "' in " + Arrays.toString(args) + ": " + ex.toString();
12474 return new ParameterException(cmd, msg, ex, null, arg);
12475 }
12476 }
12477 /**
12478 * Exception indicating that a required parameter was not specified.
12479 */
12480 public static class MissingParameterException extends ParameterException {
12481 private static final long serialVersionUID = 5075678535706338753L;
12482 private final List<ArgSpec> missing;
12483 public MissingParameterException(CommandLine commandLine, ArgSpec missing, String msg) { this(commandLine, Arrays.asList(missing), msg); }
12484 public MissingParameterException(CommandLine commandLine, Collection<ArgSpec> missing, String msg) {
12485 super(commandLine, msg);
12486 this.missing = Collections.unmodifiableList(new ArrayList<ArgSpec>(missing));
12487 }
12488 public List<ArgSpec> getMissing() { return missing; }
12489 private static MissingParameterException create(CommandLine cmd, Collection<ArgSpec> missing, String separator) {
12490 if (missing.size() == 1) {
12491 return new MissingParameterException(cmd, missing, "Missing required option '"
12492 + ArgSpec.describe(missing.iterator().next(), separator) + "'");
12493 }
12494 List<String> names = new ArrayList<String>(missing.size());
12495 for (ArgSpec argSpec : missing) {
12496 names.add(ArgSpec.describe(argSpec, separator));
12497 }
12498 return new MissingParameterException(cmd, missing, "Missing required options " + names.toString());
12499 }
12500 }
12501 /** Exception indicating that the user input included multiple arguments from a mutually exclusive group.
12502 * @since 4.0 */
12503 public static class MutuallyExclusiveArgsException extends ParameterException {
12504 private static final long serialVersionUID = -5557715106221420986L;
12505 public MutuallyExclusiveArgsException(CommandLine commandLine, String msg) { super(commandLine, msg); }
12506 }
12507
12508 /** Exception indicating that multiple named elements have incorrectly used the same name.
12509 * @since 4.0 */
12510 public static class DuplicateNameException extends InitializationException {
12511 private static final long serialVersionUID = -4126747467955626054L;
12512 public DuplicateNameException(String msg) { super(msg); }
12513 }
12514 /**
12515 * Exception indicating that multiple fields have been annotated with the same Option name.
12516 */
12517 public static class DuplicateOptionAnnotationsException extends DuplicateNameException {
12518 private static final long serialVersionUID = -3355128012575075641L;
12519 public DuplicateOptionAnnotationsException(String msg) { super(msg); }
12520
12521 private static DuplicateOptionAnnotationsException create(String name, ArgSpec argSpec1, ArgSpec argSpec2) {
12522 return new DuplicateOptionAnnotationsException("Option name '" + name + "' is used by both " +
12523 argSpec1.toString() + " and " + argSpec2.toString());
12524 }
12525 }
12526 /** Exception indicating that there was a gap in the indices of the fields annotated with {@link Parameters}. */
12527 public static class ParameterIndexGapException extends InitializationException {
12528 private static final long serialVersionUID = -1520981133257618319L;
12529 public ParameterIndexGapException(String msg) { super(msg); }
12530 }
12531 /** Exception indicating that a command line argument could not be mapped to any of the fields annotated with
12532 * {@link Option} or {@link Parameters}. */
12533 public static class UnmatchedArgumentException extends ParameterException {
12534 private static final long serialVersionUID = -8700426380701452440L;
12535 private List<String> unmatched = Collections.<String>emptyList();
12536 public UnmatchedArgumentException(CommandLine commandLine, String msg) { super(commandLine, msg); }
12537 public UnmatchedArgumentException(CommandLine commandLine, Stack<String> args) { this(commandLine, new ArrayList<String>(reverse(args))); }
12538 public UnmatchedArgumentException(CommandLine commandLine, List<String> args) {
12539 this(commandLine, describe(args, commandLine) + (args.size() == 1 ? ": " : "s: ") + str(args));
12540 unmatched = args == null ? Collections.<String>emptyList() : args;
12541 }
12542 /** Returns {@code true} and prints suggested solutions to the specified stream if such solutions exist, otherwise returns {@code false}.
12543 * @since 3.3.0 */
12544 public static boolean printSuggestions(ParameterException ex, PrintStream out) {
12545 return ex instanceof UnmatchedArgumentException && ((UnmatchedArgumentException) ex).printSuggestions(out);
12546 }
12547 /** Returns the unmatched command line arguments.
12548 * @since 3.3.0 */
12549 public List<String> getUnmatched() { return stripErrorMessage(unmatched); }
12550 static List<String> stripErrorMessage(List<String> unmatched) {
12551 List<String> result = new ArrayList<String>();
12552 for (String s : unmatched) {
12553 if (s == null) { result.add(null); }
12554 int pos = s.indexOf(" (while processing option:");
12555 result.add(pos < 0 ? s : s.substring(0, pos));
12556 }
12557 return Collections.unmodifiableList(result);
12558 }
12559 /** Returns {@code true} if the first unmatched command line arguments resembles an option, {@code false} otherwise.
12560 * @since 3.3.0 */
12561 public boolean isUnknownOption() { return isUnknownOption(unmatched, getCommandLine()); }
12562 /** Returns {@code true} and prints suggested solutions to the specified stream if such solutions exist, otherwise returns {@code false}.
12563 * @since 3.3.0 */
12564 public boolean printSuggestions(PrintStream out) {
12565 List<String> suggestions = getSuggestions();
12566 if (!suggestions.isEmpty()) {
12567 out.println(isUnknownOption()
12568 ? "Possible solutions: " + str(suggestions)
12569 : "Did you mean: " + str(suggestions).replace(", ", " or ") + "?");
12570 }
12571 return !suggestions.isEmpty();
12572 }
12573 /** Returns suggested solutions if such solutions exist, otherwise returns an empty list.
12574 * @since 3.3.0 */
12575 public List<String> getSuggestions() {
12576 if (unmatched == null || unmatched.isEmpty()) { return Collections.emptyList(); }
12577 String arg = unmatched.get(0);
12578 String stripped = CommandSpec.stripPrefix(arg);
12579 CommandSpec spec = getCommandLine().getCommandSpec();
12580 if (spec.resemblesOption(arg, null)) {
12581 return spec.findOptionNamesWithPrefix(stripped.substring(0, Math.min(2, stripped.length())));
12582 } else if (!spec.subcommands().isEmpty()) {
12583 List<String> mostSimilar = CosineSimilarity.mostSimilar(arg, spec.subcommands().keySet());
12584 return mostSimilar.subList(0, Math.min(3, mostSimilar.size()));
12585 }
12586 return Collections.emptyList();
12587 }
12588 private static boolean isUnknownOption(List<String> unmatch, CommandLine cmd) {
12589 return unmatch != null && !unmatch.isEmpty() && cmd.getCommandSpec().resemblesOption(unmatch.get(0), null);
12590 }
12591 private static String describe(List<String> unmatch, CommandLine cmd) {
12592 return isUnknownOption(unmatch, cmd) ? "Unknown option" : "Unmatched argument";
12593 }
12594 static String str(List<String> list) {
12595 String s = list.toString();
12596 return s.substring(0, s.length() - 1).substring(1);
12597 }
12598 }
12599 /** Exception indicating that more values were specified for an option or parameter than its {@link Option#arity() arity} allows. */
12600 public static class MaxValuesExceededException extends ParameterException {
12601 private static final long serialVersionUID = 6536145439570100641L;
12602 public MaxValuesExceededException(CommandLine commandLine, String msg) { super(commandLine, msg); }
12603 }
12604 /** Exception indicating that an option for a single-value option field has been specified multiple times on the command line. */
12605 public static class OverwrittenOptionException extends ParameterException {
12606 private static final long serialVersionUID = 1338029208271055776L;
12607 private final ArgSpec overwrittenArg;
12608 public OverwrittenOptionException(CommandLine commandLine, ArgSpec overwritten, String msg) {
12609 super(commandLine, msg);
12610 overwrittenArg = overwritten;
12611 }
12612 /** Returns the {@link ArgSpec} for the option which was being overwritten.
12613 * @since 3.8 */
12614 public ArgSpec getOverwritten() { return overwrittenArg; }
12615 }
12616 /**
12617 * Exception indicating that an annotated field had a type for which no {@link ITypeConverter} was
12618 * {@linkplain #registerConverter(Class, ITypeConverter) registered}.
12619 */
12620 public static class MissingTypeConverterException extends ParameterException {
12621 private static final long serialVersionUID = -6050931703233083760L;
12622 public MissingTypeConverterException(CommandLine commandLine, String msg) { super(commandLine, msg); }
12623 }
12624}