001 /*
002 * Copyright 2008-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2017 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021 package com.unboundid.util.args;
022
023
024
025 import java.io.BufferedReader;
026 import java.io.File;
027 import java.io.FileReader;
028 import java.io.IOException;
029 import java.io.OutputStream;
030 import java.io.PrintWriter;
031 import java.io.Serializable;
032 import java.util.ArrayList;
033 import java.util.Arrays;
034 import java.util.Collection;
035 import java.util.Collections;
036 import java.util.HashMap;
037 import java.util.Iterator;
038 import java.util.LinkedHashSet;
039 import java.util.LinkedHashMap;
040 import java.util.List;
041 import java.util.Map;
042 import java.util.Set;
043
044 import com.unboundid.util.Debug;
045 import com.unboundid.util.ObjectPair;
046 import com.unboundid.util.ThreadSafety;
047 import com.unboundid.util.ThreadSafetyLevel;
048
049 import static com.unboundid.util.StaticUtils.*;
050 import static com.unboundid.util.Validator.*;
051 import static com.unboundid.util.args.ArgsMessages.*;
052
053
054
055 /**
056 * This class provides an argument parser, which may be used to process command
057 * line arguments provided to Java applications. See the package-level Javadoc
058 * documentation for details regarding the capabilities of the argument parser.
059 */
060 @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
061 public final class ArgumentParser
062 implements Serializable
063 {
064 /**
065 * The name of the system property that can be used to specify the default
066 * properties file that should be used to obtain the default values for
067 * arguments not specified via the command line.
068 */
069 public static final String PROPERTY_DEFAULT_PROPERTIES_FILE_PATH =
070 ArgumentParser.class.getName() + ".propertiesFilePath";
071
072
073
074 /**
075 * The name of an environment variable that can be used to specify the default
076 * properties file that should be used to obtain the default values for
077 * arguments not specified via the command line.
078 */
079 public static final String ENV_DEFAULT_PROPERTIES_FILE_PATH =
080 "UNBOUNDID_TOOL_PROPERTIES_FILE_PATH";
081
082
083
084 /**
085 * The name of the argument used to specify the path to a file to which all
086 * output should be written.
087 */
088 private static final String ARG_NAME_OUTPUT_FILE = "outputFile";
089
090
091
092 /**
093 * The name of the argument used to indicate that output should be written to
094 * both the output file and the console.
095 */
096 private static final String ARG_NAME_TEE_OUTPUT = "teeOutput";
097
098
099
100 /**
101 * The name of the argument used to specify the path to a properties file from
102 * which to obtain the default values for arguments not specified via the
103 * command line.
104 */
105 private static final String ARG_NAME_PROPERTIES_FILE_PATH =
106 "propertiesFilePath";
107
108
109
110 /**
111 * The name of the argument used to specify the path to a file to be generated
112 * with information about the properties that the tool supports.
113 */
114 private static final String ARG_NAME_GENERATE_PROPERTIES_FILE =
115 "generatePropertiesFile";
116
117
118
119 /**
120 * The name of the argument used to indicate that the tool should not use any
121 * properties file to obtain default values for arguments not specified via
122 * the command line.
123 */
124 private static final String ARG_NAME_NO_PROPERTIES_FILE = "noPropertiesFile";
125
126
127
128 /**
129 * The serial version UID for this serializable class.
130 */
131 private static final long serialVersionUID = 3053102992180360269L;
132
133
134
135 // The properties file used to obtain arguments for this tool.
136 private volatile File propertiesFileUsed;
137
138 // The maximum number of trailing arguments allowed to be provided.
139 private final int maxTrailingArgs;
140
141 // The minimum number of trailing arguments allowed to be provided.
142 private final int minTrailingArgs;
143
144 // The set of named arguments associated with this parser, indexed by short
145 // identifier.
146 private final LinkedHashMap<Character,Argument> namedArgsByShortID;
147
148 // The set of named arguments associated with this parser, indexed by long
149 // identifier.
150 private final LinkedHashMap<String,Argument> namedArgsByLongID;
151
152 // The set of subcommands associated with this parser, indexed by name.
153 private final LinkedHashMap<String,SubCommand> subCommandsByName;
154
155 // The full set of named arguments associated with this parser.
156 private final List<Argument> namedArgs;
157
158 // Sets of arguments in which if the key argument is provided, then at least
159 // one of the value arguments must also be provided.
160 private final List<ObjectPair<Argument,Set<Argument>>> dependentArgumentSets;
161
162 // Sets of arguments in which at most one argument in the list is allowed to
163 // be present.
164 private final List<Set<Argument>> exclusiveArgumentSets;
165
166 // Sets of arguments in which at least one argument in the list is required to
167 // be present.
168 private final List<Set<Argument>> requiredArgumentSets;
169
170 // A list of any arguments set from the properties file rather than explicitly
171 // provided on the command line.
172 private final List<String> argumentsSetFromPropertiesFile;
173
174 // The list of trailing arguments provided on the command line.
175 private final List<String> trailingArgs;
176
177 // The full list of subcommands associated with this argument parser.
178 private final List<SubCommand> subCommands;
179
180 // The description for the associated command.
181 private final String commandDescription;
182
183 // The name for the associated command.
184 private final String commandName;
185
186 // The placeholder string for the trailing arguments.
187 private final String trailingArgsPlaceholder;
188
189 // The subcommand with which this argument parser is associated.
190 private volatile SubCommand parentSubCommand;
191
192 // The subcommand that was included in the set of command-line arguments.
193 private volatile SubCommand selectedSubCommand;
194
195
196
197 /**
198 * Creates a new instance of this argument parser with the provided
199 * information. It will not allow unnamed trailing arguments.
200 *
201 * @param commandName The name of the application or utility with
202 * which this argument parser is associated. It
203 * must not be {@code null}.
204 * @param commandDescription A description of the application or utility
205 * with which this argument parser is associated.
206 * It will be included in generated usage
207 * information. It must not be {@code null}.
208 *
209 * @throws ArgumentException If either the command name or command
210 * description is {@code null},
211 */
212 public ArgumentParser(final String commandName,
213 final String commandDescription)
214 throws ArgumentException
215 {
216 this(commandName, commandDescription, 0, null);
217 }
218
219
220
221 /**
222 * Creates a new instance of this argument parser with the provided
223 * information.
224 *
225 * @param commandName The name of the application or utility
226 * with which this argument parser is
227 * associated. It must not be {@code null}.
228 * @param commandDescription A description of the application or
229 * utility with which this argument parser is
230 * associated. It will be included in
231 * generated usage information. It must not
232 * be {@code null}.
233 * @param maxTrailingArgs The maximum number of trailing arguments
234 * that may be provided to this command. A
235 * value of zero indicates that no trailing
236 * arguments will be allowed. A value less
237 * than zero will indicate that there is no
238 * limit on the number of trailing arguments
239 * allowed.
240 * @param trailingArgsPlaceholder A placeholder string that will be included
241 * in usage output to indicate what trailing
242 * arguments may be provided. It must not be
243 * {@code null} if {@code maxTrailingArgs} is
244 * anything other than zero.
245 *
246 * @throws ArgumentException If either the command name or command
247 * description is {@code null}, or if the maximum
248 * number of trailing arguments is non-zero and
249 * the trailing arguments placeholder is
250 * {@code null}.
251 */
252 public ArgumentParser(final String commandName,
253 final String commandDescription,
254 final int maxTrailingArgs,
255 final String trailingArgsPlaceholder)
256 throws ArgumentException
257 {
258 this(commandName, commandDescription, 0, maxTrailingArgs,
259 trailingArgsPlaceholder);
260 }
261
262
263
264 /**
265 * Creates a new instance of this argument parser with the provided
266 * information.
267 *
268 * @param commandName The name of the application or utility
269 * with which this argument parser is
270 * associated. It must not be {@code null}.
271 * @param commandDescription A description of the application or
272 * utility with which this argument parser is
273 * associated. It will be included in
274 * generated usage information. It must not
275 * be {@code null}.
276 * @param minTrailingArgs The minimum number of trailing arguments
277 * that must be provided for this command. A
278 * value of zero indicates that the command
279 * may be invoked without any trailing
280 * arguments.
281 * @param maxTrailingArgs The maximum number of trailing arguments
282 * that may be provided to this command. A
283 * value of zero indicates that no trailing
284 * arguments will be allowed. A value less
285 * than zero will indicate that there is no
286 * limit on the number of trailing arguments
287 * allowed.
288 * @param trailingArgsPlaceholder A placeholder string that will be included
289 * in usage output to indicate what trailing
290 * arguments may be provided. It must not be
291 * {@code null} if {@code maxTrailingArgs} is
292 * anything other than zero.
293 *
294 * @throws ArgumentException If either the command name or command
295 * description is {@code null}, or if the maximum
296 * number of trailing arguments is non-zero and
297 * the trailing arguments placeholder is
298 * {@code null}.
299 */
300 public ArgumentParser(final String commandName,
301 final String commandDescription,
302 final int minTrailingArgs,
303 final int maxTrailingArgs,
304 final String trailingArgsPlaceholder)
305 throws ArgumentException
306 {
307 if (commandName == null)
308 {
309 throw new ArgumentException(ERR_PARSER_COMMAND_NAME_NULL.get());
310 }
311
312 if (commandDescription == null)
313 {
314 throw new ArgumentException(ERR_PARSER_COMMAND_DESCRIPTION_NULL.get());
315 }
316
317 if ((maxTrailingArgs != 0) && (trailingArgsPlaceholder == null))
318 {
319 throw new ArgumentException(
320 ERR_PARSER_TRAILING_ARGS_PLACEHOLDER_NULL.get());
321 }
322
323 this.commandName = commandName;
324 this.commandDescription = commandDescription;
325 this.trailingArgsPlaceholder = trailingArgsPlaceholder;
326
327 if (minTrailingArgs >= 0)
328 {
329 this.minTrailingArgs = minTrailingArgs;
330 }
331 else
332 {
333 this.minTrailingArgs = 0;
334 }
335
336 if (maxTrailingArgs >= 0)
337 {
338 this.maxTrailingArgs = maxTrailingArgs;
339 }
340 else
341 {
342 this.maxTrailingArgs = Integer.MAX_VALUE;
343 }
344
345 if (this.minTrailingArgs > this.maxTrailingArgs)
346 {
347 throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_COUNT_MISMATCH.get(
348 this.minTrailingArgs, this.maxTrailingArgs));
349 }
350
351 namedArgsByShortID = new LinkedHashMap<Character,Argument>();
352 namedArgsByLongID = new LinkedHashMap<String,Argument>();
353 namedArgs = new ArrayList<Argument>();
354 trailingArgs = new ArrayList<String>();
355 dependentArgumentSets = new ArrayList<ObjectPair<Argument,Set<Argument>>>();
356 exclusiveArgumentSets = new ArrayList<Set<Argument>>();
357 requiredArgumentSets = new ArrayList<Set<Argument>>();
358 parentSubCommand = null;
359 selectedSubCommand = null;
360 subCommands = new ArrayList<SubCommand>();
361 subCommandsByName = new LinkedHashMap<String,SubCommand>(10);
362 propertiesFileUsed = null;
363 argumentsSetFromPropertiesFile = new ArrayList<String>();
364 }
365
366
367
368 /**
369 * Creates a new argument parser that is a "clean" copy of the provided source
370 * argument parser.
371 *
372 * @param source The source argument parser to use for this argument
373 * parser.
374 * @param subCommand The subcommand with which this argument parser is to be
375 * associated.
376 */
377 ArgumentParser(final ArgumentParser source, final SubCommand subCommand)
378 {
379 commandName = source.commandName;
380 commandDescription = source.commandDescription;
381 minTrailingArgs = source.minTrailingArgs;
382 maxTrailingArgs = source.maxTrailingArgs;
383 trailingArgsPlaceholder = source.trailingArgsPlaceholder;
384
385 propertiesFileUsed = null;
386 argumentsSetFromPropertiesFile = new ArrayList<String>();
387 trailingArgs = new ArrayList<String>();
388
389 namedArgs = new ArrayList<Argument>(source.namedArgs.size());
390 namedArgsByLongID =
391 new LinkedHashMap<String,Argument>(source.namedArgsByLongID.size());
392 namedArgsByShortID = new LinkedHashMap<Character,Argument>(
393 source.namedArgsByShortID.size());
394
395 final LinkedHashMap<String,Argument> argsByID =
396 new LinkedHashMap<String,Argument>(source.namedArgs.size());
397 for (final Argument sourceArg : source.namedArgs)
398 {
399 final Argument a = sourceArg.getCleanCopy();
400
401 try
402 {
403 a.setRegistered();
404 }
405 catch (final ArgumentException ae)
406 {
407 // This should never happen.
408 Debug.debugException(ae);
409 }
410
411 namedArgs.add(a);
412 argsByID.put(a.getIdentifierString(), a);
413
414 for (final Character c : a.getShortIdentifiers())
415 {
416 namedArgsByShortID.put(c, a);
417 }
418
419 for (final String s : a.getLongIdentifiers())
420 {
421 namedArgsByLongID.put(toLowerCase(s), a);
422 }
423 }
424
425 dependentArgumentSets = new ArrayList<ObjectPair<Argument,Set<Argument>>>(
426 source.dependentArgumentSets.size());
427 for (final ObjectPair<Argument,Set<Argument>> p :
428 source.dependentArgumentSets)
429 {
430 final Set<Argument> sourceSet = p.getSecond();
431 final LinkedHashSet<Argument> newSet =
432 new LinkedHashSet<Argument>(sourceSet.size());
433 for (final Argument a : sourceSet)
434 {
435 newSet.add(argsByID.get(a.getIdentifierString()));
436 }
437
438 final Argument sourceFirst = p.getFirst();
439 final Argument newFirst = argsByID.get(sourceFirst.getIdentifierString());
440 dependentArgumentSets.add(
441 new ObjectPair<Argument, Set<Argument>>(newFirst, newSet));
442 }
443
444 exclusiveArgumentSets =
445 new ArrayList<Set<Argument>>(source.exclusiveArgumentSets.size());
446 for (final Set<Argument> sourceSet : source.exclusiveArgumentSets)
447 {
448 final LinkedHashSet<Argument> newSet =
449 new LinkedHashSet<Argument>(sourceSet.size());
450 for (final Argument a : sourceSet)
451 {
452 newSet.add(argsByID.get(a.getIdentifierString()));
453 }
454
455 exclusiveArgumentSets.add(newSet);
456 }
457
458 requiredArgumentSets =
459 new ArrayList<Set<Argument>>(source.requiredArgumentSets.size());
460 for (final Set<Argument> sourceSet : source.requiredArgumentSets)
461 {
462 final LinkedHashSet<Argument> newSet =
463 new LinkedHashSet<Argument>(sourceSet.size());
464 for (final Argument a : sourceSet)
465 {
466 newSet.add(argsByID.get(a.getIdentifierString()));
467 }
468 requiredArgumentSets.add(newSet);
469 }
470
471 parentSubCommand = subCommand;
472 selectedSubCommand = null;
473 subCommands = new ArrayList<SubCommand>(source.subCommands.size());
474 subCommandsByName =
475 new LinkedHashMap<String,SubCommand>(source.subCommandsByName.size());
476 for (final SubCommand sc : source.subCommands)
477 {
478 subCommands.add(sc.getCleanCopy());
479 for (final String name : sc.getNames())
480 {
481 subCommandsByName.put(toLowerCase(name), sc);
482 }
483 }
484 }
485
486
487
488 /**
489 * Retrieves the name of the application or utility with which this command
490 * line argument parser is associated.
491 *
492 * @return The name of the application or utility with which this command
493 * line argument parser is associated.
494 */
495 public String getCommandName()
496 {
497 return commandName;
498 }
499
500
501
502 /**
503 * Retrieves a description of the application or utility with which this
504 * command line argument parser is associated.
505 *
506 * @return A description of the application or utility with which this
507 * command line argument parser is associated.
508 */
509 public String getCommandDescription()
510 {
511 return commandDescription;
512 }
513
514
515
516 /**
517 * Indicates whether this argument parser allows any unnamed trailing
518 * arguments to be provided.
519 *
520 * @return {@code true} if at least one unnamed trailing argument may be
521 * provided, or {@code false} if not.
522 */
523 public boolean allowsTrailingArguments()
524 {
525 return (maxTrailingArgs != 0);
526 }
527
528
529
530 /**
531 * Indicates whether this argument parser requires at least unnamed trailing
532 * argument to be provided.
533 *
534 * @return {@code true} if at least one unnamed trailing argument must be
535 * provided, or {@code false} if the tool may be invoked without any
536 * such arguments.
537 */
538 public boolean requiresTrailingArguments()
539 {
540 return (minTrailingArgs != 0);
541 }
542
543
544
545 /**
546 * Retrieves the placeholder string that will be provided in usage information
547 * to indicate what may be included in the trailing arguments.
548 *
549 * @return The placeholder string that will be provided in usage information
550 * to indicate what may be included in the trailing arguments, or
551 * {@code null} if unnamed trailing arguments are not allowed.
552 */
553 public String getTrailingArgumentsPlaceholder()
554 {
555 return trailingArgsPlaceholder;
556 }
557
558
559
560 /**
561 * Retrieves the minimum number of unnamed trailing arguments that must be
562 * provided.
563 *
564 * @return The minimum number of unnamed trailing arguments that must be
565 * provided.
566 */
567 public int getMinTrailingArguments()
568 {
569 return minTrailingArgs;
570 }
571
572
573
574 /**
575 * Retrieves the maximum number of unnamed trailing arguments that may be
576 * provided.
577 *
578 * @return The maximum number of unnamed trailing arguments that may be
579 * provided.
580 */
581 public int getMaxTrailingArguments()
582 {
583 return maxTrailingArgs;
584 }
585
586
587
588 /**
589 * Updates this argument parser to enable support for a properties file that
590 * can be used to specify the default values for any properties that were not
591 * supplied via the command line. This method should be invoked after the
592 * argument parser has been configured with all of the other arguments that it
593 * supports and before the {@link #parse} method is invoked. In addition,
594 * after invoking the {@code parse} method, the caller must also invoke the
595 * {@link #getGeneratedPropertiesFile} method to determine if the only
596 * processing performed that should be performed is the generation of a
597 * properties file that will have already been performed.
598 * <BR><BR>
599 * This method will update the argument parser to add the following additional
600 * arguments:
601 * <UL>
602 * <LI>
603 * {@code propertiesFilePath} -- Specifies the path to the properties file
604 * that should be used to obtain default values for any arguments not
605 * provided on the command line. If this is not specified and the
606 * {@code noPropertiesFile} argument is not present, then the argument
607 * parser may use a default properties file path specified using either
608 * the {@code com.unboundid.util.args.ArgumentParser..propertiesFilePath}
609 * system property or the {@code UNBOUNDID_TOOL_PROPERTIES_FILE_PATH}
610 * environment variable.
611 * </LI>
612 * <LI>
613 * {@code generatePropertiesFile} -- Indicates that the tool should
614 * generate a properties file for this argument parser and write it to the
615 * specified location. The generated properties file will not have any
616 * properties set, but will include comments that describe all of the
617 * supported arguments, as well general information about the use of a
618 * properties file. If this argument is specified on the command line,
619 * then no other arguments should be given.
620 * </LI>
621 * <LI>
622 * {@code noPropertiesFile} -- Indicates that the tool should not use a
623 * properties file to obtain default values for any arguments not provided
624 * on the command line.
625 * </LI>
626 * </UL>
627 *
628 * @throws ArgumentException If any of the arguments related to properties
629 * file processing conflicts with an argument that
630 * has already been added to the argument parser.
631 */
632 public void enablePropertiesFileSupport()
633 throws ArgumentException
634 {
635 final FileArgument propertiesFilePath = new FileArgument(null,
636 ARG_NAME_PROPERTIES_FILE_PATH, false, 1, null,
637 INFO_ARG_DESCRIPTION_PROP_FILE_PATH.get(), true, true, true, false);
638 propertiesFilePath.setUsageArgument(true);
639 propertiesFilePath.addLongIdentifier("properties-file-path");
640 addArgument(propertiesFilePath);
641
642 final FileArgument generatePropertiesFile = new FileArgument(null,
643 ARG_NAME_GENERATE_PROPERTIES_FILE, false, 1, null,
644 INFO_ARG_DESCRIPTION_GEN_PROP_FILE.get(), false, true, true, false);
645 generatePropertiesFile.setUsageArgument(true);
646 generatePropertiesFile.addLongIdentifier("generate-properties-file");
647 addArgument(generatePropertiesFile);
648
649 final BooleanArgument noPropertiesFile = new BooleanArgument(null,
650 ARG_NAME_NO_PROPERTIES_FILE, INFO_ARG_DESCRIPTION_NO_PROP_FILE.get());
651 noPropertiesFile.setUsageArgument(true);
652 noPropertiesFile.addLongIdentifier("no-properties-file");
653 addArgument(noPropertiesFile);
654
655
656 // The propertiesFilePath and noPropertiesFile arguments cannot be used
657 // together.
658 addExclusiveArgumentSet(propertiesFilePath, noPropertiesFile);
659 }
660
661
662
663 /**
664 * Indicates whether this argument parser was used to generate a properties
665 * file. If so, then the tool invoking the parser should return without
666 * performing any further processing.
667 *
668 * @return A {@code File} object that represents the path to the properties
669 * file that was generated, or {@code null} if no properties file was
670 * generated.
671 */
672 public File getGeneratedPropertiesFile()
673 {
674 final Argument a = getNamedArgument(ARG_NAME_GENERATE_PROPERTIES_FILE);
675 if ((a == null) || (! a.isPresent()) || (! (a instanceof FileArgument)))
676 {
677 return null;
678 }
679
680 return ((FileArgument) a).getValue();
681 }
682
683
684
685 /**
686 * Retrieves the named argument with the specified short identifier.
687 *
688 * @param shortIdentifier The short identifier of the argument to retrieve.
689 * It must not be {@code null}.
690 *
691 * @return The named argument with the specified short identifier, or
692 * {@code null} if there is no such argument.
693 */
694 public Argument getNamedArgument(final Character shortIdentifier)
695 {
696 ensureNotNull(shortIdentifier);
697 return namedArgsByShortID.get(shortIdentifier);
698 }
699
700
701
702 /**
703 * Retrieves the named argument with the specified identifier.
704 *
705 * @param identifier The identifier of the argument to retrieve. It may be
706 * the long identifier without any dashes, the short
707 * identifier character preceded by a single dash, or the
708 * long identifier preceded by two dashes. It must not be
709 * {@code null}.
710 *
711 * @return The named argument with the specified long identifier, or
712 * {@code null} if there is no such argument.
713 */
714 public Argument getNamedArgument(final String identifier)
715 {
716 ensureNotNull(identifier);
717
718 if (identifier.startsWith("--") && (identifier.length() > 2))
719 {
720 return namedArgsByLongID.get(toLowerCase(identifier.substring(2)));
721 }
722 else if (identifier.startsWith("-") && (identifier.length() == 2))
723 {
724 return namedArgsByShortID.get(identifier.charAt(1));
725 }
726 else
727 {
728 return namedArgsByLongID.get(toLowerCase(identifier));
729 }
730 }
731
732
733
734 /**
735 * Retrieves the argument list argument with the specified identifier.
736 *
737 * @param identifier The identifier of the argument to retrieve. It may be
738 * the long identifier without any dashes, the short
739 * identifier character preceded by a single dash, or the
740 * long identifier preceded by two dashes. It must not be
741 * {@code null}.
742 *
743 * @return The argument list argument with the specified identifier, or
744 * {@code null} if there is no such argument.
745 */
746 public ArgumentListArgument getArgumentListArgument(final String identifier)
747 {
748 final Argument a = getNamedArgument(identifier);
749 if (a == null)
750 {
751 return null;
752 }
753 else
754 {
755 return (ArgumentListArgument) a;
756 }
757 }
758
759
760
761 /**
762 * Retrieves the Boolean argument with the specified identifier.
763 *
764 * @param identifier The identifier of the argument to retrieve. It may be
765 * the long identifier without any dashes, the short
766 * identifier character preceded by a single dash, or the
767 * long identifier preceded by two dashes. It must not be
768 * {@code null}.
769 *
770 * @return The Boolean argument with the specified identifier, or
771 * {@code null} if there is no such argument.
772 */
773 public BooleanArgument getBooleanArgument(final String identifier)
774 {
775 final Argument a = getNamedArgument(identifier);
776 if (a == null)
777 {
778 return null;
779 }
780 else
781 {
782 return (BooleanArgument) a;
783 }
784 }
785
786
787
788 /**
789 * Retrieves the Boolean value argument with the specified identifier.
790 *
791 * @param identifier The identifier of the argument to retrieve. It may be
792 * the long identifier without any dashes, the short
793 * identifier character preceded by a single dash, or the
794 * long identifier preceded by two dashes. It must not be
795 * {@code null}.
796 *
797 * @return The Boolean value argument with the specified identifier, or
798 * {@code null} if there is no such argument.
799 */
800 public BooleanValueArgument getBooleanValueArgument(final String identifier)
801 {
802 final Argument a = getNamedArgument(identifier);
803 if (a == null)
804 {
805 return null;
806 }
807 else
808 {
809 return (BooleanValueArgument) a;
810 }
811 }
812
813
814
815 /**
816 * Retrieves the control argument with the specified identifier.
817 *
818 * @param identifier The identifier of the argument to retrieve. It may be
819 * the long identifier without any dashes, the short
820 * identifier character preceded by a single dash, or the
821 * long identifier preceded by two dashes. It must not be
822 * {@code null}.
823 *
824 * @return The control argument with the specified identifier, or
825 * {@code null} if there is no such argument.
826 */
827 public ControlArgument getControlArgument(final String identifier)
828 {
829 final Argument a = getNamedArgument(identifier);
830 if (a == null)
831 {
832 return null;
833 }
834 else
835 {
836 return (ControlArgument) a;
837 }
838 }
839
840
841
842 /**
843 * Retrieves the DN argument with the specified identifier.
844 *
845 * @param identifier The identifier of the argument to retrieve. It may be
846 * the long identifier without any dashes, the short
847 * identifier character preceded by a single dash, or the
848 * long identifier preceded by two dashes. It must not be
849 * {@code null}.
850 *
851 * @return The DN argument with the specified identifier, or
852 * {@code null} if there is no such argument.
853 */
854 public DNArgument getDNArgument(final String identifier)
855 {
856 final Argument a = getNamedArgument(identifier);
857 if (a == null)
858 {
859 return null;
860 }
861 else
862 {
863 return (DNArgument) a;
864 }
865 }
866
867
868
869 /**
870 * Retrieves the duration argument with the specified identifier.
871 *
872 * @param identifier The identifier of the argument to retrieve. It may be
873 * the long identifier without any dashes, the short
874 * identifier character preceded by a single dash, or the
875 * long identifier preceded by two dashes. It must not be
876 * {@code null}.
877 *
878 * @return The duration argument with the specified identifier, or
879 * {@code null} if there is no such argument.
880 */
881 public DurationArgument getDurationArgument(final String identifier)
882 {
883 final Argument a = getNamedArgument(identifier);
884 if (a == null)
885 {
886 return null;
887 }
888 else
889 {
890 return (DurationArgument) a;
891 }
892 }
893
894
895
896 /**
897 * Retrieves the file argument with the specified identifier.
898 *
899 * @param identifier The identifier of the argument to retrieve. It may be
900 * the long identifier without any dashes, the short
901 * identifier character preceded by a single dash, or the
902 * long identifier preceded by two dashes. It must not be
903 * {@code null}.
904 *
905 * @return The file argument with the specified identifier, or
906 * {@code null} if there is no such argument.
907 */
908 public FileArgument getFileArgument(final String identifier)
909 {
910 final Argument a = getNamedArgument(identifier);
911 if (a == null)
912 {
913 return null;
914 }
915 else
916 {
917 return (FileArgument) a;
918 }
919 }
920
921
922
923 /**
924 * Retrieves the filter argument with the specified identifier.
925 *
926 * @param identifier The identifier of the argument to retrieve. It may be
927 * the long identifier without any dashes, the short
928 * identifier character preceded by a single dash, or the
929 * long identifier preceded by two dashes. It must not be
930 * {@code null}.
931 *
932 * @return The filter argument with the specified identifier, or
933 * {@code null} if there is no such argument.
934 */
935 public FilterArgument getFilterArgument(final String identifier)
936 {
937 final Argument a = getNamedArgument(identifier);
938 if (a == null)
939 {
940 return null;
941 }
942 else
943 {
944 return (FilterArgument) a;
945 }
946 }
947
948
949
950 /**
951 * Retrieves the integer argument with the specified identifier.
952 *
953 * @param identifier The identifier of the argument to retrieve. It may be
954 * the long identifier without any dashes, the short
955 * identifier character preceded by a single dash, or the
956 * long identifier preceded by two dashes. It must not be
957 * {@code null}.
958 *
959 * @return The integer argument with the specified identifier, or
960 * {@code null} if there is no such argument.
961 */
962 public IntegerArgument getIntegerArgument(final String identifier)
963 {
964 final Argument a = getNamedArgument(identifier);
965 if (a == null)
966 {
967 return null;
968 }
969 else
970 {
971 return (IntegerArgument) a;
972 }
973 }
974
975
976
977 /**
978 * Retrieves the scope argument with the specified identifier.
979 *
980 * @param identifier The identifier of the argument to retrieve. It may be
981 * the long identifier without any dashes, the short
982 * identifier character preceded by a single dash, or the
983 * long identifier preceded by two dashes. It must not be
984 * {@code null}.
985 *
986 * @return The scope argument with the specified identifier, or
987 * {@code null} if there is no such argument.
988 */
989 public ScopeArgument getScopeArgument(final String identifier)
990 {
991 final Argument a = getNamedArgument(identifier);
992 if (a == null)
993 {
994 return null;
995 }
996 else
997 {
998 return (ScopeArgument) a;
999 }
1000 }
1001
1002
1003
1004 /**
1005 * Retrieves the string argument with the specified identifier.
1006 *
1007 * @param identifier The identifier of the argument to retrieve. It may be
1008 * the long identifier without any dashes, the short
1009 * identifier character preceded by a single dash, or the
1010 * long identifier preceded by two dashes. It must not be
1011 * {@code null}.
1012 *
1013 * @return The string argument with the specified identifier, or
1014 * {@code null} if there is no such argument.
1015 */
1016 public StringArgument getStringArgument(final String identifier)
1017 {
1018 final Argument a = getNamedArgument(identifier);
1019 if (a == null)
1020 {
1021 return null;
1022 }
1023 else
1024 {
1025 return (StringArgument) a;
1026 }
1027 }
1028
1029
1030
1031 /**
1032 * Retrieves the timestamp argument with the specified identifier.
1033 *
1034 * @param identifier The identifier of the argument to retrieve. It may be
1035 * the long identifier without any dashes, the short
1036 * identifier character preceded by a single dash, or the
1037 * long identifier preceded by two dashes. It must not be
1038 * {@code null}.
1039 *
1040 * @return The timestamp argument with the specified identifier, or
1041 * {@code null} if there is no such argument.
1042 */
1043 public TimestampArgument getTimestampArgument(final String identifier)
1044 {
1045 final Argument a = getNamedArgument(identifier);
1046 if (a == null)
1047 {
1048 return null;
1049 }
1050 else
1051 {
1052 return (TimestampArgument) a;
1053 }
1054 }
1055
1056
1057
1058 /**
1059 * Retrieves the set of named arguments defined for use with this argument
1060 * parser.
1061 *
1062 * @return The set of named arguments defined for use with this argument
1063 * parser.
1064 */
1065 public List<Argument> getNamedArguments()
1066 {
1067 return Collections.unmodifiableList(namedArgs);
1068 }
1069
1070
1071
1072 /**
1073 * Registers the provided argument with this argument parser.
1074 *
1075 * @param argument The argument to be registered.
1076 *
1077 * @throws ArgumentException If the provided argument conflicts with another
1078 * argument already registered with this parser.
1079 */
1080 public void addArgument(final Argument argument)
1081 throws ArgumentException
1082 {
1083 argument.setRegistered();
1084 for (final Character c : argument.getShortIdentifiers())
1085 {
1086 if (namedArgsByShortID.containsKey(c))
1087 {
1088 throw new ArgumentException(ERR_PARSER_SHORT_ID_CONFLICT.get(c));
1089 }
1090
1091 if ((parentSubCommand != null) &&
1092 (parentSubCommand.getArgumentParser().namedArgsByShortID.containsKey(
1093 c)))
1094 {
1095 throw new ArgumentException(ERR_PARSER_SHORT_ID_CONFLICT.get(c));
1096 }
1097 }
1098
1099 for (final String s : argument.getLongIdentifiers())
1100 {
1101 if (namedArgsByLongID.containsKey(toLowerCase(s)))
1102 {
1103 throw new ArgumentException(ERR_PARSER_LONG_ID_CONFLICT.get(s));
1104 }
1105
1106 if ((parentSubCommand != null) &&
1107 (parentSubCommand.getArgumentParser().namedArgsByLongID.containsKey(
1108 toLowerCase(s))))
1109 {
1110 throw new ArgumentException(ERR_PARSER_LONG_ID_CONFLICT.get(s));
1111 }
1112 }
1113
1114 for (final SubCommand sc : subCommands)
1115 {
1116 final ArgumentParser parser = sc.getArgumentParser();
1117 for (final Character c : argument.getShortIdentifiers())
1118 {
1119 if (parser.namedArgsByShortID.containsKey(c))
1120 {
1121 throw new ArgumentException(
1122 ERR_PARSER_SHORT_ID_CONFLICT_WITH_SUBCOMMAND.get(c,
1123 sc.getPrimaryName()));
1124 }
1125 }
1126
1127 for (final String s : argument.getLongIdentifiers())
1128 {
1129 if (parser.namedArgsByLongID.containsKey(toLowerCase(s)))
1130 {
1131 throw new ArgumentException(
1132 ERR_PARSER_LONG_ID_CONFLICT_WITH_SUBCOMMAND.get(s,
1133 sc.getPrimaryName()));
1134 }
1135 }
1136 }
1137
1138 for (final Character c : argument.getShortIdentifiers())
1139 {
1140 namedArgsByShortID.put(c, argument);
1141 }
1142
1143 for (final String s : argument.getLongIdentifiers())
1144 {
1145 namedArgsByLongID.put(toLowerCase(s), argument);
1146 }
1147
1148 namedArgs.add(argument);
1149 }
1150
1151
1152
1153 /**
1154 * Retrieves the list of dependent argument sets for this argument parser. If
1155 * an argument contained as the first object in the pair in a dependent
1156 * argument set is provided, then at least one of the arguments in the paired
1157 * set must also be provided.
1158 *
1159 * @return The list of dependent argument sets for this argument parser.
1160 */
1161 public List<ObjectPair<Argument,Set<Argument>>> getDependentArgumentSets()
1162 {
1163 return Collections.unmodifiableList(dependentArgumentSets);
1164 }
1165
1166
1167
1168 /**
1169 * Adds the provided collection of arguments as dependent upon the given
1170 * argument. All of the arguments must have already been registered with this
1171 * argument parser using the {@link #addArgument} method.
1172 *
1173 * @param targetArgument The argument whose presence indicates that at
1174 * least one of the dependent arguments must also
1175 * be present. It must not be {@code null}, and
1176 * it must have already been registered with this
1177 * argument parser.
1178 * @param dependentArguments The set of arguments from which at least one
1179 * argument must be present if the target argument
1180 * is present. It must not be {@code null} or
1181 * empty, and all arguments must have already been
1182 * registered with this argument parser.
1183 */
1184 public void addDependentArgumentSet(final Argument targetArgument,
1185 final Collection<Argument> dependentArguments)
1186 {
1187 ensureNotNull(targetArgument, dependentArguments);
1188
1189 ensureFalse(dependentArguments.isEmpty(),
1190 "The ArgumentParser.addDependentArgumentSet method must not be " +
1191 "called with an empty collection of dependentArguments");
1192
1193 ensureTrue(namedArgs.contains(targetArgument),
1194 "The ArgumentParser.addDependentArgumentSet method may only be used " +
1195 "if all of the provided arguments have already been registered " +
1196 "with the argument parser via the ArgumentParser.addArgument " +
1197 "method. The " + targetArgument.getIdentifierString() +
1198 " argument has not been registered with the argument parser.");
1199 for (final Argument a : dependentArguments)
1200 {
1201 ensureTrue(namedArgs.contains(a),
1202 "The ArgumentParser.addDependentArgumentSet method may only be " +
1203 "used if all of the provided arguments have already been " +
1204 "registered with the argument parser via the " +
1205 "ArgumentParser.addArgument method. The " +
1206 a.getIdentifierString() + " argument has not been registered " +
1207 "with the argument parser.");
1208 }
1209
1210 final LinkedHashSet<Argument> argSet =
1211 new LinkedHashSet<Argument>(dependentArguments);
1212 dependentArgumentSets.add(
1213 new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet));
1214 }
1215
1216
1217
1218 /**
1219 * Adds the provided collection of arguments as dependent upon the given
1220 * argument. All of the arguments must have already been registered with this
1221 * argument parser using the {@link #addArgument} method.
1222 *
1223 * @param targetArgument The argument whose presence indicates that at least
1224 * one of the dependent arguments must also be
1225 * present. It must not be {@code null}, and it must
1226 * have already been registered with this argument
1227 * parser.
1228 * @param dependentArg1 The first argument in the set of arguments in which
1229 * at least one argument must be present if the target
1230 * argument is present. It must not be {@code null},
1231 * and it must have already been registered with this
1232 * argument parser.
1233 * @param remaining The remaining arguments in the set of arguments in
1234 * which at least one argument must be present if the
1235 * target argument is present. It may be {@code null}
1236 * or empty if no additional dependent arguments are
1237 * needed, but if it is non-empty then all arguments
1238 * must have already been registered with this
1239 * argument parser.
1240 */
1241 public void addDependentArgumentSet(final Argument targetArgument,
1242 final Argument dependentArg1,
1243 final Argument... remaining)
1244 {
1245 ensureNotNull(targetArgument, dependentArg1);
1246
1247 ensureTrue(namedArgs.contains(targetArgument),
1248 "The ArgumentParser.addDependentArgumentSet method may only be used " +
1249 "if all of the provided arguments have already been registered " +
1250 "with the argument parser via the ArgumentParser.addArgument " +
1251 "method. The " + targetArgument.getIdentifierString() +
1252 " argument has not been registered with the argument parser.");
1253 ensureTrue(namedArgs.contains(dependentArg1),
1254 "The ArgumentParser.addDependentArgumentSet method may only be used " +
1255 "if all of the provided arguments have already been registered " +
1256 "with the argument parser via the ArgumentParser.addArgument " +
1257 "method. The " + dependentArg1.getIdentifierString() +
1258 " argument has not been registered with the argument parser.");
1259 if (remaining != null)
1260 {
1261 for (final Argument a : remaining)
1262 {
1263 ensureTrue(namedArgs.contains(a),
1264 "The ArgumentParser.addDependentArgumentSet method may only be " +
1265 "used if all of the provided arguments have already been " +
1266 "registered with the argument parser via the " +
1267 "ArgumentParser.addArgument method. The " +
1268 a.getIdentifierString() + " argument has not been " +
1269 "registered with the argument parser.");
1270 }
1271 }
1272
1273 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>();
1274 argSet.add(dependentArg1);
1275 if (remaining != null)
1276 {
1277 argSet.addAll(Arrays.asList(remaining));
1278 }
1279
1280 dependentArgumentSets.add(
1281 new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet));
1282 }
1283
1284
1285
1286 /**
1287 * Retrieves the list of exclusive argument sets for this argument parser.
1288 * If an argument contained in an exclusive argument set is provided, then
1289 * none of the other arguments in that set may be provided. It is acceptable
1290 * for none of the arguments in the set to be provided, unless the same set
1291 * of arguments is also defined as a required argument set.
1292 *
1293 * @return The list of exclusive argument sets for this argument parser.
1294 */
1295 public List<Set<Argument>> getExclusiveArgumentSets()
1296 {
1297 return Collections.unmodifiableList(exclusiveArgumentSets);
1298 }
1299
1300
1301
1302 /**
1303 * Adds the provided collection of arguments as an exclusive argument set, in
1304 * which at most one of the arguments may be provided. All of the arguments
1305 * must have already been registered with this argument parser using the
1306 * {@link #addArgument} method.
1307 *
1308 * @param exclusiveArguments The collection of arguments to form an
1309 * exclusive argument set. It must not be
1310 * {@code null}, and all of the arguments must
1311 * have already been registered with this argument
1312 * parser.
1313 */
1314 public void addExclusiveArgumentSet(
1315 final Collection<Argument> exclusiveArguments)
1316 {
1317 ensureNotNull(exclusiveArguments);
1318
1319 for (final Argument a : exclusiveArguments)
1320 {
1321 ensureTrue(namedArgs.contains(a),
1322 "The ArgumentParser.addExclusiveArgumentSet method may only be " +
1323 "used if all of the provided arguments have already been " +
1324 "registered with the argument parser via the " +
1325 "ArgumentParser.addArgument method. The " +
1326 a.getIdentifierString() + " argument has not been " +
1327 "registered with the argument parser.");
1328 }
1329
1330 final LinkedHashSet<Argument> argSet =
1331 new LinkedHashSet<Argument>(exclusiveArguments);
1332 exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet));
1333 }
1334
1335
1336
1337 /**
1338 * Adds the provided set of arguments as an exclusive argument set, in
1339 * which at most one of the arguments may be provided. All of the arguments
1340 * must have already been registered with this argument parser using the
1341 * {@link #addArgument} method.
1342 *
1343 * @param arg1 The first argument to include in the exclusive argument
1344 * set. It must not be {@code null}, and it must have
1345 * already been registered with this argument parser.
1346 * @param arg2 The second argument to include in the exclusive argument
1347 * set. It must not be {@code null}, and it must have
1348 * already been registered with this argument parser.
1349 * @param remaining Any additional arguments to include in the exclusive
1350 * argument set. It may be {@code null} or empty if no
1351 * additional exclusive arguments are needed, but if it is
1352 * non-empty then all arguments must have already been
1353 * registered with this argument parser.
1354 */
1355 public void addExclusiveArgumentSet(final Argument arg1, final Argument arg2,
1356 final Argument... remaining)
1357 {
1358 ensureNotNull(arg1, arg2);
1359
1360 ensureTrue(namedArgs.contains(arg1),
1361 "The ArgumentParser.addExclusiveArgumentSet method may only be " +
1362 "used if all of the provided arguments have already been " +
1363 "registered with the argument parser via the " +
1364 "ArgumentParser.addArgument method. The " +
1365 arg1.getIdentifierString() + " argument has not been " +
1366 "registered with the argument parser.");
1367 ensureTrue(namedArgs.contains(arg2),
1368 "The ArgumentParser.addExclusiveArgumentSet method may only be " +
1369 "used if all of the provided arguments have already been " +
1370 "registered with the argument parser via the " +
1371 "ArgumentParser.addArgument method. The " +
1372 arg2.getIdentifierString() + " argument has not been " +
1373 "registered with the argument parser.");
1374
1375 if (remaining != null)
1376 {
1377 for (final Argument a : remaining)
1378 {
1379 ensureTrue(namedArgs.contains(a),
1380 "The ArgumentParser.addExclusiveArgumentSet method may only be " +
1381 "used if all of the provided arguments have already been " +
1382 "registered with the argument parser via the " +
1383 "ArgumentParser.addArgument method. The " +
1384 a.getIdentifierString() + " argument has not been " +
1385 "registered with the argument parser.");
1386 }
1387 }
1388
1389 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>();
1390 argSet.add(arg1);
1391 argSet.add(arg2);
1392 argSet.addAll(Arrays.asList(remaining));
1393
1394 exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet));
1395 }
1396
1397
1398
1399 /**
1400 * Retrieves the list of required argument sets for this argument parser. At
1401 * least one of the arguments contained in this set must be provided. If this
1402 * same set is also defined as an exclusive argument set, then exactly one
1403 * of those arguments must be provided.
1404 *
1405 * @return The list of required argument sets for this argument parser.
1406 */
1407 public List<Set<Argument>> getRequiredArgumentSets()
1408 {
1409 return Collections.unmodifiableList(requiredArgumentSets);
1410 }
1411
1412
1413
1414 /**
1415 * Adds the provided collection of arguments as a required argument set, in
1416 * which at least one of the arguments must be provided. All of the arguments
1417 * must have already been registered with this argument parser using the
1418 * {@link #addArgument} method.
1419 *
1420 * @param requiredArguments The collection of arguments to form an
1421 * required argument set. It must not be
1422 * {@code null}, and all of the arguments must have
1423 * already been registered with this argument
1424 * parser.
1425 */
1426 public void addRequiredArgumentSet(
1427 final Collection<Argument> requiredArguments)
1428 {
1429 ensureNotNull(requiredArguments);
1430
1431 for (final Argument a : requiredArguments)
1432 {
1433 ensureTrue(namedArgs.contains(a),
1434 "The ArgumentParser.addRequiredArgumentSet method may only be " +
1435 "used if all of the provided arguments have already been " +
1436 "registered with the argument parser via the " +
1437 "ArgumentParser.addArgument method. The " +
1438 a.getIdentifierString() + " argument has not been " +
1439 "registered with the argument parser.");
1440 }
1441
1442 final LinkedHashSet<Argument> argSet =
1443 new LinkedHashSet<Argument>(requiredArguments);
1444 requiredArgumentSets.add(Collections.unmodifiableSet(argSet));
1445 }
1446
1447
1448
1449 /**
1450 * Adds the provided set of arguments as a required argument set, in which
1451 * at least one of the arguments must be provided. All of the arguments must
1452 * have already been registered with this argument parser using the
1453 * {@link #addArgument} method.
1454 *
1455 * @param arg1 The first argument to include in the required argument
1456 * set. It must not be {@code null}, and it must have
1457 * already been registered with this argument parser.
1458 * @param arg2 The second argument to include in the required argument
1459 * set. It must not be {@code null}, and it must have
1460 * already been registered with this argument parser.
1461 * @param remaining Any additional arguments to include in the required
1462 * argument set. It may be {@code null} or empty if no
1463 * additional required arguments are needed, but if it is
1464 * non-empty then all arguments must have already been
1465 * registered with this argument parser.
1466 */
1467 public void addRequiredArgumentSet(final Argument arg1, final Argument arg2,
1468 final Argument... remaining)
1469 {
1470 ensureNotNull(arg1, arg2);
1471
1472 ensureTrue(namedArgs.contains(arg1),
1473 "The ArgumentParser.addRequiredArgumentSet method may only be " +
1474 "used if all of the provided arguments have already been " +
1475 "registered with the argument parser via the " +
1476 "ArgumentParser.addArgument method. The " +
1477 arg1.getIdentifierString() + " argument has not been " +
1478 "registered with the argument parser.");
1479 ensureTrue(namedArgs.contains(arg2),
1480 "The ArgumentParser.addRequiredArgumentSet method may only be " +
1481 "used if all of the provided arguments have already been " +
1482 "registered with the argument parser via the " +
1483 "ArgumentParser.addArgument method. The " +
1484 arg2.getIdentifierString() + " argument has not been " +
1485 "registered with the argument parser.");
1486
1487 if (remaining != null)
1488 {
1489 for (final Argument a : remaining)
1490 {
1491 ensureTrue(namedArgs.contains(a),
1492 "The ArgumentParser.addRequiredArgumentSet method may only be " +
1493 "used if all of the provided arguments have already been " +
1494 "registered with the argument parser via the " +
1495 "ArgumentParser.addArgument method. The " +
1496 a.getIdentifierString() + " argument has not been " +
1497 "registered with the argument parser.");
1498 }
1499 }
1500
1501 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>();
1502 argSet.add(arg1);
1503 argSet.add(arg2);
1504 argSet.addAll(Arrays.asList(remaining));
1505
1506 requiredArgumentSets.add(Collections.unmodifiableSet(argSet));
1507 }
1508
1509
1510
1511 /**
1512 * Indicates whether any subcommands have been registered with this argument
1513 * parser.
1514 *
1515 * @return {@code true} if one or more subcommands have been registered with
1516 * this argument parser, or {@code false} if not.
1517 */
1518 public boolean hasSubCommands()
1519 {
1520 return (! subCommands.isEmpty());
1521 }
1522
1523
1524
1525 /**
1526 * Retrieves the subcommand that was provided in the set of command-line
1527 * arguments, if any.
1528 *
1529 * @return The subcommand that was provided in the set of command-line
1530 * arguments, or {@code null} if there is none.
1531 */
1532 public SubCommand getSelectedSubCommand()
1533 {
1534 return selectedSubCommand;
1535 }
1536
1537
1538
1539 /**
1540 * Specifies the subcommand that was provided in the set of command-line
1541 * arguments.
1542 *
1543 * @param subcommand The subcommand that was provided in the set of
1544 * command-line arguments. It may be {@code null} if no
1545 * subcommand should be used.
1546 */
1547 void setSelectedSubCommand(final SubCommand subcommand)
1548 {
1549 selectedSubCommand = subcommand;
1550 if (subcommand != null)
1551 {
1552 subcommand.setPresent();
1553 }
1554 }
1555
1556
1557
1558 /**
1559 * Retrieves a list of all subcommands associated with this argument parser.
1560 *
1561 * @return A list of all subcommands associated with this argument parser, or
1562 * an empty list if there are no associated subcommands.
1563 */
1564 public List<SubCommand> getSubCommands()
1565 {
1566 return Collections.unmodifiableList(subCommands);
1567 }
1568
1569
1570
1571 /**
1572 * Retrieves the subcommand for the provided name.
1573 *
1574 * @param name The name of the subcommand to retrieve.
1575 *
1576 * @return The subcommand with the provided name, or {@code null} if there is
1577 * no such subcommand.
1578 */
1579 public SubCommand getSubCommand(final String name)
1580 {
1581 if (name == null)
1582 {
1583 return null;
1584 }
1585
1586 return subCommandsByName.get(toLowerCase(name));
1587 }
1588
1589
1590
1591 /**
1592 * Registers the provided subcommand with this argument parser.
1593 *
1594 * @param subCommand The subcommand to register with this argument parser.
1595 * It must not be {@code null}.
1596 *
1597 * @throws ArgumentException If this argument parser does not allow
1598 * subcommands, if there is a conflict between any
1599 * of the names of the provided subcommand and an
1600 * already-registered subcommand, or if there is a
1601 * conflict between any of the subcommand-specific
1602 * arguments and global arguments.
1603 */
1604 public void addSubCommand(final SubCommand subCommand)
1605 throws ArgumentException
1606 {
1607 // Ensure that the subcommand isn't already registered with an argument
1608 // parser.
1609 if (subCommand.getGlobalArgumentParser() != null)
1610 {
1611 throw new ArgumentException(
1612 ERR_PARSER_SUBCOMMAND_ALREADY_REGISTERED_WITH_PARSER.get());
1613 }
1614
1615 // Ensure that the caller isn't trying to create a nested subcommand.
1616 if (this.parentSubCommand != null)
1617 {
1618 throw new ArgumentException(
1619 ERR_PARSER_CANNOT_CREATE_NESTED_SUBCOMMAND.get(
1620 this.parentSubCommand.getPrimaryName()));
1621 }
1622
1623 // Ensure that this argument parser doesn't allow trailing arguments.
1624 if (allowsTrailingArguments())
1625 {
1626 throw new ArgumentException(
1627 ERR_PARSER_WITH_TRAILING_ARGS_CANNOT_HAVE_SUBCOMMANDS.get());
1628 }
1629
1630 // Ensure that the subcommand doesn't have any names that conflict with an
1631 // existing subcommand.
1632 for (final String name : subCommand.getNames())
1633 {
1634 if (subCommandsByName.containsKey(toLowerCase(name)))
1635 {
1636 throw new ArgumentException(
1637 ERR_SUBCOMMAND_NAME_ALREADY_IN_USE.get(name));
1638 }
1639 }
1640
1641 // Register the subcommand.
1642 for (final String name : subCommand.getNames())
1643 {
1644 subCommandsByName.put(toLowerCase(name), subCommand);
1645 }
1646 subCommands.add(subCommand);
1647 subCommand.setGlobalArgumentParser(this);
1648 }
1649
1650
1651
1652 /**
1653 * Registers the provided additional name for this subcommand.
1654 *
1655 * @param name The name to be registered. It must not be
1656 * {@code null} or empty.
1657 * @param subCommand The subcommand with which the name is associated. It
1658 * must not be {@code null}.
1659 *
1660 * @throws ArgumentException If the provided name is already in use.
1661 */
1662 void addSubCommand(final String name, final SubCommand subCommand)
1663 throws ArgumentException
1664 {
1665 final String lowerName = toLowerCase(name);
1666 if (subCommandsByName.containsKey(lowerName))
1667 {
1668 throw new ArgumentException(
1669 ERR_SUBCOMMAND_NAME_ALREADY_IN_USE.get(name));
1670 }
1671
1672 subCommandsByName.put(lowerName, subCommand);
1673 }
1674
1675
1676
1677 /**
1678 * Retrieves the set of unnamed trailing arguments in the provided command
1679 * line arguments.
1680 *
1681 * @return The set of unnamed trailing arguments in the provided command line
1682 * arguments, or an empty list if there were none.
1683 */
1684 public List<String> getTrailingArguments()
1685 {
1686 return Collections.unmodifiableList(trailingArgs);
1687 }
1688
1689
1690
1691 /**
1692 * Clears the set of trailing arguments for this argument parser.
1693 */
1694 void resetTrailingArguments()
1695 {
1696 trailingArgs.clear();
1697 }
1698
1699
1700
1701 /**
1702 * Adds the provided value to the set of trailing arguments.
1703 *
1704 * @param value The value to add to the set of trailing arguments.
1705 *
1706 * @throws ArgumentException If the parser already has the maximum allowed
1707 * number of trailing arguments.
1708 */
1709 void addTrailingArgument(final String value)
1710 throws ArgumentException
1711 {
1712 if ((maxTrailingArgs > 0) && (trailingArgs.size() >= maxTrailingArgs))
1713 {
1714 throw new ArgumentException(ERR_PARSER_TOO_MANY_TRAILING_ARGS.get(value,
1715 commandName, maxTrailingArgs));
1716 }
1717
1718 trailingArgs.add(value);
1719 }
1720
1721
1722
1723 /**
1724 * Retrieves the properties file that was used to obtain values for arguments
1725 * not set on the command line.
1726 *
1727 * @return The properties file that was used to obtain values for arguments
1728 * not set on the command line, or {@code null} if no properties file
1729 * was used.
1730 */
1731 public File getPropertiesFileUsed()
1732 {
1733 return propertiesFileUsed;
1734 }
1735
1736
1737
1738 /**
1739 * Retrieves a list of the string representations of any arguments used for
1740 * the associated tool that were set from a properties file rather than
1741 * provided on the command line. The values of any arguments marked as
1742 * sensitive will be obscured.
1743 *
1744 * @return A list of the string representations any arguments used for the
1745 * associated tool that were set from a properties file rather than
1746 * provided on the command line, or an empty list if no arguments
1747 * were set from a properties file.
1748 */
1749 public List<String> getArgumentsSetFromPropertiesFile()
1750 {
1751 return Collections.unmodifiableList(argumentsSetFromPropertiesFile);
1752 }
1753
1754
1755
1756 /**
1757 * Creates a copy of this argument parser that is "clean" and appears as if it
1758 * has not been used to parse an argument set. The new parser will have all
1759 * of the same arguments and constraints as this parser.
1760 *
1761 * @return The "clean" copy of this argument parser.
1762 */
1763 public ArgumentParser getCleanCopy()
1764 {
1765 return new ArgumentParser(this, null);
1766 }
1767
1768
1769
1770 /**
1771 * Parses the provided set of arguments.
1772 *
1773 * @param args An array containing the argument information to parse. It
1774 * must not be {@code null}.
1775 *
1776 * @throws ArgumentException If a problem occurs while attempting to parse
1777 * the argument information.
1778 */
1779 public void parse(final String[] args)
1780 throws ArgumentException
1781 {
1782 // Iterate through the provided args strings and process them.
1783 ArgumentParser subCommandParser = null;
1784 boolean inTrailingArgs = false;
1785 boolean skipFinalValidation = false;
1786 String subCommandName = null;
1787 for (int i=0; i < args.length; i++)
1788 {
1789 final String s = args[i];
1790
1791 if (inTrailingArgs)
1792 {
1793 if (maxTrailingArgs == 0)
1794 {
1795 throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get(
1796 s, commandName));
1797 }
1798 else if (trailingArgs.size() >= maxTrailingArgs)
1799 {
1800 throw new ArgumentException(ERR_PARSER_TOO_MANY_TRAILING_ARGS.get(s,
1801 commandName, maxTrailingArgs));
1802 }
1803 else
1804 {
1805 trailingArgs.add(s);
1806 }
1807 }
1808 else if (s.equals("--"))
1809 {
1810 // This signifies the end of the named arguments and the beginning of
1811 // the trailing arguments.
1812 inTrailingArgs = true;
1813 }
1814 else if (s.startsWith("--"))
1815 {
1816 // There may be an equal sign to separate the name from the value.
1817 final String argName;
1818 final int equalPos = s.indexOf('=');
1819 if (equalPos > 0)
1820 {
1821 argName = s.substring(2, equalPos);
1822 }
1823 else
1824 {
1825 argName = s.substring(2);
1826 }
1827
1828 final String lowerName = toLowerCase(argName);
1829 Argument a = namedArgsByLongID.get(lowerName);
1830 if ((a == null) && (subCommandParser != null))
1831 {
1832 a = subCommandParser.namedArgsByLongID.get(lowerName);
1833 }
1834
1835 if (a == null)
1836 {
1837 throw new ArgumentException(ERR_PARSER_NO_SUCH_LONG_ID.get(argName));
1838 }
1839 else if (a.isUsageArgument())
1840 {
1841 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
1842 }
1843
1844 a.incrementOccurrences();
1845 if (a.takesValue())
1846 {
1847 if (equalPos > 0)
1848 {
1849 a.addValue(s.substring(equalPos+1));
1850 }
1851 else
1852 {
1853 i++;
1854 if (i >= args.length)
1855 {
1856 throw new ArgumentException(ERR_PARSER_LONG_ARG_MISSING_VALUE.get(
1857 argName));
1858 }
1859 else
1860 {
1861 a.addValue(args[i]);
1862 }
1863 }
1864 }
1865 else
1866 {
1867 if (equalPos > 0)
1868 {
1869 throw new ArgumentException(
1870 ERR_PARSER_LONG_ARG_DOESNT_TAKE_VALUE.get(argName));
1871 }
1872 }
1873 }
1874 else if (s.startsWith("-"))
1875 {
1876 if (s.length() == 1)
1877 {
1878 throw new ArgumentException(ERR_PARSER_UNEXPECTED_DASH.get());
1879 }
1880 else if (s.length() == 2)
1881 {
1882 final char c = s.charAt(1);
1883
1884 Argument a = namedArgsByShortID.get(c);
1885 if ((a == null) && (subCommandParser != null))
1886 {
1887 a = subCommandParser.namedArgsByShortID.get(c);
1888 }
1889
1890 if (a == null)
1891 {
1892 throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c));
1893 }
1894 else if (a.isUsageArgument())
1895 {
1896 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
1897 }
1898
1899 a.incrementOccurrences();
1900 if (a.takesValue())
1901 {
1902 i++;
1903 if (i >= args.length)
1904 {
1905 throw new ArgumentException(
1906 ERR_PARSER_SHORT_ARG_MISSING_VALUE.get(c));
1907 }
1908 else
1909 {
1910 a.addValue(args[i]);
1911 }
1912 }
1913 }
1914 else
1915 {
1916 char c = s.charAt(1);
1917 Argument a = namedArgsByShortID.get(c);
1918 if ((a == null) && (subCommandParser != null))
1919 {
1920 a = subCommandParser.namedArgsByShortID.get(c);
1921 }
1922
1923 if (a == null)
1924 {
1925 throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c));
1926 }
1927 else if (a.isUsageArgument())
1928 {
1929 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
1930 }
1931
1932 a.incrementOccurrences();
1933 if (a.takesValue())
1934 {
1935 a.addValue(s.substring(2));
1936 }
1937 else
1938 {
1939 // The rest of the characters in the string must also resolve to
1940 // arguments that don't take values.
1941 for (int j=2; j < s.length(); j++)
1942 {
1943 c = s.charAt(j);
1944 a = namedArgsByShortID.get(c);
1945 if ((a == null) && (subCommandParser != null))
1946 {
1947 a = subCommandParser.namedArgsByShortID.get(c);
1948 }
1949
1950 if (a == null)
1951 {
1952 throw new ArgumentException(
1953 ERR_PARSER_NO_SUBSEQUENT_SHORT_ARG.get(c, s));
1954 }
1955 else if (a.isUsageArgument())
1956 {
1957 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
1958 }
1959
1960 a.incrementOccurrences();
1961 if (a.takesValue())
1962 {
1963 throw new ArgumentException(
1964 ERR_PARSER_SUBSEQUENT_SHORT_ARG_TAKES_VALUE.get(
1965 c, s));
1966 }
1967 }
1968 }
1969 }
1970 }
1971 else if (subCommands.isEmpty())
1972 {
1973 inTrailingArgs = true;
1974 if (maxTrailingArgs == 0)
1975 {
1976 throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get(
1977 s, commandName));
1978 }
1979 else
1980 {
1981 trailingArgs.add(s);
1982 }
1983 }
1984 else
1985 {
1986 if (selectedSubCommand == null)
1987 {
1988 subCommandName = s;
1989 selectedSubCommand = subCommandsByName.get(toLowerCase(s));
1990 if (selectedSubCommand == null)
1991 {
1992 throw new ArgumentException(ERR_PARSER_NO_SUCH_SUBCOMMAND.get(s,
1993 commandName));
1994 }
1995 else
1996 {
1997 selectedSubCommand.setPresent();
1998 subCommandParser = selectedSubCommand.getArgumentParser();
1999 }
2000 }
2001 else
2002 {
2003 throw new ArgumentException(ERR_PARSER_CONFLICTING_SUBCOMMANDS.get(
2004 subCommandName, s));
2005 }
2006 }
2007 }
2008
2009
2010 // Perform any appropriate processing related to the use of a properties
2011 // file.
2012 if (! handlePropertiesFile())
2013 {
2014 return;
2015 }
2016
2017
2018 // If a usage argument was provided, then no further validation should be
2019 // performed.
2020 if (skipFinalValidation)
2021 {
2022 return;
2023 }
2024
2025
2026 // If any subcommands are defined, then one must have been provided.
2027 if ((! subCommands.isEmpty()) && (selectedSubCommand == null))
2028 {
2029 throw new ArgumentException(
2030 ERR_PARSER_MISSING_SUBCOMMAND.get(commandName));
2031 }
2032
2033
2034 doFinalValidation(this);
2035 if (selectedSubCommand != null)
2036 {
2037 doFinalValidation(selectedSubCommand.getArgumentParser());
2038 }
2039 }
2040
2041
2042
2043 /**
2044 * Performs the final validation for the provided argument parser.
2045 *
2046 * @param parser The argument parser for which to perform the final
2047 * validation.
2048 *
2049 * @throws ArgumentException If a validation problem is encountered.
2050 */
2051 private static void doFinalValidation(final ArgumentParser parser)
2052 throws ArgumentException
2053 {
2054 // Make sure that all required arguments have values.
2055 for (final Argument a : parser.namedArgs)
2056 {
2057 if (a.isRequired() && (! a.isPresent()))
2058 {
2059 throw new ArgumentException(ERR_PARSER_MISSING_REQUIRED_ARG.get(
2060 a.getIdentifierString()));
2061 }
2062 }
2063
2064
2065 // Make sure that at least the minimum number of trailing arguments were
2066 // provided.
2067 if (parser.trailingArgs.size() < parser.minTrailingArgs)
2068 {
2069 throw new ArgumentException(ERR_PARSER_NOT_ENOUGH_TRAILING_ARGS.get(
2070 parser.commandName, parser.minTrailingArgs,
2071 parser.trailingArgsPlaceholder));
2072 }
2073
2074
2075 // Make sure that there are no dependent argument set conflicts.
2076 for (final ObjectPair<Argument,Set<Argument>> p :
2077 parser.dependentArgumentSets)
2078 {
2079 final Argument targetArg = p.getFirst();
2080 if (targetArg.getNumOccurrences() > 0)
2081 {
2082 final Set<Argument> argSet = p.getSecond();
2083 boolean found = false;
2084 for (final Argument a : argSet)
2085 {
2086 if (a.getNumOccurrences() > 0)
2087 {
2088 found = true;
2089 break;
2090 }
2091 }
2092
2093 if (! found)
2094 {
2095 if (argSet.size() == 1)
2096 {
2097 throw new ArgumentException(
2098 ERR_PARSER_DEPENDENT_CONFLICT_SINGLE.get(
2099 targetArg.getIdentifierString(),
2100 argSet.iterator().next().getIdentifierString()));
2101 }
2102 else
2103 {
2104 boolean first = true;
2105 final StringBuilder buffer = new StringBuilder();
2106 for (final Argument a : argSet)
2107 {
2108 if (first)
2109 {
2110 first = false;
2111 }
2112 else
2113 {
2114 buffer.append(", ");
2115 }
2116 buffer.append(a.getIdentifierString());
2117 }
2118 throw new ArgumentException(
2119 ERR_PARSER_DEPENDENT_CONFLICT_MULTIPLE.get(
2120 targetArg.getIdentifierString(), buffer.toString()));
2121 }
2122 }
2123 }
2124 }
2125
2126
2127 // Make sure that there are no exclusive argument set conflicts.
2128 for (final Set<Argument> argSet : parser.exclusiveArgumentSets)
2129 {
2130 Argument setArg = null;
2131 for (final Argument a : argSet)
2132 {
2133 if (a.getNumOccurrences() > 0)
2134 {
2135 if (setArg == null)
2136 {
2137 setArg = a;
2138 }
2139 else
2140 {
2141 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
2142 setArg.getIdentifierString(),
2143 a.getIdentifierString()));
2144 }
2145 }
2146 }
2147 }
2148
2149 // Make sure that there are no required argument set conflicts.
2150 for (final Set<Argument> argSet : parser.requiredArgumentSets)
2151 {
2152 boolean found = false;
2153 for (final Argument a : argSet)
2154 {
2155 if (a.getNumOccurrences() > 0)
2156 {
2157 found = true;
2158 break;
2159 }
2160 }
2161
2162 if (! found)
2163 {
2164 boolean first = true;
2165 final StringBuilder buffer = new StringBuilder();
2166 for (final Argument a : argSet)
2167 {
2168 if (first)
2169 {
2170 first = false;
2171 }
2172 else
2173 {
2174 buffer.append(", ");
2175 }
2176 buffer.append(a.getIdentifierString());
2177 }
2178 throw new ArgumentException(ERR_PARSER_REQUIRED_CONFLICT.get(
2179 buffer.toString()));
2180 }
2181 }
2182 }
2183
2184
2185
2186 /**
2187 * Indicates whether the provided argument is one that indicates that the
2188 * parser should skip all validation except that performed when assigning
2189 * values from command-line arguments. Validation that will be skipped
2190 * includes ensuring that all required arguments have values, ensuring that
2191 * the minimum number of trailing arguments were provided, and ensuring that
2192 * there were no dependent/exclusive/required argument set conflicts.
2193 *
2194 * @param a The argument for which to make the determination.
2195 *
2196 * @return {@code true} if the provided argument is one that indicates that
2197 * final validation should be skipped, or {@code false} if not.
2198 */
2199 private static boolean skipFinalValidationBecauseOfArgument(final Argument a)
2200 {
2201 // We will skip final validation for all usage arguments except the
2202 // propertiesFilePath and noPropertiesFile arguments.
2203 if (ARG_NAME_PROPERTIES_FILE_PATH.equals(a.getLongIdentifier()) ||
2204 ARG_NAME_NO_PROPERTIES_FILE.equals(a.getLongIdentifier()) ||
2205 ARG_NAME_OUTPUT_FILE.equals(a.getLongIdentifier()) ||
2206 ARG_NAME_TEE_OUTPUT.equals(a.getLongIdentifier()))
2207 {
2208 return false;
2209 }
2210
2211 return a.isUsageArgument();
2212 }
2213
2214
2215
2216 /**
2217 * Performs any appropriate properties file processing for this argument
2218 * parser.
2219 *
2220 * @return {@code true} if the tool should continue processing, or
2221 * {@code false} if it should return immediately.
2222 *
2223 * @throws ArgumentException If a problem is encountered while attempting
2224 * to parse a properties file or update arguments
2225 * with the values contained in it.
2226 */
2227 private boolean handlePropertiesFile()
2228 throws ArgumentException
2229 {
2230 final BooleanArgument noPropertiesFile;
2231 final FileArgument generatePropertiesFile;
2232 final FileArgument propertiesFilePath;
2233 try
2234 {
2235 propertiesFilePath = getFileArgument(ARG_NAME_PROPERTIES_FILE_PATH);
2236 generatePropertiesFile =
2237 getFileArgument(ARG_NAME_GENERATE_PROPERTIES_FILE);
2238 noPropertiesFile = getBooleanArgument(ARG_NAME_NO_PROPERTIES_FILE);
2239 }
2240 catch (final Exception e)
2241 {
2242 Debug.debugException(e);
2243
2244 // This should only ever happen if the argument parser has an argument
2245 // with a name that conflicts with one of the properties file arguments
2246 // but isn't of the right type. In this case, we'll assume that no
2247 // properties file will be used.
2248 return true;
2249 }
2250
2251
2252 // If any of the properties file arguments isn't defined, then we'll assume
2253 // that no properties file will be used.
2254 if ((propertiesFilePath == null) || (generatePropertiesFile == null) ||
2255 (noPropertiesFile == null))
2256 {
2257 return true;
2258 }
2259
2260
2261 // If the noPropertiesFile argument is present, then don't do anything but
2262 // make sure that neither of the other arguments was specified.
2263 if (noPropertiesFile.isPresent())
2264 {
2265 if (propertiesFilePath.isPresent())
2266 {
2267 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
2268 noPropertiesFile.getIdentifierString(),
2269 propertiesFilePath.getIdentifierString()));
2270 }
2271 else if (generatePropertiesFile.isPresent())
2272 {
2273 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
2274 noPropertiesFile.getIdentifierString(),
2275 generatePropertiesFile.getIdentifierString()));
2276 }
2277 else
2278 {
2279 return true;
2280 }
2281 }
2282
2283
2284 // If the generatePropertiesFile argument is present, then make sure the
2285 // propertiesFilePath argument is not set and generate the output.
2286 if (generatePropertiesFile.isPresent())
2287 {
2288 if (propertiesFilePath.isPresent())
2289 {
2290 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
2291 generatePropertiesFile.getIdentifierString(),
2292 propertiesFilePath.getIdentifierString()));
2293 }
2294 else
2295 {
2296 generatePropertiesFile(
2297 generatePropertiesFile.getValue().getAbsolutePath());
2298 return false;
2299 }
2300 }
2301
2302
2303 // If the propertiesFilePath argument is present, then try to make use of
2304 // the specified file.
2305 if (propertiesFilePath.isPresent())
2306 {
2307 final File propertiesFile = propertiesFilePath.getValue();
2308 if (propertiesFile.exists() && propertiesFile.isFile())
2309 {
2310 handlePropertiesFile(propertiesFilePath.getValue());
2311 }
2312 else
2313 {
2314 throw new ArgumentException(
2315 ERR_PARSER_NO_SUCH_PROPERTIES_FILE.get(
2316 propertiesFilePath.getIdentifierString(),
2317 propertiesFile.getAbsolutePath()));
2318 }
2319 return true;
2320 }
2321
2322
2323 // We may still use a properties file if the path was specified in either a
2324 // JVM property or an environment variable. If both are defined, the JVM
2325 // property will take precedence. If a property or environment variable
2326 // specifies an invalid value, then we'll just ignore it.
2327 String path = System.getProperty(PROPERTY_DEFAULT_PROPERTIES_FILE_PATH);
2328 if (path == null)
2329 {
2330 path = System.getenv(ENV_DEFAULT_PROPERTIES_FILE_PATH);
2331 }
2332
2333 if (path != null)
2334 {
2335 final File propertiesFile = new File(path);
2336 if (propertiesFile.exists() && propertiesFile.isFile())
2337 {
2338 handlePropertiesFile(propertiesFile);
2339 }
2340 }
2341
2342 return true;
2343 }
2344
2345
2346
2347 /**
2348 * Write an empty properties file for this argument parser to the specified
2349 * path.
2350 *
2351 * @param path The path to the properties file to be written.
2352 *
2353 * @throws ArgumentException If a problem is encountered while writing the
2354 * properties file.
2355 */
2356 private void generatePropertiesFile(final String path)
2357 throws ArgumentException
2358 {
2359 final PrintWriter w;
2360 try
2361 {
2362 w = new PrintWriter(path);
2363 }
2364 catch (final Exception e)
2365 {
2366 Debug.debugException(e);
2367 throw new ArgumentException(
2368 ERR_PARSER_GEN_PROPS_CANNOT_OPEN_FILE.get(path,
2369 getExceptionMessage(e)),
2370 e);
2371 }
2372
2373 try
2374 {
2375 wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_1.get(commandName));
2376 w.println('#');
2377 wrapComment(w,
2378 INFO_PARSER_GEN_PROPS_HEADER_2.get(commandName,
2379 ARG_NAME_PROPERTIES_FILE_PATH,
2380 PROPERTY_DEFAULT_PROPERTIES_FILE_PATH,
2381 ENV_DEFAULT_PROPERTIES_FILE_PATH, ARG_NAME_NO_PROPERTIES_FILE));
2382 w.println('#');
2383 wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_3.get());
2384 w.println('#');
2385
2386 wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_4.get());
2387 w.println('#');
2388 wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_5.get(commandName));
2389
2390 for (final Argument a : getNamedArguments())
2391 {
2392 writeArgumentProperties(w, null, a);
2393 }
2394
2395 for (final SubCommand sc : getSubCommands())
2396 {
2397 for (final Argument a : sc.getArgumentParser().getNamedArguments())
2398 {
2399 writeArgumentProperties(w, sc, a);
2400 }
2401 }
2402 }
2403 finally
2404 {
2405 w.close();
2406 }
2407 }
2408
2409
2410
2411 /**
2412 * Writes information about the provided argument to the given writer.
2413 *
2414 * @param w The writer to which the properties should be written. It must
2415 * not be {@code null}.
2416 * @param sc The subcommand with which the argument is associated. It may
2417 * be {@code null} if the provided argument is a global argument.
2418 * @param a The argument for which to write the properties. It must not be
2419 * {@code null}.
2420 */
2421 private void writeArgumentProperties(final PrintWriter w,
2422 final SubCommand sc,
2423 final Argument a)
2424 {
2425 if (a.isUsageArgument() || a.isHidden())
2426 {
2427 return;
2428 }
2429
2430 w.println();
2431 w.println();
2432 wrapComment(w, a.getDescription());
2433 w.println('#');
2434
2435 final String constraints = a.getValueConstraints();
2436 if ((constraints != null) && (constraints.length() > 0) &&
2437 (! (a instanceof BooleanArgument)))
2438 {
2439 wrapComment(w, constraints);
2440 w.println('#');
2441 }
2442
2443 final String identifier;
2444 if (a.getLongIdentifier() != null)
2445 {
2446 identifier = a.getLongIdentifier();
2447 }
2448 else
2449 {
2450 identifier = a.getIdentifierString();
2451 }
2452
2453 String placeholder = a.getValuePlaceholder();
2454 if (placeholder == null)
2455 {
2456 if (a instanceof BooleanArgument)
2457 {
2458 placeholder = "{true|false}";
2459 }
2460 else
2461 {
2462 placeholder = "";
2463 }
2464 }
2465
2466 final String propertyName;
2467 if (sc == null)
2468 {
2469 propertyName = commandName + '.' + identifier;
2470 }
2471 else
2472 {
2473 propertyName = commandName + '.' + sc.getPrimaryName() + '.' + identifier;
2474 }
2475
2476 w.println("# " + propertyName + '=' + placeholder);
2477
2478 if (a.isPresent())
2479 {
2480 for (final String s : a.getValueStringRepresentations(false))
2481 {
2482 w.println(propertyName + '=' + s);
2483 }
2484 }
2485 }
2486
2487
2488
2489 /**
2490 * Wraps the given string and writes it as a comment to the provided writer.
2491 *
2492 * @param w The writer to use to write the wrapped and commented string.
2493 * @param s The string to be wrapped and written.
2494 */
2495 private static void wrapComment(final PrintWriter w, final String s)
2496 {
2497 for (final String line : wrapLine(s, 77))
2498 {
2499 w.println("# " + line);
2500 }
2501 }
2502
2503
2504
2505 /**
2506 * Reads the contents of the specified properties file and updates the
2507 * configured arguments as appropriate.
2508 *
2509 * @param propertiesFile The properties file to process.
2510 *
2511 * @throws ArgumentException If a problem is encountered while examining the
2512 * properties file, or while trying to assign a
2513 * property value to a corresponding argument.
2514 */
2515 private void handlePropertiesFile(final File propertiesFile)
2516 throws ArgumentException
2517 {
2518 final BufferedReader reader;
2519 try
2520 {
2521 reader = new BufferedReader(new FileReader(propertiesFile));
2522 }
2523 catch (final Exception e)
2524 {
2525 Debug.debugException(e);
2526 throw new ArgumentException(
2527 ERR_PARSER_CANNOT_OPEN_PROP_FILE.get(
2528 propertiesFile.getAbsolutePath(), getExceptionMessage(e)),
2529 e);
2530 }
2531
2532 try
2533 {
2534 // Read all of the lines of the file, ignoring comments and unwrapping
2535 // properties that span multiple lines.
2536 boolean lineIsContinued = false;
2537 int lineNumber = 0;
2538 final ArrayList<ObjectPair<Integer,StringBuilder>> propertyLines =
2539 new ArrayList<ObjectPair<Integer,StringBuilder>>(10);
2540 while (true)
2541 {
2542 String line;
2543 try
2544 {
2545 line = reader.readLine();
2546 lineNumber++;
2547 }
2548 catch (final Exception e)
2549 {
2550 Debug.debugException(e);
2551 throw new ArgumentException(
2552 ERR_PARSER_ERROR_READING_PROP_FILE.get(
2553 propertiesFile.getAbsolutePath(), getExceptionMessage(e)),
2554 e);
2555 }
2556
2557
2558 // If the line is null, then we've reached the end of the file. If we
2559 // expect a previous line to have been continued, then this is an error.
2560 if (line == null)
2561 {
2562 if (lineIsContinued)
2563 {
2564 throw new ArgumentException(
2565 ERR_PARSER_PROP_FILE_MISSING_CONTINUATION.get(
2566 (lineNumber-1), propertiesFile.getAbsolutePath()));
2567 }
2568 break;
2569 }
2570
2571
2572 // See if the line has any leading whitespace, and if so then trim it
2573 // off. If there is leading whitespace, then make sure that we expect
2574 // the previous line to be continued.
2575 final int initialLength = line.length();
2576 line = trimLeading(line);
2577 final boolean hasLeadingWhitespace = (line.length() < initialLength);
2578 if (hasLeadingWhitespace && (! lineIsContinued))
2579 {
2580 throw new ArgumentException(
2581 ERR_PARSER_PROP_FILE_UNEXPECTED_LEADING_SPACE.get(
2582 propertiesFile.getAbsolutePath(), lineNumber));
2583 }
2584
2585
2586 // If the line is empty or starts with "#", then skip it. But make sure
2587 // we didn't expect the previous line to be continued.
2588 if ((line.length() == 0) || line.startsWith("#"))
2589 {
2590 if (lineIsContinued)
2591 {
2592 throw new ArgumentException(
2593 ERR_PARSER_PROP_FILE_MISSING_CONTINUATION.get(
2594 (lineNumber-1), propertiesFile.getAbsolutePath()));
2595 }
2596 continue;
2597 }
2598
2599
2600 // See if the line ends with a backslash and if so then trim it off.
2601 final boolean hasTrailingBackslash = line.endsWith("\\");
2602 if (line.endsWith("\\"))
2603 {
2604 line = line.substring(0, (line.length() - 1));
2605 }
2606
2607
2608 // If the previous line needs to be continued, then append the new line
2609 // to it. Otherwise, add it as a new line.
2610 if (lineIsContinued)
2611 {
2612 propertyLines.get(propertyLines.size() - 1).getSecond().append(line);
2613 }
2614 else
2615 {
2616 propertyLines.add(new ObjectPair<Integer,StringBuilder>(lineNumber,
2617 new StringBuilder(line)));
2618 }
2619
2620 lineIsContinued = hasTrailingBackslash;
2621 }
2622
2623
2624 // Parse all of the lines into a map of identifiers and their
2625 // corresponding values.
2626 propertiesFileUsed = propertiesFile;
2627 if (propertyLines.isEmpty())
2628 {
2629 return;
2630 }
2631
2632 final HashMap<String,ArrayList<String>> propertyMap =
2633 new HashMap<String,ArrayList<String>>(propertyLines.size());
2634 for (final ObjectPair<Integer,StringBuilder> p : propertyLines)
2635 {
2636 final String line = p.getSecond().toString();
2637 final int equalPos = line.indexOf('=');
2638 if (equalPos <= 0)
2639 {
2640 throw new ArgumentException(ERR_PARSER_MALFORMED_PROP_LINE.get(
2641 propertiesFile.getAbsolutePath(), p.getFirst(), line));
2642 }
2643
2644 final String propertyName = line.substring(0, equalPos).trim();
2645 final String propertyValue = line.substring(equalPos+1).trim();
2646 if (propertyValue.length() == 0)
2647 {
2648 // The property doesn't have a value, so we can ignore it.
2649 continue;
2650 }
2651
2652
2653 // An argument can have multiple identifiers, and we will allow any of
2654 // them to be used to reference it. To deal with this, we'll map the
2655 // argument identifier to its corresponding argument and then use the
2656 // preferred identifier for that argument in the map. The same applies
2657 // to subcommand names.
2658 boolean prefixedWithToolName = false;
2659 boolean prefixedWithSubCommandName = false;
2660 Argument a = getNamedArgument(propertyName);
2661 if (a == null)
2662 {
2663 // It could be that the argument name was prefixed with the tool name.
2664 // Check to see if that was the case.
2665 if (propertyName.startsWith(commandName + '.'))
2666 {
2667 prefixedWithToolName = true;
2668
2669 String basePropertyName =
2670 propertyName.substring(commandName.length()+1);
2671 a = getNamedArgument(basePropertyName);
2672
2673 if (a == null)
2674 {
2675 final int periodPos = basePropertyName.indexOf('.');
2676 if (periodPos > 0)
2677 {
2678 final String subCommandName =
2679 basePropertyName.substring(0, periodPos);
2680 if ((selectedSubCommand != null) &&
2681 selectedSubCommand.hasName(subCommandName))
2682 {
2683 prefixedWithSubCommandName = true;
2684 basePropertyName = basePropertyName.substring(periodPos+1);
2685 a = selectedSubCommand.getArgumentParser().getNamedArgument(
2686 basePropertyName);
2687 }
2688 }
2689 else if (selectedSubCommand != null)
2690 {
2691 a = selectedSubCommand.getArgumentParser().getNamedArgument(
2692 basePropertyName);
2693 }
2694 }
2695 }
2696 else if (selectedSubCommand != null)
2697 {
2698 a = selectedSubCommand.getArgumentParser().getNamedArgument(
2699 propertyName);
2700 }
2701 }
2702
2703 if (a == null)
2704 {
2705 // This could mean that there's a typo in the property name, but it's
2706 // more likely the case that the property is for a different tool. In
2707 // either case, we'll ignore it.
2708 continue;
2709 }
2710
2711 final String canonicalPropertyName;
2712 if (prefixedWithToolName)
2713 {
2714 if (prefixedWithSubCommandName)
2715 {
2716 canonicalPropertyName = commandName + '.' +
2717 selectedSubCommand.getPrimaryName() + '.' +
2718 a.getIdentifierString();
2719 }
2720 else
2721 {
2722 canonicalPropertyName = commandName + '.' + a.getIdentifierString();
2723 }
2724 }
2725 else
2726 {
2727 canonicalPropertyName = a.getIdentifierString();
2728 }
2729
2730 ArrayList<String> valueList = propertyMap.get(canonicalPropertyName);
2731 if (valueList == null)
2732 {
2733 valueList = new ArrayList<String>(5);
2734 propertyMap.put(canonicalPropertyName, valueList);
2735 }
2736 valueList.add(propertyValue);
2737 }
2738
2739
2740 // Iterate through all of the named arguments for the argument parser and
2741 // see if we should use the properties to assign values to any of the
2742 // arguments that weren't provided on the command line.
2743 setArgsFromPropertiesFile(propertyMap, false);
2744
2745
2746 // If there is a selected subcommand, then iterate through all of its
2747 // arguments.
2748 if (selectedSubCommand != null)
2749 {
2750 setArgsFromPropertiesFile(propertyMap, true);
2751 }
2752 }
2753 finally
2754 {
2755 try
2756 {
2757 reader.close();
2758 }
2759 catch (final Exception e)
2760 {
2761 Debug.debugException(e);
2762 }
2763 }
2764 }
2765
2766
2767
2768 /**
2769 * Sets the values of any arguments not provided on the command line but
2770 * defined in the properties file.
2771 *
2772 * @param propertyMap A map of properties read from the properties file.
2773 * @param useSubCommand Indicates whether to use the argument parser
2774 * associated with the selected subcommand rather than
2775 * the global argument parser.
2776 *
2777 * @throws ArgumentException If a problem is encountered while examining the
2778 * properties file, or while trying to assign a
2779 * property value to a corresponding argument.
2780 */
2781 private void setArgsFromPropertiesFile(
2782 final Map<String,ArrayList<String>> propertyMap,
2783 final boolean useSubCommand)
2784 throws ArgumentException
2785 {
2786 final ArgumentParser p;
2787 if (useSubCommand)
2788 {
2789 p = selectedSubCommand.getArgumentParser();
2790 }
2791 else
2792 {
2793 p = this;
2794 }
2795
2796
2797 for (final Argument a : p.namedArgs)
2798 {
2799 if (a.getNumOccurrences() > 0)
2800 {
2801 // The argument was provided on the command line, and that will always
2802 // override anything that might be in the properties file.
2803 continue;
2804 }
2805
2806
2807 // If we should use a subcommand, then see if the properties file has a
2808 // property that is specific to the selected subcommand. Then fall back
2809 // to a property that is specific to the tool, and finally fall back to
2810 // checking for a set of values that are generic to any tool that has an
2811 // argument with that name.
2812 List<String> values = null;
2813 if (useSubCommand)
2814 {
2815 values = propertyMap.get(commandName + '.' +
2816 selectedSubCommand.getPrimaryName() + '.' +
2817 a.getIdentifierString());
2818 }
2819
2820 if (values == null)
2821 {
2822 values = propertyMap.get(commandName + '.' + a.getIdentifierString());
2823 }
2824
2825 if (values == null)
2826 {
2827 values = propertyMap.get(a.getIdentifierString());
2828 }
2829
2830 if (values != null)
2831 {
2832 for (final String value : values)
2833 {
2834 if (a instanceof BooleanArgument)
2835 {
2836 // We'll treat this as a BooleanValueArgument.
2837 final BooleanValueArgument bva = new BooleanValueArgument(
2838 a.getShortIdentifier(), a.getLongIdentifier(), false, null,
2839 a.getDescription());
2840 bva.addValue(value);
2841 if (bva.getValue())
2842 {
2843 a.incrementOccurrences();
2844 }
2845
2846 argumentsSetFromPropertiesFile.add(a.getIdentifierString());
2847 }
2848 else
2849 {
2850 a.addValue(value);
2851 a.incrementOccurrences();
2852
2853 argumentsSetFromPropertiesFile.add(a.getIdentifierString());
2854 if (a.isSensitive())
2855 {
2856 argumentsSetFromPropertiesFile.add("***REDACTED***");
2857 }
2858 else
2859 {
2860 argumentsSetFromPropertiesFile.add(value);
2861 }
2862 }
2863 }
2864 }
2865 }
2866 }
2867
2868
2869
2870 /**
2871 * Retrieves lines that make up the usage information for this program,
2872 * optionally wrapping long lines.
2873 *
2874 * @param maxWidth The maximum line width to use for the output. If this is
2875 * less than or equal to zero, then no wrapping will be
2876 * performed.
2877 *
2878 * @return The lines that make up the usage information for this program.
2879 */
2880 public List<String> getUsage(final int maxWidth)
2881 {
2882 // If a subcommand was selected, then provide usage specific to that
2883 // subcommand.
2884 if (selectedSubCommand != null)
2885 {
2886 return getSubCommandUsage(maxWidth);
2887 }
2888
2889 // First is a description of the command.
2890 final ArrayList<String> lines = new ArrayList<String>(100);
2891 lines.addAll(wrapLine(commandDescription, maxWidth));
2892 lines.add("");
2893
2894
2895 // If the tool supports subcommands, and if there are fewer than 10
2896 // subcommands, then display them inline.
2897 if ((! subCommands.isEmpty()) && (subCommands.size() < 10))
2898 {
2899 lines.add(INFO_USAGE_SUBCOMMANDS_HEADER.get());
2900 lines.add("");
2901
2902 for (final SubCommand sc : subCommands)
2903 {
2904 final StringBuilder nameBuffer = new StringBuilder();
2905 nameBuffer.append(" ");
2906
2907 final Iterator<String> nameIterator = sc.getNames().iterator();
2908 while (nameIterator.hasNext())
2909 {
2910 nameBuffer.append(nameIterator.next());
2911 if (nameIterator.hasNext())
2912 {
2913 nameBuffer.append(", ");
2914 }
2915 }
2916 lines.add(nameBuffer.toString());
2917
2918 for (final String descriptionLine :
2919 wrapLine(sc.getDescription(), (maxWidth - 4)))
2920 {
2921 lines.add(" " + descriptionLine);
2922 }
2923 lines.add("");
2924 }
2925 }
2926
2927
2928 // Next comes the usage. It may include neither, either, or both of the
2929 // set of options and trailing arguments.
2930 if (! subCommands.isEmpty())
2931 {
2932 lines.addAll(wrapLine(INFO_USAGE_SUBCOMMAND_USAGE.get(commandName),
2933 maxWidth));
2934 }
2935 else if (namedArgs.isEmpty())
2936 {
2937 if (maxTrailingArgs == 0)
2938 {
2939 lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_NOTRAILING.get(commandName),
2940 maxWidth));
2941 }
2942 else
2943 {
2944 lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_TRAILING.get(
2945 commandName, trailingArgsPlaceholder),
2946 maxWidth));
2947 }
2948 }
2949 else
2950 {
2951 if (maxTrailingArgs == 0)
2952 {
2953 lines.addAll(wrapLine(INFO_USAGE_OPTIONS_NOTRAILING.get(commandName),
2954 maxWidth));
2955 }
2956 else
2957 {
2958 lines.addAll(wrapLine(INFO_USAGE_OPTIONS_TRAILING.get(
2959 commandName, trailingArgsPlaceholder),
2960 maxWidth));
2961 }
2962 }
2963
2964 if (! namedArgs.isEmpty())
2965 {
2966 lines.add("");
2967 lines.add(INFO_USAGE_OPTIONS_INCLUDE.get());
2968
2969
2970 // If there are any argument groups, then collect the arguments in those
2971 // groups.
2972 boolean hasRequired = false;
2973 final LinkedHashMap<String,List<Argument>> argumentsByGroup =
2974 new LinkedHashMap<String,List<Argument>>(10);
2975 final ArrayList<Argument> argumentsWithoutGroup =
2976 new ArrayList<Argument>(namedArgs.size());
2977 final ArrayList<Argument> usageArguments =
2978 new ArrayList<Argument>(namedArgs.size());
2979 for (final Argument a : namedArgs)
2980 {
2981 if (a.isHidden())
2982 {
2983 // This argument shouldn't be included in the usage output.
2984 continue;
2985 }
2986
2987 if (a.isRequired() && (! a.hasDefaultValue()))
2988 {
2989 hasRequired = true;
2990 }
2991
2992 final String argumentGroup = a.getArgumentGroupName();
2993 if (argumentGroup == null)
2994 {
2995 if (a.isUsageArgument())
2996 {
2997 usageArguments.add(a);
2998 }
2999 else
3000 {
3001 argumentsWithoutGroup.add(a);
3002 }
3003 }
3004 else
3005 {
3006 List<Argument> groupArgs = argumentsByGroup.get(argumentGroup);
3007 if (groupArgs == null)
3008 {
3009 groupArgs = new ArrayList<Argument>(10);
3010 argumentsByGroup.put(argumentGroup, groupArgs);
3011 }
3012
3013 groupArgs.add(a);
3014 }
3015 }
3016
3017
3018 // Iterate through the defined argument groups and display usage
3019 // information for each of them.
3020 for (final Map.Entry<String,List<Argument>> e :
3021 argumentsByGroup.entrySet())
3022 {
3023 lines.add("");
3024 lines.add(" " + e.getKey());
3025 lines.add("");
3026 for (final Argument a : e.getValue())
3027 {
3028 getArgUsage(a, lines, true, maxWidth);
3029 }
3030 }
3031
3032 if (! argumentsWithoutGroup.isEmpty())
3033 {
3034 if (argumentsByGroup.isEmpty())
3035 {
3036 for (final Argument a : argumentsWithoutGroup)
3037 {
3038 getArgUsage(a, lines, false, maxWidth);
3039 }
3040 }
3041 else
3042 {
3043 lines.add("");
3044 lines.add(" " + INFO_USAGE_UNGROUPED_ARGS.get());
3045 lines.add("");
3046 for (final Argument a : argumentsWithoutGroup)
3047 {
3048 getArgUsage(a, lines, true, maxWidth);
3049 }
3050 }
3051 }
3052
3053 if (! usageArguments.isEmpty())
3054 {
3055 if (argumentsByGroup.isEmpty())
3056 {
3057 for (final Argument a : usageArguments)
3058 {
3059 getArgUsage(a, lines, false, maxWidth);
3060 }
3061 }
3062 else
3063 {
3064 lines.add("");
3065 lines.add(" " + INFO_USAGE_USAGE_ARGS.get());
3066 lines.add("");
3067 for (final Argument a : usageArguments)
3068 {
3069 getArgUsage(a, lines, true, maxWidth);
3070 }
3071 }
3072 }
3073
3074 if (hasRequired)
3075 {
3076 lines.add("");
3077 if (argumentsByGroup.isEmpty())
3078 {
3079 lines.add("* " + INFO_USAGE_ARG_IS_REQUIRED.get());
3080 }
3081 else
3082 {
3083 lines.add(" * " + INFO_USAGE_ARG_IS_REQUIRED.get());
3084 }
3085 }
3086 }
3087
3088 return lines;
3089 }
3090
3091
3092
3093 /**
3094 * Retrieves lines that make up the usage information for the selected
3095 * subcommand.
3096 *
3097 * @param maxWidth The maximum line width to use for the output. If this is
3098 * less than or equal to zero, then no wrapping will be
3099 * performed.
3100 *
3101 * @return The lines that make up the usage information for the selected
3102 * subcommand.
3103 */
3104 private List<String> getSubCommandUsage(final int maxWidth)
3105 {
3106 // First is a description of the subcommand.
3107 final ArrayList<String> lines = new ArrayList<String>(100);
3108 lines.addAll(wrapLine(selectedSubCommand.getDescription(), maxWidth));
3109 lines.add("");
3110
3111 // Next comes the usage.
3112 lines.addAll(wrapLine(
3113 INFO_SUBCOMMAND_USAGE_OPTIONS.get(commandName,
3114 selectedSubCommand.getPrimaryName()),
3115 maxWidth));
3116
3117
3118 final ArgumentParser parser = selectedSubCommand.getArgumentParser();
3119 if (! parser.namedArgs.isEmpty())
3120 {
3121 lines.add("");
3122 lines.add(INFO_USAGE_OPTIONS_INCLUDE.get());
3123
3124
3125 // If there are any argument groups, then collect the arguments in those
3126 // groups.
3127 boolean hasRequired = false;
3128 final LinkedHashMap<String,List<Argument>> argumentsByGroup =
3129 new LinkedHashMap<String,List<Argument>>(10);
3130 final ArrayList<Argument> argumentsWithoutGroup =
3131 new ArrayList<Argument>(parser.namedArgs.size());
3132 final ArrayList<Argument> usageArguments =
3133 new ArrayList<Argument>(parser.namedArgs.size());
3134 for (final Argument a : parser.namedArgs)
3135 {
3136 if (a.isHidden())
3137 {
3138 // This argument shouldn't be included in the usage output.
3139 continue;
3140 }
3141
3142 if (a.isRequired() && (! a.hasDefaultValue()))
3143 {
3144 hasRequired = true;
3145 }
3146
3147 final String argumentGroup = a.getArgumentGroupName();
3148 if (argumentGroup == null)
3149 {
3150 if (a.isUsageArgument())
3151 {
3152 usageArguments.add(a);
3153 }
3154 else
3155 {
3156 argumentsWithoutGroup.add(a);
3157 }
3158 }
3159 else
3160 {
3161 List<Argument> groupArgs = argumentsByGroup.get(argumentGroup);
3162 if (groupArgs == null)
3163 {
3164 groupArgs = new ArrayList<Argument>(10);
3165 argumentsByGroup.put(argumentGroup, groupArgs);
3166 }
3167
3168 groupArgs.add(a);
3169 }
3170 }
3171
3172
3173 // Iterate through the defined argument groups and display usage
3174 // information for each of them.
3175 for (final Map.Entry<String,List<Argument>> e :
3176 argumentsByGroup.entrySet())
3177 {
3178 lines.add("");
3179 lines.add(" " + e.getKey());
3180 lines.add("");
3181 for (final Argument a : e.getValue())
3182 {
3183 getArgUsage(a, lines, true, maxWidth);
3184 }
3185 }
3186
3187 if (! argumentsWithoutGroup.isEmpty())
3188 {
3189 if (argumentsByGroup.isEmpty())
3190 {
3191 for (final Argument a : argumentsWithoutGroup)
3192 {
3193 getArgUsage(a, lines, false, maxWidth);
3194 }
3195 }
3196 else
3197 {
3198 lines.add("");
3199 lines.add(" " + INFO_USAGE_UNGROUPED_ARGS.get());
3200 lines.add("");
3201 for (final Argument a : argumentsWithoutGroup)
3202 {
3203 getArgUsage(a, lines, true, maxWidth);
3204 }
3205 }
3206 }
3207
3208 if (! usageArguments.isEmpty())
3209 {
3210 if (argumentsByGroup.isEmpty())
3211 {
3212 for (final Argument a : usageArguments)
3213 {
3214 getArgUsage(a, lines, false, maxWidth);
3215 }
3216 }
3217 else
3218 {
3219 lines.add("");
3220 lines.add(" " + INFO_USAGE_USAGE_ARGS.get());
3221 lines.add("");
3222 for (final Argument a : usageArguments)
3223 {
3224 getArgUsage(a, lines, true, maxWidth);
3225 }
3226 }
3227 }
3228
3229 if (hasRequired)
3230 {
3231 lines.add("");
3232 if (argumentsByGroup.isEmpty())
3233 {
3234 lines.add("* " + INFO_USAGE_ARG_IS_REQUIRED.get());
3235 }
3236 else
3237 {
3238 lines.add(" * " + INFO_USAGE_ARG_IS_REQUIRED.get());
3239 }
3240 }
3241 }
3242
3243 return lines;
3244 }
3245
3246
3247
3248 /**
3249 * Adds usage information for the provided argument to the given list.
3250 *
3251 * @param a The argument for which to get the usage information.
3252 * @param lines The list to which the resulting lines should be added.
3253 * @param indent Indicates whether to indent each line.
3254 * @param maxWidth The maximum width of each line, in characters.
3255 */
3256 private static void getArgUsage(final Argument a, final List<String> lines,
3257 final boolean indent, final int maxWidth)
3258 {
3259 final StringBuilder argLine = new StringBuilder();
3260 if (indent && (maxWidth > 10))
3261 {
3262 if (a.isRequired() && (! a.hasDefaultValue()))
3263 {
3264 argLine.append(" * ");
3265 }
3266 else
3267 {
3268 argLine.append(" ");
3269 }
3270 }
3271 else if (a.isRequired() && (! a.hasDefaultValue()))
3272 {
3273 argLine.append("* ");
3274 }
3275
3276 boolean first = true;
3277 for (final Character c : a.getShortIdentifiers())
3278 {
3279 if (first)
3280 {
3281 argLine.append('-');
3282 first = false;
3283 }
3284 else
3285 {
3286 argLine.append(", -");
3287 }
3288 argLine.append(c);
3289 }
3290
3291 for (final String s : a.getLongIdentifiers())
3292 {
3293 if (first)
3294 {
3295 argLine.append("--");
3296 first = false;
3297 }
3298 else
3299 {
3300 argLine.append(", --");
3301 }
3302 argLine.append(s);
3303 }
3304
3305 final String valuePlaceholder = a.getValuePlaceholder();
3306 if (valuePlaceholder != null)
3307 {
3308 argLine.append(' ');
3309 argLine.append(valuePlaceholder);
3310 }
3311
3312 // If we need to wrap the argument line, then align the dashes on the left
3313 // edge.
3314 int subsequentLineWidth = maxWidth - 4;
3315 if (subsequentLineWidth < 4)
3316 {
3317 subsequentLineWidth = maxWidth;
3318 }
3319 final List<String> identifierLines =
3320 wrapLine(argLine.toString(), maxWidth, subsequentLineWidth);
3321 for (int i=0; i < identifierLines.size(); i++)
3322 {
3323 if (i == 0)
3324 {
3325 lines.add(identifierLines.get(0));
3326 }
3327 else
3328 {
3329 lines.add(" " + identifierLines.get(i));
3330 }
3331 }
3332
3333
3334 // The description should be wrapped, if necessary. We'll also want to
3335 // indent it (unless someone chose an absurdly small wrap width) to make
3336 // it stand out from the argument lines.
3337 final String description = a.getDescription();
3338 if (maxWidth > 10)
3339 {
3340 final String indentString;
3341 if (indent)
3342 {
3343 indentString = " ";
3344 }
3345 else
3346 {
3347 indentString = " ";
3348 }
3349
3350 final List<String> descLines = wrapLine(description,
3351 (maxWidth-indentString.length()));
3352 for (final String s : descLines)
3353 {
3354 lines.add(indentString + s);
3355 }
3356 }
3357 else
3358 {
3359 lines.addAll(wrapLine(description, maxWidth));
3360 }
3361 }
3362
3363
3364
3365 /**
3366 * Writes usage information for this program to the provided output stream
3367 * using the UTF-8 encoding, optionally wrapping long lines.
3368 *
3369 * @param outputStream The output stream to which the usage information
3370 * should be written. It must not be {@code null}.
3371 * @param maxWidth The maximum line width to use for the output. If
3372 * this is less than or equal to zero, then no wrapping
3373 * will be performed.
3374 *
3375 * @throws IOException If an error occurs while attempting to write to the
3376 * provided output stream.
3377 */
3378 public void getUsage(final OutputStream outputStream, final int maxWidth)
3379 throws IOException
3380 {
3381 final List<String> usageLines = getUsage(maxWidth);
3382 for (final String s : usageLines)
3383 {
3384 outputStream.write(getBytes(s));
3385 outputStream.write(EOL_BYTES);
3386 }
3387 }
3388
3389
3390
3391 /**
3392 * Retrieves a string representation of the usage information.
3393 *
3394 * @param maxWidth The maximum line width to use for the output. If this is
3395 * less than or equal to zero, then no wrapping will be
3396 * performed.
3397 *
3398 * @return A string representation of the usage information
3399 */
3400 public String getUsageString(final int maxWidth)
3401 {
3402 final StringBuilder buffer = new StringBuilder();
3403 getUsageString(buffer, maxWidth);
3404 return buffer.toString();
3405 }
3406
3407
3408
3409 /**
3410 * Appends a string representation of the usage information to the provided
3411 * buffer.
3412 *
3413 * @param buffer The buffer to which the information should be appended.
3414 * @param maxWidth The maximum line width to use for the output. If this is
3415 * less than or equal to zero, then no wrapping will be
3416 * performed.
3417 */
3418 public void getUsageString(final StringBuilder buffer, final int maxWidth)
3419 {
3420 for (final String line : getUsage(maxWidth))
3421 {
3422 buffer.append(line);
3423 buffer.append(EOL);
3424 }
3425 }
3426
3427
3428
3429 /**
3430 * Retrieves a string representation of this argument parser.
3431 *
3432 * @return A string representation of this argument parser.
3433 */
3434 @Override()
3435 public String toString()
3436 {
3437 final StringBuilder buffer = new StringBuilder();
3438 toString(buffer);
3439 return buffer.toString();
3440 }
3441
3442
3443
3444 /**
3445 * Appends a string representation of this argument parser to the provided
3446 * buffer.
3447 *
3448 * @param buffer The buffer to which the information should be appended.
3449 */
3450 public void toString(final StringBuilder buffer)
3451 {
3452 buffer.append("ArgumentParser(commandName='");
3453 buffer.append(commandName);
3454 buffer.append("', commandDescription='");
3455 buffer.append(commandDescription);
3456 buffer.append("', minTrailingArgs=");
3457 buffer.append(minTrailingArgs);
3458 buffer.append("', maxTrailingArgs=");
3459 buffer.append(maxTrailingArgs);
3460
3461 if (trailingArgsPlaceholder != null)
3462 {
3463 buffer.append(", trailingArgsPlaceholder='");
3464 buffer.append(trailingArgsPlaceholder);
3465 buffer.append('\'');
3466 }
3467
3468 buffer.append("namedArgs={");
3469
3470 final Iterator<Argument> iterator = namedArgs.iterator();
3471 while (iterator.hasNext())
3472 {
3473 iterator.next().toString(buffer);
3474 if (iterator.hasNext())
3475 {
3476 buffer.append(", ");
3477 }
3478 }
3479
3480 buffer.append('}');
3481
3482 if (! subCommands.isEmpty())
3483 {
3484 buffer.append(", subCommands={");
3485
3486 final Iterator<SubCommand> subCommandIterator = subCommands.iterator();
3487 while (subCommandIterator.hasNext())
3488 {
3489 subCommandIterator.next().toString(buffer);
3490 if (subCommandIterator.hasNext())
3491 {
3492 buffer.append(", ");
3493 }
3494 }
3495
3496 buffer.append('}');
3497 }
3498
3499 buffer.append(')');
3500 }
3501 }