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