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