001 /*
002 * Copyright 2010-2013 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2010-2013 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.concurrent.TimeUnit;
026
027 import com.unboundid.util.Debug;
028 import com.unboundid.util.LDAPSDKUsageException;
029 import com.unboundid.util.Mutable;
030 import com.unboundid.util.StaticUtils;
031 import com.unboundid.util.ThreadSafety;
032 import com.unboundid.util.ThreadSafetyLevel;
033
034 import 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)
059 public 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 }