001/*
002 * Copyright 2008-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2014 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 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}