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}