View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.core5.util;
29  
30  import java.text.ParseException;
31  import java.time.Duration;
32  import java.time.temporal.ChronoUnit;
33  import java.util.Locale;
34  import java.util.Objects;
35  import java.util.concurrent.TimeUnit;
36  
37  import org.apache.hc.core5.annotation.Contract;
38  import org.apache.hc.core5.annotation.ThreadingBehavior;
39  
40  /**
41   * Represents a time value as a {@code long} time and a {@link TimeUnit}.
42   *
43   * @since 5.0
44   */
45  @Contract(threading = ThreadingBehavior.IMMUTABLE)
46  public class TimeValue implements Comparable<TimeValue> {
47  
48      static final int INT_UNDEFINED = -1;
49  
50      /**
51       * A constant holding the maximum value a {@code TimeValue} can have: {@code Long.MAX_VALUE} days.
52       */
53      public static final TimeValue MAX_VALUE = ofDays(Long.MAX_VALUE);
54  
55      /**
56       * A negative one millisecond {@link TimeValue}.
57       */
58      public static final TimeValue NEG_ONE_MILLISECOND = TimeValue.of(INT_UNDEFINED, TimeUnit.MILLISECONDS);
59  
60      /**
61       * A negative one second {@link TimeValue}.
62       */
63      public static final TimeValue NEG_ONE_SECOND = TimeValue.of(INT_UNDEFINED, TimeUnit.SECONDS);
64  
65      /**
66       * A zero milliseconds {@link TimeValue}.
67       */
68      public static final TimeValue ZERO_MILLISECONDS = TimeValue.of(0, TimeUnit.MILLISECONDS);
69  
70      /**
71       * Returns the given {@code long} value as an {@code int} where long values out of int range are returned as
72       * {@link Integer#MIN_VALUE} and {@link Integer#MAX_VALUE}.
73       *
74       * <p>
75       * For example: {@code TimeValue.asBoundInt(Long.MAX_VALUE)} returns {@code Integer.MAX_VALUE}.
76       * </p>
77       *
78       * @param value a long value to convert
79       * @return an int value bound within {@link Integer#MIN_VALUE} and {@link Integer#MAX_VALUE}.
80       */
81      public static int asBoundInt(final long value) {
82          if (value > Integer.MAX_VALUE) {
83              return Integer.MAX_VALUE;
84          } else if (value < Integer.MIN_VALUE) {
85              return Integer.MIN_VALUE;
86          }
87          return (int) value;
88      }
89  
90      /**
91       * Returns the given {@code timeValue} if it is not {@code null}, if {@code null} then returns the given
92       * {@code defaultValue}.
93       *
94       * @param timeValue may be {@code null}
95       * @param defaultValue may be {@code null}
96       * @return {@code timeValue} or {@code defaultValue}
97       */
98      public static <T extends TimeValue> T defaultsTo(final T timeValue, final T defaultValue) {
99          return timeValue != null ? timeValue : defaultValue;
100     }
101 
102     /**
103      * Returns the given {@code timeValue} if it is not {@code null}, if {@code null} then returns
104      * {@link #NEG_ONE_SECOND}.
105      *
106      * @param timeValue may be {@code null}
107      * @return {@code timeValue} or {@link #NEG_ONE_SECOND}
108      */
109     public static TimeValue defaultsToNegativeOneMillisecond(final TimeValue timeValue) {
110         return defaultsTo(timeValue, NEG_ONE_MILLISECOND);
111     }
112 
113     /**
114      * Returns the given {@code timeValue} if it is not {@code null}, if {@code null} then returns
115      * {@link #NEG_ONE_SECOND}.
116      *
117      * @param timeValue may be {@code null}
118      * @return {@code timeValue} or {@link #NEG_ONE_SECOND}
119      */
120     public static TimeValue defaultsToNegativeOneSecond(final TimeValue timeValue) {
121         return defaultsTo(timeValue, NEG_ONE_SECOND);
122     }
123 
124     /**
125      * Returns the given {@code timeValue} if it is not {@code null}, if {@code null} then returns
126      * {@link #ZERO_MILLISECONDS}.
127      *
128      * @param timeValue may be {@code null}
129      * @return {@code timeValue} or {@link #ZERO_MILLISECONDS}
130      */
131     public static TimeValue defaultsToZeroMilliseconds(final TimeValue timeValue) {
132         return defaultsTo(timeValue, ZERO_MILLISECONDS);
133     }
134 
135     public static boolean isNonNegative(final TimeValue timeValue) {
136         return timeValue != null && timeValue.getDuration() >= 0;
137     }
138 
139     public static boolean isPositive(final TimeValue timeValue) {
140         return timeValue != null && timeValue.getDuration() > 0;
141     }
142 
143     /**
144      * Creates a TimeValue.
145      *
146      * @param duration the time duration in the given {@code timeUnit}.
147      * @param timeUnit the time unit for the given duration.
148      * @return a Timeout.
149      */
150     public static TimeValue of(final long duration, final TimeUnit timeUnit) {
151         return new TimeValue(duration, timeUnit);
152     }
153 
154     /**
155      * Creates a TimeValue from a Duration.
156      *
157      * @param duration the time duration.
158      * @return a Timeout
159      * @since 5.2
160      */
161     public static TimeValue of(final Duration duration) {
162         final long seconds = duration.getSeconds();
163         final long nanoOfSecond = duration.getNano();
164         if (seconds == 0) {
165             // no conversion
166             return of(nanoOfSecond, TimeUnit.NANOSECONDS);
167         } else if (nanoOfSecond == 0) {
168             // no conversion
169             return of(seconds, TimeUnit.SECONDS);
170         }
171         // conversion attempts
172         try {
173             return of(duration.toNanos(), TimeUnit.NANOSECONDS);
174         } catch (final ArithmeticException e) {
175             try {
176                 return of(duration.toMillis(), TimeUnit.MILLISECONDS);
177             } catch (final ArithmeticException e1) {
178                 // backstop
179                 return of(seconds, TimeUnit.SECONDS);
180             }
181         }
182     }
183 
184     public static TimeValue ofDays(final long days) {
185         return of(days, TimeUnit.DAYS);
186     }
187 
188     public static TimeValue ofHours(final long hours) {
189         return of(hours, TimeUnit.HOURS);
190     }
191 
192     public static TimeValue ofMicroseconds(final long microseconds) {
193         return of(microseconds, TimeUnit.MICROSECONDS);
194     }
195 
196     public static TimeValue ofMilliseconds(final long millis) {
197         return of(millis, TimeUnit.MILLISECONDS);
198     }
199 
200     public static TimeValue ofMinutes(final long minutes) {
201         return of(minutes, TimeUnit.MINUTES);
202     }
203 
204     public static TimeValue ofNanoseconds(final long nanoseconds) {
205         return of(nanoseconds, TimeUnit.NANOSECONDS);
206     }
207 
208     public static TimeValue ofSeconds(final long seconds) {
209         return of(seconds, TimeUnit.SECONDS);
210     }
211 
212     /**
213      * Converts a {@link TimeUnit} to the equivalent {@link ChronoUnit}.
214      *
215      * @return the converted equivalent ChronoUnit
216      */
217     static ChronoUnit toChronoUnit(final TimeUnit timeUnit) {
218         switch (Objects.requireNonNull(timeUnit)) {
219         case NANOSECONDS:
220             return ChronoUnit.NANOS;
221         case MICROSECONDS:
222             return ChronoUnit.MICROS;
223         case MILLISECONDS:
224             return ChronoUnit.MILLIS;
225         case SECONDS:
226             return ChronoUnit.SECONDS;
227         case MINUTES:
228             return ChronoUnit.MINUTES;
229         case HOURS:
230             return ChronoUnit.HOURS;
231         case DAYS:
232             return ChronoUnit.DAYS;
233         default:
234             throw new IllegalArgumentException(timeUnit.toString());
235         }
236     }
237 
238     /**
239      * Parses a TimeValue in the format {@code <Long><SPACE><TimeUnit>}, for example {@code "1200 MILLISECONDS"}.
240      * <p>
241      * Parses:
242      * </p>
243      * <ul>
244      * <li>{@code "1200 MILLISECONDS"}.</li>
245      * <li>{@code " 1200 MILLISECONDS "}, spaces are ignored.</li>
246      * <li>{@code "1 MINUTE"}, singular units.</li>
247      * <li></li>
248      * </ul>
249      *
250      *
251      * @param value the TimeValue to parse
252      * @return a new TimeValue
253      * @throws ParseException if the number cannot be parsed
254      */
255     public static TimeValue parse(final String value) throws ParseException {
256         final String split[] = value.trim().split("\\s+");
257         if (split.length < 2) {
258             throw new IllegalArgumentException(
259                     String.format("Expected format for <Long><SPACE><java.util.concurrent.TimeUnit>: %s", value));
260         }
261         final String clean0 = split[0].trim();
262         final String clean1 = split[1].trim().toUpperCase(Locale.ROOT);
263         final String timeUnitStr = clean1.endsWith("S") ? clean1 : clean1 + "S";
264         return TimeValue.of(Long.parseLong(clean0), TimeUnit.valueOf(timeUnitStr));
265     }
266 
267     private final long duration;
268 
269     private final TimeUnit timeUnit;
270 
271     TimeValue(final long duration, final TimeUnit timeUnit) {
272         super();
273         this.duration = duration;
274         this.timeUnit = Args.notNull(timeUnit, "timeUnit");
275     }
276 
277     public long convert(final TimeUnit targetTimeUnit) {
278         Args.notNull(targetTimeUnit, "timeUnit");
279         return targetTimeUnit.convert(duration, timeUnit);
280     }
281 
282     @Override
283     public boolean equals(final Object obj) {
284         if (this == obj) {
285             return true;
286         }
287         if (obj instanceof TimeValue) {
288             final TimeValue that = (TimeValue) obj;
289             final long thisDuration = this.convert(TimeUnit.NANOSECONDS);
290             final long thatDuration = that.convert(TimeUnit.NANOSECONDS);
291             return thisDuration == thatDuration;
292         }
293         return false;
294     }
295 
296     /**
297      * Returns a TimeValue whose value is {@code (this / divisor)}.
298      *
299      * @param divisor
300      *            value by which this TimeValue is to be divided.
301      * @return {@code this / divisor}
302      * @throws ArithmeticException
303      *             if {@code divisor} is zero.
304      */
305     public TimeValue divide(final long divisor) {
306         final long newDuration = duration / divisor;
307         return of(newDuration, timeUnit);
308     }
309 
310     /**
311      * Returns a TimeValue whose value is {@code (this / divisor)}.
312      *
313      * @param divisor
314      *            value by which this TimeValue is to be divided.
315      * @param targetTimeUnit
316      *            the target TimeUnit
317      * @return {@code this / divisor}
318      * @throws ArithmeticException
319      *             if {@code divisor} is zero.
320      */
321     public TimeValue divide(final long divisor, final TimeUnit targetTimeUnit) {
322         return of(convert(targetTimeUnit) / divisor, targetTimeUnit);
323     }
324 
325     public long getDuration() {
326         return duration;
327     }
328 
329     public TimeUnit getTimeUnit() {
330         return timeUnit;
331     }
332 
333     @Override
334     public int hashCode() {
335         int hash = LangUtils.HASH_SEED;
336         hash = LangUtils.hashCode(hash, this.convert(TimeUnit.NANOSECONDS));
337         return hash;
338     }
339 
340     public TimeValue min(final TimeValue other) {
341         return this.compareTo(other) > 0 ? other : this;
342     }
343 
344     private TimeUnit min(final TimeUnit other) {
345         return scale() > scale(other) ? other : getTimeUnit();
346     }
347 
348     private int scale() {
349         return scale(timeUnit);
350     }
351 
352     /**
353      * Returns a made up scale for TimeUnits.
354      *
355      * @param tUnit
356      *            a TimeUnit
357      * @return a number from 1 to 7, where 1 is NANOSECONDS and 7 DAYS.
358      */
359     private int scale(final TimeUnit tUnit) {
360         switch (tUnit) {
361         case NANOSECONDS:
362             return 1;
363         case MICROSECONDS:
364             return 2;
365         case MILLISECONDS:
366             return 3;
367         case SECONDS:
368             return 4;
369         case MINUTES:
370             return 5;
371         case HOURS:
372             return 6;
373         case DAYS:
374             return 7;
375         default:
376             // Should never happens unless Java adds to the enum.
377             throw new IllegalStateException();
378         }
379     }
380 
381     public void sleep() throws InterruptedException {
382         timeUnit.sleep(duration);
383     }
384 
385     public void timedJoin(final Thread thread) throws InterruptedException {
386         timeUnit.timedJoin(thread, duration);
387     }
388 
389     public void timedWait(final Object obj) throws InterruptedException {
390         timeUnit.timedWait(obj, duration);
391     }
392 
393     public long toDays() {
394         return timeUnit.toDays(duration);
395     }
396 
397     /**
398      * Converts this instance of to a Duration.
399      *
400      * @return a Duration.
401      * @since 5.2
402      */
403     public Duration toDuration() {
404         return duration == 0 ? Duration.ZERO : Duration.of(duration, toChronoUnit(timeUnit));
405     }
406 
407     public long toHours() {
408         return timeUnit.toHours(duration);
409     }
410 
411     public long toMicroseconds() {
412         return timeUnit.toMicros(duration);
413     }
414 
415     public long toMilliseconds() {
416         return timeUnit.toMillis(duration);
417     }
418 
419     public int toMillisecondsIntBound() {
420         return asBoundInt(toMilliseconds());
421     }
422 
423     public long toMinutes() {
424         return timeUnit.toMinutes(duration);
425     }
426 
427     public long toNanoseconds() {
428         return timeUnit.toNanos(duration);
429     }
430 
431     public long toSeconds() {
432         return timeUnit.toSeconds(duration);
433     }
434 
435     public int toSecondsIntBound() {
436         return asBoundInt(toSeconds());
437     }
438 
439     @Override
440     public int compareTo(final TimeValue other) {
441         final TimeUnit targetTimeUnit = min(other.getTimeUnit());
442         return Long.compare(convert(targetTimeUnit), other.convert(targetTimeUnit));
443     }
444 
445     @Override
446     public String toString() {
447         return String.format("%d %s", duration, timeUnit);
448     }
449 
450     public Timeout toTimeout() {
451         return Timeout.of(duration, timeUnit);
452     }
453 
454 }