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