001/* 002 * Copyright 2010-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2010-2014 UnboundID Corp. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.util.args; 022 023 024 025import java.util.concurrent.TimeUnit; 026 027import com.unboundid.util.Debug; 028import com.unboundid.util.LDAPSDKUsageException; 029import com.unboundid.util.Mutable; 030import com.unboundid.util.StaticUtils; 031import com.unboundid.util.ThreadSafety; 032import com.unboundid.util.ThreadSafetyLevel; 033 034import static com.unboundid.util.args.ArgsMessages.*; 035 036 037 038/** 039 * Creates a new argument that is intended to represent a duration. Duration 040 * values contain an integer portion and a unit portion which represents the 041 * time unit. The unit must be one of the following: 042 * <UL> 043 * <LI>Nanoseconds -- ns, nano, nanos, nanosecond, nanoseconds</LI> 044 * <LI>Microseconds -- us, micro, micros, microsecond, microseconds</LI> 045 * <LI>Milliseconds -- ms, milli, millis, millisecond, milliseconds</LI> 046 * <LI>Seconds -- s, sec, secs, second, seconds</LI> 047 * <LI>Minutes -- m, min, mins, minute, minutes</LI> 048 * <LI>Hours -- h, hr, hrs, hour, hours</LI> 049 * <LI>Days -- d, day, days</LI> 050 * </UL> 051 * 052 * There may be zero or more spaces between the integer portion and the unit 053 * portion. However, if spaces are used in the command-line argument, then the 054 * value must be enquoted or the spaces must be escaped so that the duration 055 * is not seen as multiple arguments. 056 */ 057@Mutable() 058@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 059public final class DurationArgument 060 extends Argument 061{ 062 /** 063 * The serial version UID for this serializable class. 064 */ 065 private static final long serialVersionUID = -8824262632728709264L; 066 067 068 069 // The default value for this argument, in nanoseconds. 070 private final Long defaultValueNanos; 071 072 // The maximum allowed value for this argument, in nanoseconds. 073 private final long maxValueNanos; 074 075 // The minimum allowed value for this argument, in nanoseconds. 076 private final long minValueNanos; 077 078 // The provided value for this argument, in nanoseconds. 079 private Long valueNanos; 080 081 // The string representation of the lower bound, using the user-supplied 082 // value. 083 private final String lowerBoundStr; 084 085 // The string representation of the upper bound, using the user-supplied 086 // value. 087 private final String upperBoundStr; 088 089 090 091 /** 092 * Creates a new duration argument with no default value and no bounds on the 093 * set of allowed values. 094 * 095 * @param shortIdentifier The short identifier for this argument. It may 096 * not be {@code null} if the long identifier is 097 * {@code null}. 098 * @param longIdentifier The long identifier for this argument. It may 099 * not be {@code null} if the short identifier is 100 * {@code null}. 101 * @param isRequired Indicates whether this argument is required to 102 * be provided. 103 * @param valuePlaceholder A placeholder to display in usage information to 104 * indicate that a value must be provided. It must 105 * not be {@code null}. 106 * @param description A human-readable description for this argument. 107 * It must not be {@code null}. 108 * 109 * @throws ArgumentException If there is a problem with the definition of 110 * this argument. 111 */ 112 public DurationArgument(final Character shortIdentifier, 113 final String longIdentifier, final boolean isRequired, 114 final String valuePlaceholder, 115 final String description) 116 throws ArgumentException 117 { 118 this(shortIdentifier, longIdentifier, isRequired, valuePlaceholder, 119 description, null, null, null, null, null, null); 120 } 121 122 123 124 /** 125 * Creates a new duration argument with the provided information. 126 * 127 * @param shortIdentifier The short identifier for this argument. It may 128 * not be {@code null} if the long identifier is 129 * {@code null}. 130 * @param longIdentifier The long identifier for this argument. It may 131 * not be {@code null} if the short identifier is 132 * {@code null}. 133 * @param isRequired Indicates whether this argument is required to 134 * be provided. 135 * @param valuePlaceholder A placeholder to display in usage information to 136 * indicate that a value must be provided. It must 137 * not be {@code null}. 138 * @param description A human-readable description for this argument. 139 * It must not be {@code null}. 140 * @param defaultValue The default value that will be used for this 141 * argument if none is provided. It may be 142 * {@code null} if there should not be a default 143 * value. 144 * @param defaultValueUnit The time unit for the default value. It may be 145 * {@code null} only if the default value is also 146 * {@code null}. 147 * @param lowerBound The value for the minimum duration that may be 148 * represented using this argument, in conjunction 149 * with the {@code lowerBoundUnit} parameter to 150 * specify the unit for this value. If this is 151 * {@code null}, then a lower bound of 0 nanoseconds 152 * will be used. 153 * @param lowerBoundUnit The time unit for the lower bound value. It may 154 * be {@code null} only if the lower bound is also 155 * {@code null}. 156 * @param upperBound The value for the maximum duration that may be 157 * represented using this argument, in conjunction 158 * with the {@code upperBoundUnit} parameter to 159 * specify the unit for this value. If this is 160 * {@code null}, then an upper bound of 161 * {@code Long.MAX_VALUE} nanoseconds will be used. 162 * @param upperBoundUnit The time unit for the upper bound value. It may 163 * be {@code null} only if the upper bound is also 164 * {@code null}. 165 * 166 * @throws ArgumentException If there is a problem with the definition of 167 * this argument. 168 */ 169 public DurationArgument(final Character shortIdentifier, 170 final String longIdentifier, final boolean isRequired, 171 final String valuePlaceholder, 172 final String description, final Long defaultValue, 173 final TimeUnit defaultValueUnit, 174 final Long lowerBound, final TimeUnit lowerBoundUnit, 175 final Long upperBound, final TimeUnit upperBoundUnit) 176 throws ArgumentException 177 { 178 super(shortIdentifier, longIdentifier, isRequired, 1, valuePlaceholder, 179 description); 180 181 if (valuePlaceholder == null) 182 { 183 throw new ArgumentException( 184 ERR_ARG_MUST_TAKE_VALUE.get(getIdentifierString())); 185 } 186 187 if (defaultValue == null) 188 { 189 defaultValueNanos = null; 190 } 191 else 192 { 193 if (defaultValueUnit == null) 194 { 195 throw new ArgumentException(ERR_DURATION_DEFAULT_REQUIRES_UNIT.get( 196 getIdentifierString())); 197 } 198 199 defaultValueNanos = defaultValueUnit.toNanos(defaultValue); 200 } 201 202 if (lowerBound == null) 203 { 204 minValueNanos = 0L; 205 lowerBoundStr = "0ns"; 206 } 207 else 208 { 209 if (lowerBoundUnit == null) 210 { 211 throw new ArgumentException(ERR_DURATION_LOWER_REQUIRES_UNIT.get( 212 getIdentifierString())); 213 } 214 215 minValueNanos = lowerBoundUnit.toNanos(lowerBound); 216 final String lowerBoundUnitName = lowerBoundUnit.name(); 217 if (lowerBoundUnitName.equals("NANOSECONDS")) 218 { 219 lowerBoundStr = minValueNanos + "ns"; 220 } 221 else if (lowerBoundUnitName.equals("MICROSECONDS")) 222 { 223 lowerBoundStr = lowerBound + "us"; 224 } 225 else if (lowerBoundUnitName.equals("MILLISECONDS")) 226 { 227 lowerBoundStr = lowerBound + "ms"; 228 } 229 else if (lowerBoundUnitName.equals("SECONDS")) 230 { 231 lowerBoundStr = lowerBound + "s"; 232 } 233 else if (lowerBoundUnitName.equals("MINUTES")) 234 { 235 lowerBoundStr = lowerBound + "m"; 236 } 237 else if (lowerBoundUnitName.equals("HOURS")) 238 { 239 lowerBoundStr = lowerBound + "h"; 240 } 241 else if (lowerBoundUnitName.equals("DAYS")) 242 { 243 lowerBoundStr = lowerBound + "d"; 244 } 245 else 246 { 247 throw new LDAPSDKUsageException( 248 ERR_DURATION_UNSUPPORTED_LOWER_BOUND_UNIT.get(lowerBoundUnitName)); 249 } 250 } 251 252 if (upperBound == null) 253 { 254 maxValueNanos = Long.MAX_VALUE; 255 upperBoundStr = Long.MAX_VALUE + "ns"; 256 } 257 else 258 { 259 if (upperBoundUnit == null) 260 { 261 throw new ArgumentException(ERR_DURATION_UPPER_REQUIRES_UNIT.get( 262 getIdentifierString())); 263 } 264 265 maxValueNanos = upperBoundUnit.toNanos(upperBound); 266 final String upperBoundUnitName = upperBoundUnit.name(); 267 if (upperBoundUnitName.equals("NANOSECONDS")) 268 { 269 upperBoundStr = minValueNanos + "ns"; 270 } 271 else if (upperBoundUnitName.equals("MICROSECONDS")) 272 { 273 upperBoundStr = upperBound + "us"; 274 } 275 else if (upperBoundUnitName.equals("MILLISECONDS")) 276 { 277 upperBoundStr = upperBound + "ms"; 278 } 279 else if (upperBoundUnitName.equals("SECONDS")) 280 { 281 upperBoundStr = upperBound + "s"; 282 } 283 else if (upperBoundUnitName.equals("MINUTES")) 284 { 285 upperBoundStr = upperBound + "m"; 286 } 287 else if (upperBoundUnitName.equals("HOURS")) 288 { 289 upperBoundStr = upperBound + "h"; 290 } 291 else if (upperBoundUnitName.equals("DAYS")) 292 { 293 upperBoundStr = upperBound + "d"; 294 } 295 else 296 { 297 throw new LDAPSDKUsageException( 298 ERR_DURATION_UNSUPPORTED_UPPER_BOUND_UNIT.get(upperBoundUnitName)); 299 } 300 } 301 302 if (minValueNanos > maxValueNanos) 303 { 304 throw new ArgumentException(ERR_DURATION_LOWER_GT_UPPER.get( 305 getIdentifierString(), lowerBoundStr, upperBoundStr)); 306 } 307 308 valueNanos = null; 309 } 310 311 312 313 /** 314 * Creates a new duration argument that is a "clean" copy of the provided 315 * source argument. 316 * 317 * @param source The source argument to use for this argument. 318 */ 319 private DurationArgument(final DurationArgument source) 320 { 321 super(source); 322 323 defaultValueNanos = source.defaultValueNanos; 324 maxValueNanos = source.maxValueNanos; 325 minValueNanos = source.minValueNanos; 326 lowerBoundStr = source.lowerBoundStr; 327 upperBoundStr = source.upperBoundStr; 328 valueNanos = null; 329 } 330 331 332 333 /** 334 * Retrieves the lower bound for this argument using the specified time unit. 335 * 336 * @param unit The time unit in which the lower bound value may be 337 * expressed. 338 * 339 * @return The lower bound for this argument using the specified time unit. 340 */ 341 public long getLowerBound(final TimeUnit unit) 342 { 343 return unit.convert(minValueNanos, TimeUnit.NANOSECONDS); 344 } 345 346 347 348 /** 349 * Retrieves the upper bound for this argument using the specified time unit. 350 * 351 * @param unit The time unit in which the upper bound value may be 352 * expressed. 353 * 354 * @return The upper bound for this argument using the specified time unit. 355 */ 356 public long getUpperBound(final TimeUnit unit) 357 { 358 return unit.convert(maxValueNanos, TimeUnit.NANOSECONDS); 359 } 360 361 362 363 /** 364 * {@inheritDoc} 365 */ 366 @Override() 367 protected boolean hasDefaultValue() 368 { 369 return (defaultValueNanos != null); 370 } 371 372 373 374 /** 375 * Retrieves the default value for this argument using the specified time 376 * unit, if defined. 377 * 378 * @param unit The time unit in which the default value should be expressed. 379 * 380 * @return The default value for this argument using the specified time unit, 381 * or {@code null} if none is defined. 382 */ 383 public Long getDefaultValue(final TimeUnit unit) 384 { 385 if (defaultValueNanos == null) 386 { 387 return null; 388 } 389 390 return unit.convert(defaultValueNanos, TimeUnit.NANOSECONDS); 391 } 392 393 394 395 /** 396 * Retrieves the value for this argument using the specified time unit, if one 397 * was provided. 398 * 399 * @param unit The time unit in which to express the value for this 400 * argument. 401 * 402 * @return The value for this argument using the specified time unit. If no 403 * value was provided but a default value was defined, then the 404 * default value will be returned. If no value was provided and no 405 * default value was defined, then {@code null} will be returned. 406 */ 407 public Long getValue(final TimeUnit unit) 408 { 409 if (valueNanos == null) 410 { 411 if (defaultValueNanos == null) 412 { 413 return null; 414 } 415 416 return unit.convert(defaultValueNanos, TimeUnit.NANOSECONDS); 417 } 418 else 419 { 420 return unit.convert(valueNanos, TimeUnit.NANOSECONDS); 421 } 422 } 423 424 425 426 /** 427 * {@inheritDoc} 428 */ 429 @Override() 430 protected void addValue(final String valueString) 431 throws ArgumentException 432 { 433 if (valueNanos != null) 434 { 435 throw new ArgumentException( 436 ERR_ARG_MAX_OCCURRENCES_EXCEEDED.get(getIdentifierString())); 437 } 438 439 final long proposedValueNanos; 440 try 441 { 442 proposedValueNanos = parseDuration(valueString, TimeUnit.NANOSECONDS); 443 } 444 catch (final ArgumentException ae) 445 { 446 Debug.debugException(ae); 447 throw new ArgumentException( 448 ERR_DURATION_MALFORMED_VALUE.get(valueString, getIdentifierString(), 449 ae.getMessage()), 450 ae); 451 } 452 453 if (proposedValueNanos < minValueNanos) 454 { 455 throw new ArgumentException(ERR_DURATION_BELOW_LOWER_BOUND.get( 456 getIdentifierString(), lowerBoundStr)); 457 } 458 else if (proposedValueNanos > maxValueNanos) 459 { 460 throw new ArgumentException(ERR_DURATION_ABOVE_UPPER_BOUND.get( 461 getIdentifierString(), upperBoundStr)); 462 } 463 else 464 { 465 valueNanos = proposedValueNanos; 466 } 467 } 468 469 470 471 /** 472 * Parses the provided string representation of a duration to a corresponding 473 * numeric representation. 474 * 475 * @param durationString The string representation of the duration to be 476 * parsed. 477 * @param timeUnit The time unit to use for the return value. 478 * 479 * @return The parsed duration as a count in the specified time unit. 480 * 481 * @throws ArgumentException If the provided string cannot be parsed as a 482 * valid duration. 483 */ 484 public static long parseDuration(final String durationString, 485 final TimeUnit timeUnit) 486 throws ArgumentException 487 { 488 // The string must not be empty. 489 final String lowerStr = StaticUtils.toLowerCase(durationString); 490 if (lowerStr.length() == 0) 491 { 492 throw new ArgumentException(ERR_DURATION_EMPTY_VALUE.get()); 493 } 494 495 // Find the position of the first non-digit character. 496 boolean digitFound = false; 497 boolean nonDigitFound = false; 498 int nonDigitPos = -1; 499 for (int i=0; i < lowerStr.length(); i++) 500 { 501 final char c = lowerStr.charAt(i); 502 if (Character.isDigit(c)) 503 { 504 digitFound = true; 505 } 506 else 507 { 508 nonDigitFound = true; 509 nonDigitPos = i; 510 if (! digitFound) 511 { 512 throw new ArgumentException(ERR_DURATION_NO_DIGIT.get()); 513 } 514 break; 515 } 516 } 517 518 if (! nonDigitFound) 519 { 520 throw new ArgumentException(ERR_DURATION_NO_UNIT.get()); 521 } 522 523 // Separate the integer portion from the unit. 524 long integerPortion = Long.parseLong(lowerStr.substring(0, nonDigitPos)); 525 final String unitStr = lowerStr.substring(nonDigitPos).trim(); 526 527 // Parse the time unit. 528 final TimeUnit unitFromString; 529 if (unitStr.equals("ns") || 530 unitStr.equals("nano") || 531 unitStr.equals("nanos") || 532 unitStr.equals("nanosecond") || 533 unitStr.equals("nanoseconds")) 534 { 535 unitFromString = TimeUnit.NANOSECONDS; 536 } 537 else if (unitStr.equals("us") || 538 unitStr.equals("micro") || 539 unitStr.equals("micros") || 540 unitStr.equals("microsecond") || 541 unitStr.equals("microseconds")) 542 { 543 unitFromString = TimeUnit.MICROSECONDS; 544 } 545 else if (unitStr.equals("ms") || 546 unitStr.equals("milli") || 547 unitStr.equals("millis") || 548 unitStr.equals("millisecond") || 549 unitStr.equals("milliseconds")) 550 { 551 unitFromString = TimeUnit.MILLISECONDS; 552 } 553 else if (unitStr.equals("s") || 554 unitStr.equals("sec") || 555 unitStr.equals("secs") || 556 unitStr.equals("second") || 557 unitStr.equals("seconds")) 558 { 559 unitFromString = TimeUnit.SECONDS; 560 } 561 else if (unitStr.equals("m") || 562 unitStr.equals("min") || 563 unitStr.equals("mins") || 564 unitStr.equals("minute") || 565 unitStr.equals("minutes")) 566 { 567 integerPortion *= 60L; 568 unitFromString = TimeUnit.SECONDS; 569 } 570 else if (unitStr.equals("h") || 571 unitStr.equals("hr") || 572 unitStr.equals("hrs") || 573 unitStr.equals("hour") || 574 unitStr.equals("hours")) 575 { 576 integerPortion *= 3600L; 577 unitFromString = TimeUnit.SECONDS; 578 } 579 else if (unitStr.equals("d") || 580 unitStr.equals("day") || 581 unitStr.equals("days")) 582 { 583 integerPortion *= 86400L; 584 unitFromString = TimeUnit.SECONDS; 585 } 586 else 587 { 588 throw new ArgumentException(ERR_DURATION_UNRECOGNIZED_UNIT.get(unitStr)); 589 } 590 591 return timeUnit.convert(integerPortion, unitFromString); 592 } 593 594 595 596 /** 597 * {@inheritDoc} 598 */ 599 @Override() 600 public String getDataTypeName() 601 { 602 return INFO_DURATION_TYPE_NAME.get(); 603 } 604 605 606 607 /** 608 * {@inheritDoc} 609 */ 610 @Override() 611 public String getValueConstraints() 612 { 613 final StringBuilder buffer = new StringBuilder(); 614 buffer.append(INFO_DURATION_CONSTRAINTS_FORMAT.get()); 615 616 if (lowerBoundStr != null) 617 { 618 if (upperBoundStr == null) 619 { 620 buffer.append(" "); 621 buffer.append(INFO_DURATION_CONSTRAINTS_LOWER_BOUND.get(lowerBoundStr)); 622 } 623 else 624 { 625 buffer.append(" "); 626 buffer.append(INFO_DURATION_CONSTRAINTS_LOWER_AND_UPPER_BOUND.get( 627 lowerBoundStr, upperBoundStr)); 628 } 629 } 630 else 631 { 632 if (upperBoundStr != null) 633 { 634 buffer.append(" "); 635 buffer.append(INFO_DURATION_CONSTRAINTS_UPPER_BOUND.get(upperBoundStr)); 636 } 637 } 638 639 return buffer.toString(); 640 } 641 642 643 644 /** 645 * {@inheritDoc} 646 */ 647 @Override() 648 public DurationArgument getCleanCopy() 649 { 650 return new DurationArgument(this); 651 } 652 653 654 655 /** 656 * {@inheritDoc} 657 */ 658 @Override() 659 public void toString(final StringBuilder buffer) 660 { 661 buffer.append("DurationArgument("); 662 appendBasicToStringInfo(buffer); 663 664 if (lowerBoundStr != null) 665 { 666 buffer.append(", lowerBound='"); 667 buffer.append(lowerBoundStr); 668 buffer.append('\''); 669 } 670 671 if (upperBoundStr != null) 672 { 673 buffer.append(", upperBound='"); 674 buffer.append(upperBoundStr); 675 buffer.append('\''); 676 } 677 678 if (defaultValueNanos != null) 679 { 680 buffer.append(", defaultValueNanos="); 681 buffer.append(defaultValueNanos); 682 } 683 684 buffer.append(')'); 685 } 686}