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