001 /*
002 * Copyright 2008-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2016 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.
1171 *
1172 * @param targetArgument The argument whose presence indicates that at
1173 * least one of the dependent arguments must also
1174 * be present. It must not be {@code null}.
1175 * @param dependentArguments The set of arguments from which at least one
1176 * argument must be present if the target argument
1177 * is present. It must not be {@code null} or
1178 * empty.
1179 */
1180 public void addDependentArgumentSet(final Argument targetArgument,
1181 final Collection<Argument> dependentArguments)
1182 {
1183 ensureNotNull(targetArgument, dependentArguments);
1184
1185 final LinkedHashSet<Argument> argSet =
1186 new LinkedHashSet<Argument>(dependentArguments);
1187 dependentArgumentSets.add(
1188 new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet));
1189 }
1190
1191
1192
1193 /**
1194 * Adds the provided collection of arguments as dependent upon the given
1195 * argument.
1196 *
1197 * @param targetArgument The argument whose presence indicates that at least
1198 * one of the dependent arguments must also be
1199 * present. It must not be {@code null}.
1200 * @param dependentArg1 The first argument in the set of arguments in which
1201 * at least one argument must be present if the target
1202 * argument is present. It must not be {@code null}.
1203 * @param remaining The remaining arguments in the set of arguments in
1204 * which at least one argument must be present if the
1205 * target argument is present.
1206 */
1207 public void addDependentArgumentSet(final Argument targetArgument,
1208 final Argument dependentArg1,
1209 final Argument... remaining)
1210 {
1211 ensureNotNull(targetArgument, dependentArg1);
1212
1213 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>();
1214 argSet.add(dependentArg1);
1215 argSet.addAll(Arrays.asList(remaining));
1216
1217 dependentArgumentSets.add(
1218 new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet));
1219 }
1220
1221
1222
1223 /**
1224 * Retrieves the list of exclusive argument sets for this argument parser.
1225 * If an argument contained in an exclusive argument set is provided, then
1226 * none of the other arguments in that set may be provided. It is acceptable
1227 * for none of the arguments in the set to be provided, unless the same set
1228 * of arguments is also defined as a required argument set.
1229 *
1230 * @return The list of exclusive argument sets for this argument parser.
1231 */
1232 public List<Set<Argument>> getExclusiveArgumentSets()
1233 {
1234 return Collections.unmodifiableList(exclusiveArgumentSets);
1235 }
1236
1237
1238
1239 /**
1240 * Adds the provided collection of arguments as an exclusive argument set, in
1241 * which at most one of the arguments may be provided.
1242 *
1243 * @param exclusiveArguments The collection of arguments to form an
1244 * exclusive argument set. It must not be
1245 * {@code null}.
1246 */
1247 public void addExclusiveArgumentSet(
1248 final Collection<Argument> exclusiveArguments)
1249 {
1250 ensureNotNull(exclusiveArguments);
1251 final LinkedHashSet<Argument> argSet =
1252 new LinkedHashSet<Argument>(exclusiveArguments);
1253 exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet));
1254 }
1255
1256
1257
1258 /**
1259 * Adds the provided set of arguments as an exclusive argument set, in
1260 * which at most one of the arguments may be provided.
1261 *
1262 * @param arg1 The first argument to include in the exclusive argument
1263 * set. It must not be {@code null}.
1264 * @param arg2 The second argument to include in the exclusive argument
1265 * set. It must not be {@code null}.
1266 * @param remaining Any additional arguments to include in the exclusive
1267 * argument set.
1268 */
1269 public void addExclusiveArgumentSet(final Argument arg1, final Argument arg2,
1270 final Argument... remaining)
1271 {
1272 ensureNotNull(arg1, arg2);
1273
1274 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>();
1275 argSet.add(arg1);
1276 argSet.add(arg2);
1277 argSet.addAll(Arrays.asList(remaining));
1278
1279 exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet));
1280 }
1281
1282
1283
1284 /**
1285 * Retrieves the list of required argument sets for this argument parser. At
1286 * least one of the arguments contained in this set must be provided. If this
1287 * same set is also defined as an exclusive argument set, then exactly one
1288 * of those arguments must be provided.
1289 *
1290 * @return The list of required argument sets for this argument parser.
1291 */
1292 public List<Set<Argument>> getRequiredArgumentSets()
1293 {
1294 return Collections.unmodifiableList(requiredArgumentSets);
1295 }
1296
1297
1298
1299 /**
1300 * Adds the provided collection of arguments as a required argument set, in
1301 * which at least one of the arguments must be provided.
1302 *
1303 * @param requiredArguments The collection of arguments to form an
1304 * required argument set. It must not be
1305 * {@code null}.
1306 */
1307 public void addRequiredArgumentSet(
1308 final Collection<Argument> requiredArguments)
1309 {
1310 ensureNotNull(requiredArguments);
1311 final LinkedHashSet<Argument> argSet =
1312 new LinkedHashSet<Argument>(requiredArguments);
1313 requiredArgumentSets.add(Collections.unmodifiableSet(argSet));
1314 }
1315
1316
1317
1318 /**
1319 * Adds the provided set of arguments as a required argument set, in which
1320 * at least one of the arguments must be provided.
1321 *
1322 * @param arg1 The first argument to include in the required argument
1323 * set. It must not be {@code null}.
1324 * @param arg2 The second argument to include in the required argument
1325 * set. It must not be {@code null}.
1326 * @param remaining Any additional arguments to include in the required
1327 * argument set.
1328 */
1329 public void addRequiredArgumentSet(final Argument arg1, final Argument arg2,
1330 final Argument... remaining)
1331 {
1332 ensureNotNull(arg1, arg2);
1333
1334 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>();
1335 argSet.add(arg1);
1336 argSet.add(arg2);
1337 argSet.addAll(Arrays.asList(remaining));
1338
1339 requiredArgumentSets.add(Collections.unmodifiableSet(argSet));
1340 }
1341
1342
1343
1344 /**
1345 * Indicates whether any subcommands have been registered with this argument
1346 * parser.
1347 *
1348 * @return {@code true} if one or more subcommands have been registered with
1349 * this argument parser, or {@code false} if not.
1350 */
1351 public boolean hasSubCommands()
1352 {
1353 return (! subCommands.isEmpty());
1354 }
1355
1356
1357
1358 /**
1359 * Retrieves the subcommand that was provided in the set of command-line
1360 * arguments, if any.
1361 *
1362 * @return The subcommand that was provided in the set of command-line
1363 * arguments, or {@code null} if there is none.
1364 */
1365 public SubCommand getSelectedSubCommand()
1366 {
1367 return selectedSubCommand;
1368 }
1369
1370
1371
1372 /**
1373 * Specifies the subcommand that was provided in the set of command-line
1374 * arguments.
1375 *
1376 * @param subcommand The subcommand that was provided in the set of
1377 * command-line arguments. It may be {@code null} if no
1378 * subcommand should be used.
1379 */
1380 void setSelectedSubCommand(final SubCommand subcommand)
1381 {
1382 selectedSubCommand = subcommand;
1383 if (subcommand != null)
1384 {
1385 subcommand.setPresent();
1386 }
1387 }
1388
1389
1390
1391 /**
1392 * Retrieves a list of all subcommands associated with this argument parser.
1393 *
1394 * @return A list of all subcommands associated with this argument parser, or
1395 * an empty list if there are no associated subcommands.
1396 */
1397 public List<SubCommand> getSubCommands()
1398 {
1399 return Collections.unmodifiableList(subCommands);
1400 }
1401
1402
1403
1404 /**
1405 * Retrieves the subcommand for the provided name.
1406 *
1407 * @param name The name of the subcommand to retrieve.
1408 *
1409 * @return The subcommand with the provided name, or {@code null} if there is
1410 * no such subcommand.
1411 */
1412 public SubCommand getSubCommand(final String name)
1413 {
1414 if (name == null)
1415 {
1416 return null;
1417 }
1418
1419 return subCommandsByName.get(toLowerCase(name));
1420 }
1421
1422
1423
1424 /**
1425 * Registers the provided subcommand with this argument parser.
1426 *
1427 * @param subCommand The subcommand to register with this argument parser.
1428 * It must not be {@code null}.
1429 *
1430 * @throws ArgumentException If this argument parser does not allow
1431 * subcommands, if there is a conflict between any
1432 * of the names of the provided subcommand and an
1433 * already-registered subcommand, or if there is a
1434 * conflict between any of the subcommand-specific
1435 * arguments and global arguments.
1436 */
1437 public void addSubCommand(final SubCommand subCommand)
1438 throws ArgumentException
1439 {
1440 // Ensure that the subcommand isn't already registered with an argument
1441 // parser.
1442 if (subCommand.getGlobalArgumentParser() != null)
1443 {
1444 throw new ArgumentException(
1445 ERR_PARSER_SUBCOMMAND_ALREADY_REGISTERED_WITH_PARSER.get());
1446 }
1447
1448 // Ensure that the caller isn't trying to create a nested subcommand.
1449 if (this.parentSubCommand != null)
1450 {
1451 throw new ArgumentException(
1452 ERR_PARSER_CANNOT_CREATE_NESTED_SUBCOMMAND.get(
1453 this.parentSubCommand.getPrimaryName()));
1454 }
1455
1456 // Ensure that this argument parser doesn't allow trailing arguments.
1457 if (allowsTrailingArguments())
1458 {
1459 throw new ArgumentException(
1460 ERR_PARSER_WITH_TRAILING_ARGS_CANNOT_HAVE_SUBCOMMANDS.get());
1461 }
1462
1463 // Ensure that the subcommand doesn't have any names that conflict with an
1464 // existing subcommand.
1465 for (final String name : subCommand.getNames())
1466 {
1467 if (subCommandsByName.containsKey(toLowerCase(name)))
1468 {
1469 throw new ArgumentException(
1470 ERR_SUBCOMMAND_NAME_ALREADY_IN_USE.get(name));
1471 }
1472 }
1473
1474 // Register the subcommand.
1475 for (final String name : subCommand.getNames())
1476 {
1477 subCommandsByName.put(toLowerCase(name), subCommand);
1478 }
1479 subCommands.add(subCommand);
1480 subCommand.setGlobalArgumentParser(this);
1481 }
1482
1483
1484
1485 /**
1486 * Registers the provided additional name for this subcommand.
1487 *
1488 * @param name The name to be registered. It must not be
1489 * {@code null} or empty.
1490 * @param subCommand The subcommand with which the name is associated. It
1491 * must not be {@code null}.
1492 *
1493 * @throws ArgumentException If the provided name is already in use.
1494 */
1495 void addSubCommand(final String name, final SubCommand subCommand)
1496 throws ArgumentException
1497 {
1498 final String lowerName = toLowerCase(name);
1499 if (subCommandsByName.containsKey(lowerName))
1500 {
1501 throw new ArgumentException(
1502 ERR_SUBCOMMAND_NAME_ALREADY_IN_USE.get(name));
1503 }
1504
1505 subCommandsByName.put(lowerName, subCommand);
1506 }
1507
1508
1509
1510 /**
1511 * Retrieves the set of unnamed trailing arguments in the provided command
1512 * line arguments.
1513 *
1514 * @return The set of unnamed trailing arguments in the provided command line
1515 * arguments, or an empty list if there were none.
1516 */
1517 public List<String> getTrailingArguments()
1518 {
1519 return Collections.unmodifiableList(trailingArgs);
1520 }
1521
1522
1523
1524 /**
1525 * Clears the set of trailing arguments for this argument parser.
1526 */
1527 void resetTrailingArguments()
1528 {
1529 trailingArgs.clear();
1530 }
1531
1532
1533
1534 /**
1535 * Adds the provided value to the set of trailing arguments.
1536 *
1537 * @param value The value to add to the set of trailing arguments.
1538 *
1539 * @throws ArgumentException If the parser already has the maximum allowed
1540 * number of trailing arguments.
1541 */
1542 void addTrailingArgument(final String value)
1543 throws ArgumentException
1544 {
1545 if ((maxTrailingArgs > 0) && (trailingArgs.size() >= maxTrailingArgs))
1546 {
1547 throw new ArgumentException(ERR_PARSER_TOO_MANY_TRAILING_ARGS.get(value,
1548 commandName, maxTrailingArgs));
1549 }
1550
1551 trailingArgs.add(value);
1552 }
1553
1554
1555
1556 /**
1557 * Retrieves the properties file that was used to obtain values for arguments
1558 * not set on the command line.
1559 *
1560 * @return The properties file that was used to obtain values for arguments
1561 * not set on the command line, or {@code null} if no properties file
1562 * was used.
1563 */
1564 public File getPropertiesFileUsed()
1565 {
1566 return propertiesFileUsed;
1567 }
1568
1569
1570
1571 /**
1572 * Retrieves a list of the string representations of any arguments used for
1573 * the associated tool that were set from a properties file rather than
1574 * provided on the command line. The values of any arguments marked as
1575 * sensitive will be obscured.
1576 *
1577 * @return A list of the string representations any arguments used for the
1578 * associated tool that were set from a properties file rather than
1579 * provided on the command line, or an empty list if no arguments
1580 * were set from a properties file.
1581 */
1582 public List<String> getArgumentsSetFromPropertiesFile()
1583 {
1584 return Collections.unmodifiableList(argumentsSetFromPropertiesFile);
1585 }
1586
1587
1588
1589 /**
1590 * Creates a copy of this argument parser that is "clean" and appears as if it
1591 * has not been used to parse an argument set. The new parser will have all
1592 * of the same arguments and constraints as this parser.
1593 *
1594 * @return The "clean" copy of this argument parser.
1595 */
1596 public ArgumentParser getCleanCopy()
1597 {
1598 return new ArgumentParser(this, null);
1599 }
1600
1601
1602
1603 /**
1604 * Parses the provided set of arguments.
1605 *
1606 * @param args An array containing the argument information to parse. It
1607 * must not be {@code null}.
1608 *
1609 * @throws ArgumentException If a problem occurs while attempting to parse
1610 * the argument information.
1611 */
1612 public void parse(final String[] args)
1613 throws ArgumentException
1614 {
1615 // Iterate through the provided args strings and process them.
1616 ArgumentParser subCommandParser = null;
1617 boolean inTrailingArgs = false;
1618 boolean skipFinalValidation = false;
1619 String subCommandName = null;
1620 for (int i=0; i < args.length; i++)
1621 {
1622 final String s = args[i];
1623
1624 if (inTrailingArgs)
1625 {
1626 if (maxTrailingArgs == 0)
1627 {
1628 throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get(
1629 s, commandName));
1630 }
1631 else if (trailingArgs.size() >= maxTrailingArgs)
1632 {
1633 throw new ArgumentException(ERR_PARSER_TOO_MANY_TRAILING_ARGS.get(s,
1634 commandName, maxTrailingArgs));
1635 }
1636 else
1637 {
1638 trailingArgs.add(s);
1639 }
1640 }
1641 else if (s.equals("--"))
1642 {
1643 // This signifies the end of the named arguments and the beginning of
1644 // the trailing arguments.
1645 inTrailingArgs = true;
1646 }
1647 else if (s.startsWith("--"))
1648 {
1649 // There may be an equal sign to separate the name from the value.
1650 final String argName;
1651 final int equalPos = s.indexOf('=');
1652 if (equalPos > 0)
1653 {
1654 argName = s.substring(2, equalPos);
1655 }
1656 else
1657 {
1658 argName = s.substring(2);
1659 }
1660
1661 final String lowerName = toLowerCase(argName);
1662 Argument a = namedArgsByLongID.get(lowerName);
1663 if ((a == null) && (subCommandParser != null))
1664 {
1665 a = subCommandParser.namedArgsByLongID.get(lowerName);
1666 }
1667
1668 if (a == null)
1669 {
1670 throw new ArgumentException(ERR_PARSER_NO_SUCH_LONG_ID.get(argName));
1671 }
1672 else if (a.isUsageArgument())
1673 {
1674 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
1675 }
1676
1677 a.incrementOccurrences();
1678 if (a.takesValue())
1679 {
1680 if (equalPos > 0)
1681 {
1682 a.addValue(s.substring(equalPos+1));
1683 }
1684 else
1685 {
1686 i++;
1687 if (i >= args.length)
1688 {
1689 throw new ArgumentException(ERR_PARSER_LONG_ARG_MISSING_VALUE.get(
1690 argName));
1691 }
1692 else
1693 {
1694 a.addValue(args[i]);
1695 }
1696 }
1697 }
1698 else
1699 {
1700 if (equalPos > 0)
1701 {
1702 throw new ArgumentException(
1703 ERR_PARSER_LONG_ARG_DOESNT_TAKE_VALUE.get(argName));
1704 }
1705 }
1706 }
1707 else if (s.startsWith("-"))
1708 {
1709 if (s.length() == 1)
1710 {
1711 throw new ArgumentException(ERR_PARSER_UNEXPECTED_DASH.get());
1712 }
1713 else if (s.length() == 2)
1714 {
1715 final char c = s.charAt(1);
1716
1717 Argument a = namedArgsByShortID.get(c);
1718 if ((a == null) && (subCommandParser != null))
1719 {
1720 a = subCommandParser.namedArgsByShortID.get(c);
1721 }
1722
1723 if (a == null)
1724 {
1725 throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c));
1726 }
1727 else if (a.isUsageArgument())
1728 {
1729 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
1730 }
1731
1732 a.incrementOccurrences();
1733 if (a.takesValue())
1734 {
1735 i++;
1736 if (i >= args.length)
1737 {
1738 throw new ArgumentException(
1739 ERR_PARSER_SHORT_ARG_MISSING_VALUE.get(c));
1740 }
1741 else
1742 {
1743 a.addValue(args[i]);
1744 }
1745 }
1746 }
1747 else
1748 {
1749 char c = s.charAt(1);
1750 Argument a = namedArgsByShortID.get(c);
1751 if ((a == null) && (subCommandParser != null))
1752 {
1753 a = subCommandParser.namedArgsByShortID.get(c);
1754 }
1755
1756 if (a == null)
1757 {
1758 throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c));
1759 }
1760 else if (a.isUsageArgument())
1761 {
1762 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
1763 }
1764
1765 a.incrementOccurrences();
1766 if (a.takesValue())
1767 {
1768 a.addValue(s.substring(2));
1769 }
1770 else
1771 {
1772 // The rest of the characters in the string must also resolve to
1773 // arguments that don't take values.
1774 for (int j=2; j < s.length(); j++)
1775 {
1776 c = s.charAt(j);
1777 a = namedArgsByShortID.get(c);
1778 if ((a == null) && (subCommandParser != null))
1779 {
1780 a = subCommandParser.namedArgsByShortID.get(c);
1781 }
1782
1783 if (a == null)
1784 {
1785 throw new ArgumentException(
1786 ERR_PARSER_NO_SUBSEQUENT_SHORT_ARG.get(c, s));
1787 }
1788 else if (a.isUsageArgument())
1789 {
1790 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
1791 }
1792
1793 a.incrementOccurrences();
1794 if (a.takesValue())
1795 {
1796 throw new ArgumentException(
1797 ERR_PARSER_SUBSEQUENT_SHORT_ARG_TAKES_VALUE.get(
1798 c, s));
1799 }
1800 }
1801 }
1802 }
1803 }
1804 else if (subCommands.isEmpty())
1805 {
1806 inTrailingArgs = true;
1807 if (maxTrailingArgs == 0)
1808 {
1809 throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get(
1810 s, commandName));
1811 }
1812 else
1813 {
1814 trailingArgs.add(s);
1815 }
1816 }
1817 else
1818 {
1819 if (selectedSubCommand == null)
1820 {
1821 subCommandName = s;
1822 selectedSubCommand = subCommandsByName.get(toLowerCase(s));
1823 if (selectedSubCommand == null)
1824 {
1825 throw new ArgumentException(ERR_PARSER_NO_SUCH_SUBCOMMAND.get(s,
1826 commandName));
1827 }
1828 else
1829 {
1830 selectedSubCommand.setPresent();
1831 subCommandParser = selectedSubCommand.getArgumentParser();
1832 }
1833 }
1834 else
1835 {
1836 throw new ArgumentException(ERR_PARSER_CONFLICTING_SUBCOMMANDS.get(
1837 subCommandName, s));
1838 }
1839 }
1840 }
1841
1842
1843 // Perform any appropriate processing related to the use of a properties
1844 // file.
1845 if (! handlePropertiesFile())
1846 {
1847 return;
1848 }
1849
1850
1851 // If a usage argument was provided, then no further validation should be
1852 // performed.
1853 if (skipFinalValidation)
1854 {
1855 return;
1856 }
1857
1858
1859 // If any subcommands are defined, then one must have been provided.
1860 if ((! subCommands.isEmpty()) && (selectedSubCommand == null))
1861 {
1862 throw new ArgumentException(
1863 ERR_PARSER_MISSING_SUBCOMMAND.get(commandName));
1864 }
1865
1866
1867 doFinalValidation(this);
1868 if (selectedSubCommand != null)
1869 {
1870 doFinalValidation(selectedSubCommand.getArgumentParser());
1871 }
1872 }
1873
1874
1875
1876 /**
1877 * Performs the final validation for the provided argument parser.
1878 *
1879 * @param parser The argument parser for which to perform the final
1880 * validation.
1881 *
1882 * @throws ArgumentException If a validation problem is encountered.
1883 */
1884 private static void doFinalValidation(final ArgumentParser parser)
1885 throws ArgumentException
1886 {
1887 // Make sure that all required arguments have values.
1888 for (final Argument a : parser.namedArgs)
1889 {
1890 if (a.isRequired() && (! a.isPresent()))
1891 {
1892 throw new ArgumentException(ERR_PARSER_MISSING_REQUIRED_ARG.get(
1893 a.getIdentifierString()));
1894 }
1895 }
1896
1897
1898 // Make sure that at least the minimum number of trailing arguments were
1899 // provided.
1900 if (parser.trailingArgs.size() < parser.minTrailingArgs)
1901 {
1902 throw new ArgumentException(ERR_PARSER_NOT_ENOUGH_TRAILING_ARGS.get(
1903 parser.commandName, parser.minTrailingArgs,
1904 parser.trailingArgsPlaceholder));
1905 }
1906
1907
1908 // Make sure that there are no dependent argument set conflicts.
1909 for (final ObjectPair<Argument,Set<Argument>> p :
1910 parser.dependentArgumentSets)
1911 {
1912 final Argument targetArg = p.getFirst();
1913 if (targetArg.getNumOccurrences() > 0)
1914 {
1915 final Set<Argument> argSet = p.getSecond();
1916 boolean found = false;
1917 for (final Argument a : argSet)
1918 {
1919 if (a.getNumOccurrences() > 0)
1920 {
1921 found = true;
1922 break;
1923 }
1924 }
1925
1926 if (! found)
1927 {
1928 if (argSet.size() == 1)
1929 {
1930 throw new ArgumentException(
1931 ERR_PARSER_DEPENDENT_CONFLICT_SINGLE.get(
1932 targetArg.getIdentifierString(),
1933 argSet.iterator().next().getIdentifierString()));
1934 }
1935 else
1936 {
1937 boolean first = true;
1938 final StringBuilder buffer = new StringBuilder();
1939 for (final Argument a : argSet)
1940 {
1941 if (first)
1942 {
1943 first = false;
1944 }
1945 else
1946 {
1947 buffer.append(", ");
1948 }
1949 buffer.append(a.getIdentifierString());
1950 }
1951 throw new ArgumentException(
1952 ERR_PARSER_DEPENDENT_CONFLICT_MULTIPLE.get(
1953 targetArg.getIdentifierString(), buffer.toString()));
1954 }
1955 }
1956 }
1957 }
1958
1959
1960 // Make sure that there are no exclusive argument set conflicts.
1961 for (final Set<Argument> argSet : parser.exclusiveArgumentSets)
1962 {
1963 Argument setArg = null;
1964 for (final Argument a : argSet)
1965 {
1966 if (a.getNumOccurrences() > 0)
1967 {
1968 if (setArg == null)
1969 {
1970 setArg = a;
1971 }
1972 else
1973 {
1974 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
1975 setArg.getIdentifierString(),
1976 a.getIdentifierString()));
1977 }
1978 }
1979 }
1980 }
1981
1982 // Make sure that there are no required argument set conflicts.
1983 for (final Set<Argument> argSet : parser.requiredArgumentSets)
1984 {
1985 boolean found = false;
1986 for (final Argument a : argSet)
1987 {
1988 if (a.getNumOccurrences() > 0)
1989 {
1990 found = true;
1991 break;
1992 }
1993 }
1994
1995 if (! found)
1996 {
1997 boolean first = true;
1998 final StringBuilder buffer = new StringBuilder();
1999 for (final Argument a : argSet)
2000 {
2001 if (first)
2002 {
2003 first = false;
2004 }
2005 else
2006 {
2007 buffer.append(", ");
2008 }
2009 buffer.append(a.getIdentifierString());
2010 }
2011 throw new ArgumentException(ERR_PARSER_REQUIRED_CONFLICT.get(
2012 buffer.toString()));
2013 }
2014 }
2015 }
2016
2017
2018
2019 /**
2020 * Indicates whether the provided argument is one that indicates that the
2021 * parser should skip all validation except that performed when assigning
2022 * values from command-line arguments. Validation that will be skipped
2023 * includes ensuring that all required arguments have values, ensuring that
2024 * the minimum number of trailing arguments were provided, and ensuring that
2025 * there were no dependent/exclusive/required argument set conflicts.
2026 *
2027 * @param a The argument for which to make the determination.
2028 *
2029 * @return {@code true} if the provided argument is one that indicates that
2030 * final validation should be skipped, or {@code false} if not.
2031 */
2032 private static boolean skipFinalValidationBecauseOfArgument(final Argument a)
2033 {
2034 // We will skip final validation for all usage arguments except the
2035 // propertiesFilePath and noPropertiesFile arguments.
2036 if (ARG_NAME_PROPERTIES_FILE_PATH.equals(a.getLongIdentifier()) ||
2037 ARG_NAME_NO_PROPERTIES_FILE.equals(a.getLongIdentifier()) ||
2038 ARG_NAME_OUTPUT_FILE.equals(a.getLongIdentifier()) ||
2039 ARG_NAME_TEE_OUTPUT.equals(a.getLongIdentifier()))
2040 {
2041 return false;
2042 }
2043
2044 return a.isUsageArgument();
2045 }
2046
2047
2048
2049 /**
2050 * Performs any appropriate properties file processing for this argument
2051 * parser.
2052 *
2053 * @return {@code true} if the tool should continue processing, or
2054 * {@code false} if it should return immediately.
2055 *
2056 * @throws ArgumentException If a problem is encountered while attempting
2057 * to parse a properties file or update arguments
2058 * with the values contained in it.
2059 */
2060 private boolean handlePropertiesFile()
2061 throws ArgumentException
2062 {
2063 final BooleanArgument noPropertiesFile;
2064 final FileArgument generatePropertiesFile;
2065 final FileArgument propertiesFilePath;
2066 try
2067 {
2068 propertiesFilePath = getFileArgument(ARG_NAME_PROPERTIES_FILE_PATH);
2069 generatePropertiesFile =
2070 getFileArgument(ARG_NAME_GENERATE_PROPERTIES_FILE);
2071 noPropertiesFile = getBooleanArgument(ARG_NAME_NO_PROPERTIES_FILE);
2072 }
2073 catch (final Exception e)
2074 {
2075 Debug.debugException(e);
2076
2077 // This should only ever happen if the argument parser has an argument
2078 // with a name that conflicts with one of the properties file arguments
2079 // but isn't of the right type. In this case, we'll assume that no
2080 // properties file will be used.
2081 return true;
2082 }
2083
2084
2085 // If any of the properties file arguments isn't defined, then we'll assume
2086 // that no properties file will be used.
2087 if ((propertiesFilePath == null) || (generatePropertiesFile == null) ||
2088 (noPropertiesFile == null))
2089 {
2090 return true;
2091 }
2092
2093
2094 // If the noPropertiesFile argument is present, then don't do anything but
2095 // make sure that neither of the other arguments was specified.
2096 if (noPropertiesFile.isPresent())
2097 {
2098 if (propertiesFilePath.isPresent())
2099 {
2100 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
2101 noPropertiesFile.getIdentifierString(),
2102 propertiesFilePath.getIdentifierString()));
2103 }
2104 else if (generatePropertiesFile.isPresent())
2105 {
2106 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
2107 noPropertiesFile.getIdentifierString(),
2108 generatePropertiesFile.getIdentifierString()));
2109 }
2110 else
2111 {
2112 return true;
2113 }
2114 }
2115
2116
2117 // If the generatePropertiesFile argument is present, then make sure the
2118 // propertiesFilePath argument is not set and generate the output.
2119 if (generatePropertiesFile.isPresent())
2120 {
2121 if (propertiesFilePath.isPresent())
2122 {
2123 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
2124 generatePropertiesFile.getIdentifierString(),
2125 propertiesFilePath.getIdentifierString()));
2126 }
2127 else
2128 {
2129 generatePropertiesFile(
2130 generatePropertiesFile.getValue().getAbsolutePath());
2131 return false;
2132 }
2133 }
2134
2135
2136 // If the propertiesFilePath argument is present, then try to make use of
2137 // the specified file.
2138 if (propertiesFilePath.isPresent())
2139 {
2140 final File propertiesFile = propertiesFilePath.getValue();
2141 if (propertiesFile.exists() && propertiesFile.isFile())
2142 {
2143 handlePropertiesFile(propertiesFilePath.getValue());
2144 }
2145 else
2146 {
2147 throw new ArgumentException(
2148 ERR_PARSER_NO_SUCH_PROPERTIES_FILE.get(
2149 propertiesFilePath.getIdentifierString(),
2150 propertiesFile.getAbsolutePath()));
2151 }
2152 return true;
2153 }
2154
2155
2156 // We may still use a properties file if the path was specified in either a
2157 // JVM property or an environment variable. If both are defined, the JVM
2158 // property will take precedence. If a property or environment variable
2159 // specifies an invalid value, then we'll just ignore it.
2160 String path = System.getProperty(PROPERTY_DEFAULT_PROPERTIES_FILE_PATH);
2161 if (path == null)
2162 {
2163 path = System.getenv(ENV_DEFAULT_PROPERTIES_FILE_PATH);
2164 }
2165
2166 if (path != null)
2167 {
2168 final File propertiesFile = new File(path);
2169 if (propertiesFile.exists() && propertiesFile.isFile())
2170 {
2171 handlePropertiesFile(propertiesFile);
2172 }
2173 }
2174
2175 return true;
2176 }
2177
2178
2179
2180 /**
2181 * Write an empty properties file for this argument parser to the specified
2182 * path.
2183 *
2184 * @param path The path to the properties file to be written.
2185 *
2186 * @throws ArgumentException If a problem is encountered while writing the
2187 * properties file.
2188 */
2189 private void generatePropertiesFile(final String path)
2190 throws ArgumentException
2191 {
2192 final PrintWriter w;
2193 try
2194 {
2195 w = new PrintWriter(path);
2196 }
2197 catch (final Exception e)
2198 {
2199 Debug.debugException(e);
2200 throw new ArgumentException(
2201 ERR_PARSER_GEN_PROPS_CANNOT_OPEN_FILE.get(path,
2202 getExceptionMessage(e)),
2203 e);
2204 }
2205
2206 try
2207 {
2208 wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_1.get(commandName));
2209 w.println('#');
2210 wrapComment(w,
2211 INFO_PARSER_GEN_PROPS_HEADER_2.get(commandName,
2212 ARG_NAME_PROPERTIES_FILE_PATH,
2213 PROPERTY_DEFAULT_PROPERTIES_FILE_PATH,
2214 ENV_DEFAULT_PROPERTIES_FILE_PATH, ARG_NAME_NO_PROPERTIES_FILE));
2215 w.println('#');
2216 wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_3.get());
2217 w.println('#');
2218
2219 wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_4.get());
2220 w.println('#');
2221 wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_5.get(commandName));
2222
2223 for (final Argument a : getNamedArguments())
2224 {
2225 writeArgumentProperties(w, null, a);
2226 }
2227
2228 for (final SubCommand sc : getSubCommands())
2229 {
2230 for (final Argument a : sc.getArgumentParser().getNamedArguments())
2231 {
2232 writeArgumentProperties(w, sc, a);
2233 }
2234 }
2235 }
2236 finally
2237 {
2238 w.close();
2239 }
2240 }
2241
2242
2243
2244 /**
2245 * Writes information about the provided argument to the given writer.
2246 *
2247 * @param w The writer to which the properties should be written. It must
2248 * not be {@code null}.
2249 * @param sc The subcommand with which the argument is associated. It may
2250 * be {@code null} if the provided argument is a global argument.
2251 * @param a The argument for which to write the properties. It must not be
2252 * {@code null}.
2253 */
2254 private void writeArgumentProperties(final PrintWriter w,
2255 final SubCommand sc,
2256 final Argument a)
2257 {
2258 if (a.isUsageArgument() || a.isHidden())
2259 {
2260 return;
2261 }
2262
2263 w.println();
2264 w.println();
2265 wrapComment(w, a.getDescription());
2266 w.println('#');
2267
2268 final String constraints = a.getValueConstraints();
2269 if ((constraints != null) && (constraints.length() > 0) &&
2270 (! (a instanceof BooleanArgument)))
2271 {
2272 wrapComment(w, constraints);
2273 w.println('#');
2274 }
2275
2276 final String identifier;
2277 if (a.getLongIdentifier() != null)
2278 {
2279 identifier = a.getLongIdentifier();
2280 }
2281 else
2282 {
2283 identifier = a.getIdentifierString();
2284 }
2285
2286 String placeholder = a.getValuePlaceholder();
2287 if (placeholder == null)
2288 {
2289 if (a instanceof BooleanArgument)
2290 {
2291 placeholder = "{true|false}";
2292 }
2293 else
2294 {
2295 placeholder = "";
2296 }
2297 }
2298
2299 final String propertyName;
2300 if (sc == null)
2301 {
2302 propertyName = commandName + '.' + identifier;
2303 }
2304 else
2305 {
2306 propertyName = commandName + '.' + sc.getPrimaryName() + '.' + identifier;
2307 }
2308
2309 w.println("# " + propertyName + '=' + placeholder);
2310
2311 if (a.isPresent())
2312 {
2313 for (final String s : a.getValueStringRepresentations(false))
2314 {
2315 w.println(propertyName + '=' + s);
2316 }
2317 }
2318 }
2319
2320
2321
2322 /**
2323 * Wraps the given string and writes it as a comment to the provided writer.
2324 *
2325 * @param w The writer to use to write the wrapped and commented string.
2326 * @param s The string to be wrapped and written.
2327 */
2328 private static void wrapComment(final PrintWriter w, final String s)
2329 {
2330 for (final String line : wrapLine(s, 77))
2331 {
2332 w.println("# " + line);
2333 }
2334 }
2335
2336
2337
2338 /**
2339 * Reads the contents of the specified properties file and updates the
2340 * configured arguments as appropriate.
2341 *
2342 * @param propertiesFile The properties file to process.
2343 *
2344 * @throws ArgumentException If a problem is encountered while examining the
2345 * properties file, or while trying to assign a
2346 * property value to a corresponding argument.
2347 */
2348 private void handlePropertiesFile(final File propertiesFile)
2349 throws ArgumentException
2350 {
2351 final BufferedReader reader;
2352 try
2353 {
2354 reader = new BufferedReader(new FileReader(propertiesFile));
2355 }
2356 catch (final Exception e)
2357 {
2358 Debug.debugException(e);
2359 throw new ArgumentException(
2360 ERR_PARSER_CANNOT_OPEN_PROP_FILE.get(
2361 propertiesFile.getAbsolutePath(), getExceptionMessage(e)),
2362 e);
2363 }
2364
2365 try
2366 {
2367 // Read all of the lines of the file, ignoring comments and unwrapping
2368 // properties that span multiple lines.
2369 boolean lineIsContinued = false;
2370 int lineNumber = 0;
2371 final ArrayList<ObjectPair<Integer,StringBuilder>> propertyLines =
2372 new ArrayList<ObjectPair<Integer,StringBuilder>>(10);
2373 while (true)
2374 {
2375 String line;
2376 try
2377 {
2378 line = reader.readLine();
2379 lineNumber++;
2380 }
2381 catch (final Exception e)
2382 {
2383 Debug.debugException(e);
2384 throw new ArgumentException(
2385 ERR_PARSER_ERROR_READING_PROP_FILE.get(
2386 propertiesFile.getAbsolutePath(), getExceptionMessage(e)),
2387 e);
2388 }
2389
2390
2391 // If the line is null, then we've reached the end of the file. If we
2392 // expect a previous line to have been continued, then this is an error.
2393 if (line == null)
2394 {
2395 if (lineIsContinued)
2396 {
2397 throw new ArgumentException(
2398 ERR_PARSER_PROP_FILE_MISSING_CONTINUATION.get(
2399 (lineNumber-1), propertiesFile.getAbsolutePath()));
2400 }
2401 break;
2402 }
2403
2404
2405 // See if the line has any leading whitespace, and if so then trim it
2406 // off. If there is leading whitespace, then make sure that we expect
2407 // the previous line to be continued.
2408 final int initialLength = line.length();
2409 line = trimLeading(line);
2410 final boolean hasLeadingWhitespace = (line.length() < initialLength);
2411 if (hasLeadingWhitespace && (! lineIsContinued))
2412 {
2413 throw new ArgumentException(
2414 ERR_PARSER_PROP_FILE_UNEXPECTED_LEADING_SPACE.get(
2415 propertiesFile.getAbsolutePath(), lineNumber));
2416 }
2417
2418
2419 // If the line is empty or starts with "#", then skip it. But make sure
2420 // we didn't expect the previous line to be continued.
2421 if ((line.length() == 0) || line.startsWith("#"))
2422 {
2423 if (lineIsContinued)
2424 {
2425 throw new ArgumentException(
2426 ERR_PARSER_PROP_FILE_MISSING_CONTINUATION.get(
2427 (lineNumber-1), propertiesFile.getAbsolutePath()));
2428 }
2429 continue;
2430 }
2431
2432
2433 // See if the line ends with a backslash and if so then trim it off.
2434 final boolean hasTrailingBackslash = line.endsWith("\\");
2435 if (line.endsWith("\\"))
2436 {
2437 line = line.substring(0, (line.length() - 1));
2438 }
2439
2440
2441 // If the previous line needs to be continued, then append the new line
2442 // to it. Otherwise, add it as a new line.
2443 if (lineIsContinued)
2444 {
2445 propertyLines.get(propertyLines.size() - 1).getSecond().append(line);
2446 }
2447 else
2448 {
2449 propertyLines.add(new ObjectPair<Integer,StringBuilder>(lineNumber,
2450 new StringBuilder(line)));
2451 }
2452
2453 lineIsContinued = hasTrailingBackslash;
2454 }
2455
2456
2457 // Parse all of the lines into a map of identifiers and their
2458 // corresponding values.
2459 propertiesFileUsed = propertiesFile;
2460 if (propertyLines.isEmpty())
2461 {
2462 return;
2463 }
2464
2465 final HashMap<String,ArrayList<String>> propertyMap =
2466 new HashMap<String,ArrayList<String>>(propertyLines.size());
2467 for (final ObjectPair<Integer,StringBuilder> p : propertyLines)
2468 {
2469 final String line = p.getSecond().toString();
2470 final int equalPos = line.indexOf('=');
2471 if (equalPos <= 0)
2472 {
2473 throw new ArgumentException(ERR_PARSER_MALFORMED_PROP_LINE.get(
2474 propertiesFile.getAbsolutePath(), p.getFirst(), line));
2475 }
2476
2477 final String propertyName = line.substring(0, equalPos).trim();
2478 final String propertyValue = line.substring(equalPos+1).trim();
2479 if (propertyValue.length() == 0)
2480 {
2481 // The property doesn't have a value, so we can ignore it.
2482 continue;
2483 }
2484
2485
2486 // An argument can have multiple identifiers, and we will allow any of
2487 // them to be used to reference it. To deal with this, we'll map the
2488 // argument identifier to its corresponding argument and then use the
2489 // preferred identifier for that argument in the map. The same applies
2490 // to subcommand names.
2491 boolean prefixedWithToolName = false;
2492 boolean prefixedWithSubCommandName = false;
2493 Argument a = getNamedArgument(propertyName);
2494 if (a == null)
2495 {
2496 // It could be that the argument name was prefixed with the tool name.
2497 // Check to see if that was the case.
2498 if (propertyName.startsWith(commandName + '.'))
2499 {
2500 prefixedWithToolName = true;
2501
2502 String basePropertyName =
2503 propertyName.substring(commandName.length()+1);
2504 a = getNamedArgument(basePropertyName);
2505
2506 if (a == null)
2507 {
2508 final int periodPos = basePropertyName.indexOf('.');
2509 if (periodPos > 0)
2510 {
2511 final String subCommandName =
2512 basePropertyName.substring(0, periodPos);
2513 if ((selectedSubCommand != null) &&
2514 selectedSubCommand.hasName(subCommandName))
2515 {
2516 prefixedWithSubCommandName = true;
2517 basePropertyName = basePropertyName.substring(periodPos+1);
2518 a = selectedSubCommand.getArgumentParser().getNamedArgument(
2519 basePropertyName);
2520 }
2521 }
2522 else if (selectedSubCommand != null)
2523 {
2524 a = selectedSubCommand.getArgumentParser().getNamedArgument(
2525 basePropertyName);
2526 }
2527 }
2528 }
2529 else if (selectedSubCommand != null)
2530 {
2531 a = selectedSubCommand.getArgumentParser().getNamedArgument(
2532 propertyName);
2533 }
2534 }
2535
2536 if (a == null)
2537 {
2538 // This could mean that there's a typo in the property name, but it's
2539 // more likely the case that the property is for a different tool. In
2540 // either case, we'll ignore it.
2541 continue;
2542 }
2543
2544 final String canonicalPropertyName;
2545 if (prefixedWithToolName)
2546 {
2547 if (prefixedWithSubCommandName)
2548 {
2549 canonicalPropertyName = commandName + '.' +
2550 selectedSubCommand.getPrimaryName() + '.' +
2551 a.getIdentifierString();
2552 }
2553 else
2554 {
2555 canonicalPropertyName = commandName + '.' + a.getIdentifierString();
2556 }
2557 }
2558 else
2559 {
2560 canonicalPropertyName = a.getIdentifierString();
2561 }
2562
2563 ArrayList<String> valueList = propertyMap.get(canonicalPropertyName);
2564 if (valueList == null)
2565 {
2566 valueList = new ArrayList<String>(5);
2567 propertyMap.put(canonicalPropertyName, valueList);
2568 }
2569 valueList.add(propertyValue);
2570 }
2571
2572
2573 // Iterate through all of the named arguments for the argument parser and
2574 // see if we should use the properties to assign values to any of the
2575 // arguments that weren't provided on the command line.
2576 setArgsFromPropertiesFile(propertyMap, false);
2577
2578
2579 // If there is a selected subcommand, then iterate through all of its
2580 // arguments.
2581 if (selectedSubCommand != null)
2582 {
2583 setArgsFromPropertiesFile(propertyMap, true);
2584 }
2585 }
2586 finally
2587 {
2588 try
2589 {
2590 reader.close();
2591 }
2592 catch (final Exception e)
2593 {
2594 Debug.debugException(e);
2595 }
2596 }
2597 }
2598
2599
2600
2601 /**
2602 * Sets the values of any arguments not provided on the command line but
2603 * defined in the properties file.
2604 *
2605 * @param propertyMap A map of properties read from the properties file.
2606 * @param useSubCommand Indicates whether to use the argument parser
2607 * associated with the selected subcommand rather than
2608 * the global argument parser.
2609 *
2610 * @throws ArgumentException If a problem is encountered while examining the
2611 * properties file, or while trying to assign a
2612 * property value to a corresponding argument.
2613 */
2614 private void setArgsFromPropertiesFile(
2615 final Map<String,ArrayList<String>> propertyMap,
2616 final boolean useSubCommand)
2617 throws ArgumentException
2618 {
2619 final ArgumentParser p;
2620 if (useSubCommand)
2621 {
2622 p = selectedSubCommand.getArgumentParser();
2623 }
2624 else
2625 {
2626 p = this;
2627 }
2628
2629
2630 for (final Argument a : p.namedArgs)
2631 {
2632 if (a.getNumOccurrences() > 0)
2633 {
2634 // The argument was provided on the command line, and that will always
2635 // override anything that might be in the properties file.
2636 continue;
2637 }
2638
2639
2640 // If we should use a subcommand, then see if the properties file has a
2641 // property that is specific to the selected subcommand. Then fall back
2642 // to a property that is specific to the tool, and finally fall back to
2643 // checking for a set of values that are generic to any tool that has an
2644 // argument with that name.
2645 List<String> values = null;
2646 if (useSubCommand)
2647 {
2648 values = propertyMap.get(commandName + '.' +
2649 selectedSubCommand.getPrimaryName() + '.' +
2650 a.getIdentifierString());
2651 }
2652
2653 if (values == null)
2654 {
2655 values = propertyMap.get(commandName + '.' + a.getIdentifierString());
2656 }
2657
2658 if (values == null)
2659 {
2660 values = propertyMap.get(a.getIdentifierString());
2661 }
2662
2663 if (values != null)
2664 {
2665 for (final String value : values)
2666 {
2667 if (a instanceof BooleanArgument)
2668 {
2669 // We'll treat this as a BooleanValueArgument.
2670 final BooleanValueArgument bva = new BooleanValueArgument(
2671 a.getShortIdentifier(), a.getLongIdentifier(), false, null,
2672 a.getDescription());
2673 bva.addValue(value);
2674 if (bva.getValue())
2675 {
2676 a.incrementOccurrences();
2677 }
2678
2679 argumentsSetFromPropertiesFile.add(a.getIdentifierString());
2680 }
2681 else
2682 {
2683 a.addValue(value);
2684 a.incrementOccurrences();
2685
2686 argumentsSetFromPropertiesFile.add(a.getIdentifierString());
2687 if (a.isSensitive())
2688 {
2689 argumentsSetFromPropertiesFile.add("***REDACTED***");
2690 }
2691 else
2692 {
2693 argumentsSetFromPropertiesFile.add(value);
2694 }
2695 }
2696 }
2697 }
2698 }
2699 }
2700
2701
2702
2703 /**
2704 * Retrieves lines that make up the usage information for this program,
2705 * optionally wrapping long lines.
2706 *
2707 * @param maxWidth The maximum line width to use for the output. If this is
2708 * less than or equal to zero, then no wrapping will be
2709 * performed.
2710 *
2711 * @return The lines that make up the usage information for this program.
2712 */
2713 public List<String> getUsage(final int maxWidth)
2714 {
2715 // If a subcommand was selected, then provide usage specific to that
2716 // subcommand.
2717 if (selectedSubCommand != null)
2718 {
2719 return getSubCommandUsage(maxWidth);
2720 }
2721
2722 // First is a description of the command.
2723 final ArrayList<String> lines = new ArrayList<String>(100);
2724 lines.addAll(wrapLine(commandDescription, maxWidth));
2725 lines.add("");
2726
2727
2728 // If the tool supports subcommands, and if there are fewer than 10
2729 // subcommands, then display them inline.
2730 if ((! subCommands.isEmpty()) && (subCommands.size() < 10))
2731 {
2732 lines.add(INFO_USAGE_SUBCOMMANDS_HEADER.get());
2733 lines.add("");
2734
2735 for (final SubCommand sc : subCommands)
2736 {
2737 final StringBuilder nameBuffer = new StringBuilder();
2738 nameBuffer.append(" ");
2739
2740 final Iterator<String> nameIterator = sc.getNames().iterator();
2741 while (nameIterator.hasNext())
2742 {
2743 nameBuffer.append(nameIterator.next());
2744 if (nameIterator.hasNext())
2745 {
2746 nameBuffer.append(", ");
2747 }
2748 }
2749 lines.add(nameBuffer.toString());
2750
2751 for (final String descriptionLine :
2752 wrapLine(sc.getDescription(), (maxWidth - 4)))
2753 {
2754 lines.add(" " + descriptionLine);
2755 }
2756 lines.add("");
2757 }
2758 }
2759
2760
2761 // Next comes the usage. It may include neither, either, or both of the
2762 // set of options and trailing arguments.
2763 if (! subCommands.isEmpty())
2764 {
2765 lines.addAll(wrapLine(INFO_USAGE_SUBCOMMAND_USAGE.get(commandName),
2766 maxWidth));
2767 }
2768 else if (namedArgs.isEmpty())
2769 {
2770 if (maxTrailingArgs == 0)
2771 {
2772 lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_NOTRAILING.get(commandName),
2773 maxWidth));
2774 }
2775 else
2776 {
2777 lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_TRAILING.get(
2778 commandName, trailingArgsPlaceholder),
2779 maxWidth));
2780 }
2781 }
2782 else
2783 {
2784 if (maxTrailingArgs == 0)
2785 {
2786 lines.addAll(wrapLine(INFO_USAGE_OPTIONS_NOTRAILING.get(commandName),
2787 maxWidth));
2788 }
2789 else
2790 {
2791 lines.addAll(wrapLine(INFO_USAGE_OPTIONS_TRAILING.get(
2792 commandName, trailingArgsPlaceholder),
2793 maxWidth));
2794 }
2795 }
2796
2797 if (! namedArgs.isEmpty())
2798 {
2799 lines.add("");
2800 lines.add(INFO_USAGE_OPTIONS_INCLUDE.get());
2801
2802
2803 // If there are any argument groups, then collect the arguments in those
2804 // groups.
2805 boolean hasRequired = false;
2806 final LinkedHashMap<String,List<Argument>> argumentsByGroup =
2807 new LinkedHashMap<String,List<Argument>>(10);
2808 final ArrayList<Argument> argumentsWithoutGroup =
2809 new ArrayList<Argument>(namedArgs.size());
2810 final ArrayList<Argument> usageArguments =
2811 new ArrayList<Argument>(namedArgs.size());
2812 for (final Argument a : namedArgs)
2813 {
2814 if (a.isHidden())
2815 {
2816 // This argument shouldn't be included in the usage output.
2817 continue;
2818 }
2819
2820 if (a.isRequired() && (! a.hasDefaultValue()))
2821 {
2822 hasRequired = true;
2823 }
2824
2825 final String argumentGroup = a.getArgumentGroupName();
2826 if (argumentGroup == null)
2827 {
2828 if (a.isUsageArgument())
2829 {
2830 usageArguments.add(a);
2831 }
2832 else
2833 {
2834 argumentsWithoutGroup.add(a);
2835 }
2836 }
2837 else
2838 {
2839 List<Argument> groupArgs = argumentsByGroup.get(argumentGroup);
2840 if (groupArgs == null)
2841 {
2842 groupArgs = new ArrayList<Argument>(10);
2843 argumentsByGroup.put(argumentGroup, groupArgs);
2844 }
2845
2846 groupArgs.add(a);
2847 }
2848 }
2849
2850
2851 // Iterate through the defined argument groups and display usage
2852 // information for each of them.
2853 for (final Map.Entry<String,List<Argument>> e :
2854 argumentsByGroup.entrySet())
2855 {
2856 lines.add("");
2857 lines.add(" " + e.getKey());
2858 lines.add("");
2859 for (final Argument a : e.getValue())
2860 {
2861 getArgUsage(a, lines, true, maxWidth);
2862 }
2863 }
2864
2865 if (! argumentsWithoutGroup.isEmpty())
2866 {
2867 if (argumentsByGroup.isEmpty())
2868 {
2869 for (final Argument a : argumentsWithoutGroup)
2870 {
2871 getArgUsage(a, lines, false, maxWidth);
2872 }
2873 }
2874 else
2875 {
2876 lines.add("");
2877 lines.add(" " + INFO_USAGE_UNGROUPED_ARGS.get());
2878 lines.add("");
2879 for (final Argument a : argumentsWithoutGroup)
2880 {
2881 getArgUsage(a, lines, true, maxWidth);
2882 }
2883 }
2884 }
2885
2886 if (! usageArguments.isEmpty())
2887 {
2888 if (argumentsByGroup.isEmpty())
2889 {
2890 for (final Argument a : usageArguments)
2891 {
2892 getArgUsage(a, lines, false, maxWidth);
2893 }
2894 }
2895 else
2896 {
2897 lines.add("");
2898 lines.add(" " + INFO_USAGE_USAGE_ARGS.get());
2899 lines.add("");
2900 for (final Argument a : usageArguments)
2901 {
2902 getArgUsage(a, lines, true, maxWidth);
2903 }
2904 }
2905 }
2906
2907 if (hasRequired)
2908 {
2909 lines.add("");
2910 if (argumentsByGroup.isEmpty())
2911 {
2912 lines.add("* " + INFO_USAGE_ARG_IS_REQUIRED.get());
2913 }
2914 else
2915 {
2916 lines.add(" * " + INFO_USAGE_ARG_IS_REQUIRED.get());
2917 }
2918 }
2919 }
2920
2921 return lines;
2922 }
2923
2924
2925
2926 /**
2927 * Retrieves lines that make up the usage information for the selected
2928 * subcommand.
2929 *
2930 * @param maxWidth The maximum line width to use for the output. If this is
2931 * less than or equal to zero, then no wrapping will be
2932 * performed.
2933 *
2934 * @return The lines that make up the usage information for the selected
2935 * subcommand.
2936 */
2937 private List<String> getSubCommandUsage(final int maxWidth)
2938 {
2939 // First is a description of the subcommand.
2940 final ArrayList<String> lines = new ArrayList<String>(100);
2941 lines.addAll(wrapLine(selectedSubCommand.getDescription(), maxWidth));
2942 lines.add("");
2943
2944 // Next comes the usage.
2945 lines.addAll(wrapLine(
2946 INFO_SUBCOMMAND_USAGE_OPTIONS.get(commandName,
2947 selectedSubCommand.getPrimaryName()),
2948 maxWidth));
2949
2950
2951 final ArgumentParser parser = selectedSubCommand.getArgumentParser();
2952 if (! parser.namedArgs.isEmpty())
2953 {
2954 lines.add("");
2955 lines.add(INFO_USAGE_OPTIONS_INCLUDE.get());
2956
2957
2958 // If there are any argument groups, then collect the arguments in those
2959 // groups.
2960 boolean hasRequired = false;
2961 final LinkedHashMap<String,List<Argument>> argumentsByGroup =
2962 new LinkedHashMap<String,List<Argument>>(10);
2963 final ArrayList<Argument> argumentsWithoutGroup =
2964 new ArrayList<Argument>(parser.namedArgs.size());
2965 final ArrayList<Argument> usageArguments =
2966 new ArrayList<Argument>(parser.namedArgs.size());
2967 for (final Argument a : parser.namedArgs)
2968 {
2969 if (a.isHidden())
2970 {
2971 // This argument shouldn't be included in the usage output.
2972 continue;
2973 }
2974
2975 if (a.isRequired() && (! a.hasDefaultValue()))
2976 {
2977 hasRequired = true;
2978 }
2979
2980 final String argumentGroup = a.getArgumentGroupName();
2981 if (argumentGroup == null)
2982 {
2983 if (a.isUsageArgument())
2984 {
2985 usageArguments.add(a);
2986 }
2987 else
2988 {
2989 argumentsWithoutGroup.add(a);
2990 }
2991 }
2992 else
2993 {
2994 List<Argument> groupArgs = argumentsByGroup.get(argumentGroup);
2995 if (groupArgs == null)
2996 {
2997 groupArgs = new ArrayList<Argument>(10);
2998 argumentsByGroup.put(argumentGroup, groupArgs);
2999 }
3000
3001 groupArgs.add(a);
3002 }
3003 }
3004
3005
3006 // Iterate through the defined argument groups and display usage
3007 // information for each of them.
3008 for (final Map.Entry<String,List<Argument>> e :
3009 argumentsByGroup.entrySet())
3010 {
3011 lines.add("");
3012 lines.add(" " + e.getKey());
3013 lines.add("");
3014 for (final Argument a : e.getValue())
3015 {
3016 getArgUsage(a, lines, true, maxWidth);
3017 }
3018 }
3019
3020 if (! argumentsWithoutGroup.isEmpty())
3021 {
3022 if (argumentsByGroup.isEmpty())
3023 {
3024 for (final Argument a : argumentsWithoutGroup)
3025 {
3026 getArgUsage(a, lines, false, maxWidth);
3027 }
3028 }
3029 else
3030 {
3031 lines.add("");
3032 lines.add(" " + INFO_USAGE_UNGROUPED_ARGS.get());
3033 lines.add("");
3034 for (final Argument a : argumentsWithoutGroup)
3035 {
3036 getArgUsage(a, lines, true, maxWidth);
3037 }
3038 }
3039 }
3040
3041 if (! usageArguments.isEmpty())
3042 {
3043 if (argumentsByGroup.isEmpty())
3044 {
3045 for (final Argument a : usageArguments)
3046 {
3047 getArgUsage(a, lines, false, maxWidth);
3048 }
3049 }
3050 else
3051 {
3052 lines.add("");
3053 lines.add(" " + INFO_USAGE_USAGE_ARGS.get());
3054 lines.add("");
3055 for (final Argument a : usageArguments)
3056 {
3057 getArgUsage(a, lines, true, maxWidth);
3058 }
3059 }
3060 }
3061
3062 if (hasRequired)
3063 {
3064 lines.add("");
3065 if (argumentsByGroup.isEmpty())
3066 {
3067 lines.add("* " + INFO_USAGE_ARG_IS_REQUIRED.get());
3068 }
3069 else
3070 {
3071 lines.add(" * " + INFO_USAGE_ARG_IS_REQUIRED.get());
3072 }
3073 }
3074 }
3075
3076 return lines;
3077 }
3078
3079
3080
3081 /**
3082 * Adds usage information for the provided argument to the given list.
3083 *
3084 * @param a The argument for which to get the usage information.
3085 * @param lines The list to which the resulting lines should be added.
3086 * @param indent Indicates whether to indent each line.
3087 * @param maxWidth The maximum width of each line, in characters.
3088 */
3089 private static void getArgUsage(final Argument a, final List<String> lines,
3090 final boolean indent, final int maxWidth)
3091 {
3092 final StringBuilder argLine = new StringBuilder();
3093 if (indent && (maxWidth > 10))
3094 {
3095 if (a.isRequired() && (! a.hasDefaultValue()))
3096 {
3097 argLine.append(" * ");
3098 }
3099 else
3100 {
3101 argLine.append(" ");
3102 }
3103 }
3104 else if (a.isRequired() && (! a.hasDefaultValue()))
3105 {
3106 argLine.append("* ");
3107 }
3108
3109 boolean first = true;
3110 for (final Character c : a.getShortIdentifiers())
3111 {
3112 if (first)
3113 {
3114 argLine.append('-');
3115 first = false;
3116 }
3117 else
3118 {
3119 argLine.append(", -");
3120 }
3121 argLine.append(c);
3122 }
3123
3124 for (final String s : a.getLongIdentifiers())
3125 {
3126 if (first)
3127 {
3128 argLine.append("--");
3129 first = false;
3130 }
3131 else
3132 {
3133 argLine.append(", --");
3134 }
3135 argLine.append(s);
3136 }
3137
3138 final String valuePlaceholder = a.getValuePlaceholder();
3139 if (valuePlaceholder != null)
3140 {
3141 argLine.append(' ');
3142 argLine.append(valuePlaceholder);
3143 }
3144
3145 // If we need to wrap the argument line, then align the dashes on the left
3146 // edge.
3147 int subsequentLineWidth = maxWidth - 4;
3148 if (subsequentLineWidth < 4)
3149 {
3150 subsequentLineWidth = maxWidth;
3151 }
3152 final List<String> identifierLines =
3153 wrapLine(argLine.toString(), maxWidth, subsequentLineWidth);
3154 for (int i=0; i < identifierLines.size(); i++)
3155 {
3156 if (i == 0)
3157 {
3158 lines.add(identifierLines.get(0));
3159 }
3160 else
3161 {
3162 lines.add(" " + identifierLines.get(i));
3163 }
3164 }
3165
3166
3167 // The description should be wrapped, if necessary. We'll also want to
3168 // indent it (unless someone chose an absurdly small wrap width) to make
3169 // it stand out from the argument lines.
3170 final String description = a.getDescription();
3171 if (maxWidth > 10)
3172 {
3173 final String indentString;
3174 if (indent)
3175 {
3176 indentString = " ";
3177 }
3178 else
3179 {
3180 indentString = " ";
3181 }
3182
3183 final List<String> descLines = wrapLine(description,
3184 (maxWidth-indentString.length()));
3185 for (final String s : descLines)
3186 {
3187 lines.add(indentString + s);
3188 }
3189 }
3190 else
3191 {
3192 lines.addAll(wrapLine(description, maxWidth));
3193 }
3194 }
3195
3196
3197
3198 /**
3199 * Writes usage information for this program to the provided output stream
3200 * using the UTF-8 encoding, optionally wrapping long lines.
3201 *
3202 * @param outputStream The output stream to which the usage information
3203 * should be written. It must not be {@code null}.
3204 * @param maxWidth The maximum line width to use for the output. If
3205 * this is less than or equal to zero, then no wrapping
3206 * will be performed.
3207 *
3208 * @throws IOException If an error occurs while attempting to write to the
3209 * provided output stream.
3210 */
3211 public void getUsage(final OutputStream outputStream, final int maxWidth)
3212 throws IOException
3213 {
3214 final List<String> usageLines = getUsage(maxWidth);
3215 for (final String s : usageLines)
3216 {
3217 outputStream.write(getBytes(s));
3218 outputStream.write(EOL_BYTES);
3219 }
3220 }
3221
3222
3223
3224 /**
3225 * Retrieves a string representation of the usage information.
3226 *
3227 * @param maxWidth The maximum line width to use for the output. If this is
3228 * less than or equal to zero, then no wrapping will be
3229 * performed.
3230 *
3231 * @return A string representation of the usage information
3232 */
3233 public String getUsageString(final int maxWidth)
3234 {
3235 final StringBuilder buffer = new StringBuilder();
3236 getUsageString(buffer, maxWidth);
3237 return buffer.toString();
3238 }
3239
3240
3241
3242 /**
3243 * Appends a string representation of the usage information to the provided
3244 * buffer.
3245 *
3246 * @param buffer The buffer to which the information should be appended.
3247 * @param maxWidth The maximum line width to use for the output. If this is
3248 * less than or equal to zero, then no wrapping will be
3249 * performed.
3250 */
3251 public void getUsageString(final StringBuilder buffer, final int maxWidth)
3252 {
3253 for (final String line : getUsage(maxWidth))
3254 {
3255 buffer.append(line);
3256 buffer.append(EOL);
3257 }
3258 }
3259
3260
3261
3262 /**
3263 * Retrieves a string representation of this argument parser.
3264 *
3265 * @return A string representation of this argument parser.
3266 */
3267 @Override()
3268 public String toString()
3269 {
3270 final StringBuilder buffer = new StringBuilder();
3271 toString(buffer);
3272 return buffer.toString();
3273 }
3274
3275
3276
3277 /**
3278 * Appends a string representation of this argument parser to the provided
3279 * buffer.
3280 *
3281 * @param buffer The buffer to which the information should be appended.
3282 */
3283 public void toString(final StringBuilder buffer)
3284 {
3285 buffer.append("ArgumentParser(commandName='");
3286 buffer.append(commandName);
3287 buffer.append("', commandDescription='");
3288 buffer.append(commandDescription);
3289 buffer.append("', minTrailingArgs=");
3290 buffer.append(minTrailingArgs);
3291 buffer.append("', maxTrailingArgs=");
3292 buffer.append(maxTrailingArgs);
3293
3294 if (trailingArgsPlaceholder != null)
3295 {
3296 buffer.append(", trailingArgsPlaceholder='");
3297 buffer.append(trailingArgsPlaceholder);
3298 buffer.append('\'');
3299 }
3300
3301 buffer.append("namedArgs={");
3302
3303 final Iterator<Argument> iterator = namedArgs.iterator();
3304 while (iterator.hasNext())
3305 {
3306 iterator.next().toString(buffer);
3307 if (iterator.hasNext())
3308 {
3309 buffer.append(", ");
3310 }
3311 }
3312
3313 buffer.append('}');
3314
3315 if (! subCommands.isEmpty())
3316 {
3317 buffer.append(", subCommands={");
3318
3319 final Iterator<SubCommand> subCommandIterator = subCommands.iterator();
3320 while (subCommandIterator.hasNext())
3321 {
3322 subCommandIterator.next().toString(buffer);
3323 if (subCommandIterator.hasNext())
3324 {
3325 buffer.append(", ");
3326 }
3327 }
3328
3329 buffer.append('}');
3330 }
3331
3332 buffer.append(')');
3333 }
3334 }