001/*
002 * Copyright 2010-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2010-2018 Ping Identity Corporation
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.Collections;
027import java.util.List;
028import java.util.concurrent.TimeUnit;
029
030import com.unboundid.util.Debug;
031import com.unboundid.util.LDAPSDKUsageException;
032import com.unboundid.util.Mutable;
033import com.unboundid.util.StaticUtils;
034import com.unboundid.util.ThreadSafety;
035import com.unboundid.util.ThreadSafetyLevel;
036
037import static com.unboundid.util.args.ArgsMessages.*;
038
039
040
041/**
042 * Creates a new argument that is intended to represent a duration.  Duration
043 * values contain an integer portion and a unit portion which represents the
044 * time unit.  The unit must be one of the following:
045 * <UL>
046 *   <LI>Nanoseconds -- ns, nano, nanos, nanosecond, nanoseconds</LI>
047 *   <LI>Microseconds -- us, micro, micros, microsecond, microseconds</LI>
048 *   <LI>Milliseconds -- ms, milli, millis, millisecond, milliseconds</LI>
049 *   <LI>Seconds -- s, sec, secs, second, seconds</LI>
050 *   <LI>Minutes -- m, min, mins, minute, minutes</LI>
051 *   <LI>Hours -- h, hr, hrs, hour, hours</LI>
052 *   <LI>Days -- d, day, days</LI>
053 *   <LI>Weeks -- w, week, weeks</LI>
054 * </UL>
055 *
056 * There may be zero or more spaces between the integer portion and the unit
057 * portion.  However, if spaces are used in the command-line argument, then the
058 * value must be enquoted or the spaces must be escaped so that the duration
059 * is not seen as multiple arguments.
060 */
061@Mutable()
062@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
063public final class DurationArgument
064       extends Argument
065{
066  /**
067   * The serial version UID for this serializable class.
068   */
069  private static final long serialVersionUID = -8824262632728709264L;
070
071
072
073  // The argument value validators that have been registered for this argument.
074  private final List<ArgumentValueValidator> validators;
075
076  // The default value for this argument, in nanoseconds.
077  private final Long defaultValueNanos;
078
079  // The maximum allowed value for this argument, in nanoseconds.
080  private final long maxValueNanos;
081
082  // The minimum allowed value for this argument, in nanoseconds.
083  private final long minValueNanos;
084
085  // The provided value for this argument, in nanoseconds.
086  private Long valueNanos;
087
088  // The string representation of the lower bound, using the user-supplied
089  // value.
090  private final String lowerBoundStr;
091
092  // The string representation of the upper bound, using the user-supplied
093  // value.
094  private final String upperBoundStr;
095
096
097
098  /**
099   * Creates a new duration argument that will not be required, will use a
100   * default placeholder, and will have no default value and no bounds on the
101   * set of allowed values.
102   *
103   * @param  shortIdentifier   The short identifier for this argument.  It may
104   *                           not be {@code null} if the long identifier is
105   *                           {@code null}.
106   * @param  longIdentifier    The long identifier for this argument.  It may
107   *                           not be {@code null} if the short identifier is
108   *                           {@code null}.
109   * @param  description       A human-readable description for this argument.
110   *                           It must not be {@code null}.
111   *
112   * @throws  ArgumentException  If there is a problem with the definition of
113   *                             this argument.
114   */
115  public DurationArgument(final Character shortIdentifier,
116                          final String longIdentifier, final String description)
117         throws ArgumentException
118  {
119    this(shortIdentifier, longIdentifier, false, null, description);
120  }
121
122
123
124  /**
125   * Creates a new duration argument with no default value and no bounds on the
126   * set of allowed values.
127   *
128   * @param  shortIdentifier   The short identifier for this argument.  It may
129   *                           not be {@code null} if the long identifier is
130   *                           {@code null}.
131   * @param  longIdentifier    The long identifier for this argument.  It may
132   *                           not be {@code null} if the short identifier is
133   *                           {@code null}.
134   * @param  isRequired        Indicates whether this argument is required to
135   *                           be provided.
136   * @param  valuePlaceholder  A placeholder to display in usage information to
137   *                           indicate that a value must be provided.  It may
138   *                           be {@code null} if a default placeholder should
139   *                           be used.
140   * @param  description       A human-readable description for this argument.
141   *                           It must not be {@code null}.
142   *
143   * @throws  ArgumentException  If there is a problem with the definition of
144   *                             this argument.
145   */
146  public DurationArgument(final Character shortIdentifier,
147                          final String longIdentifier, final boolean isRequired,
148                          final String valuePlaceholder,
149                          final String description)
150         throws ArgumentException
151  {
152    this(shortIdentifier, longIdentifier, isRequired, valuePlaceholder,
153         description, null, null, null, null, null, null);
154  }
155
156
157
158  /**
159   * Creates a new duration argument with the provided information.
160   *
161   * @param  shortIdentifier   The short identifier for this argument.  It may
162   *                           not be {@code null} if the long identifier is
163   *                           {@code null}.
164   * @param  longIdentifier    The long identifier for this argument.  It may
165   *                           not be {@code null} if the short identifier is
166   *                           {@code null}.
167   * @param  isRequired        Indicates whether this argument is required to
168   *                           be provided.
169   * @param  valuePlaceholder  A placeholder to display in usage information to
170   *                           indicate that a value must be provided.  It may
171   *                           be {@code null} if a default placeholder should
172   *                           be used.
173   * @param  description       A human-readable description for this argument.
174   *                           It must not be {@code null}.
175   * @param  defaultValue      The default value that will be used for this
176   *                           argument if none is provided.  It may be
177   *                           {@code null} if there should not be a default
178   *                           value.
179   * @param  defaultValueUnit  The time unit for the default value.  It may be
180   *                           {@code null} only if the default value is also
181   *                           {@code null}.
182   * @param  lowerBound        The value for the minimum duration that may be
183   *                           represented using this argument, in conjunction
184   *                           with the {@code lowerBoundUnit} parameter to
185   *                           specify the unit for this value.  If this is
186   *                           {@code null}, then a lower bound of 0 nanoseconds
187   *                           will be used.
188   * @param  lowerBoundUnit    The time unit for the lower bound value.  It may
189   *                           be {@code null} only if the lower bound is also
190   *                           {@code null}.
191   * @param  upperBound        The value for the maximum duration that may be
192   *                           represented using this argument, in conjunction
193   *                           with the {@code upperBoundUnit} parameter to
194   *                           specify the unit for this value.  If this is
195   *                           {@code null}, then an upper bound of
196   *                           {@code Long.MAX_VALUE} nanoseconds will be used.
197   * @param  upperBoundUnit    The time unit for the upper bound value.  It may
198   *                           be {@code null} only if the upper bound is also
199   *                           {@code null}.
200   *
201   * @throws  ArgumentException  If there is a problem with the definition of
202   *                             this argument.
203   */
204  public DurationArgument(final Character shortIdentifier,
205                          final String longIdentifier, final boolean isRequired,
206                          final String valuePlaceholder,
207                          final String description, final Long defaultValue,
208                          final TimeUnit defaultValueUnit,
209                          final Long lowerBound, final TimeUnit lowerBoundUnit,
210                          final Long upperBound, final TimeUnit upperBoundUnit)
211         throws ArgumentException
212  {
213    super(shortIdentifier, longIdentifier, isRequired, 1,
214         (valuePlaceholder == null)
215              ? INFO_PLACEHOLDER_DURATION.get()
216              : valuePlaceholder,
217         description);
218
219    if (defaultValue == null)
220    {
221      defaultValueNanos = null;
222    }
223    else
224    {
225      if (defaultValueUnit == null)
226      {
227        throw new ArgumentException(ERR_DURATION_DEFAULT_REQUIRES_UNIT.get(
228             getIdentifierString()));
229      }
230
231      defaultValueNanos = defaultValueUnit.toNanos(defaultValue);
232    }
233
234    if (lowerBound == null)
235    {
236      minValueNanos = 0L;
237      lowerBoundStr = "0ns";
238    }
239    else
240    {
241      if (lowerBoundUnit == null)
242      {
243        throw new ArgumentException(ERR_DURATION_LOWER_REQUIRES_UNIT.get(
244             getIdentifierString()));
245      }
246
247      minValueNanos = lowerBoundUnit.toNanos(lowerBound);
248      switch (lowerBoundUnit)
249      {
250        case NANOSECONDS:
251          lowerBoundStr = minValueNanos + "ns";
252          break;
253        case MICROSECONDS:
254          lowerBoundStr = lowerBound + "us";
255          break;
256        case MILLISECONDS:
257          lowerBoundStr = lowerBound + "ms";
258          break;
259        case SECONDS:
260          lowerBoundStr = lowerBound + "s";
261          break;
262        case MINUTES:
263          lowerBoundStr = lowerBound + "m";
264          break;
265        case HOURS:
266          lowerBoundStr = lowerBound + "h";
267          break;
268        case DAYS:
269          lowerBoundStr = lowerBound + "d";
270          break;
271        default:
272          throw new LDAPSDKUsageException(
273               ERR_DURATION_UNSUPPORTED_LOWER_BOUND_UNIT.get(
274                    lowerBoundUnit.name()));
275      }
276    }
277
278    if (upperBound == null)
279    {
280      maxValueNanos = Long.MAX_VALUE;
281      upperBoundStr = Long.MAX_VALUE + "ns";
282    }
283    else
284    {
285      if (upperBoundUnit == null)
286      {
287        throw new ArgumentException(ERR_DURATION_UPPER_REQUIRES_UNIT.get(
288             getIdentifierString()));
289      }
290
291      maxValueNanos = upperBoundUnit.toNanos(upperBound);
292      switch (upperBoundUnit)
293      {
294        case NANOSECONDS:
295          upperBoundStr = minValueNanos + "ns";
296          break;
297        case MICROSECONDS:
298          upperBoundStr = upperBound + "us";
299          break;
300        case MILLISECONDS:
301          upperBoundStr = upperBound + "ms";
302          break;
303        case SECONDS:
304          upperBoundStr = upperBound + "s";
305          break;
306        case MINUTES:
307          upperBoundStr = upperBound + "m";
308          break;
309        case HOURS:
310          upperBoundStr = upperBound + "h";
311          break;
312        case DAYS:
313          upperBoundStr = upperBound + "d";
314          break;
315        default:
316          throw new LDAPSDKUsageException(
317               ERR_DURATION_UNSUPPORTED_UPPER_BOUND_UNIT.get(
318                    upperBoundUnit.name()));
319      }
320    }
321
322    if (minValueNanos > maxValueNanos)
323    {
324      throw new ArgumentException(ERR_DURATION_LOWER_GT_UPPER.get(
325           getIdentifierString(), lowerBoundStr, upperBoundStr));
326    }
327
328    valueNanos = null;
329    validators = new ArrayList<>(5);
330  }
331
332
333
334  /**
335   * Creates a new duration argument that is a "clean" copy of the provided
336   * source argument.
337   *
338   * @param  source  The source argument to use for this argument.
339   */
340  private DurationArgument(final DurationArgument source)
341  {
342    super(source);
343
344    defaultValueNanos = source.defaultValueNanos;
345    maxValueNanos     = source.maxValueNanos;
346    minValueNanos     = source.minValueNanos;
347    lowerBoundStr     = source.lowerBoundStr;
348    upperBoundStr     = source.upperBoundStr;
349    validators        = new ArrayList<>(source.validators);
350    valueNanos        = null;
351  }
352
353
354
355  /**
356   * Retrieves the lower bound for this argument using the specified time unit.
357   *
358   * @param  unit  The time unit in which the lower bound value may be
359   *               expressed.
360   *
361   * @return  The lower bound for this argument using the specified time unit.
362   */
363  public long getLowerBound(final TimeUnit unit)
364  {
365    return unit.convert(minValueNanos, TimeUnit.NANOSECONDS);
366  }
367
368
369
370  /**
371   * Retrieves the upper bound for this argument using the specified time unit.
372   *
373   * @param  unit  The time unit in which the upper bound value may be
374   *               expressed.
375   *
376   * @return  The upper bound for this argument using the specified time unit.
377   */
378  public long getUpperBound(final TimeUnit unit)
379  {
380    return unit.convert(maxValueNanos, TimeUnit.NANOSECONDS);
381  }
382
383
384
385  /**
386   * {@inheritDoc}
387   */
388  @Override()
389  public List<String> getValueStringRepresentations(final boolean useDefault)
390  {
391    final long v;
392    if (valueNanos != null)
393    {
394      v = valueNanos;
395    }
396    else if (useDefault && (defaultValueNanos != null))
397    {
398      v = defaultValueNanos;
399    }
400    else
401    {
402      return Collections.emptyList();
403    }
404
405    return Collections.singletonList(nanosToDuration(v));
406  }
407
408
409
410  /**
411   * {@inheritDoc}
412   */
413  @Override()
414  protected boolean hasDefaultValue()
415  {
416    return (defaultValueNanos != null);
417  }
418
419
420
421  /**
422   * Retrieves the default value for this argument using the specified time
423   * unit, if defined.
424   *
425   * @param  unit  The time unit in which the default value should be expressed.
426   *
427   * @return  The default value for this argument using the specified time unit,
428   *          or {@code null} if none is defined.
429   */
430  public Long getDefaultValue(final TimeUnit unit)
431  {
432    if (defaultValueNanos == null)
433    {
434      return null;
435    }
436
437    return unit.convert(defaultValueNanos, TimeUnit.NANOSECONDS);
438  }
439
440
441
442  /**
443   * Retrieves the value for this argument using the specified time unit, if one
444   * was provided.
445   *
446   * @param  unit  The time unit in which to express the value for this
447   *               argument.
448   *
449   * @return  The value for this argument using the specified time unit.  If no
450   *          value was provided but a default value was defined, then the
451   *          default value will be returned.  If no value was provided and no
452   *          default value was defined, then {@code null} will be returned.
453   */
454  public Long getValue(final TimeUnit unit)
455  {
456    if (valueNanos == null)
457    {
458      if (defaultValueNanos == null)
459      {
460        return null;
461      }
462
463      return unit.convert(defaultValueNanos, TimeUnit.NANOSECONDS);
464    }
465    else
466    {
467      return unit.convert(valueNanos, TimeUnit.NANOSECONDS);
468    }
469  }
470
471
472
473  /**
474   * Updates this argument to ensure that the provided validator will be invoked
475   * for any values provided to this argument.  This validator will be invoked
476   * after all other validation has been performed for this argument.
477   *
478   * @param  validator  The argument value validator to be invoked.  It must not
479   *                    be {@code null}.
480   */
481  public void addValueValidator(final ArgumentValueValidator validator)
482  {
483    validators.add(validator);
484  }
485
486
487
488  /**
489   * {@inheritDoc}
490   */
491  @Override()
492  protected void addValue(final String valueString)
493            throws ArgumentException
494  {
495    if (valueNanos != null)
496    {
497      throw new ArgumentException(
498           ERR_ARG_MAX_OCCURRENCES_EXCEEDED.get(getIdentifierString()));
499    }
500
501    final long proposedValueNanos;
502    try
503    {
504      proposedValueNanos = parseDuration(valueString, TimeUnit.NANOSECONDS);
505    }
506    catch (final ArgumentException ae)
507    {
508      Debug.debugException(ae);
509      throw new ArgumentException(
510           ERR_DURATION_MALFORMED_VALUE.get(valueString, getIdentifierString(),
511                ae.getMessage()),
512           ae);
513    }
514
515    if (proposedValueNanos < minValueNanos)
516    {
517      throw new ArgumentException(ERR_DURATION_BELOW_LOWER_BOUND.get(
518           getIdentifierString(), lowerBoundStr));
519    }
520    else if (proposedValueNanos > maxValueNanos)
521    {
522      throw new ArgumentException(ERR_DURATION_ABOVE_UPPER_BOUND.get(
523           getIdentifierString(), upperBoundStr));
524    }
525    else
526    {
527      for (final ArgumentValueValidator v : validators)
528      {
529        v.validateArgumentValue(this, valueString);
530      }
531
532      valueNanos = proposedValueNanos;
533    }
534  }
535
536
537
538  /**
539   * Parses the provided string representation of a duration to a corresponding
540   * numeric representation.
541   *
542   * @param  durationString  The string representation of the duration to be
543   *                         parsed.
544   * @param  timeUnit        The time unit to use for the return value.
545   *
546   * @return  The parsed duration as a count in the specified time unit.
547   *
548   * @throws  ArgumentException  If the provided string cannot be parsed as a
549   *                             valid duration.
550   */
551  public static long parseDuration(final String durationString,
552                                   final TimeUnit timeUnit)
553         throws ArgumentException
554  {
555    // The string must not be empty.
556    final String lowerStr = StaticUtils.toLowerCase(durationString);
557    if (lowerStr.isEmpty())
558    {
559      throw new ArgumentException(ERR_DURATION_EMPTY_VALUE.get());
560    }
561
562    // Find the position of the first non-digit character.
563    boolean digitFound    = false;
564    boolean nonDigitFound = false;
565    int     nonDigitPos   = -1;
566    for (int i=0; i < lowerStr.length(); i++)
567    {
568      final char c = lowerStr.charAt(i);
569      if (Character.isDigit(c))
570      {
571        digitFound = true;
572      }
573      else
574      {
575        nonDigitFound = true;
576        nonDigitPos   = i;
577        if (! digitFound)
578        {
579          throw new ArgumentException(ERR_DURATION_NO_DIGIT.get());
580        }
581        break;
582      }
583    }
584
585    if (! nonDigitFound)
586    {
587      throw new ArgumentException(ERR_DURATION_NO_UNIT.get());
588    }
589
590    // Separate the integer portion from the unit.
591    long integerPortion = Long.parseLong(lowerStr.substring(0, nonDigitPos));
592    final String unitStr = lowerStr.substring(nonDigitPos).trim();
593
594    // Parse the time unit.
595    final TimeUnit unitFromString;
596    if (unitStr.equals("ns") ||
597        unitStr.equals("nano") ||
598        unitStr.equals("nanos") ||
599        unitStr.equals("nanosecond") ||
600        unitStr.equals("nanoseconds"))
601    {
602      unitFromString = TimeUnit.NANOSECONDS;
603    }
604    else if (unitStr.equals("us") ||
605             unitStr.equals("micro") ||
606             unitStr.equals("micros") ||
607             unitStr.equals("microsecond") ||
608             unitStr.equals("microseconds"))
609    {
610      unitFromString = TimeUnit.MICROSECONDS;
611    }
612    else if (unitStr.equals("ms") ||
613             unitStr.equals("milli") ||
614             unitStr.equals("millis") ||
615             unitStr.equals("millisecond") ||
616             unitStr.equals("milliseconds"))
617    {
618      unitFromString = TimeUnit.MILLISECONDS;
619    }
620    else if (unitStr.equals("s") ||
621             unitStr.equals("sec") ||
622             unitStr.equals("secs") ||
623             unitStr.equals("second") ||
624             unitStr.equals("seconds"))
625    {
626      unitFromString = TimeUnit.SECONDS;
627    }
628    else if (unitStr.equals("m") ||
629             unitStr.equals("min") ||
630             unitStr.equals("mins") ||
631             unitStr.equals("minute") ||
632             unitStr.equals("minutes"))
633    {
634      integerPortion *= 60L;
635      unitFromString = TimeUnit.SECONDS;
636    }
637    else if (unitStr.equals("h") ||
638             unitStr.equals("hr") ||
639             unitStr.equals("hrs") ||
640             unitStr.equals("hour") ||
641             unitStr.equals("hours"))
642    {
643      integerPortion *= 3600L;
644      unitFromString = TimeUnit.SECONDS;
645    }
646    else if (unitStr.equals("d") ||
647             unitStr.equals("day") ||
648             unitStr.equals("days"))
649    {
650      integerPortion *= 86_400L;
651      unitFromString = TimeUnit.SECONDS;
652    }
653    else if (unitStr.equals("w") ||
654             unitStr.equals("week") ||
655             unitStr.equals("weeks"))
656    {
657      integerPortion *= 604_800;
658      unitFromString = TimeUnit.SECONDS;
659    }
660    else
661    {
662      throw new ArgumentException(ERR_DURATION_UNRECOGNIZED_UNIT.get(unitStr));
663    }
664
665    return timeUnit.convert(integerPortion, unitFromString);
666  }
667
668
669
670  /**
671   * {@inheritDoc}
672   */
673  @Override()
674  public String getDataTypeName()
675  {
676    return INFO_DURATION_TYPE_NAME.get();
677  }
678
679
680
681  /**
682   * {@inheritDoc}
683   */
684  @Override()
685  public String getValueConstraints()
686  {
687    final StringBuilder buffer = new StringBuilder();
688    buffer.append(INFO_DURATION_CONSTRAINTS_FORMAT.get());
689
690    if (lowerBoundStr != null)
691    {
692      if (upperBoundStr == null)
693      {
694        buffer.append("  ");
695        buffer.append(INFO_DURATION_CONSTRAINTS_LOWER_BOUND.get(lowerBoundStr));
696      }
697      else
698      {
699        buffer.append("  ");
700        buffer.append(INFO_DURATION_CONSTRAINTS_LOWER_AND_UPPER_BOUND.get(
701             lowerBoundStr, upperBoundStr));
702      }
703    }
704    else
705    {
706      if (upperBoundStr != null)
707      {
708        buffer.append("  ");
709        buffer.append(INFO_DURATION_CONSTRAINTS_UPPER_BOUND.get(upperBoundStr));
710      }
711    }
712
713    return buffer.toString();
714  }
715
716
717
718  /**
719   * {@inheritDoc}
720   */
721  @Override()
722  protected void reset()
723  {
724    super.reset();
725    valueNanos = null;
726  }
727
728
729
730  /**
731   * {@inheritDoc}
732   */
733  @Override()
734  public DurationArgument getCleanCopy()
735  {
736    return new DurationArgument(this);
737  }
738
739
740
741  /**
742   * Converts the specified number of nanoseconds into a duration string using
743   * the largest possible whole unit (e.g., if the value represents a whole
744   * number of seconds, then the returned string will be expressed in seconds).
745   *
746   * @param  nanos  The number of nanoseconds to convert to a duration string.
747   *
748   * @return  The duration string for the specified number of nanoseconds.
749   */
750  public static String nanosToDuration(final long nanos)
751  {
752    if (nanos == 0)
753    {
754      return "0 nanoseconds";
755    }
756
757    if (nanos == 604_800_000_000_000L)
758    {
759      return "1 week";
760    }
761    else if ((nanos % 604_800_000_000_000L) == 0L)
762    {
763      return (nanos / 604_800_000_000_000L) + " weeks";
764    }
765    else if (nanos == 86_400_000_000_000L)
766    {
767      return "1 day";
768    }
769    else if ((nanos % 86_400_000_000_000L) == 0L)
770    {
771      return (nanos / 86_400_000_000_000L) + " days";
772    }
773    else if (nanos == 3_600_000_000_000L)
774    {
775      return "1 hour";
776    }
777    else if ((nanos % 3_600_000_000_000L) == 0L)
778    {
779      return (nanos / 3_600_000_000_000L) + " hours";
780    }
781    else if (nanos == 60_000_000_000L)
782    {
783      return "1 minute";
784    }
785    else if ((nanos % 60_000_000_000L) == 0L)
786    {
787      return (nanos / 60_000_000_000L) + " minutes";
788    }
789    else if (nanos == 1_000_000_000L)
790    {
791      return "1 second";
792    }
793    else if ((nanos % 1_000_000_000L) == 0L)
794    {
795      return (nanos / 1_000_000_000L) + " seconds";
796    }
797    else if (nanos == 1_000_000L)
798    {
799      return "1 millisecond";
800    }
801    else if ((nanos % 1_000_000L) == 0L)
802    {
803     return (nanos / 1_000_000L) + " milliseconds";
804    }
805    else if (nanos == 1000L)
806    {
807      return "1 microsecond";
808    }
809    else if ((nanos % 1000L) == 0L)
810    {
811     return (nanos / 1000L) + " microseconds";
812    }
813    else if (nanos == 1L)
814    {
815      return "1 nanosecond";
816    }
817    else
818    {
819      return nanos + " nanoseconds";
820    }
821  }
822
823
824
825  /**
826   * {@inheritDoc}
827   */
828  @Override()
829  protected void addToCommandLine(final List<String> argStrings)
830  {
831    if (valueNanos != null)
832    {
833      argStrings.add(getIdentifierString());
834      if (isSensitive())
835      {
836        argStrings.add("***REDACTED***");
837      }
838      else
839      {
840        argStrings.add(nanosToDuration(valueNanos));
841      }
842    }
843  }
844
845
846
847  /**
848   * {@inheritDoc}
849   */
850  @Override()
851  public void toString(final StringBuilder buffer)
852  {
853    buffer.append("DurationArgument(");
854    appendBasicToStringInfo(buffer);
855
856    if (lowerBoundStr != null)
857    {
858      buffer.append(", lowerBound='");
859      buffer.append(lowerBoundStr);
860      buffer.append('\'');
861    }
862
863    if (upperBoundStr != null)
864    {
865      buffer.append(", upperBound='");
866      buffer.append(upperBoundStr);
867      buffer.append('\'');
868    }
869
870    if (defaultValueNanos != null)
871    {
872      buffer.append(", defaultValueNanos=");
873      buffer.append(defaultValueNanos);
874    }
875
876    buffer.append(')');
877  }
878}