001 /*
002 * Copyright 2008-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2014 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.IOException;
026 import java.io.OutputStream;
027 import java.io.Serializable;
028 import java.util.ArrayList;
029 import java.util.Arrays;
030 import java.util.Collection;
031 import java.util.Collections;
032 import java.util.Iterator;
033 import java.util.LinkedHashSet;
034 import java.util.LinkedHashMap;
035 import java.util.List;
036 import java.util.Set;
037
038 import com.unboundid.util.Debug;
039 import com.unboundid.util.ObjectPair;
040 import com.unboundid.util.ThreadSafety;
041 import com.unboundid.util.ThreadSafetyLevel;
042
043 import static com.unboundid.util.StaticUtils.*;
044 import static com.unboundid.util.Validator.*;
045 import static com.unboundid.util.args.ArgsMessages.*;
046
047
048
049 /**
050 * This class provides an argument parser, which may be used to process command
051 * line arguments provided to Java applications. See the package-level Javadoc
052 * documentation for details regarding the capabilities of the argument parser.
053 */
054 @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
055 public final class ArgumentParser
056 implements Serializable
057 {
058 /**
059 * The serial version UID for this serializable class.
060 */
061 private static final long serialVersionUID = 361008526269946465L;
062
063
064
065 // The maximum number of trailing arguments allowed to be provided.
066 private final int maxTrailingArgs;
067
068 // The set of named arguments associated with this parser, indexed by short
069 // identifier.
070 private final LinkedHashMap<Character,Argument> namedArgsByShortID;
071
072 // The set of named arguments associated with this parser, indexed by long
073 // identifier.
074 private final LinkedHashMap<String,Argument> namedArgsByLongID;
075
076 // The full set of named arguments associated with this parser.
077 private final List<Argument> namedArgs;
078
079 // Sets of arguments in which if the key argument is provided, then at least
080 // one of the value arguments must also be provided.
081 private final List<ObjectPair<Argument,Set<Argument>>> dependentArgumentSets;
082
083 // Sets of arguments in which at most one argument in the list is allowed to
084 // be present.
085 private final List<Set<Argument>> exclusiveArgumentSets;
086
087 // Sets of arguments in which at least one argument in the list is required to
088 // be present.
089 private final List<Set<Argument>> requiredArgumentSets;
090
091 // The list of trailing arguments provided on the command line.
092 private final List<String> trailingArgs;
093
094 // The description for the associated command.
095 private final String commandDescription;
096
097 // The name for the associated command.
098 private final String commandName;
099
100 // The placeholder string for the trailing arguments.
101 private final String trailingArgsPlaceholder;
102
103
104
105 /**
106 * Creates a new instance of this argument parser with the provided
107 * information. It will not allow unnamed trailing arguments.
108 *
109 * @param commandName The name of the application or utility with
110 * which this argument parser is associated. It
111 * must not be {@code null}.
112 * @param commandDescription A description of the application or utility
113 * with which this argument parser is associated.
114 * It will be included in generated usage
115 * information. It must not be {@code null}.
116 *
117 * @throws ArgumentException If either the command name or command
118 * description is {@code null},
119 */
120 public ArgumentParser(final String commandName,
121 final String commandDescription)
122 throws ArgumentException
123 {
124 this(commandName, commandDescription, 0, null);
125 }
126
127
128
129 /**
130 * Creates a new instance of this argument parser with the provided
131 * information.
132 *
133 * @param commandName The name of the application or utility
134 * with which this argument parser is
135 * associated. It must not be {@code null}.
136 * @param commandDescription A description of the application or
137 * utility with which this argument parser is
138 * associated. It will be included in
139 * generated usage information. It must not
140 * be {@code null}.
141 * @param maxTrailingArgs The maximum number of trailing arguments
142 * that may be provided to this command. A
143 * value of zero indicates that no trailing
144 * arguments will be allowed. A value less
145 * than zero will indicate that there is no
146 * limit on the number of trailing arguments
147 * allowed.
148 * @param trailingArgsPlaceholder A placeholder string that will be included
149 * in usage output to indicate what trailing
150 * arguments may be provided. It must not be
151 * {@code null} if {@code maxTrailingArgs} is
152 * anything other than zero.
153 *
154 * @throws ArgumentException If either the command name or command
155 * description is {@code null}, or if the maximum
156 * number of trailing arguments is non-zero and
157 * the trailing arguments placeholder is
158 * {@code null}.
159 */
160 public ArgumentParser(final String commandName,
161 final String commandDescription,
162 final int maxTrailingArgs,
163 final String trailingArgsPlaceholder)
164 throws ArgumentException
165 {
166 if (commandName == null)
167 {
168 throw new ArgumentException(ERR_PARSER_COMMAND_NAME_NULL.get());
169 }
170
171 if (commandDescription == null)
172 {
173 throw new ArgumentException(ERR_PARSER_COMMAND_DESCRIPTION_NULL.get());
174 }
175
176 if ((maxTrailingArgs != 0) && (trailingArgsPlaceholder == null))
177 {
178 throw new ArgumentException(
179 ERR_PARSER_TRAILING_ARGS_PLACEHOLDER_NULL.get());
180 }
181
182 this.commandName = commandName;
183 this.commandDescription = commandDescription;
184 this.trailingArgsPlaceholder = trailingArgsPlaceholder;
185
186 if (maxTrailingArgs >= 0)
187 {
188 this.maxTrailingArgs = maxTrailingArgs;
189 }
190 else
191 {
192 this.maxTrailingArgs = Integer.MAX_VALUE;
193 }
194
195 namedArgsByShortID = new LinkedHashMap<Character,Argument>();
196 namedArgsByLongID = new LinkedHashMap<String,Argument>();
197 namedArgs = new ArrayList<Argument>();
198 trailingArgs = new ArrayList<String>();
199 dependentArgumentSets = new ArrayList<ObjectPair<Argument,Set<Argument>>>();
200 exclusiveArgumentSets = new ArrayList<Set<Argument>>();
201 requiredArgumentSets = new ArrayList<Set<Argument>>();
202 }
203
204
205
206 /**
207 * Creates a new argument parser that is a "clean" copy of the provided source
208 * argument parser.
209 *
210 * @param source The source argument parser to use for this argument parser.
211 */
212 private ArgumentParser(final ArgumentParser source)
213 {
214 commandName = source.commandName;
215 commandDescription = source.commandDescription;
216 maxTrailingArgs = source.maxTrailingArgs;
217 trailingArgsPlaceholder = source.trailingArgsPlaceholder;
218
219 trailingArgs = new ArrayList<String>();
220
221 namedArgs = new ArrayList<Argument>(source.namedArgs.size());
222 namedArgsByLongID =
223 new LinkedHashMap<String,Argument>(source.namedArgsByLongID.size());
224 namedArgsByShortID = new LinkedHashMap<Character,Argument>(
225 source.namedArgsByShortID.size());
226
227 final LinkedHashMap<String,Argument> argsByID =
228 new LinkedHashMap<String,Argument>(source.namedArgs.size());
229 for (final Argument sourceArg : source.namedArgs)
230 {
231 final Argument a = sourceArg.getCleanCopy();
232
233 try
234 {
235 a.setRegistered();
236 }
237 catch (final ArgumentException ae)
238 {
239 // This should never happen.
240 Debug.debugException(ae);
241 }
242
243 namedArgs.add(a);
244 argsByID.put(a.getIdentifierString(), a);
245
246 for (final Character c : a.getShortIdentifiers())
247 {
248 namedArgsByShortID.put(c, a);
249 }
250
251 for (final String s : a.getLongIdentifiers())
252 {
253 namedArgsByLongID.put(toLowerCase(s), a);
254 }
255 }
256
257 dependentArgumentSets = new ArrayList<ObjectPair<Argument,Set<Argument>>>(
258 source.dependentArgumentSets.size());
259 for (final ObjectPair<Argument,Set<Argument>> p :
260 source.dependentArgumentSets)
261 {
262 final Set<Argument> sourceSet = p.getSecond();
263 final LinkedHashSet<Argument> newSet =
264 new LinkedHashSet<Argument>(sourceSet.size());
265 for (final Argument a : sourceSet)
266 {
267 newSet.add(argsByID.get(a.getIdentifierString()));
268 }
269
270 final Argument sourceFirst = p.getFirst();
271 final Argument newFirst = argsByID.get(sourceFirst.getIdentifierString());
272 dependentArgumentSets.add(
273 new ObjectPair<Argument, Set<Argument>>(newFirst, newSet));
274 }
275
276 exclusiveArgumentSets =
277 new ArrayList<Set<Argument>>(source.exclusiveArgumentSets.size());
278 for (final Set<Argument> sourceSet : source.exclusiveArgumentSets)
279 {
280 final LinkedHashSet<Argument> newSet =
281 new LinkedHashSet<Argument>(sourceSet.size());
282 for (final Argument a : sourceSet)
283 {
284 newSet.add(argsByID.get(a.getIdentifierString()));
285 }
286
287 exclusiveArgumentSets.add(newSet);
288 }
289
290 requiredArgumentSets =
291 new ArrayList<Set<Argument>>(source.requiredArgumentSets.size());
292 for (final Set<Argument> sourceSet : source.requiredArgumentSets)
293 {
294 final LinkedHashSet<Argument> newSet =
295 new LinkedHashSet<Argument>(sourceSet.size());
296 for (final Argument a : sourceSet)
297 {
298 newSet.add(argsByID.get(a.getIdentifierString()));
299 }
300 requiredArgumentSets.add(newSet);
301 }
302 }
303
304
305
306 /**
307 * Retrieves the name of the application or utility with which this command
308 * line argument parser is associated.
309 *
310 * @return The name of the application or utility with which this command
311 * line argument parser is associated.
312 */
313 public String getCommandName()
314 {
315 return commandName;
316 }
317
318
319
320 /**
321 * Retrieves a description of the application or utility with which this
322 * command line argument parser is associated.
323 *
324 * @return A description of the application or utility with which this
325 * command line argument parser is associated.
326 */
327 public String getCommandDescription()
328 {
329 return commandDescription;
330 }
331
332
333
334 /**
335 * Indicates whether this argument parser allows any unnamed trailing
336 * arguments to be provided.
337 *
338 * @return {@code true} if at least one unnamed trailing argument may be
339 * provided, or {@code false} if not.
340 */
341 public boolean allowsTrailingArguments()
342 {
343 return (maxTrailingArgs != 0);
344 }
345
346
347
348 /**
349 * Retrieves the placeholder string that will be provided in usage information
350 * to indicate what may be included in the trailing arguments.
351 *
352 * @return The placeholder string that will be provided in usage information
353 * to indicate what may be included in the trailing arguments, or
354 * {@code null} if unnamed trailing arguments are not allowed.
355 */
356 public String getTrailingArgumentsPlaceholder()
357 {
358 return trailingArgsPlaceholder;
359 }
360
361
362
363 /**
364 * Retrieves the maximum number of unnamed trailing arguments that may be
365 * provided.
366 *
367 * @return The maximum number of unnamed trailing arguments that may be
368 * provided.
369 */
370 public int getMaxTrailingArguments()
371 {
372 return maxTrailingArgs;
373 }
374
375
376
377 /**
378 * Retrieves the named argument with the specified short identifier.
379 *
380 * @param shortIdentifier The short identifier of the argument to retrieve.
381 * It must not be {@code null}.
382 *
383 * @return The named argument with the specified short identifier, or
384 * {@code null} if there is no such argument.
385 */
386 public Argument getNamedArgument(final Character shortIdentifier)
387 {
388 ensureNotNull(shortIdentifier);
389 return namedArgsByShortID.get(shortIdentifier);
390 }
391
392
393
394 /**
395 * Retrieves the named argument with the specified long identifier.
396 *
397 * @param longIdentifier The long identifier of the argument to retrieve.
398 * It must not be {@code null}.
399 *
400 * @return The named argument with the specified long identifier, or
401 * {@code null} if there is no such argument.
402 */
403 public Argument getNamedArgument(final String longIdentifier)
404 {
405 ensureNotNull(longIdentifier);
406 return namedArgsByLongID.get(toLowerCase(longIdentifier));
407 }
408
409
410
411 /**
412 * Retrieves the set of named arguments defined for use with this argument
413 * parser.
414 *
415 * @return The set of named arguments defined for use with this argument
416 * parser.
417 */
418 public List<Argument> getNamedArguments()
419 {
420 return Collections.unmodifiableList(namedArgs);
421 }
422
423
424
425 /**
426 * Registers the provided argument with this argument parser.
427 *
428 * @param argument The argument to be registered.
429 *
430 * @throws ArgumentException If the provided argument conflicts with another
431 * argument already registered with this parser.
432 */
433 public void addArgument(final Argument argument)
434 throws ArgumentException
435 {
436 argument.setRegistered();
437 for (final Character c : argument.getShortIdentifiers())
438 {
439 if (namedArgsByShortID.containsKey(c))
440 {
441 throw new ArgumentException(ERR_PARSER_SHORT_ID_CONFLICT.get(c));
442 }
443 }
444
445 for (final String s : argument.getLongIdentifiers())
446 {
447 if (namedArgsByLongID.containsKey(toLowerCase(s)))
448 {
449 throw new ArgumentException(ERR_PARSER_LONG_ID_CONFLICT.get(s));
450 }
451 }
452
453 for (final Character c : argument.getShortIdentifiers())
454 {
455 namedArgsByShortID.put(c, argument);
456 }
457
458 for (final String s : argument.getLongIdentifiers())
459 {
460 namedArgsByLongID.put(toLowerCase(s), argument);
461 }
462
463 namedArgs.add(argument);
464 }
465
466
467
468 /**
469 * Retrieves the list of dependent argument sets for this argument parser. If
470 * an argument contained as the first object in the pair in a dependent
471 * argument set is provided, then at least one of the arguments in the paired
472 * set must also be provided.
473 *
474 * @return The list of dependent argument sets for this argument parser.
475 */
476 public List<ObjectPair<Argument,Set<Argument>>> getDependentArgumentSets()
477 {
478 return Collections.unmodifiableList(dependentArgumentSets);
479 }
480
481
482
483 /**
484 * Adds the provided collection of arguments as dependent upon the given
485 * argument.
486 *
487 * @param targetArgument The argument whose presence indicates that at
488 * least one of the dependent arguments must also
489 * be present. It must not be {@code null}.
490 * @param dependentArguments The set of arguments from which at least one
491 * argument must be present if the target argument
492 * is present. It must not be {@code null} or
493 * empty.
494 */
495 public void addDependentArgumentSet(final Argument targetArgument,
496 final Collection<Argument> dependentArguments)
497 {
498 ensureNotNull(targetArgument, dependentArguments);
499
500 final LinkedHashSet<Argument> argSet =
501 new LinkedHashSet<Argument>(dependentArguments);
502 dependentArgumentSets.add(
503 new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet));
504 }
505
506
507
508 /**
509 * Adds the provided collection of arguments as dependent upon the given
510 * argument.
511 *
512 * @param targetArgument The argument whose presence indicates that at least
513 * one of the dependent arguments must also be
514 * present. It must not be {@code null}.
515 * @param dependentArg1 The first argument in the set of arguments in which
516 * at least one argument must be present if the target
517 * argument is present. It must not be {@code null}.
518 * @param remaining The remaining arguments in the set of arguments in
519 * which at least one argument must be present if the
520 * target argument is present.
521 */
522 public void addDependentArgumentSet(final Argument targetArgument,
523 final Argument dependentArg1,
524 final Argument... remaining)
525 {
526 ensureNotNull(targetArgument, dependentArg1);
527
528 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>();
529 argSet.add(dependentArg1);
530 argSet.addAll(Arrays.asList(remaining));
531
532 dependentArgumentSets.add(
533 new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet));
534 }
535
536
537
538 /**
539 * Retrieves the list of exclusive argument sets for this argument parser.
540 * If an argument contained in an exclusive argument set is provided, then
541 * none of the other arguments in that set may be provided. It is acceptable
542 * for none of the arguments in the set to be provided, unless the same set
543 * of arguments is also defined as a required argument set.
544 *
545 * @return The list of exclusive argument sets for this argument parser.
546 */
547 public List<Set<Argument>> getExclusiveArgumentSets()
548 {
549 return Collections.unmodifiableList(exclusiveArgumentSets);
550 }
551
552
553
554 /**
555 * Adds the provided collection of arguments as an exclusive argument set, in
556 * which at most one of the arguments may be provided.
557 *
558 * @param exclusiveArguments The collection of arguments to form an
559 * exclusive argument set. It must not be
560 * {@code null}.
561 */
562 public void addExclusiveArgumentSet(
563 final Collection<Argument> exclusiveArguments)
564 {
565 ensureNotNull(exclusiveArguments);
566 final LinkedHashSet<Argument> argSet =
567 new LinkedHashSet<Argument>(exclusiveArguments);
568 exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet));
569 }
570
571
572
573 /**
574 * Adds the provided set of arguments as an exclusive argument set, in
575 * which at most one of the arguments may be provided.
576 *
577 * @param arg1 The first argument to include in the exclusive argument
578 * set. It must not be {@code null}.
579 * @param arg2 The second argument to include in the exclusive argument
580 * set. It must not be {@code null}.
581 * @param remaining Any additional arguments to include in the exclusive
582 * argument set.
583 */
584 public void addExclusiveArgumentSet(final Argument arg1, final Argument arg2,
585 final Argument... remaining)
586 {
587 ensureNotNull(arg1, arg2);
588
589 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>();
590 argSet.add(arg1);
591 argSet.add(arg2);
592 argSet.addAll(Arrays.asList(remaining));
593
594 exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet));
595 }
596
597
598
599 /**
600 * Retrieves the list of required argument sets for this argument parser. At
601 * least one of the arguments contained in this set must be provided. If this
602 * same set is also defined as an exclusive argument set, then exactly one
603 * of those arguments must be provided.
604 *
605 * @return The list of required argument sets for this argument parser.
606 */
607 public List<Set<Argument>> getRequiredArgumentSets()
608 {
609 return Collections.unmodifiableList(requiredArgumentSets);
610 }
611
612
613
614 /**
615 * Adds the provided collection of arguments as a required argument set, in
616 * which at least one of the arguments must be provided.
617 *
618 * @param requiredArguments The collection of arguments to form an
619 * required argument set. It must not be
620 * {@code null}.
621 */
622 public void addRequiredArgumentSet(
623 final Collection<Argument> requiredArguments)
624 {
625 ensureNotNull(requiredArguments);
626 final LinkedHashSet<Argument> argSet =
627 new LinkedHashSet<Argument>(requiredArguments);
628 requiredArgumentSets.add(Collections.unmodifiableSet(argSet));
629 }
630
631
632
633 /**
634 * Adds the provided set of arguments as a required argument set, in which
635 * at least one of the arguments must be provided.
636 *
637 * @param arg1 The first argument to include in the required argument
638 * set. It must not be {@code null}.
639 * @param arg2 The second argument to include in the required argument
640 * set. It must not be {@code null}.
641 * @param remaining Any additional arguments to include in the required
642 * argument set.
643 */
644 public void addRequiredArgumentSet(final Argument arg1, final Argument arg2,
645 final Argument... remaining)
646 {
647 ensureNotNull(arg1, arg2);
648
649 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>();
650 argSet.add(arg1);
651 argSet.add(arg2);
652 argSet.addAll(Arrays.asList(remaining));
653
654 requiredArgumentSets.add(Collections.unmodifiableSet(argSet));
655 }
656
657
658
659 /**
660 * Retrieves the set of unnamed trailing arguments in the provided command
661 * line arguments.
662 *
663 * @return The set of unnamed trailing arguments in the provided command line
664 * arguments, or an empty list if there were none.
665 */
666 public List<String> getTrailingArguments()
667 {
668 return Collections.unmodifiableList(trailingArgs);
669 }
670
671
672
673 /**
674 * Creates a copy of this argument parser that is "clean" and appears as if it
675 * has not been used to parse an argument set. The new parser will have all
676 * of the same arguments and constraints as this parser.
677 *
678 * @return The "clean" copy of this argument parser.
679 */
680 public ArgumentParser getCleanCopy()
681 {
682 return new ArgumentParser(this);
683 }
684
685
686
687 /**
688 * Parses the provided set of arguments.
689 *
690 * @param args An array containing the argument information to parse. It
691 * must not be {@code null}.
692 *
693 * @throws ArgumentException If a problem occurs while attempting to parse
694 * the argument information.
695 */
696 public void parse(final String[] args)
697 throws ArgumentException
698 {
699 // Iterate through the provided args strings and process them.
700 boolean inTrailingArgs = false;
701 boolean usageArgProvided = false;
702 for (int i=0; i < args.length; i++)
703 {
704 final String s = args[i];
705
706 if (inTrailingArgs)
707 {
708 if (maxTrailingArgs == 0)
709 {
710 throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get(
711 s, commandName));
712 }
713 else if (trailingArgs.size() >= maxTrailingArgs)
714 {
715 throw new ArgumentException(ERR_PARSER_TOO_MANY_TRAILING_ARGS.get(s,
716 commandName, maxTrailingArgs));
717 }
718 else
719 {
720 trailingArgs.add(s);
721 }
722 }
723 else if (s.equals("--"))
724 {
725 // This signifies the end of the named arguments and the beginning of
726 // the trailing arguments.
727 inTrailingArgs = true;
728 }
729 else if (s.startsWith("--"))
730 {
731 // There may be an equal sign to separate the name from the value.
732 final String argName;
733 final int equalPos = s.indexOf('=');
734 if (equalPos > 0)
735 {
736 argName = s.substring(2, equalPos);
737 }
738 else
739 {
740 argName = s.substring(2);
741 }
742
743 final Argument a = namedArgsByLongID.get(toLowerCase(argName));
744 if (a == null)
745 {
746 throw new ArgumentException(ERR_PARSER_NO_SUCH_LONG_ID.get(argName));
747 }
748 else if(a.isUsageArgument())
749 {
750 usageArgProvided = true;
751 }
752
753 a.incrementOccurrences();
754 if (a.takesValue())
755 {
756 if (equalPos > 0)
757 {
758 a.addValue(s.substring(equalPos+1));
759 }
760 else
761 {
762 i++;
763 if (i >= args.length)
764 {
765 throw new ArgumentException(ERR_PARSER_LONG_ARG_MISSING_VALUE.get(
766 argName));
767 }
768 else
769 {
770 a.addValue(args[i]);
771 }
772 }
773 }
774 else
775 {
776 if (equalPos > 0)
777 {
778 throw new ArgumentException(
779 ERR_PARSER_LONG_ARG_DOESNT_TAKE_VALUE.get(argName));
780 }
781 }
782 }
783 else if (s.startsWith("-"))
784 {
785 if (s.length() == 1)
786 {
787 throw new ArgumentException(ERR_PARSER_UNEXPECTED_DASH.get());
788 }
789 else if (s.length() == 2)
790 {
791 final char c = s.charAt(1);
792 final Argument a = namedArgsByShortID.get(c);
793 if (a == null)
794 {
795 throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c));
796 }
797 else if(a.isUsageArgument())
798 {
799 usageArgProvided = true;
800 }
801
802 a.incrementOccurrences();
803 if (a.takesValue())
804 {
805 i++;
806 if (i >= args.length)
807 {
808 throw new ArgumentException(
809 ERR_PARSER_SHORT_ARG_MISSING_VALUE.get(c));
810 }
811 else
812 {
813 a.addValue(args[i]);
814 }
815 }
816 }
817 else
818 {
819 char c = s.charAt(1);
820 Argument a = namedArgsByShortID.get(c);
821 if (a == null)
822 {
823 throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c));
824 }
825 else if(a.isUsageArgument())
826 {
827 usageArgProvided = true;
828 }
829
830 a.incrementOccurrences();
831 if (a.takesValue())
832 {
833 a.addValue(s.substring(2));
834 }
835 else
836 {
837 // The rest of the characters in the string must also resolve to
838 // arguments that don't take values.
839 for (int j=2; j < s.length(); j++)
840 {
841 c = s.charAt(j);
842 a = namedArgsByShortID.get(c);
843 if (a == null)
844 {
845 throw new ArgumentException(
846 ERR_PARSER_NO_SUBSEQUENT_SHORT_ARG.get(c, s));
847 }
848 else if(a.isUsageArgument())
849 {
850 usageArgProvided = true;
851 }
852
853 a.incrementOccurrences();
854 if (a.takesValue())
855 {
856 throw new ArgumentException(
857 ERR_PARSER_SUBSEQUENT_SHORT_ARG_TAKES_VALUE.get(
858 c, s));
859 }
860 }
861 }
862 }
863 }
864 else
865 {
866 inTrailingArgs = true;
867 if (maxTrailingArgs == 0)
868 {
869 throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get(
870 s, commandName));
871 }
872 else
873 {
874 trailingArgs.add(s);
875 }
876 }
877 }
878
879
880 // If a usage argument was provided, then no further validation should be
881 // performed.
882 if (usageArgProvided)
883 {
884 return;
885 }
886
887
888 // Make sure that all required arguments have values.
889 for (final Argument a : namedArgs)
890 {
891 if (a.isRequired() && (! a.isPresent()))
892 {
893 throw new ArgumentException(ERR_PARSER_MISSING_REQUIRED_ARG.get(
894 a.getIdentifierString()));
895 }
896 }
897
898
899 // Make sure that there are no dependent argument set conflicts.
900 for (final ObjectPair<Argument,Set<Argument>> p : dependentArgumentSets)
901 {
902 final Argument targetArg = p.getFirst();
903 if (targetArg.isPresent())
904 {
905 final Set<Argument> argSet = p.getSecond();
906 boolean found = false;
907 for (final Argument a : argSet)
908 {
909 if (a.isPresent())
910 {
911 found = true;
912 break;
913 }
914 }
915
916 if (! found)
917 {
918 if (argSet.size() == 1)
919 {
920 throw new ArgumentException(
921 ERR_PARSER_DEPENDENT_CONFLICT_SINGLE.get(
922 targetArg.getIdentifierString(),
923 argSet.iterator().next().getIdentifierString()));
924 }
925 else
926 {
927 boolean first = true;
928 final StringBuilder buffer = new StringBuilder();
929 for (final Argument a : argSet)
930 {
931 if (first)
932 {
933 first = false;
934 }
935 else
936 {
937 buffer.append(", ");
938 }
939 buffer.append(a.getIdentifierString());
940 }
941 throw new ArgumentException(
942 ERR_PARSER_DEPENDENT_CONFLICT_MULTIPLE.get(
943 targetArg.getIdentifierString(), buffer.toString()));
944 }
945 }
946 }
947 }
948
949
950 // Make sure that there are no exclusive argument set conflicts.
951 for (final Set<Argument> argSet : exclusiveArgumentSets)
952 {
953 Argument setArg = null;
954 for (final Argument a : argSet)
955 {
956 if (a.isPresent())
957 {
958 if (setArg == null)
959 {
960 setArg = a;
961 }
962 else
963 {
964 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
965 setArg.getIdentifierString(),
966 a.getIdentifierString()));
967 }
968 }
969 }
970 }
971
972 // Make sure that there are no required argument set conflicts.
973 for (final Set<Argument> argSet : requiredArgumentSets)
974 {
975 boolean found = false;
976 for (final Argument a : argSet)
977 {
978 if (a.isPresent())
979 {
980 found = true;
981 break;
982 }
983 }
984
985 if (! found)
986 {
987 boolean first = true;
988 final StringBuilder buffer = new StringBuilder();
989 for (final Argument a : argSet)
990 {
991 if (first)
992 {
993 first = false;
994 }
995 else
996 {
997 buffer.append(", ");
998 }
999 buffer.append(a.getIdentifierString());
1000 }
1001 throw new ArgumentException(ERR_PARSER_REQUIRED_CONFLICT.get(
1002 buffer.toString()));
1003 }
1004 }
1005 }
1006
1007
1008
1009 /**
1010 * Retrieves lines that make up the usage information for this program,
1011 * optionally wrapping long lines.
1012 *
1013 * @param maxWidth The maximum line width to use for the output. If this is
1014 * less than or equal to zero, then no wrapping will be
1015 * performed.
1016 *
1017 * @return The lines that make up the usage information for this program.
1018 */
1019 public List<String> getUsage(final int maxWidth)
1020 {
1021 final ArrayList<String> lines = new ArrayList<String>(100);
1022
1023 // First is a description of the command.
1024 lines.addAll(wrapLine(commandDescription, maxWidth));
1025 lines.add("");
1026
1027 // Next comes the usage. It may include neither, either, or both of the
1028 // set of options and trailing arguments.
1029 if (namedArgs.isEmpty())
1030 {
1031 if (maxTrailingArgs == 0)
1032 {
1033 lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_NOTRAILING.get(commandName),
1034 maxWidth));
1035 }
1036 else
1037 {
1038 lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_TRAILING.get(
1039 commandName, trailingArgsPlaceholder),
1040 maxWidth));
1041 }
1042 }
1043 else
1044 {
1045 if (maxTrailingArgs == 0)
1046 {
1047 lines.addAll(wrapLine(INFO_USAGE_OPTIONS_NOTRAILING.get(commandName),
1048 maxWidth));
1049 }
1050 else
1051 {
1052 lines.addAll(wrapLine(INFO_USAGE_OPTIONS_TRAILING.get(
1053 commandName, trailingArgsPlaceholder),
1054 maxWidth));
1055 }
1056
1057 lines.add("");
1058 lines.add(INFO_USAGE_OPTIONS_INCLUDE.get());
1059
1060
1061 // We know that there are named arguments, so we'll want to display them
1062 // and their descriptions.
1063 for (final Argument a : namedArgs)
1064 {
1065 if (a.isHidden())
1066 {
1067 // This shouldn't be included in the usage output.
1068 continue;
1069 }
1070
1071 final StringBuilder argLine = new StringBuilder();
1072 boolean first = true;
1073 for (final Character c : a.getShortIdentifiers())
1074 {
1075 if (first)
1076 {
1077 argLine.append('-');
1078 first = false;
1079 }
1080 else
1081 {
1082 argLine.append(", -");
1083 }
1084 argLine.append(c);
1085 }
1086
1087 for (final String s : a.getLongIdentifiers())
1088 {
1089 if (first)
1090 {
1091 argLine.append("--");
1092 }
1093 else
1094 {
1095 argLine.append(", --");
1096 }
1097 argLine.append(s);
1098 }
1099
1100 final String valuePlaceholder = a.getValuePlaceholder();
1101 if (valuePlaceholder != null)
1102 {
1103 argLine.append(' ');
1104 argLine.append(valuePlaceholder);
1105 }
1106
1107 // NOTE: This line won't be wrapped. That's intentional because I
1108 // think it would probably look bad no matter how we did it.
1109 lines.add(argLine.toString());
1110
1111 // The description should be wrapped, if necessary. We'll also want to
1112 // indent it (unless someone chose an absurdly small wrap width) to make
1113 // it stand out from the argument lines.
1114 final String description = a.getDescription();
1115 if (maxWidth > 10)
1116 {
1117 final List<String> descLines = wrapLine(description, (maxWidth-4));
1118 for (final String s : descLines)
1119 {
1120 lines.add(" " + s);
1121 }
1122 }
1123 else
1124 {
1125 lines.addAll(wrapLine(description, maxWidth));
1126 }
1127 }
1128 }
1129
1130 return lines;
1131 }
1132
1133
1134
1135 /**
1136 * Writes usage information for this program to the provided output stream
1137 * using the UTF-8 encoding, optionally wrapping long lines.
1138 *
1139 * @param outputStream The output stream to which the usage information
1140 * should be written. It must not be {@code null}.
1141 * @param maxWidth The maximum line width to use for the output. If
1142 * this is less than or equal to zero, then no wrapping
1143 * will be performed.
1144 *
1145 * @throws IOException If an error occurs while attempting to write to the
1146 * provided output stream.
1147 */
1148 public void getUsage(final OutputStream outputStream, final int maxWidth)
1149 throws IOException
1150 {
1151 final List<String> usageLines = getUsage(maxWidth);
1152 for (final String s : usageLines)
1153 {
1154 outputStream.write(getBytes(s));
1155 outputStream.write(EOL_BYTES);
1156 }
1157 }
1158
1159
1160
1161 /**
1162 * Retrieves a string representation of the usage information.
1163 *
1164 * @param maxWidth The maximum line width to use for the output. If this is
1165 * less than or equal to zero, then no wrapping will be
1166 * performed.
1167 *
1168 * @return A string representation of the usage information
1169 */
1170 public String getUsageString(final int maxWidth)
1171 {
1172 final StringBuilder buffer = new StringBuilder();
1173 getUsageString(buffer, maxWidth);
1174 return buffer.toString();
1175 }
1176
1177
1178
1179 /**
1180 * Appends a string representation of the usage information to the provided
1181 * buffer.
1182 *
1183 * @param buffer The buffer to which the information should be appended.
1184 * @param maxWidth The maximum line width to use for the output. If this is
1185 * less than or equal to zero, then no wrapping will be
1186 * performed.
1187 */
1188 public void getUsageString(final StringBuilder buffer, final int maxWidth)
1189 {
1190 for (final String line : getUsage(maxWidth))
1191 {
1192 buffer.append(line);
1193 buffer.append(EOL);
1194 }
1195 }
1196
1197
1198
1199 /**
1200 * Retrieves a string representation of this argument parser.
1201 *
1202 * @return A string representation of this argument parser.
1203 */
1204 @Override()
1205 public String toString()
1206 {
1207 final StringBuilder buffer = new StringBuilder();
1208 toString(buffer);
1209 return buffer.toString();
1210 }
1211
1212
1213
1214 /**
1215 * Appends a string representation of this argument parser to the provided
1216 * buffer.
1217 *
1218 * @param buffer The buffer to which the information should be appended.
1219 */
1220 public void toString(final StringBuilder buffer)
1221 {
1222 buffer.append("ArgumentParser(commandName='");
1223 buffer.append(commandName);
1224 buffer.append("', commandDescription='");
1225 buffer.append(commandDescription);
1226 buffer.append("', maxTrailingArgs=");
1227 buffer.append(maxTrailingArgs);
1228
1229 if (trailingArgsPlaceholder != null)
1230 {
1231 buffer.append(", trailingArgsPlaceholder='");
1232 buffer.append(trailingArgsPlaceholder);
1233 buffer.append('\'');
1234 }
1235
1236 buffer.append("namedArgs={");
1237
1238 final Iterator<Argument> iterator = namedArgs.iterator();
1239 while (iterator.hasNext())
1240 {
1241 iterator.next().toString(buffer);
1242 if (iterator.hasNext())
1243 {
1244 buffer.append(", ");
1245 }
1246 }
1247
1248 buffer.append("})");
1249 }
1250 }