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