001    /*
002     * Copyright 2010-2016 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2010-2016 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     */
021    package com.unboundid.util.args;
022    
023    
024    
025    import java.util.ArrayList;
026    import java.util.Arrays;
027    import java.util.Collections;
028    import java.util.List;
029    import java.util.concurrent.TimeUnit;
030    
031    import com.unboundid.util.Debug;
032    import com.unboundid.util.LDAPSDKUsageException;
033    import com.unboundid.util.Mutable;
034    import com.unboundid.util.StaticUtils;
035    import com.unboundid.util.ThreadSafety;
036    import com.unboundid.util.ThreadSafetyLevel;
037    
038    import 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)
063    public 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    }