001/*
002 * Copyright 2008-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2018 Ping Identity Corporation
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 */
021package com.unboundid.util.args;
022
023
024
025import java.io.Serializable;
026
027import java.util.ArrayList;
028import java.util.Collections;
029import java.util.Iterator;
030import java.util.LinkedHashMap;
031import java.util.List;
032import java.util.Map;
033
034import com.unboundid.util.LDAPSDKUsageException;
035import com.unboundid.util.Mutable;
036import com.unboundid.util.NotExtensible;
037import com.unboundid.util.ThreadSafety;
038import com.unboundid.util.ThreadSafetyLevel;
039
040import static com.unboundid.util.args.ArgsMessages.*;
041
042
043
044/**
045 * This class defines a generic command line argument, which provides
046 * functionality applicable to all argument types.  Subclasses may enforce
047 * additional constraints or provide additional functionality.
048 */
049@NotExtensible()
050@Mutable()
051@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
052public abstract class Argument
053       implements Serializable
054{
055  /**
056   * The serial version UID for this serializable class.
057   */
058  private static final long serialVersionUID = -6938320885602903919L;
059
060
061
062  // Indicates whether this argument should be excluded from usage information.
063  private boolean isHidden;
064
065  // Indicates whether this argument has been registered with the argument
066  // parser.
067  private boolean isRegistered;
068
069  // Indicates whether this argument is required to be present.
070  private final boolean isRequired;
071
072  // Indicates whether values of this argument should be considered sensitive.
073  private boolean isSensitive;
074
075  // Indicates whether this argument is used to display usage information.
076  private boolean isUsageArgument;
077
078  // The maximum number of times this argument is allowed to be provided.
079  private int maxOccurrences;
080
081  // The number of times this argument was included in the provided command line
082  // arguments.
083  private int numOccurrences;
084
085  // The set of short identifiers for this argument, associated with an
086  // indication as to whether the identifier should be hidden.
087  private final Map<Character,Boolean> shortIdentifiers;
088
089  // The set of long identifiers for this argument, associated with an
090  // indication as to whether the identifier should be hidden.
091  private final Map<String,Boolean> longIdentifiers;
092
093  // The argument group name for this argument, if any.
094  private String argumentGroupName;
095
096  // The description for this argument.
097  private final String description;
098
099  // The value placeholder for this argument, or {@code null} if it does not
100  // take a value.
101  private final String valuePlaceholder;
102
103
104
105  /**
106   * Creates a new argument with the provided information.
107   *
108   * @param  shortIdentifier   The short identifier for this argument.  It may
109   *                           not be {@code null} if the long identifier is
110   *                           {@code null}.
111   * @param  longIdentifier    The long identifier for this argument.  It may
112   *                           not be {@code null} if the short identifier is
113   *                           {@code null}.
114   * @param  isRequired        Indicates whether this argument is required to
115   *                           be provided.
116   * @param  maxOccurrences    The maximum number of times this argument may be
117   *                           provided on the command line.  A value less than
118   *                           or equal to zero indicates that it may be present
119   *                           any number of times.
120   * @param  valuePlaceholder  A placeholder to display in usage information to
121   *                           indicate that a value must be provided.  If this
122   *                           is {@code null}, then the argument will not be
123   *                           allowed to take a value.  If it is not
124   *                           {@code null}, then the argument will be required
125   *                           to take a value.
126   * @param  description       A human-readable description for this argument.
127   *                           It must not be {@code null}.
128   *
129   * @throws  ArgumentException  If there is a problem with the definition of
130   *                             this argument.
131   */
132  protected Argument(final Character shortIdentifier,
133                     final String longIdentifier,
134                     final boolean isRequired, final int maxOccurrences,
135                     final String valuePlaceholder, final String description)
136            throws ArgumentException
137  {
138    if (description == null)
139    {
140      throw new ArgumentException(ERR_ARG_DESCRIPTION_NULL.get());
141    }
142
143    if ((shortIdentifier == null) && (longIdentifier == null))
144    {
145      throw new ArgumentException(ERR_ARG_NO_IDENTIFIERS.get());
146    }
147
148    shortIdentifiers = new LinkedHashMap<>(5);
149    if (shortIdentifier != null)
150    {
151      shortIdentifiers.put(shortIdentifier, false);
152    }
153
154    longIdentifiers = new LinkedHashMap<>(5);
155    if (longIdentifier != null)
156    {
157      longIdentifiers.put(longIdentifier, false);
158    }
159
160    this.isRequired       = isRequired;
161    this.valuePlaceholder = valuePlaceholder;
162    this.description      = description;
163
164    if (maxOccurrences > 0)
165    {
166      this.maxOccurrences = maxOccurrences;
167    }
168    else
169    {
170      this.maxOccurrences = Integer.MAX_VALUE;
171    }
172
173    argumentGroupName = null;
174    numOccurrences    = 0;
175    isHidden          = false;
176    isRegistered      = false;
177    isSensitive       = false;
178    isUsageArgument   = false;
179  }
180
181
182
183  /**
184   * Creates a new argument with the same generic information as the provided
185   * argument.  It will not be registered with any argument parser.
186   *
187   * @param  source  The argument to use as the source for this argument.
188   */
189  protected Argument(final Argument source)
190  {
191    argumentGroupName = source.argumentGroupName;
192    isHidden          = source.isHidden;
193    isRequired        = source.isRequired;
194    isSensitive       = source.isSensitive;
195    isUsageArgument   = source.isUsageArgument;
196    maxOccurrences    = source.maxOccurrences;
197    description       = source.description;
198    valuePlaceholder  = source.valuePlaceholder;
199
200    isRegistered   = false;
201    numOccurrences = 0;
202
203    shortIdentifiers = new LinkedHashMap<>(source.shortIdentifiers);
204    longIdentifiers  = new LinkedHashMap<>(source.longIdentifiers);
205  }
206
207
208
209  /**
210   * Indicates whether this argument has a short identifier.
211   *
212   * @return  {@code true} if it has a short identifier, or {@code false} if
213   *          not.
214   */
215  public final boolean hasShortIdentifier()
216  {
217    return (! shortIdentifiers.isEmpty());
218  }
219
220
221
222  /**
223   * Retrieves the short identifier for this argument.  If there is more than
224   * one, then the first will be returned.
225   *
226   * @return  The short identifier for this argument, or {@code null} if none is
227   *          defined.
228   */
229  public final Character getShortIdentifier()
230  {
231    for (final Map.Entry<Character,Boolean> e : shortIdentifiers.entrySet())
232    {
233      if (e.getValue())
234      {
235        continue;
236      }
237
238      return e.getKey();
239    }
240
241    return null;
242  }
243
244
245
246  /**
247   * Retrieves the list of all short identifiers, including hidden identifiers,
248   * for this argument.
249   *
250   * @return  The list of all short identifiers for this argument, or an empty
251   *          list if there are no short identifiers.
252   */
253  public final List<Character> getShortIdentifiers()
254  {
255    return getShortIdentifiers(true);
256  }
257
258
259
260  /**
261   * Retrieves the list of short identifiers for this argument.
262   *
263   * @param  includeHidden  Indicates whether to include hidden identifiers in
264   *                        the list that is returned.
265   *
266   * @return  The list of short identifiers for this argument, or an empty list
267   *          if there are none.
268   */
269  public final List<Character> getShortIdentifiers(final boolean includeHidden)
270  {
271    final ArrayList<Character> identifierList =
272         new ArrayList<>(shortIdentifiers.size());
273    for (final Map.Entry<Character,Boolean> e : shortIdentifiers.entrySet())
274    {
275      if (includeHidden || (! e.getValue()))
276      {
277        identifierList.add(e.getKey());
278      }
279    }
280
281    return Collections.unmodifiableList(identifierList);
282  }
283
284
285
286  /**
287   * Adds the provided character to the set of short identifiers for this
288   * argument.  It will not be hidden.  Note that this must be called before
289   * this argument is registered with the argument parser.
290   *
291   * @param  c  The character to add to the set of short identifiers for this
292   *            argument.  It must not be {@code null}.
293   *
294   * @throws  ArgumentException  If this argument is already registered with the
295   *                             argument parser.
296   */
297  public final void addShortIdentifier(final Character c)
298         throws ArgumentException
299  {
300    addShortIdentifier(c, false);
301  }
302
303
304
305  /**
306   * Adds the provided character to the set of short identifiers for this
307   * argument.  Note that this must be called before this argument is registered
308   * with the argument parser.
309   *
310   * @param  c         The character to add to the set of short identifiers for
311   *                   this argument.  It must not be {@code null}.
312   * @param  isHidden  Indicates whether the provided identifier should be
313   *                   hidden.  If this is {@code true}, then the identifier can
314   *                   be used to target this argument on the command line, but
315   *                   it will not be included in usage information.
316   *
317   * @throws  ArgumentException  If this argument is already registered with the
318   *                             argument parser.
319   */
320  public final void addShortIdentifier(final Character c,
321                                       final boolean isHidden)
322         throws ArgumentException
323  {
324    if (isRegistered)
325    {
326      throw new ArgumentException(ERR_ARG_ID_CHANGE_AFTER_REGISTERED.get(
327                                       getIdentifierString()));
328    }
329
330    shortIdentifiers.put(c, isHidden);
331  }
332
333
334
335  /**
336   * Indicates whether this argument has a long identifier.
337   *
338   * @return  {@code true} if it has a long identifier, or {@code false} if
339   *          not.
340   */
341  public final boolean hasLongIdentifier()
342  {
343    return (! longIdentifiers.isEmpty());
344  }
345
346
347
348  /**
349   * Retrieves the long identifier for this argument.  If it has multiple long
350   * identifiers, then the first will be returned.
351   *
352   * @return  The long identifier for this argument, or {@code null} if none is
353   *          defined.
354   */
355  public final String getLongIdentifier()
356  {
357    for (final Map.Entry<String,Boolean> e : longIdentifiers.entrySet())
358    {
359      if (e.getValue())
360      {
361        continue;
362      }
363
364      return e.getKey();
365    }
366
367    return null;
368  }
369
370
371
372  /**
373   * Retrieves the list of all long identifiers, including hidden identifiers,
374   * for this argument.
375   *
376   * @return  The list of all long identifiers for this argument, or an empty
377   *          list if there are no long identifiers.
378   */
379  public final List<String> getLongIdentifiers()
380  {
381    return getLongIdentifiers(true);
382  }
383
384
385
386  /**
387   * Retrieves the list of long identifiers for this argument.
388   *
389   * @param  includeHidden  Indicates whether to include hidden identifiers in
390   *                        the list that is returned.
391   *
392   * @return  The long identifier for this argument, or an empty list if there
393   *          are none.
394   */
395  public final List<String> getLongIdentifiers(final boolean includeHidden)
396  {
397    final ArrayList<String> identifierList =
398         new ArrayList<>(longIdentifiers.size());
399    for (final Map.Entry<String,Boolean> e : longIdentifiers.entrySet())
400    {
401      if (includeHidden || (! e.getValue()))
402      {
403        identifierList.add(e.getKey());
404      }
405    }
406
407    return Collections.unmodifiableList(identifierList);
408  }
409
410
411
412  /**
413   * Adds the provided string to the set of short identifiers for this argument.
414   * It will not be hidden.  Note that this must be called before this argument
415   * is registered with the argument parser.
416   *
417   * @param  s  The string to add to the set of short identifiers for this
418   *            argument.  It must not be {@code null}.
419   *
420   * @throws  ArgumentException  If this argument is already registered with the
421   *                             argument parser.
422   */
423  public final void addLongIdentifier(final String s)
424         throws ArgumentException
425  {
426    addLongIdentifier(s, false);
427  }
428
429
430
431  /**
432   * Adds the provided string to the set of short identifiers for this argument.
433   * Note that this must be called before this argument is registered with the
434   * argument parser.
435   *
436   * @param  s         The string to add to the set of short identifiers for
437   *                   this argument.  It must not be {@code null}.
438   * @param  isHidden  Indicates whether the provided identifier should be
439   *                   hidden.  If this is {@code true}, then the identifier can
440   *                   be used to target this argument on the command line, but
441   *                   it will not be included in usage information.
442   *
443   * @throws  ArgumentException  If this argument is already registered with the
444   *                             argument parser.
445   */
446  public final void addLongIdentifier(final String s, final boolean isHidden)
447         throws ArgumentException
448  {
449    if (isRegistered)
450    {
451      throw new ArgumentException(ERR_ARG_ID_CHANGE_AFTER_REGISTERED.get(
452                                       getIdentifierString()));
453    }
454
455    longIdentifiers.put(s, isHidden);
456  }
457
458
459
460  /**
461   * Retrieves a string that may be used to identify this argument.  If a long
462   * identifier is defined, then the value returned will be two dashes followed
463   * by that string.  Otherwise, the value returned will be a single dash
464   * followed by the short identifier.
465   *
466   * @return  A string that may be used to identify this argument.
467   */
468  public final String getIdentifierString()
469  {
470    for (final Map.Entry<String,Boolean> e : longIdentifiers.entrySet())
471    {
472      if (! e.getValue())
473      {
474        return "--" + e.getKey();
475      }
476    }
477
478    for (final Map.Entry<Character,Boolean> e : shortIdentifiers.entrySet())
479    {
480      if (! e.getValue())
481      {
482        return "-" + e.getKey();
483      }
484    }
485
486    // This should never happen.
487    throw new LDAPSDKUsageException(
488         ERR_ARG_NO_NON_HIDDEN_IDENTIFIER.get(toString()));
489  }
490
491
492
493  /**
494   * Indicates whether this argument is required to be provided.
495   *
496   * @return  {@code true} if this argument is required to be provided, or
497   *          {@code false} if not.
498   */
499  public final boolean isRequired()
500  {
501    return isRequired;
502  }
503
504
505
506  /**
507   * Retrieves the maximum number of times that this argument may be provided.
508   *
509   * @return  The maximum number of times that this argument may be provided.
510   */
511  public final int getMaxOccurrences()
512  {
513    return maxOccurrences;
514  }
515
516
517
518  /**
519   * Specifies the maximum number of times that this argument may be provided.
520   *
521   * @param  maxOccurrences  The maximum number of times that this argument
522   *                         may be provided.  A value less than or equal to
523   *                         zero indicates that there should be no limit on the
524   *                         maximum number of occurrences.
525   */
526  public final void setMaxOccurrences(final int maxOccurrences)
527  {
528    if (maxOccurrences <= 0)
529    {
530      this.maxOccurrences = Integer.MAX_VALUE;
531    }
532    else
533    {
534      this.maxOccurrences = maxOccurrences;
535    }
536  }
537
538
539
540  /**
541   * Indicates whether this argument takes a value.
542   *
543   * @return  {@code true} if this argument takes a value, or {@code false} if
544   *          not.
545   */
546  public boolean takesValue()
547  {
548    return (valuePlaceholder != null);
549  }
550
551
552
553  /**
554   * Retrieves the value placeholder string for this argument.
555   *
556   * @return  The value placeholder string for this argument, or {@code null} if
557   *          it does not take a value.
558   */
559  public final String getValuePlaceholder()
560  {
561    return valuePlaceholder;
562  }
563
564
565
566  /**
567   * Retrieves a list containing the string representations of the values for
568   * this argument, if any.  The list returned does not necessarily need to
569   * include values that will be acceptable to the argument, but it should imply
570   * what the values are (e.g., in the case of a boolean argument that doesn't
571   * take a value, it may be the string "true" or "false" even if those values
572   * are not acceptable to the argument itself).
573   *
574   * @param  useDefault  Indicates whether to use any configured default value
575   *                     if the argument doesn't have a user-specified value.
576   *
577   * @return  A string representation of the value for this argument, or an
578   *          empty list if the argument does not have a value.
579   */
580  public abstract List<String> getValueStringRepresentations(
581                                    boolean useDefault);
582
583
584
585  /**
586   * Retrieves the description for this argument.
587   *
588   * @return  The description for this argument.
589   */
590  public final String getDescription()
591  {
592    return description;
593  }
594
595
596
597  /**
598   * Retrieves the name of the argument group to which this argument belongs.
599   *
600   * @return  The name of the argument group to which this argument belongs, or
601   *          {@code null} if this argument has not been assigned to any group.
602   */
603  public final String getArgumentGroupName()
604  {
605    return argumentGroupName;
606  }
607
608
609
610  /**
611   * Sets the name of the argument group to which this argument belongs.  If
612   * a tool updates arguments to specify an argument group for some or all of
613   * the arguments, then the usage information will have the arguments listed
614   * together in their respective groups.  Note that usage arguments should
615   * generally not be assigned to an argument group.
616   *
617   * @param  argumentGroupName  The argument group name for this argument.  It
618   *                            may be {@code null} if this argument should not
619   *                            be assigned to any particular group.
620   */
621  public final void setArgumentGroupName(final String argumentGroupName)
622  {
623    this.argumentGroupName = argumentGroupName;
624  }
625
626
627
628  /**
629   * Indicates whether this argument should be excluded from usage information.
630   *
631   * @return  {@code true} if this argument should be excluded from usage
632   *          information, or {@code false} if not.
633   */
634  public final boolean isHidden()
635  {
636    return isHidden;
637  }
638
639
640
641  /**
642   * Specifies whether this argument should be excluded from usage information.
643   *
644   * @param  isHidden  Specifies whether this argument should be excluded from
645   *                   usage information.
646   */
647  public final void setHidden(final boolean isHidden)
648  {
649    this.isHidden = isHidden;
650  }
651
652
653
654  /**
655   * Indicates whether this argument is intended to be used to trigger the
656   * display of usage information.  If a usage argument is provided on the
657   * command line, then the argument parser will not complain about missing
658   * required arguments or unresolved dependencies.
659   *
660   * @return  {@code true} if this argument is a usage argument, or
661   *          {@code false} if not.
662   */
663  public final boolean isUsageArgument()
664  {
665    return isUsageArgument;
666  }
667
668
669
670  /**
671   * Specifies whether this argument should be considered a usage argument.
672   *
673   * @param  isUsageArgument  Specifies whether this argument should be
674   *                          considered a usage argument.
675   */
676  public final void setUsageArgument(final boolean isUsageArgument)
677  {
678    this.isUsageArgument = isUsageArgument;
679  }
680
681
682
683  /**
684   * Indicates whether this argument was either included in the provided set of
685   * command line arguments or has a default value that can be used instead.
686   * This method should not be called until after the argument parser has
687   * processed the provided set of arguments.
688   *
689   * @return  {@code true} if this argument was included in the provided set of
690   *          command line arguments, or {@code false} if not.
691   */
692  public final boolean isPresent()
693  {
694    return ((numOccurrences > 0) || hasDefaultValue());
695  }
696
697
698
699  /**
700   * Retrieves the number of times that this argument was included in the
701   * provided set of command line arguments.  This method should not be called
702   * until after the argument parser has processed the provided set of
703   * arguments.
704   *
705   * @return  The number of times that this argument was included in the
706   *          provided set of command line arguments.
707   */
708  public final int getNumOccurrences()
709  {
710    return numOccurrences;
711  }
712
713
714
715  /**
716   * Increments the number of occurrences for this argument in the provided set
717   * of command line arguments.  This method should only be called by the
718   * argument parser.
719   *
720   * @throws  ArgumentException  If incrementing the number of occurrences would
721   *                             exceed the maximum allowed number.
722   */
723  final void incrementOccurrences()
724        throws ArgumentException
725  {
726    if (numOccurrences >= maxOccurrences)
727    {
728      throw new ArgumentException(ERR_ARG_MAX_OCCURRENCES_EXCEEDED.get(
729                                       getIdentifierString()));
730    }
731
732    numOccurrences++;
733  }
734
735
736
737  /**
738   * Adds the provided value to the set of values for this argument.  This
739   * method should only be called by the argument parser.
740   *
741   * @param  valueString  The string representation of the value.
742   *
743   * @throws  ArgumentException  If the provided value is not acceptable, if
744   *                             this argument does not accept values, or if
745   *                             this argument already has the maximum allowed
746   *                             number of values.
747   */
748  protected abstract void addValue(String valueString)
749            throws ArgumentException;
750
751
752
753  /**
754   * Indicates whether this argument has one or more default values that will be
755   * used if it is not provided on the command line.
756   *
757   * @return  {@code true} if this argument has one or more default values, or
758   *          {@code false} if not.
759   */
760  protected abstract boolean hasDefaultValue();
761
762
763
764  /**
765   * Indicates whether values of this argument are considered sensitive.
766   * Argument values that are considered sensitive will be obscured in places
767   * where they may be shown.
768   *
769   * @return  {@code true} if values of this argument are considered sensitive,
770   *          or {@code false} if not.
771   */
772  public final boolean isSensitive()
773  {
774    return isSensitive;
775  }
776
777
778
779  /**
780   * Specifies whether values of this argument are considered sensitive.
781   * Argument values that are considered sensitive will be obscured in places
782   * where they may be shown.
783   *
784   * @param  isSensitive  Indicates whether values of this argument are
785   *                      considered sensitive.
786   */
787  public final void setSensitive(final boolean isSensitive)
788  {
789    this.isSensitive = isSensitive;
790  }
791
792
793
794  /**
795   * Indicates whether this argument has been registered with the argument
796   * parser.
797   *
798   * @return  {@code true} if this argument has been registered with the
799   *          argument parser, or {@code false} if not.
800   */
801  boolean isRegistered()
802  {
803    return isRegistered;
804  }
805
806
807
808  /**
809   * Specifies that this argument has been registered with the argument parser.
810   * This method should only be called by the argument parser method used to
811   * register the argument.
812   *
813   * @throws  ArgumentException  If this argument has already been registered.
814   */
815  void setRegistered()
816       throws ArgumentException
817  {
818    if (isRegistered)
819    {
820      throw new ArgumentException(ERR_ARG_ALREADY_REGISTERED.get(
821                                       getIdentifierString()));
822    }
823
824    isRegistered = true;
825  }
826
827
828
829  /**
830   * Retrieves a concise name of the data type with which this argument is
831   * associated.
832   *
833   * @return  A concise name of the data type with which this argument is
834   *          associated.
835   */
836  public abstract String getDataTypeName();
837
838
839
840  /**
841   * Retrieves a human-readable string with information about any constraints
842   * that may be imposed for values of this argument.
843   *
844   * @return  A human-readable string with information about any constraints
845   *          that may be imposed for values of this argument, or {@code null}
846   *          if there are none.
847   */
848  public String getValueConstraints()
849  {
850    return null;
851  }
852
853
854
855  /**
856   * Resets this argument so that it appears in the same form as before it was
857   * used to parse arguments.  Subclasses that override this method must call
858   * {@code super.reset()} to ensure that all necessary reset processing is
859   * performed.
860   */
861  protected void reset()
862  {
863    numOccurrences = 0;
864  }
865
866
867
868  /**
869   * Creates a copy of this argument that is "clean" and appears as if it has
870   * not been used in the course of parsing an argument set.  The new argument
871   * will have all of the same identifiers and constraints as this parser.
872   *
873   * @return  The "clean" copy of this argument.
874   */
875  public abstract Argument getCleanCopy();
876
877
878
879  /**
880   * Updates the provided list to add any strings that should be included on the
881   * command line in order to represent this argument's current state.
882   *
883   * @param  argStrings  The list to update with the string representation of
884   *                     the command-line arguments.
885   */
886  protected abstract void addToCommandLine(List<String> argStrings);
887
888
889
890  /**
891   * Retrieves a string representation of this argument.
892   *
893   * @return  A string representation of this argument.
894   */
895  public final String toString()
896  {
897    final StringBuilder buffer = new StringBuilder();
898    toString(buffer);
899    return buffer.toString();
900  }
901
902
903
904  /**
905   * Appends a string representation of this argument to the provided buffer.
906   *
907   * @param  buffer  The buffer to which the information should be appended.
908   */
909  public abstract void toString(StringBuilder buffer);
910
911
912
913  /**
914   * Appends a basic set of information for this argument to the provided
915   * buffer in a form suitable for use in the {@code toString} method.
916   *
917   * @param  buffer  The buffer to which information should be appended.
918   */
919  protected void appendBasicToStringInfo(final StringBuilder buffer)
920  {
921    switch (shortIdentifiers.size())
922    {
923      case 0:
924        // Nothing to add.
925        break;
926
927      case 1:
928        buffer.append("shortIdentifier='-");
929        buffer.append(shortIdentifiers.keySet().iterator().next());
930        buffer.append('\'');
931        break;
932
933      default:
934        buffer.append("shortIdentifiers={");
935
936        final Iterator<Character> iterator =
937             shortIdentifiers.keySet().iterator();
938        while (iterator.hasNext())
939        {
940          buffer.append("'-");
941          buffer.append(iterator.next());
942          buffer.append('\'');
943
944          if (iterator.hasNext())
945          {
946            buffer.append(", ");
947          }
948        }
949        buffer.append('}');
950        break;
951    }
952
953    if (! shortIdentifiers.isEmpty())
954    {
955      buffer.append(", ");
956    }
957
958    switch (longIdentifiers.size())
959    {
960      case 0:
961        // Nothing to add.
962        break;
963
964      case 1:
965        buffer.append("longIdentifier='--");
966        buffer.append(longIdentifiers.keySet().iterator().next());
967        buffer.append('\'');
968        break;
969
970      default:
971        buffer.append("longIdentifiers={");
972
973        final Iterator<String> iterator = longIdentifiers.keySet().iterator();
974        while (iterator.hasNext())
975        {
976          buffer.append("'--");
977          buffer.append(iterator.next());
978          buffer.append('\'');
979
980          if (iterator.hasNext())
981          {
982            buffer.append(", ");
983          }
984        }
985        buffer.append('}');
986        break;
987    }
988
989    buffer.append(", description='");
990    buffer.append(description);
991
992    if (argumentGroupName != null)
993    {
994      buffer.append("', argumentGroup='");
995      buffer.append(argumentGroupName);
996    }
997
998    buffer.append("', isRequired=");
999    buffer.append(isRequired);
1000
1001    buffer.append(", maxOccurrences=");
1002    if (maxOccurrences == 0)
1003    {
1004      buffer.append("unlimited");
1005    }
1006    else
1007    {
1008      buffer.append(maxOccurrences);
1009    }
1010
1011    if (valuePlaceholder == null)
1012    {
1013      buffer.append(", takesValue=false");
1014    }
1015    else
1016    {
1017      buffer.append(", takesValue=true, valuePlaceholder='");
1018      buffer.append(valuePlaceholder);
1019      buffer.append('\'');
1020    }
1021
1022    if (isHidden)
1023    {
1024      buffer.append(", isHidden=true");
1025    }
1026  }
1027}