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