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