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 */
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.List;
031
032import com.unboundid.util.Mutable;
033import com.unboundid.util.NotExtensible;
034import com.unboundid.util.ThreadSafety;
035import com.unboundid.util.ThreadSafetyLevel;
036
037import 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)
049public 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}