001/*
002 * Copyright 2010-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2010-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.util.ArrayList;
026import java.util.Arrays;
027import java.util.Collections;
028import java.util.List;
029import java.util.concurrent.TimeUnit;
030
031import com.unboundid.util.Debug;
032import com.unboundid.util.LDAPSDKUsageException;
033import com.unboundid.util.Mutable;
034import com.unboundid.util.StaticUtils;
035import com.unboundid.util.ThreadSafety;
036import com.unboundid.util.ThreadSafetyLevel;
037
038import static com.unboundid.util.args.ArgsMessages.*;
039
040
041
042/**
043 * Creates a new argument that is intended to represent a duration.  Duration
044 * values contain an integer portion and a unit portion which represents the
045 * time unit.  The unit must be one of the following:
046 * <UL>
047 *   <LI>Nanoseconds -- ns, nano, nanos, nanosecond, nanoseconds</LI>
048 *   <LI>Microseconds -- us, micro, micros, microsecond, microseconds</LI>
049 *   <LI>Milliseconds -- ms, milli, millis, millisecond, milliseconds</LI>
050 *   <LI>Seconds -- s, sec, secs, second, seconds</LI>
051 *   <LI>Minutes -- m, min, mins, minute, minutes</LI>
052 *   <LI>Hours -- h, hr, hrs, hour, hours</LI>
053 *   <LI>Days -- d, day, days</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      final String lowerBoundUnitName = lowerBoundUnit.name();
249      if (lowerBoundUnitName.equals("NANOSECONDS"))
250      {
251        lowerBoundStr = minValueNanos + "ns";
252      }
253      else if (lowerBoundUnitName.equals("MICROSECONDS"))
254      {
255        lowerBoundStr = lowerBound + "us";
256      }
257      else if (lowerBoundUnitName.equals("MILLISECONDS"))
258      {
259        lowerBoundStr = lowerBound + "ms";
260      }
261      else if (lowerBoundUnitName.equals("SECONDS"))
262      {
263        lowerBoundStr = lowerBound + "s";
264      }
265      else if (lowerBoundUnitName.equals("MINUTES"))
266      {
267        lowerBoundStr = lowerBound + "m";
268      }
269      else if (lowerBoundUnitName.equals("HOURS"))
270      {
271        lowerBoundStr = lowerBound + "h";
272      }
273      else if (lowerBoundUnitName.equals("DAYS"))
274      {
275        lowerBoundStr = lowerBound + "d";
276      }
277      else
278      {
279        throw new LDAPSDKUsageException(
280             ERR_DURATION_UNSUPPORTED_LOWER_BOUND_UNIT.get(lowerBoundUnitName));
281      }
282    }
283
284    if (upperBound == null)
285    {
286      maxValueNanos = Long.MAX_VALUE;
287      upperBoundStr = Long.MAX_VALUE + "ns";
288    }
289    else
290    {
291      if (upperBoundUnit == null)
292      {
293        throw new ArgumentException(ERR_DURATION_UPPER_REQUIRES_UNIT.get(
294             getIdentifierString()));
295      }
296
297      maxValueNanos = upperBoundUnit.toNanos(upperBound);
298      final String upperBoundUnitName = upperBoundUnit.name();
299      if (upperBoundUnitName.equals("NANOSECONDS"))
300      {
301        upperBoundStr = minValueNanos + "ns";
302      }
303      else if (upperBoundUnitName.equals("MICROSECONDS"))
304      {
305        upperBoundStr = upperBound + "us";
306      }
307      else if (upperBoundUnitName.equals("MILLISECONDS"))
308      {
309        upperBoundStr = upperBound + "ms";
310      }
311      else if (upperBoundUnitName.equals("SECONDS"))
312      {
313        upperBoundStr = upperBound + "s";
314      }
315      else if (upperBoundUnitName.equals("MINUTES"))
316      {
317        upperBoundStr = upperBound + "m";
318      }
319      else if (upperBoundUnitName.equals("HOURS"))
320      {
321        upperBoundStr = upperBound + "h";
322      }
323      else if (upperBoundUnitName.equals("DAYS"))
324      {
325        upperBoundStr = upperBound + "d";
326      }
327      else
328      {
329        throw new LDAPSDKUsageException(
330             ERR_DURATION_UNSUPPORTED_UPPER_BOUND_UNIT.get(upperBoundUnitName));
331      }
332    }
333
334    if (minValueNanos > maxValueNanos)
335    {
336      throw new ArgumentException(ERR_DURATION_LOWER_GT_UPPER.get(
337           getIdentifierString(), lowerBoundStr, upperBoundStr));
338    }
339
340    valueNanos = null;
341    validators = new ArrayList<ArgumentValueValidator>(5);
342  }
343
344
345
346  /**
347   * Creates a new duration argument that is a "clean" copy of the provided
348   * source argument.
349   *
350   * @param  source  The source argument to use for this argument.
351   */
352  private DurationArgument(final DurationArgument source)
353  {
354    super(source);
355
356    defaultValueNanos = source.defaultValueNanos;
357    maxValueNanos     = source.maxValueNanos;
358    minValueNanos     = source.minValueNanos;
359    lowerBoundStr     = source.lowerBoundStr;
360    upperBoundStr     = source.upperBoundStr;
361    validators        =
362         new ArrayList<ArgumentValueValidator>(source.validators);
363    valueNanos        = null;
364  }
365
366
367
368  /**
369   * Retrieves the lower bound for this argument using the specified time unit.
370   *
371   * @param  unit  The time unit in which the lower bound value may be
372   *               expressed.
373   *
374   * @return  The lower bound for this argument using the specified time unit.
375   */
376  public long getLowerBound(final TimeUnit unit)
377  {
378    return unit.convert(minValueNanos, TimeUnit.NANOSECONDS);
379  }
380
381
382
383  /**
384   * Retrieves the upper bound for this argument using the specified time unit.
385   *
386   * @param  unit  The time unit in which the upper bound value may be
387   *               expressed.
388   *
389   * @return  The upper bound for this argument using the specified time unit.
390   */
391  public long getUpperBound(final TimeUnit unit)
392  {
393    return unit.convert(maxValueNanos, TimeUnit.NANOSECONDS);
394  }
395
396
397
398  /**
399   * {@inheritDoc}
400   */
401  @Override()
402  public List<String> getValueStringRepresentations(final boolean useDefault)
403  {
404    final long v;
405    if (valueNanos != null)
406    {
407      v = valueNanos;
408    }
409    else if (useDefault && (defaultValueNanos != null))
410    {
411      v = defaultValueNanos;
412    }
413    else
414    {
415      return Collections.emptyList();
416    }
417
418    return Collections.unmodifiableList(Arrays.asList(nanosToDuration(v)));
419  }
420
421
422
423  /**
424   * {@inheritDoc}
425   */
426  @Override()
427  protected boolean hasDefaultValue()
428  {
429    return (defaultValueNanos != null);
430  }
431
432
433
434  /**
435   * Retrieves the default value for this argument using the specified time
436   * unit, if defined.
437   *
438   * @param  unit  The time unit in which the default value should be expressed.
439   *
440   * @return  The default value for this argument using the specified time unit,
441   *          or {@code null} if none is defined.
442   */
443  public Long getDefaultValue(final TimeUnit unit)
444  {
445    if (defaultValueNanos == null)
446    {
447      return null;
448    }
449
450    return unit.convert(defaultValueNanos, TimeUnit.NANOSECONDS);
451  }
452
453
454
455  /**
456   * Retrieves the value for this argument using the specified time unit, if one
457   * was provided.
458   *
459   * @param  unit  The time unit in which to express the value for this
460   *               argument.
461   *
462   * @return  The value for this argument using the specified time unit.  If no
463   *          value was provided but a default value was defined, then the
464   *          default value will be returned.  If no value was provided and no
465   *          default value was defined, then {@code null} will be returned.
466   */
467  public Long getValue(final TimeUnit unit)
468  {
469    if (valueNanos == null)
470    {
471      if (defaultValueNanos == null)
472      {
473        return null;
474      }
475
476      return unit.convert(defaultValueNanos, TimeUnit.NANOSECONDS);
477    }
478    else
479    {
480      return unit.convert(valueNanos, TimeUnit.NANOSECONDS);
481    }
482  }
483
484
485
486  /**
487   * Updates this argument to ensure that the provided validator will be invoked
488   * for any values provided to this argument.  This validator will be invoked
489   * after all other validation has been performed for this argument.
490   *
491   * @param  validator  The argument value validator to be invoked.  It must not
492   *                    be {@code null}.
493   */
494  public void addValueValidator(final ArgumentValueValidator validator)
495  {
496    validators.add(validator);
497  }
498
499
500
501  /**
502   * {@inheritDoc}
503   */
504  @Override()
505  protected void addValue(final String valueString)
506            throws ArgumentException
507  {
508    if (valueNanos != null)
509    {
510      throw new ArgumentException(
511           ERR_ARG_MAX_OCCURRENCES_EXCEEDED.get(getIdentifierString()));
512    }
513
514    final long proposedValueNanos;
515    try
516    {
517      proposedValueNanos = parseDuration(valueString, TimeUnit.NANOSECONDS);
518    }
519    catch (final ArgumentException ae)
520    {
521      Debug.debugException(ae);
522      throw new ArgumentException(
523           ERR_DURATION_MALFORMED_VALUE.get(valueString, getIdentifierString(),
524                ae.getMessage()),
525           ae);
526    }
527
528    if (proposedValueNanos < minValueNanos)
529    {
530      throw new ArgumentException(ERR_DURATION_BELOW_LOWER_BOUND.get(
531           getIdentifierString(), lowerBoundStr));
532    }
533    else if (proposedValueNanos > maxValueNanos)
534    {
535      throw new ArgumentException(ERR_DURATION_ABOVE_UPPER_BOUND.get(
536           getIdentifierString(), upperBoundStr));
537    }
538    else
539    {
540      for (final ArgumentValueValidator v : validators)
541      {
542        v.validateArgumentValue(this, valueString);
543      }
544
545      valueNanos = proposedValueNanos;
546    }
547  }
548
549
550
551  /**
552   * Parses the provided string representation of a duration to a corresponding
553   * numeric representation.
554   *
555   * @param  durationString  The string representation of the duration to be
556   *                         parsed.
557   * @param  timeUnit        The time unit to use for the return value.
558   *
559   * @return  The parsed duration as a count in the specified time unit.
560   *
561   * @throws  ArgumentException  If the provided string cannot be parsed as a
562   *                             valid duration.
563   */
564  public static long parseDuration(final String durationString,
565                                   final TimeUnit timeUnit)
566         throws ArgumentException
567  {
568    // The string must not be empty.
569    final String lowerStr = StaticUtils.toLowerCase(durationString);
570    if (lowerStr.length() == 0)
571    {
572      throw new ArgumentException(ERR_DURATION_EMPTY_VALUE.get());
573    }
574
575    // Find the position of the first non-digit character.
576    boolean digitFound    = false;
577    boolean nonDigitFound = false;
578    int     nonDigitPos   = -1;
579    for (int i=0; i < lowerStr.length(); i++)
580    {
581      final char c = lowerStr.charAt(i);
582      if (Character.isDigit(c))
583      {
584        digitFound = true;
585      }
586      else
587      {
588        nonDigitFound = true;
589        nonDigitPos   = i;
590        if (! digitFound)
591        {
592          throw new ArgumentException(ERR_DURATION_NO_DIGIT.get());
593        }
594        break;
595      }
596    }
597
598    if (! nonDigitFound)
599    {
600      throw new ArgumentException(ERR_DURATION_NO_UNIT.get());
601    }
602
603    // Separate the integer portion from the unit.
604    long integerPortion = Long.parseLong(lowerStr.substring(0, nonDigitPos));
605    final String unitStr = lowerStr.substring(nonDigitPos).trim();
606
607    // Parse the time unit.
608    final TimeUnit unitFromString;
609    if (unitStr.equals("ns") ||
610        unitStr.equals("nano") ||
611        unitStr.equals("nanos") ||
612        unitStr.equals("nanosecond") ||
613        unitStr.equals("nanoseconds"))
614    {
615      unitFromString = TimeUnit.NANOSECONDS;
616    }
617    else if (unitStr.equals("us") ||
618             unitStr.equals("micro") ||
619             unitStr.equals("micros") ||
620             unitStr.equals("microsecond") ||
621             unitStr.equals("microseconds"))
622    {
623      unitFromString = TimeUnit.MICROSECONDS;
624    }
625    else if (unitStr.equals("ms") ||
626             unitStr.equals("milli") ||
627             unitStr.equals("millis") ||
628             unitStr.equals("millisecond") ||
629             unitStr.equals("milliseconds"))
630    {
631      unitFromString = TimeUnit.MILLISECONDS;
632    }
633    else if (unitStr.equals("s") ||
634             unitStr.equals("sec") ||
635             unitStr.equals("secs") ||
636             unitStr.equals("second") ||
637             unitStr.equals("seconds"))
638    {
639      unitFromString = TimeUnit.SECONDS;
640    }
641    else if (unitStr.equals("m") ||
642             unitStr.equals("min") ||
643             unitStr.equals("mins") ||
644             unitStr.equals("minute") ||
645             unitStr.equals("minutes"))
646    {
647      integerPortion *= 60L;
648      unitFromString = TimeUnit.SECONDS;
649    }
650    else if (unitStr.equals("h") ||
651             unitStr.equals("hr") ||
652             unitStr.equals("hrs") ||
653             unitStr.equals("hour") ||
654             unitStr.equals("hours"))
655    {
656      integerPortion *= 3600L;
657      unitFromString = TimeUnit.SECONDS;
658    }
659    else if (unitStr.equals("d") ||
660             unitStr.equals("day") ||
661             unitStr.equals("days"))
662    {
663      integerPortion *= 86400L;
664      unitFromString = TimeUnit.SECONDS;
665    }
666    else
667    {
668      throw new ArgumentException(ERR_DURATION_UNRECOGNIZED_UNIT.get(unitStr));
669    }
670
671    return timeUnit.convert(integerPortion, unitFromString);
672  }
673
674
675
676  /**
677   * {@inheritDoc}
678   */
679  @Override()
680  public String getDataTypeName()
681  {
682    return INFO_DURATION_TYPE_NAME.get();
683  }
684
685
686
687  /**
688   * {@inheritDoc}
689   */
690  @Override()
691  public String getValueConstraints()
692  {
693    final StringBuilder buffer = new StringBuilder();
694    buffer.append(INFO_DURATION_CONSTRAINTS_FORMAT.get());
695
696    if (lowerBoundStr != null)
697    {
698      if (upperBoundStr == null)
699      {
700        buffer.append("  ");
701        buffer.append(INFO_DURATION_CONSTRAINTS_LOWER_BOUND.get(lowerBoundStr));
702      }
703      else
704      {
705        buffer.append("  ");
706        buffer.append(INFO_DURATION_CONSTRAINTS_LOWER_AND_UPPER_BOUND.get(
707             lowerBoundStr, upperBoundStr));
708      }
709    }
710    else
711    {
712      if (upperBoundStr != null)
713      {
714        buffer.append("  ");
715        buffer.append(INFO_DURATION_CONSTRAINTS_UPPER_BOUND.get(upperBoundStr));
716      }
717    }
718
719    return buffer.toString();
720  }
721
722
723
724  /**
725   * {@inheritDoc}
726   */
727  @Override()
728  protected void reset()
729  {
730    super.reset();
731    valueNanos = null;
732  }
733
734
735
736  /**
737   * {@inheritDoc}
738   */
739  @Override()
740  public DurationArgument getCleanCopy()
741  {
742    return new DurationArgument(this);
743  }
744
745
746
747  /**
748   * Converts the specified number of nanoseconds into a duration string using
749   * the largest possible whole unit (e.g., if the value represents a whole
750   * number of seconds, then the returned string will be expressed in seconds).
751   *
752   * @param  nanos  The number of nanoseconds to convert to a duration string.
753   *
754   * @return  The duration string for the specified number of nanoseconds.
755   */
756  public static String nanosToDuration(final long nanos)
757  {
758    if (nanos == 86400000000000L)
759    {
760      return "1 day";
761    }
762    else if ((nanos % 86400000000000L) == 0L)
763    {
764      return (nanos / 86400000000000L) + " days";
765    }
766    else if (nanos == 3600000000000L)
767    {
768      return "1 hour";
769    }
770    else if ((nanos % 3600000000000L) == 0L)
771    {
772      return (nanos / 3600000000000L) + " hours";
773    }
774    else if (nanos == 60000000000L)
775    {
776      return "1 minute";
777    }
778    else if ((nanos % 60000000000L) == 0L)
779    {
780      return (nanos / 60000000000L) + " minutes";
781    }
782    else if (nanos == 1000000000L)
783    {
784      return "1 second";
785    }
786    else if ((nanos % 1000000000L) == 0L)
787    {
788      return (nanos / 1000000000L) + " seconds";
789    }
790    else if (nanos == 1000000L)
791    {
792      return "1 millisecond";
793    }
794    else if ((nanos % 1000000L) == 0L)
795    {
796     return (nanos / 1000000L) + " milliseconds";
797    }
798    else if (nanos == 1000L)
799    {
800      return "1 microsecond";
801    }
802    else if ((nanos % 1000L) == 0L)
803    {
804     return (nanos / 1000L) + " microseconds";
805    }
806    else if (nanos == 1L)
807    {
808      return "1 nanosecond";
809    }
810    else
811    {
812      return nanos + " nanoseconds";
813    }
814  }
815
816
817
818  /**
819   * {@inheritDoc}
820   */
821  @Override()
822  protected void addToCommandLine(final List<String> argStrings)
823  {
824    if (valueNanos != null)
825    {
826      argStrings.add(getIdentifierString());
827      if (isSensitive())
828      {
829        argStrings.add("***REDACTED***");
830      }
831      else
832      {
833        argStrings.add(nanosToDuration(valueNanos));
834      }
835    }
836  }
837
838
839
840  /**
841   * {@inheritDoc}
842   */
843  @Override()
844  public void toString(final StringBuilder buffer)
845  {
846    buffer.append("DurationArgument(");
847    appendBasicToStringInfo(buffer);
848
849    if (lowerBoundStr != null)
850    {
851      buffer.append(", lowerBound='");
852      buffer.append(lowerBoundStr);
853      buffer.append('\'');
854    }
855
856    if (upperBoundStr != null)
857    {
858      buffer.append(", upperBound='");
859      buffer.append(upperBoundStr);
860      buffer.append('\'');
861    }
862
863    if (defaultValueNanos != null)
864    {
865      buffer.append(", defaultValueNanos=");
866      buffer.append(defaultValueNanos);
867    }
868
869    buffer.append(')');
870  }
871}