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 */
021package com.unboundid.util.args;
022
023
024
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Collections;
028import java.util.HashSet;
029import java.util.Iterator;
030import java.util.List;
031import java.util.Set;
032import java.util.regex.Matcher;
033import java.util.regex.Pattern;
034
035import com.unboundid.util.Mutable;
036import com.unboundid.util.ThreadSafety;
037import com.unboundid.util.ThreadSafetyLevel;
038
039import static com.unboundid.util.StaticUtils.*;
040import static com.unboundid.util.args.ArgsMessages.*;
041
042
043
044/**
045 * This class defines an argument that is intended to hold one or more string
046 * values.  String arguments must take values.  By default, any value will be
047 * allowed, but it is possible to restrict the set of values so that only values
048 * from a specified set (ignoring differences in capitalization) will be
049 * allowed.
050 */
051@Mutable()
052@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
053public final class StringArgument
054       extends Argument
055{
056  /**
057   * The serial version UID for this serializable class.
058   */
059  private static final long serialVersionUID = 1088032496970585118L;
060
061
062
063  // The set of values assigned to this argument.
064  private final ArrayList<String> values;
065
066  // The argument value validators that have been registered for this argument.
067  private final List<ArgumentValueValidator> validators;
068
069  // The list of default values that will be used if no values were provided.
070  private final List<String> defaultValues;
071
072  // A regular expression that may be enforced for values of this argument.
073  private volatile Pattern valueRegex;
074
075  // The set of allowed values for this argument.
076  private final Set<String> allowedValues;
077
078  // A human-readable explanation of the regular expression pattern.
079  private volatile String valueRegexExplanation;
080
081
082
083  /**
084   * Creates a new string argument with the provided information.  There will
085   * not be any default values, nor will there be any restriction on values that
086   * may be assigned to this argument.
087   *
088   * @param  shortIdentifier   The short identifier for this argument.  It may
089   *                           not be {@code null} if the long identifier is
090   *                           {@code null}.
091   * @param  longIdentifier    The long identifier for this argument.  It may
092   *                           not be {@code null} if the short identifier is
093   *                           {@code null}.
094   * @param  isRequired        Indicates whether this argument is required to
095   *                           be provided.
096   * @param  maxOccurrences    The maximum number of times this argument may be
097   *                           provided on the command line.  A value less than
098   *                           or equal to zero indicates that it may be present
099   *                           any number of times.
100   * @param  valuePlaceholder  A placeholder to display in usage information to
101   *                           indicate that a value must be provided.  It must
102   *                           not be {@code null}.
103   * @param  description       A human-readable description for this argument.
104   *                           It must not be {@code null}.
105   *
106   * @throws  ArgumentException  If there is a problem with the definition of
107   *                             this argument.
108   */
109  public StringArgument(final Character shortIdentifier,
110                        final String longIdentifier, final boolean isRequired,
111                        final int maxOccurrences, final String valuePlaceholder,
112                        final String description)
113         throws ArgumentException
114  {
115    this(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
116         valuePlaceholder, description, null, (List<String>) null);
117  }
118
119
120
121  /**
122   * Creates a new string argument with the provided information.  There will
123   * not be any default values.
124   *
125   * @param  shortIdentifier   The short identifier for this argument.  It may
126   *                           not be {@code null} if the long identifier is
127   *                           {@code null}.
128   * @param  longIdentifier    The long identifier for this argument.  It may
129   *                           not be {@code null} if the short identifier is
130   *                           {@code null}.
131   * @param  isRequired        Indicates whether this argument is required to
132   *                           be provided.
133   * @param  maxOccurrences    The maximum number of times this argument may be
134   *                           provided on the command line.  A value less than
135   *                           or equal to zero indicates that it may be present
136   *                           any number of times.
137   * @param  valuePlaceholder  A placeholder to display in usage information to
138   *                           indicate that a value must be provided.  It must
139   *                           not be {@code null}.
140   * @param  description       A human-readable description for this argument.
141   *                           It must not be {@code null}.
142   * @param  allowedValues     The set of allowed values for this argument, or
143   *                           {@code null} if it should not be restricted.
144   *
145   * @throws  ArgumentException  If there is a problem with the definition of
146   *                             this argument.
147   */
148  public StringArgument(final Character shortIdentifier,
149                        final String longIdentifier, final boolean isRequired,
150                        final int maxOccurrences, final String valuePlaceholder,
151                        final String description,
152                        final Set<String> allowedValues)
153         throws ArgumentException
154  {
155    this(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
156         valuePlaceholder, description, allowedValues, (List<String>) null);
157  }
158
159
160
161  /**
162   * Creates a new string argument with the provided information.  There will
163   * not be any restriction on values that may be assigned to this argument.
164   *
165   * @param  shortIdentifier   The short identifier for this argument.  It may
166   *                           not be {@code null} if the long identifier is
167   *                           {@code null}.
168   * @param  longIdentifier    The long identifier for this argument.  It may
169   *                           not be {@code null} if the short identifier is
170   *                           {@code null}.
171   * @param  isRequired        Indicates whether this argument is required to
172   *                           be provided.
173   * @param  maxOccurrences    The maximum number of times this argument may be
174   *                           provided on the command line.  A value less than
175   *                           or equal to zero indicates that it may be present
176   *                           any number of times.
177   * @param  valuePlaceholder  A placeholder to display in usage information to
178   *                           indicate that a value must be provided.  It must
179   *                           not be {@code null}.
180   * @param  description       A human-readable description for this argument.
181   *                           It must not be {@code null}.
182   * @param  defaultValue      The default value that will be used for this
183   *                           argument if no values are provided.  It may be
184   *                           {@code null} if there should not be a default
185   *                           value.
186   *
187   * @throws  ArgumentException  If there is a problem with the definition of
188   *                             this argument.
189   */
190  public StringArgument(final Character shortIdentifier,
191                        final String longIdentifier, final boolean isRequired,
192                        final int maxOccurrences, final String valuePlaceholder,
193                        final String description,
194                        final String defaultValue)
195         throws ArgumentException
196  {
197    this(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
198         valuePlaceholder, description, null,
199         ((defaultValue == null) ? null : Arrays.asList(defaultValue)));
200  }
201
202
203
204  /**
205   * Creates a new string argument with the provided information.  There will
206   * not be any restriction on values that may be assigned to this argument.
207   *
208   * @param  shortIdentifier   The short identifier for this argument.  It may
209   *                           not be {@code null} if the long identifier is
210   *                           {@code null}.
211   * @param  longIdentifier    The long identifier for this argument.  It may
212   *                           not be {@code null} if the short identifier is
213   *                           {@code null}.
214   * @param  isRequired        Indicates whether this argument is required to
215   *                           be provided.
216   * @param  maxOccurrences    The maximum number of times this argument may be
217   *                           provided on the command line.  A value less than
218   *                           or equal to zero indicates that it may be present
219   *                           any number of times.
220   * @param  valuePlaceholder  A placeholder to display in usage information to
221   *                           indicate that a value must be provided.  It must
222   *                           not be {@code null}.
223   * @param  description       A human-readable description for this argument.
224   *                           It must not be {@code null}.
225   * @param  defaultValues     The set of default values that will be used for
226   *                           this argument if no values are provided.
227   *
228   * @throws  ArgumentException  If there is a problem with the definition of
229   *                             this argument.
230   */
231  public StringArgument(final Character shortIdentifier,
232                        final String longIdentifier, final boolean isRequired,
233                        final int maxOccurrences, final String valuePlaceholder,
234                        final String description,
235                        final List<String> defaultValues)
236         throws ArgumentException
237  {
238    this(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
239         valuePlaceholder, description, null, defaultValues);
240  }
241
242
243
244  /**
245   * Creates a new string argument with the provided information.
246   *
247   * @param  shortIdentifier   The short identifier for this argument.  It may
248   *                           not be {@code null} if the long identifier is
249   *                           {@code null}.
250   * @param  longIdentifier    The long identifier for this argument.  It may
251   *                           not be {@code null} if the short identifier is
252   *                           {@code null}.
253   * @param  isRequired        Indicates whether this argument is required to
254   *                           be provided.
255   * @param  maxOccurrences    The maximum number of times this argument may be
256   *                           provided on the command line.  A value less than
257   *                           or equal to zero indicates that it may be present
258   *                           any number of times.
259   * @param  valuePlaceholder  A placeholder to display in usage information to
260   *                           indicate that a value must be provided.  It must
261   *                           not be {@code null}.
262   * @param  description       A human-readable description for this argument.
263   *                           It must not be {@code null}.
264   * @param  allowedValues     The set of allowed values for this argument, or
265   *                           {@code null} if it should not be restricted.
266   * @param  defaultValue      The default value that will be used for this
267   *                           argument if no values are provided.  It may be
268   *                           {@code null} if there should not be a default
269   *                           value.
270   *
271   * @throws  ArgumentException  If there is a problem with the definition of
272   *                             this argument.
273   */
274  public StringArgument(final Character shortIdentifier,
275                        final String longIdentifier, final boolean isRequired,
276                        final int maxOccurrences, final String valuePlaceholder,
277                        final String description,
278                        final Set<String> allowedValues,
279                        final String defaultValue)
280         throws ArgumentException
281  {
282    this(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
283         valuePlaceholder, description, allowedValues,
284         ((defaultValue == null) ? null : Arrays.asList(defaultValue)));
285  }
286
287
288
289  /**
290   * Creates a new string argument with the provided information.
291   *
292   * @param  shortIdentifier   The short identifier for this argument.  It may
293   *                           not be {@code null} if the long identifier is
294   *                           {@code null}.
295   * @param  longIdentifier    The long identifier for this argument.  It may
296   *                           not be {@code null} if the short identifier is
297   *                           {@code null}.
298   * @param  isRequired        Indicates whether this argument is required to
299   *                           be provided.
300   * @param  maxOccurrences    The maximum number of times this argument may be
301   *                           provided on the command line.  A value less than
302   *                           or equal to zero indicates that it may be present
303   *                           any number of times.
304   * @param  valuePlaceholder  A placeholder to display in usage information to
305   *                           indicate that a value must be provided.  It must
306   *                           not be {@code null}.
307   * @param  description       A human-readable description for this argument.
308   *                           It must not be {@code null}.
309   * @param  allowedValues     The set of allowed values for this argument, or
310   *                           {@code null} if it should not be restricted.
311   * @param  defaultValues     The set of default values that will be used for
312   *                           this argument if no values are provided.
313   *
314   * @throws  ArgumentException  If there is a problem with the definition of
315   *                             this argument.
316   */
317  public StringArgument(final Character shortIdentifier,
318                        final String longIdentifier, final boolean isRequired,
319                        final int maxOccurrences, final String valuePlaceholder,
320                        final String description,
321                        final Set<String> allowedValues,
322                        final List<String> defaultValues)
323         throws ArgumentException
324  {
325    super(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
326          valuePlaceholder, description);
327
328    if (valuePlaceholder == null)
329    {
330      throw new ArgumentException(ERR_ARG_MUST_TAKE_VALUE.get(
331                                       getIdentifierString()));
332    }
333
334    if ((allowedValues == null) || allowedValues.isEmpty())
335    {
336      this.allowedValues = null;
337    }
338    else
339    {
340      final HashSet<String> lowerValues =
341           new HashSet<String>(allowedValues.size());
342      for (final String s : allowedValues)
343      {
344        lowerValues.add(toLowerCase(s));
345      }
346      this.allowedValues = Collections.unmodifiableSet(lowerValues);
347    }
348
349    if ((defaultValues == null) || defaultValues.isEmpty())
350    {
351      this.defaultValues = null;
352    }
353    else
354    {
355      this.defaultValues = Collections.unmodifiableList(defaultValues);
356    }
357
358    if ((this.allowedValues != null) && (this.defaultValues != null))
359    {
360      for (final String s : this.defaultValues)
361      {
362        final String lowerDefault = toLowerCase(s);
363        if (! this.allowedValues.contains(lowerDefault))
364        {
365          throw new ArgumentException(
366               ERR_ARG_DEFAULT_VALUE_NOT_ALLOWED.get(s, getIdentifierString()));
367        }
368      }
369    }
370
371    values                = new ArrayList<String>(5);
372    validators            = new ArrayList<ArgumentValueValidator>(5);
373    valueRegex            = null;
374    valueRegexExplanation = null;
375  }
376
377
378
379  /**
380   * Creates a new string argument that is a "clean" copy of the provided source
381   * argument.
382   *
383   * @param  source  The source argument to use for this argument.
384   */
385  private StringArgument(final StringArgument source)
386  {
387    super(source);
388
389    allowedValues         = source.allowedValues;
390    defaultValues         = source.defaultValues;
391    valueRegex            = source.valueRegex;
392    valueRegexExplanation = source.valueRegexExplanation;
393    values                = new ArrayList<String>(5);
394    validators            =
395         new ArrayList<ArgumentValueValidator>(source.validators);
396  }
397
398
399
400  /**
401   * Retrieves the set of allowed values for this argument, if applicable.
402   *
403   * @return  The set of allowed values for this argument, or {@code null} if
404   *          there is no restriction on the allowed values.
405   */
406  public Set<String> getAllowedValues()
407  {
408    return allowedValues;
409  }
410
411
412
413  /**
414   * Retrieves the list of default values for this argument, which will be used
415   * if no values were provided.
416   *
417   * @return   The list of default values for this argument, or {@code null} if
418   *           there are no default values.
419   */
420  public List<String> getDefaultValues()
421  {
422    return defaultValues;
423  }
424
425
426
427  /**
428   * Retrieves the regular expression that values of this argument will be
429   * required to match, if any.
430   *
431   * @return  The regular expression that values of this argument will be
432   *          required to match, or {@code null} if none is defined.
433   */
434  public Pattern getValueRegex()
435  {
436    return valueRegex;
437  }
438
439
440
441  /**
442   * Retrieves a human-readable explanation of the regular expression pattern
443   * that may be required to match any provided values, if any.
444   *
445   * @return  A human-readable explanation of the regular expression pattern, or
446   *          {@code null} if none is available.
447   */
448  public String getValueRegexExplanation()
449  {
450    return valueRegexExplanation;
451  }
452
453
454
455  /**
456   * Specifies the regular expression that values of this argument will be
457   * required to match, if any.
458   *
459   * @param  valueRegex   The regular expression that values of this argument
460   *                      will be required to match.  It may be {@code null} if
461   *                      no pattern matching should be required.
462   * @param  explanation  A human-readable explanation for the pattern which may
463   *                      be used to clarify the kinds of values that are
464   *                      acceptable.  It may be {@code null} if no pattern
465   *                      matching should be required, or if the regular
466   *                      expression pattern should be sufficiently clear for
467   *                      the target audience.
468   */
469  public void setValueRegex(final Pattern valueRegex,
470                            final String explanation)
471  {
472    this.valueRegex = valueRegex;
473    valueRegexExplanation = explanation;
474  }
475
476
477
478  /**
479   * Updates this argument to ensure that the provided validator will be invoked
480   * for any values provided to this argument.  This validator will be invoked
481   * after all other validation has been performed for this argument.
482   *
483   * @param  validator  The argument value validator to be invoked.  It must not
484   *                    be {@code null}.
485   */
486  public void addValueValidator(final ArgumentValueValidator validator)
487  {
488    validators.add(validator);
489  }
490
491
492
493  /**
494   * {@inheritDoc}
495   */
496  @Override()
497  protected void addValue(final String valueString)
498            throws ArgumentException
499  {
500    final String lowerValue = toLowerCase(valueString);
501    if (allowedValues != null)
502    {
503      if (! allowedValues.contains(lowerValue))
504      {
505        throw new ArgumentException(ERR_ARG_VALUE_NOT_ALLOWED.get(
506                                         valueString, getIdentifierString()));
507      }
508    }
509
510    if (values.size() >= getMaxOccurrences())
511    {
512      throw new ArgumentException(ERR_ARG_MAX_OCCURRENCES_EXCEEDED.get(
513                                       getIdentifierString()));
514    }
515
516    if (valueRegex != null)
517    {
518      final Matcher matcher = valueRegex.matcher(valueString);
519      if (! matcher.matches())
520      {
521        final String pattern = valueRegex.pattern();
522        if (valueRegexExplanation == null)
523        {
524          throw new ArgumentException(
525               ERR_ARG_VALUE_DOES_NOT_MATCH_PATTERN_WITHOUT_EXPLANATION.get(
526                    valueString, getIdentifierString(), pattern));
527        }
528        else
529        {
530          throw new ArgumentException(
531               ERR_ARG_VALUE_DOES_NOT_MATCH_PATTERN_WITH_EXPLANATION.get(
532                    valueString, getIdentifierString(), pattern,
533                    valueRegexExplanation));
534        }
535      }
536    }
537
538    for (final ArgumentValueValidator v : validators)
539    {
540      v.validateArgumentValue(this, valueString);
541    }
542
543    values.add(valueString);
544  }
545
546
547
548  /**
549   * Retrieves the value for this argument, or the default value if none was
550   * provided.  If this argument has multiple values, then the first will be
551   * returned.
552   *
553   * @return  The value for this argument, or the default value if none was
554   *          provided, or {@code null} if it does not have any values or
555   *          default values.
556   */
557  public String getValue()
558  {
559    if (values.isEmpty())
560    {
561      if ((defaultValues == null) || defaultValues.isEmpty())
562      {
563        return null;
564      }
565      else
566      {
567        return defaultValues.get(0);
568      }
569    }
570
571    return values.get(0);
572  }
573
574
575
576  /**
577   * Retrieves the set of values for this argument, or the default values if
578   * none were provided.
579   *
580   * @return  The set of values for this argument, or the default values if none
581   *          were provided.
582   */
583  public List<String> getValues()
584  {
585    if (values.isEmpty() && (defaultValues != null))
586    {
587      return defaultValues;
588    }
589
590    return Collections.unmodifiableList(values);
591  }
592
593
594
595  /**
596   * {@inheritDoc}
597   */
598  @Override()
599  protected boolean hasDefaultValue()
600  {
601    return ((defaultValues != null) && (! defaultValues.isEmpty()));
602  }
603
604
605
606  /**
607   * {@inheritDoc}
608   */
609  @Override()
610  public String getDataTypeName()
611  {
612    return INFO_STRING_TYPE_NAME.get();
613  }
614
615
616
617  /**
618   * {@inheritDoc}
619   */
620  @Override()
621  public String getValueConstraints()
622  {
623    StringBuilder buffer = null;
624
625    if (valueRegex != null)
626    {
627      buffer = new StringBuilder();
628      final String pattern = valueRegex.pattern();
629      if ((valueRegexExplanation == null) ||
630          (valueRegexExplanation.length() == 0))
631      {
632        buffer.append(INFO_STRING_CONSTRAINTS_REGEX_WITHOUT_EXPLANATION.get(
633             pattern));
634      }
635      else
636      {
637        buffer.append(INFO_STRING_CONSTRAINTS_REGEX_WITHOUT_EXPLANATION.get(
638             pattern, valueRegexExplanation));
639      }
640    }
641
642    if ((allowedValues != null) && (! allowedValues.isEmpty()))
643    {
644      if (buffer == null)
645      {
646        buffer = new StringBuilder();
647      }
648      else
649      {
650        buffer.append("  ");
651      }
652
653      buffer.append(INFO_STRING_CONSTRAINTS_ALLOWED_VALUE.get());
654      buffer.append("  ");
655
656      final Iterator<String> iterator = allowedValues.iterator();
657      while (iterator.hasNext())
658      {
659        buffer.append('\'');
660        buffer.append(iterator.next());
661        buffer.append('\'');
662
663        if (iterator.hasNext())
664        {
665          buffer.append(", ");
666        }
667      }
668      buffer.append('.');
669    }
670
671    if (buffer == null)
672    {
673      return null;
674    }
675    else
676    {
677      return buffer.toString();
678    }
679  }
680
681
682
683  /**
684   * {@inheritDoc}
685   */
686  @Override()
687  public StringArgument getCleanCopy()
688  {
689    return new StringArgument(this);
690  }
691
692
693
694  /**
695   * {@inheritDoc}
696   */
697  @Override()
698  public void toString(final StringBuilder buffer)
699  {
700    buffer.append("StringArgument(");
701    appendBasicToStringInfo(buffer);
702
703    if ((allowedValues != null) && (! allowedValues.isEmpty()))
704    {
705      buffer.append(", allowedValues={");
706      final Iterator<String> iterator = allowedValues.iterator();
707      while (iterator.hasNext())
708      {
709        buffer.append('\'');
710        buffer.append(iterator.next());
711        buffer.append('\'');
712
713        if (iterator.hasNext())
714        {
715          buffer.append(", ");
716        }
717      }
718      buffer.append('}');
719    }
720
721    if (valueRegex != null)
722    {
723      buffer.append(", valueRegex='");
724      buffer.append(valueRegex.pattern());
725      buffer.append('\'');
726
727      if (valueRegexExplanation != null)
728      {
729        buffer.append(", valueRegexExplanation='");
730        buffer.append(valueRegexExplanation);
731        buffer.append('\'');
732      }
733    }
734
735    if ((defaultValues != null) && (! defaultValues.isEmpty()))
736    {
737      if (defaultValues.size() == 1)
738      {
739        buffer.append(", defaultValue='");
740        buffer.append(defaultValues.get(0));
741      }
742      else
743      {
744        buffer.append(", defaultValues={");
745
746        final Iterator<String> iterator = defaultValues.iterator();
747        while (iterator.hasNext())
748        {
749          buffer.append('\'');
750          buffer.append(iterator.next());
751          buffer.append('\'');
752
753          if (iterator.hasNext())
754          {
755            buffer.append(", ");
756          }
757        }
758
759        buffer.append('}');
760      }
761    }
762
763    buffer.append(')');
764  }
765}