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 properties file from
086 * which to obtain the default values for arguments not specified via the
087 * command line.
088 */
089 private static final String ARG_NAME_PROPERTIES_FILE_PATH =
090 "propertiesFilePath";
091
092
093
094 /**
095 * The name of the argument used to specify the path to a file to be generated
096 * with information about the properties that the tool supports.
097 */
098 private static final String ARG_NAME_GENERATE_PROPERTIES_FILE =
099 "generatePropertiesFile";
100
101
102
103 /**
104 * The name of the argument used to indicate that the tool should not use any
105 * properties file to obtain default values for arguments not specified via
106 * the command line.
107 */
108 private static final String ARG_NAME_NO_PROPERTIES_FILE = "noPropertiesFile";
109
110
111
112 /**
113 * The serial version UID for this serializable class.
114 */
115 private static final long serialVersionUID = 3053102992180360269L;
116
117
118
119 // The maximum number of trailing arguments allowed to be provided.
120 private final int maxTrailingArgs;
121
122 // The minimum number of trailing arguments allowed to be provided.
123 private final int minTrailingArgs;
124
125 // The set of named arguments associated with this parser, indexed by short
126 // identifier.
127 private final LinkedHashMap<Character,Argument> namedArgsByShortID;
128
129 // The set of named arguments associated with this parser, indexed by long
130 // identifier.
131 private final LinkedHashMap<String,Argument> namedArgsByLongID;
132
133 // The full set of named arguments associated with this parser.
134 private final List<Argument> namedArgs;
135
136 // Sets of arguments in which if the key argument is provided, then at least
137 // one of the value arguments must also be provided.
138 private final List<ObjectPair<Argument,Set<Argument>>> dependentArgumentSets;
139
140 // Sets of arguments in which at most one argument in the list is allowed to
141 // be present.
142 private final List<Set<Argument>> exclusiveArgumentSets;
143
144 // Sets of arguments in which at least one argument in the list is required to
145 // be present.
146 private final List<Set<Argument>> requiredArgumentSets;
147
148 // The list of trailing arguments provided on the command line.
149 private final List<String> trailingArgs;
150
151 // The description for the associated command.
152 private final String commandDescription;
153
154 // The name for the associated command.
155 private final String commandName;
156
157 // The placeholder string for the trailing arguments.
158 private final String trailingArgsPlaceholder;
159
160
161
162 /**
163 * Creates a new instance of this argument parser with the provided
164 * information. It will not allow unnamed trailing arguments.
165 *
166 * @param commandName The name of the application or utility with
167 * which this argument parser is associated. It
168 * must not be {@code null}.
169 * @param commandDescription A description of the application or utility
170 * with which this argument parser is associated.
171 * It will be included in generated usage
172 * information. It must not be {@code null}.
173 *
174 * @throws ArgumentException If either the command name or command
175 * description is {@code null},
176 */
177 public ArgumentParser(final String commandName,
178 final String commandDescription)
179 throws ArgumentException
180 {
181 this(commandName, commandDescription, 0, null);
182 }
183
184
185
186 /**
187 * Creates a new instance of this argument parser with the provided
188 * information.
189 *
190 * @param commandName The name of the application or utility
191 * with which this argument parser is
192 * associated. It must not be {@code null}.
193 * @param commandDescription A description of the application or
194 * utility with which this argument parser is
195 * associated. It will be included in
196 * generated usage information. It must not
197 * be {@code null}.
198 * @param maxTrailingArgs The maximum number of trailing arguments
199 * that may be provided to this command. A
200 * value of zero indicates that no trailing
201 * arguments will be allowed. A value less
202 * than zero will indicate that there is no
203 * limit on the number of trailing arguments
204 * allowed.
205 * @param trailingArgsPlaceholder A placeholder string that will be included
206 * in usage output to indicate what trailing
207 * arguments may be provided. It must not be
208 * {@code null} if {@code maxTrailingArgs} is
209 * anything other than zero.
210 *
211 * @throws ArgumentException If either the command name or command
212 * description is {@code null}, or if the maximum
213 * number of trailing arguments is non-zero and
214 * the trailing arguments placeholder is
215 * {@code null}.
216 */
217 public ArgumentParser(final String commandName,
218 final String commandDescription,
219 final int maxTrailingArgs,
220 final String trailingArgsPlaceholder)
221 throws ArgumentException
222 {
223 this(commandName, commandDescription, 0, maxTrailingArgs,
224 trailingArgsPlaceholder);
225 }
226
227
228
229 /**
230 * Creates a new instance of this argument parser with the provided
231 * information.
232 *
233 * @param commandName The name of the application or utility
234 * with which this argument parser is
235 * associated. It must not be {@code null}.
236 * @param commandDescription A description of the application or
237 * utility with which this argument parser is
238 * associated. It will be included in
239 * generated usage information. It must not
240 * be {@code null}.
241 * @param minTrailingArgs The minimum number of trailing arguments
242 * that must be provided for this command. A
243 * value of zero indicates that the command
244 * may be invoked without any trailing
245 * arguments.
246 * @param maxTrailingArgs The maximum number of trailing arguments
247 * that may be provided to this command. A
248 * value of zero indicates that no trailing
249 * arguments will be allowed. A value less
250 * than zero will indicate that there is no
251 * limit on the number of trailing arguments
252 * allowed.
253 * @param trailingArgsPlaceholder A placeholder string that will be included
254 * in usage output to indicate what trailing
255 * arguments may be provided. It must not be
256 * {@code null} if {@code maxTrailingArgs} is
257 * anything other than zero.
258 *
259 * @throws ArgumentException If either the command name or command
260 * description is {@code null}, or if the maximum
261 * number of trailing arguments is non-zero and
262 * the trailing arguments placeholder is
263 * {@code null}.
264 */
265 public ArgumentParser(final String commandName,
266 final String commandDescription,
267 final int minTrailingArgs,
268 final int maxTrailingArgs,
269 final String trailingArgsPlaceholder)
270 throws ArgumentException
271 {
272 if (commandName == null)
273 {
274 throw new ArgumentException(ERR_PARSER_COMMAND_NAME_NULL.get());
275 }
276
277 if (commandDescription == null)
278 {
279 throw new ArgumentException(ERR_PARSER_COMMAND_DESCRIPTION_NULL.get());
280 }
281
282 if ((maxTrailingArgs != 0) && (trailingArgsPlaceholder == null))
283 {
284 throw new ArgumentException(
285 ERR_PARSER_TRAILING_ARGS_PLACEHOLDER_NULL.get());
286 }
287
288 this.commandName = commandName;
289 this.commandDescription = commandDescription;
290 this.trailingArgsPlaceholder = trailingArgsPlaceholder;
291
292 if (minTrailingArgs >= 0)
293 {
294 this.minTrailingArgs = minTrailingArgs;
295 }
296 else
297 {
298 this.minTrailingArgs = 0;
299 }
300
301 if (maxTrailingArgs >= 0)
302 {
303 this.maxTrailingArgs = maxTrailingArgs;
304 }
305 else
306 {
307 this.maxTrailingArgs = Integer.MAX_VALUE;
308 }
309
310 if (this.minTrailingArgs > this.maxTrailingArgs)
311 {
312 throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_COUNT_MISMATCH.get(
313 this.minTrailingArgs, this.maxTrailingArgs));
314 }
315
316 namedArgsByShortID = new LinkedHashMap<Character,Argument>();
317 namedArgsByLongID = new LinkedHashMap<String,Argument>();
318 namedArgs = new ArrayList<Argument>();
319 trailingArgs = new ArrayList<String>();
320 dependentArgumentSets = new ArrayList<ObjectPair<Argument,Set<Argument>>>();
321 exclusiveArgumentSets = new ArrayList<Set<Argument>>();
322 requiredArgumentSets = new ArrayList<Set<Argument>>();
323 }
324
325
326
327 /**
328 * Creates a new argument parser that is a "clean" copy of the provided source
329 * argument parser.
330 *
331 * @param source The source argument parser to use for this argument parser.
332 */
333 private ArgumentParser(final ArgumentParser source)
334 {
335 commandName = source.commandName;
336 commandDescription = source.commandDescription;
337 minTrailingArgs = source.minTrailingArgs;
338 maxTrailingArgs = source.maxTrailingArgs;
339 trailingArgsPlaceholder = source.trailingArgsPlaceholder;
340
341 trailingArgs = new ArrayList<String>();
342
343 namedArgs = new ArrayList<Argument>(source.namedArgs.size());
344 namedArgsByLongID =
345 new LinkedHashMap<String,Argument>(source.namedArgsByLongID.size());
346 namedArgsByShortID = new LinkedHashMap<Character,Argument>(
347 source.namedArgsByShortID.size());
348
349 final LinkedHashMap<String,Argument> argsByID =
350 new LinkedHashMap<String,Argument>(source.namedArgs.size());
351 for (final Argument sourceArg : source.namedArgs)
352 {
353 final Argument a = sourceArg.getCleanCopy();
354
355 try
356 {
357 a.setRegistered();
358 }
359 catch (final ArgumentException ae)
360 {
361 // This should never happen.
362 Debug.debugException(ae);
363 }
364
365 namedArgs.add(a);
366 argsByID.put(a.getIdentifierString(), a);
367
368 for (final Character c : a.getShortIdentifiers())
369 {
370 namedArgsByShortID.put(c, a);
371 }
372
373 for (final String s : a.getLongIdentifiers())
374 {
375 namedArgsByLongID.put(toLowerCase(s), a);
376 }
377 }
378
379 dependentArgumentSets = new ArrayList<ObjectPair<Argument,Set<Argument>>>(
380 source.dependentArgumentSets.size());
381 for (final ObjectPair<Argument,Set<Argument>> p :
382 source.dependentArgumentSets)
383 {
384 final Set<Argument> sourceSet = p.getSecond();
385 final LinkedHashSet<Argument> newSet =
386 new LinkedHashSet<Argument>(sourceSet.size());
387 for (final Argument a : sourceSet)
388 {
389 newSet.add(argsByID.get(a.getIdentifierString()));
390 }
391
392 final Argument sourceFirst = p.getFirst();
393 final Argument newFirst = argsByID.get(sourceFirst.getIdentifierString());
394 dependentArgumentSets.add(
395 new ObjectPair<Argument, Set<Argument>>(newFirst, newSet));
396 }
397
398 exclusiveArgumentSets =
399 new ArrayList<Set<Argument>>(source.exclusiveArgumentSets.size());
400 for (final Set<Argument> sourceSet : source.exclusiveArgumentSets)
401 {
402 final LinkedHashSet<Argument> newSet =
403 new LinkedHashSet<Argument>(sourceSet.size());
404 for (final Argument a : sourceSet)
405 {
406 newSet.add(argsByID.get(a.getIdentifierString()));
407 }
408
409 exclusiveArgumentSets.add(newSet);
410 }
411
412 requiredArgumentSets =
413 new ArrayList<Set<Argument>>(source.requiredArgumentSets.size());
414 for (final Set<Argument> sourceSet : source.requiredArgumentSets)
415 {
416 final LinkedHashSet<Argument> newSet =
417 new LinkedHashSet<Argument>(sourceSet.size());
418 for (final Argument a : sourceSet)
419 {
420 newSet.add(argsByID.get(a.getIdentifierString()));
421 }
422 requiredArgumentSets.add(newSet);
423 }
424 }
425
426
427
428 /**
429 * Retrieves the name of the application or utility with which this command
430 * line argument parser is associated.
431 *
432 * @return The name of the application or utility with which this command
433 * line argument parser is associated.
434 */
435 public String getCommandName()
436 {
437 return commandName;
438 }
439
440
441
442 /**
443 * Retrieves a description of the application or utility with which this
444 * command line argument parser is associated.
445 *
446 * @return A description of the application or utility with which this
447 * command line argument parser is associated.
448 */
449 public String getCommandDescription()
450 {
451 return commandDescription;
452 }
453
454
455
456 /**
457 * Indicates whether this argument parser allows any unnamed trailing
458 * arguments to be provided.
459 *
460 * @return {@code true} if at least one unnamed trailing argument may be
461 * provided, or {@code false} if not.
462 */
463 public boolean allowsTrailingArguments()
464 {
465 return (maxTrailingArgs != 0);
466 }
467
468
469
470 /**
471 * Indicates whether this argument parser requires at least unnamed trailing
472 * argument to be provided.
473 *
474 * @return {@code true} if at least one unnamed trailing argument must be
475 * provided, or {@code false} if the tool may be invoked without any
476 * such arguments.
477 */
478 public boolean requiresTrailingArguments()
479 {
480 return (minTrailingArgs != 0);
481 }
482
483
484
485 /**
486 * Retrieves the placeholder string that will be provided in usage information
487 * to indicate what may be included in the trailing arguments.
488 *
489 * @return The placeholder string that will be provided in usage information
490 * to indicate what may be included in the trailing arguments, or
491 * {@code null} if unnamed trailing arguments are not allowed.
492 */
493 public String getTrailingArgumentsPlaceholder()
494 {
495 return trailingArgsPlaceholder;
496 }
497
498
499
500 /**
501 * Retrieves the minimum number of unnamed trailing arguments that must be
502 * provided.
503 *
504 * @return The minimum number of unnamed trailing arguments that must be
505 * provided.
506 */
507 public int getMinTrailingArguments()
508 {
509 return minTrailingArgs;
510 }
511
512
513
514 /**
515 * Retrieves the maximum number of unnamed trailing arguments that may be
516 * provided.
517 *
518 * @return The maximum number of unnamed trailing arguments that may be
519 * provided.
520 */
521 public int getMaxTrailingArguments()
522 {
523 return maxTrailingArgs;
524 }
525
526
527
528 /**
529 * Updates this argument parser to enable support for a properties file that
530 * can be used to specify the default values for any properties that were not
531 * supplied via the command line. This method should be invoked after the
532 * argument parser has been configured with all of the other arguments that it
533 * supports and before the {@link #parse} method is invoked. In addition,
534 * after invoking the {@code parse} method, the caller must also invoke the
535 * {@link #getGeneratedPropertiesFile} method to determine if the only
536 * processing performed that should be performed is the generation of a
537 * properties file that will have already been performed.
538 * <BR><BR>
539 * This method will update the argument parser to add the following additional
540 * arguments:
541 * <UL>
542 * <LI>
543 * {@code propertiesFilePath} -- Specifies the path to the properties file
544 * that should be used to obtain default values for any arguments not
545 * provided on the command line. If this is not specified and the
546 * {@code noPropertiesFile} argument is not present, then the argument
547 * parser may use a default properties file path specified using either
548 * the {@code com.unboundid.util.args.ArgumentParser..propertiesFilePath}
549 * system property or the {@code UNBOUNDID_TOOL_PROPERTIES_FILE_PATH}
550 * environment variable.
551 * </LI>
552 * <LI>
553 * {@code generatePropertiesFile} -- Indicates that the tool should
554 * generate a properties file for this argument parser and write it to the
555 * specified location. The generated properties file will not have any
556 * properties set, but will include comments that describe all of the
557 * supported arguments, as well general information about the use of a
558 * properties file. If this argument is specified on the command line,
559 * then no other arguments should be given.
560 * </LI>
561 * <LI>
562 * {@code noPropertiesFile} -- Indicates that the tool should not use a
563 * properties file to obtain default values for any arguments not provided
564 * on the command line.
565 * </LI>
566 * </UL>
567 *
568 * @throws ArgumentException If any of the arguments related to properties
569 * file processing conflicts with an argument that
570 * has already been added to the argument parser.
571 */
572 public void enablePropertiesFileSupport()
573 throws ArgumentException
574 {
575 final FileArgument propertiesFilePath = new FileArgument(null,
576 ARG_NAME_PROPERTIES_FILE_PATH, false, 1, null,
577 INFO_ARG_DESCRIPTION_PROP_FILE_PATH.get(), true, true, true, false);
578 propertiesFilePath.setUsageArgument(true);
579 propertiesFilePath.addLongIdentifier("properties-file-path");
580 addArgument(propertiesFilePath);
581
582 final FileArgument generatePropertiesFile = new FileArgument(null,
583 ARG_NAME_GENERATE_PROPERTIES_FILE, false, 1, null,
584 INFO_ARG_DESCRIPTION_GEN_PROP_FILE.get(), false, true, true, false);
585 generatePropertiesFile.setUsageArgument(true);
586 generatePropertiesFile.addLongIdentifier("generate-properties-file");
587 addArgument(generatePropertiesFile);
588
589 final BooleanArgument noPropertiesFile = new BooleanArgument(null,
590 ARG_NAME_NO_PROPERTIES_FILE, INFO_ARG_DESCRIPTION_NO_PROP_FILE.get());
591 noPropertiesFile.setUsageArgument(true);
592 noPropertiesFile.addLongIdentifier("no-properties-file");
593 addArgument(noPropertiesFile);
594
595
596 // The propertiesFilePath and noPropertiesFile arguments cannot be used
597 // together.
598 addExclusiveArgumentSet(propertiesFilePath, noPropertiesFile);
599 }
600
601
602
603 /**
604 * Indicates whether this argument parser was used to generate a properties
605 * file. If so, then the tool invoking the parser should return without
606 * performing any further processing.
607 *
608 * @return A {@code File} object that represents the path to the properties
609 * file that was generated, or {@code null} if no properties file was
610 * generated.
611 */
612 public File getGeneratedPropertiesFile()
613 {
614 final Argument a = getNamedArgument(ARG_NAME_GENERATE_PROPERTIES_FILE);
615 if ((a == null) || (! a.isPresent()) || (! (a instanceof FileArgument)))
616 {
617 return null;
618 }
619
620 return ((FileArgument) a).getValue();
621 }
622
623
624
625 /**
626 * Retrieves the named argument with the specified short identifier.
627 *
628 * @param shortIdentifier The short identifier of the argument to retrieve.
629 * It must not be {@code null}.
630 *
631 * @return The named argument with the specified short identifier, or
632 * {@code null} if there is no such argument.
633 */
634 public Argument getNamedArgument(final Character shortIdentifier)
635 {
636 ensureNotNull(shortIdentifier);
637 return namedArgsByShortID.get(shortIdentifier);
638 }
639
640
641
642 /**
643 * Retrieves the named argument with the specified identifier.
644 *
645 * @param identifier The identifier of the argument to retrieve. It may be
646 * the long identifier without any dashes, the short
647 * identifier character preceded by a single dash, or the
648 * long identifier preceded by two dashes. It must not be
649 * {@code null}.
650 *
651 * @return The named argument with the specified long identifier, or
652 * {@code null} if there is no such argument.
653 */
654 public Argument getNamedArgument(final String identifier)
655 {
656 ensureNotNull(identifier);
657
658 if (identifier.startsWith("--") && (identifier.length() > 2))
659 {
660 return namedArgsByLongID.get(toLowerCase(identifier.substring(2)));
661 }
662 else if (identifier.startsWith("-") && (identifier.length() == 2))
663 {
664 return namedArgsByShortID.get(identifier.charAt(1));
665 }
666 else
667 {
668 return namedArgsByLongID.get(toLowerCase(identifier));
669 }
670 }
671
672
673
674 /**
675 * Retrieves the argument list argument with the specified identifier.
676 *
677 * @param identifier The identifier of the argument to retrieve. It may be
678 * the long identifier without any dashes, the short
679 * identifier character preceded by a single dash, or the
680 * long identifier preceded by two dashes. It must not be
681 * {@code null}.
682 *
683 * @return The argument list argument with the specified identifier, or
684 * {@code null} if there is no such argument.
685 */
686 public ArgumentListArgument getArgumentListArgument(final String identifier)
687 {
688 final Argument a = getNamedArgument(identifier);
689 if (a == null)
690 {
691 return null;
692 }
693 else
694 {
695 return (ArgumentListArgument) a;
696 }
697 }
698
699
700
701 /**
702 * Retrieves the Boolean argument with the specified identifier.
703 *
704 * @param identifier The identifier of the argument to retrieve. It may be
705 * the long identifier without any dashes, the short
706 * identifier character preceded by a single dash, or the
707 * long identifier preceded by two dashes. It must not be
708 * {@code null}.
709 *
710 * @return The Boolean argument with the specified identifier, or
711 * {@code null} if there is no such argument.
712 */
713 public BooleanArgument getBooleanArgument(final String identifier)
714 {
715 final Argument a = getNamedArgument(identifier);
716 if (a == null)
717 {
718 return null;
719 }
720 else
721 {
722 return (BooleanArgument) a;
723 }
724 }
725
726
727
728 /**
729 * Retrieves the Boolean value argument with the specified identifier.
730 *
731 * @param identifier The identifier of the argument to retrieve. It may be
732 * the long identifier without any dashes, the short
733 * identifier character preceded by a single dash, or the
734 * long identifier preceded by two dashes. It must not be
735 * {@code null}.
736 *
737 * @return The Boolean value argument with the specified identifier, or
738 * {@code null} if there is no such argument.
739 */
740 public BooleanValueArgument getBooleanValueArgument(final String identifier)
741 {
742 final Argument a = getNamedArgument(identifier);
743 if (a == null)
744 {
745 return null;
746 }
747 else
748 {
749 return (BooleanValueArgument) a;
750 }
751 }
752
753
754
755 /**
756 * Retrieves the control argument with the specified identifier.
757 *
758 * @param identifier The identifier of the argument to retrieve. It may be
759 * the long identifier without any dashes, the short
760 * identifier character preceded by a single dash, or the
761 * long identifier preceded by two dashes. It must not be
762 * {@code null}.
763 *
764 * @return The control argument with the specified identifier, or
765 * {@code null} if there is no such argument.
766 */
767 public ControlArgument getControlArgument(final String identifier)
768 {
769 final Argument a = getNamedArgument(identifier);
770 if (a == null)
771 {
772 return null;
773 }
774 else
775 {
776 return (ControlArgument) a;
777 }
778 }
779
780
781
782 /**
783 * Retrieves the DN argument with the specified identifier.
784 *
785 * @param identifier The identifier of the argument to retrieve. It may be
786 * the long identifier without any dashes, the short
787 * identifier character preceded by a single dash, or the
788 * long identifier preceded by two dashes. It must not be
789 * {@code null}.
790 *
791 * @return The DN argument with the specified identifier, or
792 * {@code null} if there is no such argument.
793 */
794 public DNArgument getDNArgument(final String identifier)
795 {
796 final Argument a = getNamedArgument(identifier);
797 if (a == null)
798 {
799 return null;
800 }
801 else
802 {
803 return (DNArgument) a;
804 }
805 }
806
807
808
809 /**
810 * Retrieves the duration argument with the specified identifier.
811 *
812 * @param identifier The identifier of the argument to retrieve. It may be
813 * the long identifier without any dashes, the short
814 * identifier character preceded by a single dash, or the
815 * long identifier preceded by two dashes. It must not be
816 * {@code null}.
817 *
818 * @return The duration argument with the specified identifier, or
819 * {@code null} if there is no such argument.
820 */
821 public DurationArgument getDurationArgument(final String identifier)
822 {
823 final Argument a = getNamedArgument(identifier);
824 if (a == null)
825 {
826 return null;
827 }
828 else
829 {
830 return (DurationArgument) a;
831 }
832 }
833
834
835
836 /**
837 * Retrieves the file argument with the specified identifier.
838 *
839 * @param identifier The identifier of the argument to retrieve. It may be
840 * the long identifier without any dashes, the short
841 * identifier character preceded by a single dash, or the
842 * long identifier preceded by two dashes. It must not be
843 * {@code null}.
844 *
845 * @return The file argument with the specified identifier, or
846 * {@code null} if there is no such argument.
847 */
848 public FileArgument getFileArgument(final String identifier)
849 {
850 final Argument a = getNamedArgument(identifier);
851 if (a == null)
852 {
853 return null;
854 }
855 else
856 {
857 return (FileArgument) a;
858 }
859 }
860
861
862
863 /**
864 * Retrieves the filter argument with the specified identifier.
865 *
866 * @param identifier The identifier of the argument to retrieve. It may be
867 * the long identifier without any dashes, the short
868 * identifier character preceded by a single dash, or the
869 * long identifier preceded by two dashes. It must not be
870 * {@code null}.
871 *
872 * @return The filter argument with the specified identifier, or
873 * {@code null} if there is no such argument.
874 */
875 public FilterArgument getFilterArgument(final String identifier)
876 {
877 final Argument a = getNamedArgument(identifier);
878 if (a == null)
879 {
880 return null;
881 }
882 else
883 {
884 return (FilterArgument) a;
885 }
886 }
887
888
889
890 /**
891 * Retrieves the integer argument with the specified identifier.
892 *
893 * @param identifier The identifier of the argument to retrieve. It may be
894 * the long identifier without any dashes, the short
895 * identifier character preceded by a single dash, or the
896 * long identifier preceded by two dashes. It must not be
897 * {@code null}.
898 *
899 * @return The integer argument with the specified identifier, or
900 * {@code null} if there is no such argument.
901 */
902 public IntegerArgument getIntegerArgument(final String identifier)
903 {
904 final Argument a = getNamedArgument(identifier);
905 if (a == null)
906 {
907 return null;
908 }
909 else
910 {
911 return (IntegerArgument) a;
912 }
913 }
914
915
916
917 /**
918 * Retrieves the scope argument with the specified identifier.
919 *
920 * @param identifier The identifier of the argument to retrieve. It may be
921 * the long identifier without any dashes, the short
922 * identifier character preceded by a single dash, or the
923 * long identifier preceded by two dashes. It must not be
924 * {@code null}.
925 *
926 * @return The scope argument with the specified identifier, or
927 * {@code null} if there is no such argument.
928 */
929 public ScopeArgument getScopeArgument(final String identifier)
930 {
931 final Argument a = getNamedArgument(identifier);
932 if (a == null)
933 {
934 return null;
935 }
936 else
937 {
938 return (ScopeArgument) a;
939 }
940 }
941
942
943
944 /**
945 * Retrieves the string argument with the specified identifier.
946 *
947 * @param identifier The identifier of the argument to retrieve. It may be
948 * the long identifier without any dashes, the short
949 * identifier character preceded by a single dash, or the
950 * long identifier preceded by two dashes. It must not be
951 * {@code null}.
952 *
953 * @return The string argument with the specified identifier, or
954 * {@code null} if there is no such argument.
955 */
956 public StringArgument getStringArgument(final String identifier)
957 {
958 final Argument a = getNamedArgument(identifier);
959 if (a == null)
960 {
961 return null;
962 }
963 else
964 {
965 return (StringArgument) a;
966 }
967 }
968
969
970
971 /**
972 * Retrieves the set of named arguments defined for use with this argument
973 * parser.
974 *
975 * @return The set of named arguments defined for use with this argument
976 * parser.
977 */
978 public List<Argument> getNamedArguments()
979 {
980 return Collections.unmodifiableList(namedArgs);
981 }
982
983
984
985 /**
986 * Registers the provided argument with this argument parser.
987 *
988 * @param argument The argument to be registered.
989 *
990 * @throws ArgumentException If the provided argument conflicts with another
991 * argument already registered with this parser.
992 */
993 public void addArgument(final Argument argument)
994 throws ArgumentException
995 {
996 argument.setRegistered();
997 for (final Character c : argument.getShortIdentifiers())
998 {
999 if (namedArgsByShortID.containsKey(c))
1000 {
1001 throw new ArgumentException(ERR_PARSER_SHORT_ID_CONFLICT.get(c));
1002 }
1003 }
1004
1005 for (final String s : argument.getLongIdentifiers())
1006 {
1007 if (namedArgsByLongID.containsKey(toLowerCase(s)))
1008 {
1009 throw new ArgumentException(ERR_PARSER_LONG_ID_CONFLICT.get(s));
1010 }
1011 }
1012
1013 for (final Character c : argument.getShortIdentifiers())
1014 {
1015 namedArgsByShortID.put(c, argument);
1016 }
1017
1018 for (final String s : argument.getLongIdentifiers())
1019 {
1020 namedArgsByLongID.put(toLowerCase(s), argument);
1021 }
1022
1023 namedArgs.add(argument);
1024 }
1025
1026
1027
1028 /**
1029 * Retrieves the list of dependent argument sets for this argument parser. If
1030 * an argument contained as the first object in the pair in a dependent
1031 * argument set is provided, then at least one of the arguments in the paired
1032 * set must also be provided.
1033 *
1034 * @return The list of dependent argument sets for this argument parser.
1035 */
1036 public List<ObjectPair<Argument,Set<Argument>>> getDependentArgumentSets()
1037 {
1038 return Collections.unmodifiableList(dependentArgumentSets);
1039 }
1040
1041
1042
1043 /**
1044 * Adds the provided collection of arguments as dependent upon the given
1045 * argument.
1046 *
1047 * @param targetArgument The argument whose presence indicates that at
1048 * least one of the dependent arguments must also
1049 * be present. It must not be {@code null}.
1050 * @param dependentArguments The set of arguments from which at least one
1051 * argument must be present if the target argument
1052 * is present. It must not be {@code null} or
1053 * empty.
1054 */
1055 public void addDependentArgumentSet(final Argument targetArgument,
1056 final Collection<Argument> dependentArguments)
1057 {
1058 ensureNotNull(targetArgument, dependentArguments);
1059
1060 final LinkedHashSet<Argument> argSet =
1061 new LinkedHashSet<Argument>(dependentArguments);
1062 dependentArgumentSets.add(
1063 new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet));
1064 }
1065
1066
1067
1068 /**
1069 * Adds the provided collection of arguments as dependent upon the given
1070 * argument.
1071 *
1072 * @param targetArgument The argument whose presence indicates that at least
1073 * one of the dependent arguments must also be
1074 * present. It must not be {@code null}.
1075 * @param dependentArg1 The first argument in the set of arguments in which
1076 * at least one argument must be present if the target
1077 * argument is present. It must not be {@code null}.
1078 * @param remaining The remaining arguments in the set of arguments in
1079 * which at least one argument must be present if the
1080 * target argument is present.
1081 */
1082 public void addDependentArgumentSet(final Argument targetArgument,
1083 final Argument dependentArg1,
1084 final Argument... remaining)
1085 {
1086 ensureNotNull(targetArgument, dependentArg1);
1087
1088 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>();
1089 argSet.add(dependentArg1);
1090 argSet.addAll(Arrays.asList(remaining));
1091
1092 dependentArgumentSets.add(
1093 new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet));
1094 }
1095
1096
1097
1098 /**
1099 * Retrieves the list of exclusive argument sets for this argument parser.
1100 * If an argument contained in an exclusive argument set is provided, then
1101 * none of the other arguments in that set may be provided. It is acceptable
1102 * for none of the arguments in the set to be provided, unless the same set
1103 * of arguments is also defined as a required argument set.
1104 *
1105 * @return The list of exclusive argument sets for this argument parser.
1106 */
1107 public List<Set<Argument>> getExclusiveArgumentSets()
1108 {
1109 return Collections.unmodifiableList(exclusiveArgumentSets);
1110 }
1111
1112
1113
1114 /**
1115 * Adds the provided collection of arguments as an exclusive argument set, in
1116 * which at most one of the arguments may be provided.
1117 *
1118 * @param exclusiveArguments The collection of arguments to form an
1119 * exclusive argument set. It must not be
1120 * {@code null}.
1121 */
1122 public void addExclusiveArgumentSet(
1123 final Collection<Argument> exclusiveArguments)
1124 {
1125 ensureNotNull(exclusiveArguments);
1126 final LinkedHashSet<Argument> argSet =
1127 new LinkedHashSet<Argument>(exclusiveArguments);
1128 exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet));
1129 }
1130
1131
1132
1133 /**
1134 * Adds the provided set of arguments as an exclusive argument set, in
1135 * which at most one of the arguments may be provided.
1136 *
1137 * @param arg1 The first argument to include in the exclusive argument
1138 * set. It must not be {@code null}.
1139 * @param arg2 The second argument to include in the exclusive argument
1140 * set. It must not be {@code null}.
1141 * @param remaining Any additional arguments to include in the exclusive
1142 * argument set.
1143 */
1144 public void addExclusiveArgumentSet(final Argument arg1, final Argument arg2,
1145 final Argument... remaining)
1146 {
1147 ensureNotNull(arg1, arg2);
1148
1149 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>();
1150 argSet.add(arg1);
1151 argSet.add(arg2);
1152 argSet.addAll(Arrays.asList(remaining));
1153
1154 exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet));
1155 }
1156
1157
1158
1159 /**
1160 * Retrieves the list of required argument sets for this argument parser. At
1161 * least one of the arguments contained in this set must be provided. If this
1162 * same set is also defined as an exclusive argument set, then exactly one
1163 * of those arguments must be provided.
1164 *
1165 * @return The list of required argument sets for this argument parser.
1166 */
1167 public List<Set<Argument>> getRequiredArgumentSets()
1168 {
1169 return Collections.unmodifiableList(requiredArgumentSets);
1170 }
1171
1172
1173
1174 /**
1175 * Adds the provided collection of arguments as a required argument set, in
1176 * which at least one of the arguments must be provided.
1177 *
1178 * @param requiredArguments The collection of arguments to form an
1179 * required argument set. It must not be
1180 * {@code null}.
1181 */
1182 public void addRequiredArgumentSet(
1183 final Collection<Argument> requiredArguments)
1184 {
1185 ensureNotNull(requiredArguments);
1186 final LinkedHashSet<Argument> argSet =
1187 new LinkedHashSet<Argument>(requiredArguments);
1188 requiredArgumentSets.add(Collections.unmodifiableSet(argSet));
1189 }
1190
1191
1192
1193 /**
1194 * Adds the provided set of arguments as a required argument set, in which
1195 * at least one of the arguments must be provided.
1196 *
1197 * @param arg1 The first argument to include in the required argument
1198 * set. It must not be {@code null}.
1199 * @param arg2 The second argument to include in the required argument
1200 * set. It must not be {@code null}.
1201 * @param remaining Any additional arguments to include in the required
1202 * argument set.
1203 */
1204 public void addRequiredArgumentSet(final Argument arg1, final Argument arg2,
1205 final Argument... remaining)
1206 {
1207 ensureNotNull(arg1, arg2);
1208
1209 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>();
1210 argSet.add(arg1);
1211 argSet.add(arg2);
1212 argSet.addAll(Arrays.asList(remaining));
1213
1214 requiredArgumentSets.add(Collections.unmodifiableSet(argSet));
1215 }
1216
1217
1218
1219 /**
1220 * Retrieves the set of unnamed trailing arguments in the provided command
1221 * line arguments.
1222 *
1223 * @return The set of unnamed trailing arguments in the provided command line
1224 * arguments, or an empty list if there were none.
1225 */
1226 public List<String> getTrailingArguments()
1227 {
1228 return Collections.unmodifiableList(trailingArgs);
1229 }
1230
1231
1232
1233 /**
1234 * Clears the set of trailing arguments for this argument parser.
1235 */
1236 void resetTrailingArguments()
1237 {
1238 trailingArgs.clear();
1239 }
1240
1241
1242
1243 /**
1244 * Adds the provided value to the set of trailing arguments.
1245 *
1246 * @param value The value to add to the set of trailing arguments.
1247 *
1248 * @throws ArgumentException If the parser already has the maximum allowed
1249 * number of trailing arguments.
1250 */
1251 void addTrailingArgument(final String value)
1252 throws ArgumentException
1253 {
1254 if ((maxTrailingArgs > 0) && (trailingArgs.size() >= maxTrailingArgs))
1255 {
1256 throw new ArgumentException(ERR_PARSER_TOO_MANY_TRAILING_ARGS.get(value,
1257 commandName, maxTrailingArgs));
1258 }
1259
1260 trailingArgs.add(value);
1261 }
1262
1263
1264
1265 /**
1266 * Creates a copy of this argument parser that is "clean" and appears as if it
1267 * has not been used to parse an argument set. The new parser will have all
1268 * of the same arguments and constraints as this parser.
1269 *
1270 * @return The "clean" copy of this argument parser.
1271 */
1272 public ArgumentParser getCleanCopy()
1273 {
1274 return new ArgumentParser(this);
1275 }
1276
1277
1278
1279 /**
1280 * Parses the provided set of arguments.
1281 *
1282 * @param args An array containing the argument information to parse. It
1283 * must not be {@code null}.
1284 *
1285 * @throws ArgumentException If a problem occurs while attempting to parse
1286 * the argument information.
1287 */
1288 public void parse(final String[] args)
1289 throws ArgumentException
1290 {
1291 // Iterate through the provided args strings and process them.
1292 boolean inTrailingArgs = false;
1293 boolean skipFinalValidation = false;
1294 for (int i=0; i < args.length; i++)
1295 {
1296 final String s = args[i];
1297
1298 if (inTrailingArgs)
1299 {
1300 if (maxTrailingArgs == 0)
1301 {
1302 throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get(
1303 s, commandName));
1304 }
1305 else if (trailingArgs.size() >= maxTrailingArgs)
1306 {
1307 throw new ArgumentException(ERR_PARSER_TOO_MANY_TRAILING_ARGS.get(s,
1308 commandName, maxTrailingArgs));
1309 }
1310 else
1311 {
1312 trailingArgs.add(s);
1313 }
1314 }
1315 else if (s.equals("--"))
1316 {
1317 // This signifies the end of the named arguments and the beginning of
1318 // the trailing arguments.
1319 inTrailingArgs = true;
1320 }
1321 else if (s.startsWith("--"))
1322 {
1323 // There may be an equal sign to separate the name from the value.
1324 final String argName;
1325 final int equalPos = s.indexOf('=');
1326 if (equalPos > 0)
1327 {
1328 argName = s.substring(2, equalPos);
1329 }
1330 else
1331 {
1332 argName = s.substring(2);
1333 }
1334
1335 final Argument a = namedArgsByLongID.get(toLowerCase(argName));
1336 if (a == null)
1337 {
1338 throw new ArgumentException(ERR_PARSER_NO_SUCH_LONG_ID.get(argName));
1339 }
1340 else if (a.isUsageArgument())
1341 {
1342 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
1343 }
1344
1345 a.incrementOccurrences();
1346 if (a.takesValue())
1347 {
1348 if (equalPos > 0)
1349 {
1350 a.addValue(s.substring(equalPos+1));
1351 }
1352 else
1353 {
1354 i++;
1355 if (i >= args.length)
1356 {
1357 throw new ArgumentException(ERR_PARSER_LONG_ARG_MISSING_VALUE.get(
1358 argName));
1359 }
1360 else
1361 {
1362 a.addValue(args[i]);
1363 }
1364 }
1365 }
1366 else
1367 {
1368 if (equalPos > 0)
1369 {
1370 throw new ArgumentException(
1371 ERR_PARSER_LONG_ARG_DOESNT_TAKE_VALUE.get(argName));
1372 }
1373 }
1374 }
1375 else if (s.startsWith("-"))
1376 {
1377 if (s.length() == 1)
1378 {
1379 throw new ArgumentException(ERR_PARSER_UNEXPECTED_DASH.get());
1380 }
1381 else if (s.length() == 2)
1382 {
1383 final char c = s.charAt(1);
1384 final Argument a = namedArgsByShortID.get(c);
1385 if (a == null)
1386 {
1387 throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c));
1388 }
1389 else if (a.isUsageArgument())
1390 {
1391 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
1392 }
1393
1394 a.incrementOccurrences();
1395 if (a.takesValue())
1396 {
1397 i++;
1398 if (i >= args.length)
1399 {
1400 throw new ArgumentException(
1401 ERR_PARSER_SHORT_ARG_MISSING_VALUE.get(c));
1402 }
1403 else
1404 {
1405 a.addValue(args[i]);
1406 }
1407 }
1408 }
1409 else
1410 {
1411 char c = s.charAt(1);
1412 Argument a = namedArgsByShortID.get(c);
1413 if (a == null)
1414 {
1415 throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c));
1416 }
1417 else if (a.isUsageArgument())
1418 {
1419 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
1420 }
1421
1422 a.incrementOccurrences();
1423 if (a.takesValue())
1424 {
1425 a.addValue(s.substring(2));
1426 }
1427 else
1428 {
1429 // The rest of the characters in the string must also resolve to
1430 // arguments that don't take values.
1431 for (int j=2; j < s.length(); j++)
1432 {
1433 c = s.charAt(j);
1434 a = namedArgsByShortID.get(c);
1435 if (a == null)
1436 {
1437 throw new ArgumentException(
1438 ERR_PARSER_NO_SUBSEQUENT_SHORT_ARG.get(c, s));
1439 }
1440 else if (a.isUsageArgument())
1441 {
1442 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
1443 }
1444
1445 a.incrementOccurrences();
1446 if (a.takesValue())
1447 {
1448 throw new ArgumentException(
1449 ERR_PARSER_SUBSEQUENT_SHORT_ARG_TAKES_VALUE.get(
1450 c, s));
1451 }
1452 }
1453 }
1454 }
1455 }
1456 else
1457 {
1458 inTrailingArgs = true;
1459 if (maxTrailingArgs == 0)
1460 {
1461 throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get(
1462 s, commandName));
1463 }
1464 else
1465 {
1466 trailingArgs.add(s);
1467 }
1468 }
1469 }
1470
1471
1472 // Perform any appropriate processing related to the use of a properties
1473 // file.
1474 if (! handlePropertiesFile())
1475 {
1476 return;
1477 }
1478
1479
1480 // If a usage argument was provided, then no further validation should be
1481 // performed.
1482 if (skipFinalValidation)
1483 {
1484 return;
1485 }
1486
1487
1488 // Make sure that all required arguments have values.
1489 for (final Argument a : namedArgs)
1490 {
1491 if (a.isRequired() && (! a.isPresent()))
1492 {
1493 throw new ArgumentException(ERR_PARSER_MISSING_REQUIRED_ARG.get(
1494 a.getIdentifierString()));
1495 }
1496 }
1497
1498
1499 // Make sure that at least the minimum number of trailing arguments were
1500 // provided.
1501 if (trailingArgs.size() < minTrailingArgs)
1502 {
1503 throw new ArgumentException(ERR_PARSER_NOT_ENOUGH_TRAILING_ARGS.get(
1504 commandName, minTrailingArgs, trailingArgsPlaceholder));
1505 }
1506
1507
1508 // Make sure that there are no dependent argument set conflicts.
1509 for (final ObjectPair<Argument,Set<Argument>> p : dependentArgumentSets)
1510 {
1511 final Argument targetArg = p.getFirst();
1512 if (targetArg.isPresent())
1513 {
1514 final Set<Argument> argSet = p.getSecond();
1515 boolean found = false;
1516 for (final Argument a : argSet)
1517 {
1518 if (a.isPresent())
1519 {
1520 found = true;
1521 break;
1522 }
1523 }
1524
1525 if (! found)
1526 {
1527 if (argSet.size() == 1)
1528 {
1529 throw new ArgumentException(
1530 ERR_PARSER_DEPENDENT_CONFLICT_SINGLE.get(
1531 targetArg.getIdentifierString(),
1532 argSet.iterator().next().getIdentifierString()));
1533 }
1534 else
1535 {
1536 boolean first = true;
1537 final StringBuilder buffer = new StringBuilder();
1538 for (final Argument a : argSet)
1539 {
1540 if (first)
1541 {
1542 first = false;
1543 }
1544 else
1545 {
1546 buffer.append(", ");
1547 }
1548 buffer.append(a.getIdentifierString());
1549 }
1550 throw new ArgumentException(
1551 ERR_PARSER_DEPENDENT_CONFLICT_MULTIPLE.get(
1552 targetArg.getIdentifierString(), buffer.toString()));
1553 }
1554 }
1555 }
1556 }
1557
1558
1559 // Make sure that there are no exclusive argument set conflicts.
1560 for (final Set<Argument> argSet : exclusiveArgumentSets)
1561 {
1562 Argument setArg = null;
1563 for (final Argument a : argSet)
1564 {
1565 if (a.isPresent())
1566 {
1567 if (setArg == null)
1568 {
1569 setArg = a;
1570 }
1571 else
1572 {
1573 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
1574 setArg.getIdentifierString(),
1575 a.getIdentifierString()));
1576 }
1577 }
1578 }
1579 }
1580
1581 // Make sure that there are no required argument set conflicts.
1582 for (final Set<Argument> argSet : requiredArgumentSets)
1583 {
1584 boolean found = false;
1585 for (final Argument a : argSet)
1586 {
1587 if (a.isPresent())
1588 {
1589 found = true;
1590 break;
1591 }
1592 }
1593
1594 if (! found)
1595 {
1596 boolean first = true;
1597 final StringBuilder buffer = new StringBuilder();
1598 for (final Argument a : argSet)
1599 {
1600 if (first)
1601 {
1602 first = false;
1603 }
1604 else
1605 {
1606 buffer.append(", ");
1607 }
1608 buffer.append(a.getIdentifierString());
1609 }
1610 throw new ArgumentException(ERR_PARSER_REQUIRED_CONFLICT.get(
1611 buffer.toString()));
1612 }
1613 }
1614 }
1615
1616
1617
1618 /**
1619 * Indicates whether the provided argument is one that indicates that the
1620 * parser should skip all validation except that performed when assigning
1621 * values from command-line arguments. Validation that will be skipped
1622 * includes ensuring that all required arguments have values, ensuring that
1623 * the minimum number of trailing arguments were provided, and ensuring that
1624 * there were no dependent/exclusive/required argument set conflicts.
1625 *
1626 * @param a The argument for which to make the determination.
1627 *
1628 * @return {@code true} if the provided argument is one that indicates that
1629 * final validation should be skipped, or {@code false} if not.
1630 */
1631 private static boolean skipFinalValidationBecauseOfArgument(final Argument a)
1632 {
1633 // We will skip final validation for all usage arguments except the
1634 // propertiesFilePath and noPropertiesFile arguments.
1635 if (ARG_NAME_PROPERTIES_FILE_PATH.equals(a.getLongIdentifier()) ||
1636 ARG_NAME_NO_PROPERTIES_FILE.equals(a.getLongIdentifier()))
1637 {
1638 return false;
1639 }
1640
1641 return a.isUsageArgument();
1642 }
1643
1644
1645
1646 /**
1647 * Performs any appropriate properties file processing for this argument
1648 * parser.
1649 *
1650 * @return {@code true} if the tool should continue processing, or
1651 * {@code false} if it should return immediately.
1652 *
1653 * @throws ArgumentException If a problem is encountered while attempting
1654 * to parse a properties file or update arguments
1655 * with the values contained in it.
1656 */
1657 private boolean handlePropertiesFile()
1658 throws ArgumentException
1659 {
1660 final BooleanArgument noPropertiesFile;
1661 final FileArgument generatePropertiesFile;
1662 final FileArgument propertiesFilePath;
1663 try
1664 {
1665 propertiesFilePath = getFileArgument(ARG_NAME_PROPERTIES_FILE_PATH);
1666 generatePropertiesFile =
1667 getFileArgument(ARG_NAME_GENERATE_PROPERTIES_FILE);
1668 noPropertiesFile = getBooleanArgument(ARG_NAME_NO_PROPERTIES_FILE);
1669 }
1670 catch (final Exception e)
1671 {
1672 Debug.debugException(e);
1673
1674 // This should only ever happen if the argument parser has an argument
1675 // with a name that conflicts with one of the properties file arguments
1676 // but isn't of the right type. In this case, we'll assume that no
1677 // properties file will be used.
1678 return true;
1679 }
1680
1681
1682 // If any of the properties file arguments isn't defined, then we'll assume
1683 // that no properties file will be used.
1684 if ((propertiesFilePath == null) || (generatePropertiesFile == null) ||
1685 (noPropertiesFile == null))
1686 {
1687 return true;
1688 }
1689
1690
1691 // If the noPropertiesFile argument is present, then don't do anything but
1692 // make sure that neither of the other arguments was specified.
1693 if (noPropertiesFile.isPresent())
1694 {
1695 if (propertiesFilePath.isPresent())
1696 {
1697 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
1698 noPropertiesFile.getIdentifierString(),
1699 propertiesFilePath.getIdentifierString()));
1700 }
1701 else if (generatePropertiesFile.isPresent())
1702 {
1703 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
1704 noPropertiesFile.getIdentifierString(),
1705 generatePropertiesFile.getIdentifierString()));
1706 }
1707 else
1708 {
1709 return true;
1710 }
1711 }
1712
1713
1714 // If the generatePropertiesFile argument is present, then make sure the
1715 // propertiesFilePath argument is not set and generate the output.
1716 if (generatePropertiesFile.isPresent())
1717 {
1718 if (propertiesFilePath.isPresent())
1719 {
1720 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
1721 generatePropertiesFile.getIdentifierString(),
1722 propertiesFilePath.getIdentifierString()));
1723 }
1724 else
1725 {
1726 generatePropertiesFile(
1727 generatePropertiesFile.getValue().getAbsolutePath());
1728 return false;
1729 }
1730 }
1731
1732
1733 // If the propertiesFilePath argument is present, then try to make use of
1734 // the specified file.
1735 if (propertiesFilePath.isPresent())
1736 {
1737 final File propertiesFile = propertiesFilePath.getValue();
1738 if (propertiesFile.exists() && propertiesFile.isFile())
1739 {
1740 handlePropertiesFile(propertiesFilePath.getValue());
1741 }
1742 else
1743 {
1744 throw new ArgumentException(
1745 ERR_PARSER_NO_SUCH_PROPERTIES_FILE.get(
1746 propertiesFilePath.getIdentifierString(),
1747 propertiesFile.getAbsolutePath()));
1748 }
1749 return true;
1750 }
1751
1752
1753 // We may still use a properties file if the path was specified in either a
1754 // JVM property or an environment variable. If both are defined, the JVM
1755 // property will take precedence. If a property or environment variable
1756 // specifies an invalid value, then we'll just ignore it.
1757 String path = System.getProperty(PROPERTY_DEFAULT_PROPERTIES_FILE_PATH);
1758 if (path == null)
1759 {
1760 path = System.getenv(ENV_DEFAULT_PROPERTIES_FILE_PATH);
1761 }
1762
1763 if (path != null)
1764 {
1765 final File propertiesFile = new File(path);
1766 if (propertiesFile.exists() && propertiesFile.isFile())
1767 {
1768 handlePropertiesFile(propertiesFile);
1769 }
1770 }
1771
1772 return true;
1773 }
1774
1775
1776
1777 /**
1778 * Write an empty properties file for this argument parser to the specified
1779 * path.
1780 *
1781 * @param path The path to the properties file to be written.
1782 *
1783 * @throws ArgumentException If a problem is encountered while writing the
1784 * properties file.
1785 */
1786 private void generatePropertiesFile(final String path)
1787 throws ArgumentException
1788 {
1789 final PrintWriter w;
1790 try
1791 {
1792 w = new PrintWriter(path);
1793 }
1794 catch (final Exception e)
1795 {
1796 Debug.debugException(e);
1797 throw new ArgumentException(
1798 ERR_PARSER_GEN_PROPS_CANNOT_OPEN_FILE.get(path,
1799 getExceptionMessage(e)),
1800 e);
1801 }
1802
1803 try
1804 {
1805 wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_1.get(commandName));
1806 w.println('#');
1807 wrapComment(w,
1808 INFO_PARSER_GEN_PROPS_HEADER_2.get(commandName,
1809 ARG_NAME_PROPERTIES_FILE_PATH,
1810 PROPERTY_DEFAULT_PROPERTIES_FILE_PATH,
1811 ENV_DEFAULT_PROPERTIES_FILE_PATH, ARG_NAME_NO_PROPERTIES_FILE));
1812 w.println('#');
1813
1814 for (final Argument a : getNamedArguments())
1815 {
1816 if (a.isUsageArgument() || a.isHidden())
1817 {
1818 continue;
1819 }
1820
1821 final String argName = a.getLongIdentifier();
1822 if (argName != null)
1823 {
1824 wrapComment(w,
1825 INFO_PARSER_GEN_PROPS_HEADER_3.get(commandName, argName));
1826 w.println('#');
1827 break;
1828 }
1829 }
1830
1831 wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_4.get());
1832 w.println('#');
1833 wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_5.get(commandName));
1834
1835 for (final Argument a : getNamedArguments())
1836 {
1837 if (a.isUsageArgument() || a.isHidden())
1838 {
1839 continue;
1840 }
1841
1842 w.println();
1843 w.println();
1844 wrapComment(w, a.getDescription());
1845 w.println('#');
1846
1847 final String constraints = a.getValueConstraints();
1848 if ((constraints != null) && (constraints.length() > 0) &&
1849 (! (a instanceof BooleanArgument)))
1850 {
1851 wrapComment(w, constraints);
1852 w.println('#');
1853 }
1854
1855 final String identifier;
1856 if (a.getLongIdentifier() != null)
1857 {
1858 identifier = a.getLongIdentifier();
1859 }
1860 else
1861 {
1862 identifier = a.getIdentifierString();
1863 }
1864
1865 String placeholder = a.getValuePlaceholder();
1866 if (placeholder == null)
1867 {
1868 if (a instanceof BooleanArgument)
1869 {
1870 placeholder = "{true|false}";
1871 }
1872 else
1873 {
1874 placeholder = "";
1875 }
1876 }
1877
1878 final String propertyName = commandName + '.' + identifier;
1879 w.println("# " + propertyName + '=' + placeholder);
1880
1881 if (a.isPresent())
1882 {
1883 for (final String s : a.getValueStringRepresentations(false))
1884 {
1885 w.println(propertyName + '=' + s);
1886 }
1887 }
1888 }
1889 }
1890 finally
1891 {
1892 w.close();
1893 }
1894 }
1895
1896
1897
1898 /**
1899 * Wraps the given string and writes it as a comment to the provided writer.
1900 *
1901 * @param w The writer to use to write the wrapped and commented string.
1902 * @param s The string to be wrapped and written.
1903 */
1904 private static void wrapComment(final PrintWriter w, final String s)
1905 {
1906 for (final String line : wrapLine(s, 77))
1907 {
1908 w.println("# " + line);
1909 }
1910 }
1911
1912
1913
1914 /**
1915 * Reads the contents of the specified properties file and updates the
1916 * configured arguments as appropriate.
1917 *
1918 * @param propertiesFile The properties file to process.
1919 *
1920 * @throws ArgumentException If a problem is encountered while examining the
1921 * properties file, or while trying to assign a
1922 * property value to a corresponding argument.
1923 */
1924 private void handlePropertiesFile(final File propertiesFile)
1925 throws ArgumentException
1926 {
1927 final BufferedReader reader;
1928 try
1929 {
1930 reader = new BufferedReader(new FileReader(propertiesFile));
1931 }
1932 catch (final Exception e)
1933 {
1934 Debug.debugException(e);
1935 throw new ArgumentException(
1936 ERR_PARSER_CANNOT_OPEN_PROP_FILE.get(
1937 propertiesFile.getAbsolutePath(), getExceptionMessage(e)),
1938 e);
1939 }
1940
1941 try
1942 {
1943 // Read all of the lines of the file, ignoring comments and unwrapping
1944 // properties that span multiple lines.
1945 boolean lineIsContinued = false;
1946 int lineNumber = 0;
1947 final ArrayList<ObjectPair<Integer,StringBuilder>> propertyLines =
1948 new ArrayList<ObjectPair<Integer,StringBuilder>>(10);
1949 while (true)
1950 {
1951 String line;
1952 try
1953 {
1954 line = reader.readLine();
1955 lineNumber++;
1956 }
1957 catch (final Exception e)
1958 {
1959 Debug.debugException(e);
1960 throw new ArgumentException(
1961 ERR_PARSER_ERROR_READING_PROP_FILE.get(
1962 propertiesFile.getAbsolutePath(), getExceptionMessage(e)),
1963 e);
1964 }
1965
1966
1967 // If the line is null, then we've reached the end of the file. If we
1968 // expect a previous line to have been continued, then this is an error.
1969 if (line == null)
1970 {
1971 if (lineIsContinued)
1972 {
1973 throw new ArgumentException(
1974 ERR_PARSER_PROP_FILE_MISSING_CONTINUATION.get(
1975 (lineNumber-1), propertiesFile.getAbsolutePath()));
1976 }
1977 break;
1978 }
1979
1980
1981 // See if the line has any leading whitespace, and if so then trim it
1982 // off. If there is leading whitespace, then make sure that we expect
1983 // the previous line to be continued.
1984 final int initialLength = line.length();
1985 line = trimLeading(line);
1986 final boolean hasLeadingWhitespace = (line.length() < initialLength);
1987 if (hasLeadingWhitespace && (! lineIsContinued))
1988 {
1989 throw new ArgumentException(
1990 ERR_PARSER_PROP_FILE_UNEXPECTED_LEADING_SPACE.get(
1991 propertiesFile.getAbsolutePath(), lineNumber));
1992 }
1993
1994
1995 // If the line is empty or starts with "#", then skip it. But make sure
1996 // we didn't expect the previous line to be continued.
1997 if ((line.length() == 0) || line.startsWith("#"))
1998 {
1999 if (lineIsContinued)
2000 {
2001 throw new ArgumentException(
2002 ERR_PARSER_PROP_FILE_MISSING_CONTINUATION.get(
2003 (lineNumber-1), propertiesFile.getAbsolutePath()));
2004 }
2005 continue;
2006 }
2007
2008
2009 // See if the line ends with a backslash and if so then trim it off.
2010 final boolean hasTrailingBackslash = line.endsWith("\\");
2011 if (line.endsWith("\\"))
2012 {
2013 line = line.substring(0, (line.length() - 1));
2014 }
2015
2016
2017 // If the previous line needs to be continued, then append the new line
2018 // to it. Otherwise, add it as a new line.
2019 if (lineIsContinued)
2020 {
2021 propertyLines.get(propertyLines.size() - 1).getSecond().append(line);
2022 }
2023 else
2024 {
2025 propertyLines.add(new ObjectPair<Integer,StringBuilder>(lineNumber,
2026 new StringBuilder(line)));
2027 }
2028
2029 lineIsContinued = hasTrailingBackslash;
2030 }
2031
2032
2033 // Parse all of the lines into a map of identifiers and their
2034 // corresponding values.
2035 if (propertyLines.isEmpty())
2036 {
2037 return;
2038 }
2039
2040 final HashMap<String,ArrayList<String>> propertyMap =
2041 new HashMap<String,ArrayList<String>>(propertyLines.size());
2042 for (final ObjectPair<Integer,StringBuilder> p : propertyLines)
2043 {
2044 final String line = p.getSecond().toString();
2045 final int equalPos = line.indexOf('=');
2046 if (equalPos <= 0)
2047 {
2048 throw new ArgumentException(ERR_PARSER_MALFORMED_PROP_LINE.get(
2049 propertiesFile.getAbsolutePath(), p.getFirst(), line));
2050 }
2051
2052 final String propertyName = line.substring(0, equalPos).trim();
2053 final String propertyValue = line.substring(equalPos+1).trim();
2054 if (propertyValue.length() == 0)
2055 {
2056 // The property doesn't have a value, so we can ignore it.
2057 continue;
2058 }
2059
2060
2061 // An argument can have multiple identifiers, and we will allow any of
2062 // them to be used to reference it. To deal with this, we'll map the
2063 // argument identifier to its corresponding argument and then use the
2064 // preferred identifier for that argument in the map.
2065 boolean prefixedWithToolName = false;
2066 Argument a = getNamedArgument(propertyName);
2067 if (a == null)
2068 {
2069 // It could be that the argument name was prefixed with the tool name.
2070 // Check to see if that was the case.
2071 if (propertyName.startsWith(commandName + '.'))
2072 {
2073 final String basePropertyName =
2074 propertyName.substring(commandName.length()+1);
2075 a = getNamedArgument(basePropertyName);
2076 prefixedWithToolName = true;
2077 }
2078 }
2079
2080 if (a == null)
2081 {
2082 // This could mean that there's a typo in the property name, but it's
2083 // more likely the case that the property is for a different tool. In
2084 // either case, we'll ignore it.
2085 continue;
2086 }
2087
2088 final String canonicalPropertyName;
2089 if (prefixedWithToolName)
2090 {
2091 canonicalPropertyName = commandName + '.' + a.getIdentifierString();
2092 }
2093 else
2094 {
2095 canonicalPropertyName = a.getIdentifierString();
2096 }
2097
2098 ArrayList<String> valueList = propertyMap.get(canonicalPropertyName);
2099 if (valueList == null)
2100 {
2101 valueList = new ArrayList<String>(5);
2102 propertyMap.put(canonicalPropertyName, valueList);
2103 }
2104 valueList.add(propertyValue);
2105 }
2106
2107
2108 // Iterate through all of the named arguments for the argument parser and
2109 // see if we should use the properties to assign values to any of the
2110 // arguments that weren't provided on the command line.
2111 for (final Argument a : namedArgs)
2112 {
2113 if (a.getNumOccurrences() > 0)
2114 {
2115 // The argument was provided on the command line, and that will always
2116 // override anything that might be in the properties file.
2117 continue;
2118 }
2119
2120
2121 // See if the properties file had a property that is specific to the
2122 // tool. If so, then try to assign its values to the argument. If not,
2123 // then fall back to checking for a set of values that are generic to
2124 // any tool that has an argument with that name.
2125 List<String> values =
2126 propertyMap.get(commandName + '.' + a.getIdentifierString());
2127 if (values == null)
2128 {
2129 values = propertyMap.get(a.getIdentifierString());
2130 }
2131
2132 if (values != null)
2133 {
2134 for (final String value : values)
2135 {
2136 if (a instanceof BooleanArgument)
2137 {
2138 // We'll treat this as a BooleanValueArgument.
2139 final BooleanValueArgument bva = new BooleanValueArgument(
2140 a.getShortIdentifier(), a.getLongIdentifier(), false, null,
2141 a.getDescription());
2142 bva.addValue(value);
2143 if (bva.getValue())
2144 {
2145 a.incrementOccurrences();
2146 }
2147 }
2148 else
2149 {
2150 a.addValue(value);
2151 a.incrementOccurrences();
2152 }
2153 }
2154 }
2155 }
2156 }
2157 finally
2158 {
2159 try
2160 {
2161 reader.close();
2162 }
2163 catch (final Exception e)
2164 {
2165 Debug.debugException(e);
2166 }
2167 }
2168 }
2169
2170
2171
2172 /**
2173 * Retrieves lines that make up the usage information for this program,
2174 * optionally wrapping long lines.
2175 *
2176 * @param maxWidth The maximum line width to use for the output. If this is
2177 * less than or equal to zero, then no wrapping will be
2178 * performed.
2179 *
2180 * @return The lines that make up the usage information for this program.
2181 */
2182 public List<String> getUsage(final int maxWidth)
2183 {
2184 final ArrayList<String> lines = new ArrayList<String>(100);
2185
2186 // First is a description of the command.
2187 lines.addAll(wrapLine(commandDescription, maxWidth));
2188 lines.add("");
2189
2190 // Next comes the usage. It may include neither, either, or both of the
2191 // set of options and trailing arguments.
2192 if (namedArgs.isEmpty())
2193 {
2194 if (maxTrailingArgs == 0)
2195 {
2196 lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_NOTRAILING.get(commandName),
2197 maxWidth));
2198 }
2199 else
2200 {
2201 lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_TRAILING.get(
2202 commandName, trailingArgsPlaceholder),
2203 maxWidth));
2204 }
2205 }
2206 else
2207 {
2208 if (maxTrailingArgs == 0)
2209 {
2210 lines.addAll(wrapLine(INFO_USAGE_OPTIONS_NOTRAILING.get(commandName),
2211 maxWidth));
2212 }
2213 else
2214 {
2215 lines.addAll(wrapLine(INFO_USAGE_OPTIONS_TRAILING.get(
2216 commandName, trailingArgsPlaceholder),
2217 maxWidth));
2218 }
2219
2220 lines.add("");
2221 lines.add(INFO_USAGE_OPTIONS_INCLUDE.get());
2222
2223
2224 // If there are any argument groups, then collect the arguments in those
2225 // groups.
2226 boolean hasRequired = false;
2227 final LinkedHashMap<String,List<Argument>> argumentsByGroup =
2228 new LinkedHashMap<String,List<Argument>>(10);
2229 final ArrayList<Argument> argumentsWithoutGroup =
2230 new ArrayList<Argument>(namedArgs.size());
2231 final ArrayList<Argument> usageArguments =
2232 new ArrayList<Argument>(namedArgs.size());
2233 for (final Argument a : namedArgs)
2234 {
2235 if (a.isHidden())
2236 {
2237 // This argument shouldn't be included in the usage output.
2238 continue;
2239 }
2240
2241 if (a.isRequired() && (! a.hasDefaultValue()))
2242 {
2243 hasRequired = true;
2244 }
2245
2246 final String argumentGroup = a.getArgumentGroupName();
2247 if (argumentGroup == null)
2248 {
2249 if (a.isUsageArgument())
2250 {
2251 usageArguments.add(a);
2252 }
2253 else
2254 {
2255 argumentsWithoutGroup.add(a);
2256 }
2257 }
2258 else
2259 {
2260 List<Argument> groupArgs = argumentsByGroup.get(argumentGroup);
2261 if (groupArgs == null)
2262 {
2263 groupArgs = new ArrayList<Argument>(10);
2264 argumentsByGroup.put(argumentGroup, groupArgs);
2265 }
2266
2267 groupArgs.add(a);
2268 }
2269 }
2270
2271
2272 // Iterate through the defined argument groups and display usage
2273 // information for each of them.
2274 for (final Map.Entry<String,List<Argument>> e :
2275 argumentsByGroup.entrySet())
2276 {
2277 lines.add("");
2278 lines.add(" " + e.getKey());
2279 lines.add("");
2280 for (final Argument a : e.getValue())
2281 {
2282 getArgUsage(a, lines, true, maxWidth);
2283 }
2284 }
2285
2286 if (! argumentsWithoutGroup.isEmpty())
2287 {
2288 if (argumentsByGroup.isEmpty())
2289 {
2290 for (final Argument a : argumentsWithoutGroup)
2291 {
2292 getArgUsage(a, lines, false, maxWidth);
2293 }
2294 }
2295 else
2296 {
2297 lines.add("");
2298 lines.add(" " + INFO_USAGE_UNGROUPED_ARGS.get());
2299 lines.add("");
2300 for (final Argument a : argumentsWithoutGroup)
2301 {
2302 getArgUsage(a, lines, true, maxWidth);
2303 }
2304 }
2305 }
2306
2307 if (! usageArguments.isEmpty())
2308 {
2309 if (argumentsByGroup.isEmpty())
2310 {
2311 for (final Argument a : usageArguments)
2312 {
2313 getArgUsage(a, lines, false, maxWidth);
2314 }
2315 }
2316 else
2317 {
2318 lines.add("");
2319 lines.add(" " + INFO_USAGE_USAGE_ARGS.get());
2320 lines.add("");
2321 for (final Argument a : usageArguments)
2322 {
2323 getArgUsage(a, lines, true, maxWidth);
2324 }
2325 }
2326 }
2327
2328 if (hasRequired)
2329 {
2330 lines.add("");
2331 if (argumentsByGroup.isEmpty())
2332 {
2333 lines.add("* " + INFO_USAGE_ARG_IS_REQUIRED.get());
2334 }
2335 else
2336 {
2337 lines.add(" * " + INFO_USAGE_ARG_IS_REQUIRED.get());
2338 }
2339 }
2340 }
2341
2342 return lines;
2343 }
2344
2345
2346
2347 /**
2348 * Adds usage information for the provided argument to the given list.
2349 *
2350 * @param a The argument for which to get the usage information.
2351 * @param lines The list to which the resulting lines should be added.
2352 * @param indent Indicates whether to indent each line.
2353 * @param maxWidth The maximum width of each line, in characters.
2354 */
2355 private static void getArgUsage(final Argument a, final List<String> lines,
2356 final boolean indent, final int maxWidth)
2357 {
2358 final StringBuilder argLine = new StringBuilder();
2359 if (indent && (maxWidth > 10))
2360 {
2361 if (a.isRequired() && (! a.hasDefaultValue()))
2362 {
2363 argLine.append(" * ");
2364 }
2365 else
2366 {
2367 argLine.append(" ");
2368 }
2369 }
2370 else if (a.isRequired() && (! a.hasDefaultValue()))
2371 {
2372 argLine.append("* ");
2373 }
2374
2375 boolean first = true;
2376 for (final Character c : a.getShortIdentifiers())
2377 {
2378 if (first)
2379 {
2380 argLine.append('-');
2381 first = false;
2382 }
2383 else
2384 {
2385 argLine.append(", -");
2386 }
2387 argLine.append(c);
2388 }
2389
2390 for (final String s : a.getLongIdentifiers())
2391 {
2392 if (first)
2393 {
2394 argLine.append("--");
2395 first = false;
2396 }
2397 else
2398 {
2399 argLine.append(", --");
2400 }
2401 argLine.append(s);
2402 }
2403
2404 final String valuePlaceholder = a.getValuePlaceholder();
2405 if (valuePlaceholder != null)
2406 {
2407 argLine.append(' ');
2408 argLine.append(valuePlaceholder);
2409 }
2410
2411 // NOTE: This line won't be wrapped. That's intentional because I
2412 // think it would probably look bad no matter how we did it.
2413 lines.add(argLine.toString());
2414
2415
2416 // The description should be wrapped, if necessary. We'll also want to
2417 // indent it (unless someone chose an absurdly small wrap width) to make
2418 // it stand out from the argument lines.
2419 final String description = a.getDescription();
2420 if (maxWidth > 10)
2421 {
2422 final String indentString;
2423 if (indent)
2424 {
2425 indentString = " ";
2426 }
2427 else
2428 {
2429 indentString = " ";
2430 }
2431
2432 final List<String> descLines = wrapLine(description,
2433 (maxWidth-indentString.length()));
2434 for (final String s : descLines)
2435 {
2436 lines.add(indentString + s);
2437 }
2438 }
2439 else
2440 {
2441 lines.addAll(wrapLine(description, maxWidth));
2442 }
2443 }
2444
2445
2446
2447 /**
2448 * Writes usage information for this program to the provided output stream
2449 * using the UTF-8 encoding, optionally wrapping long lines.
2450 *
2451 * @param outputStream The output stream to which the usage information
2452 * should be written. It must not be {@code null}.
2453 * @param maxWidth The maximum line width to use for the output. If
2454 * this is less than or equal to zero, then no wrapping
2455 * will be performed.
2456 *
2457 * @throws IOException If an error occurs while attempting to write to the
2458 * provided output stream.
2459 */
2460 public void getUsage(final OutputStream outputStream, final int maxWidth)
2461 throws IOException
2462 {
2463 final List<String> usageLines = getUsage(maxWidth);
2464 for (final String s : usageLines)
2465 {
2466 outputStream.write(getBytes(s));
2467 outputStream.write(EOL_BYTES);
2468 }
2469 }
2470
2471
2472
2473 /**
2474 * Retrieves a string representation of the usage information.
2475 *
2476 * @param maxWidth The maximum line width to use for the output. If this is
2477 * less than or equal to zero, then no wrapping will be
2478 * performed.
2479 *
2480 * @return A string representation of the usage information
2481 */
2482 public String getUsageString(final int maxWidth)
2483 {
2484 final StringBuilder buffer = new StringBuilder();
2485 getUsageString(buffer, maxWidth);
2486 return buffer.toString();
2487 }
2488
2489
2490
2491 /**
2492 * Appends a string representation of the usage information to the provided
2493 * buffer.
2494 *
2495 * @param buffer The buffer to which the information should be appended.
2496 * @param maxWidth The maximum line width to use for the output. If this is
2497 * less than or equal to zero, then no wrapping will be
2498 * performed.
2499 */
2500 public void getUsageString(final StringBuilder buffer, final int maxWidth)
2501 {
2502 for (final String line : getUsage(maxWidth))
2503 {
2504 buffer.append(line);
2505 buffer.append(EOL);
2506 }
2507 }
2508
2509
2510
2511 /**
2512 * Retrieves a string representation of this argument parser.
2513 *
2514 * @return A string representation of this argument parser.
2515 */
2516 @Override()
2517 public String toString()
2518 {
2519 final StringBuilder buffer = new StringBuilder();
2520 toString(buffer);
2521 return buffer.toString();
2522 }
2523
2524
2525
2526 /**
2527 * Appends a string representation of this argument parser to the provided
2528 * buffer.
2529 *
2530 * @param buffer The buffer to which the information should be appended.
2531 */
2532 public void toString(final StringBuilder buffer)
2533 {
2534 buffer.append("ArgumentParser(commandName='");
2535 buffer.append(commandName);
2536 buffer.append("', commandDescription='");
2537 buffer.append(commandDescription);
2538 buffer.append("', minTrailingArgs=");
2539 buffer.append(minTrailingArgs);
2540 buffer.append("', maxTrailingArgs=");
2541 buffer.append(maxTrailingArgs);
2542
2543 if (trailingArgsPlaceholder != null)
2544 {
2545 buffer.append(", trailingArgsPlaceholder='");
2546 buffer.append(trailingArgsPlaceholder);
2547 buffer.append('\'');
2548 }
2549
2550 buffer.append("namedArgs={");
2551
2552 final Iterator<Argument> iterator = namedArgs.iterator();
2553 while (iterator.hasNext())
2554 {
2555 iterator.next().toString(buffer);
2556 if (iterator.hasNext())
2557 {
2558 buffer.append(", ");
2559 }
2560 }
2561
2562 buffer.append("})");
2563 }
2564 }