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 <T> The type of {@link TimeValue}.
95       * @param timeValue may be {@code null}
96       * @param defaultValue may be {@code null}
97       * @return {@code timeValue} or {@code defaultValue}
98       */
99      public static <T extends TimeValue> T defaultsTo(final T timeValue, final T defaultValue) {
100         return timeValue != null ? timeValue : defaultValue;
101     }
102 
103     /**
104      * Returns the given {@code timeValue} if it is not {@code null}, if {@code null} then returns
105      * {@link #NEG_ONE_SECOND}.
106      *
107      * @param timeValue may be {@code null}
108      * @return {@code timeValue} or {@link #NEG_ONE_SECOND}
109      */
110     public static TimeValue defaultsToNegativeOneMillisecond(final TimeValue timeValue) {
111         return defaultsTo(timeValue, NEG_ONE_MILLISECOND);
112     }
113 
114     /**
115      * Returns the given {@code timeValue} if it is not {@code null}, if {@code null} then returns
116      * {@link #NEG_ONE_SECOND}.
117      *
118      * @param timeValue may be {@code null}
119      * @return {@code timeValue} or {@link #NEG_ONE_SECOND}
120      */
121     public static TimeValue defaultsToNegativeOneSecond(final TimeValue timeValue) {
122         return defaultsTo(timeValue, NEG_ONE_SECOND);
123     }
124 
125     /**
126      * Returns the given {@code timeValue} if it is not {@code null}, if {@code null} then returns
127      * {@link #ZERO_MILLISECONDS}.
128      *
129      * @param timeValue may be {@code null}
130      * @return {@code timeValue} or {@link #ZERO_MILLISECONDS}
131      */
132     public static TimeValue defaultsToZeroMilliseconds(final TimeValue timeValue) {
133         return defaultsTo(timeValue, ZERO_MILLISECONDS);
134     }
135 
136     public static boolean isNonNegative(final TimeValue timeValue) {
137         return timeValue != null && timeValue.getDuration() >= 0;
138     }
139 
140     public static boolean isPositive(final TimeValue timeValue) {
141         return timeValue != null && timeValue.getDuration() > 0;
142     }
143 
144     /**
145      * Creates a TimeValue.
146      *
147      * @param duration the time duration in the given {@code timeUnit}.
148      * @param timeUnit the time unit for the given duration.
149      * @return a Timeout.
150      */
151     public static TimeValue of(final long duration, final TimeUnit timeUnit) {
152         return new TimeValue(duration, timeUnit);
153     }
154 
155     /**
156      * Creates a TimeValue from a Duration.
157      *
158      * @param duration the time duration.
159      * @return a Timeout
160      * @since 5.2
161      */
162     public static TimeValue of(final Duration duration) {
163         final long seconds = duration.getSeconds();
164         final long nanoOfSecond = duration.getNano();
165         if (seconds == 0) {
166             // no conversion
167             return of(nanoOfSecond, TimeUnit.NANOSECONDS);
168         } else if (nanoOfSecond == 0) {
169             // no conversion
170             return of(seconds, TimeUnit.SECONDS);
171         }
172         // conversion attempts
173         try {
174             return of(duration.toNanos(), TimeUnit.NANOSECONDS);
175         } catch (final ArithmeticException e) {
176             try {
177                 return of(duration.toMillis(), TimeUnit.MILLISECONDS);
178             } catch (final ArithmeticException e1) {
179                 // backstop
180                 return of(seconds, TimeUnit.SECONDS);
181             }
182         }
183     }
184 
185     public static TimeValue ofDays(final long days) {
186         return of(days, TimeUnit.DAYS);
187     }
188 
189     public static TimeValue ofHours(final long hours) {
190         return of(hours, TimeUnit.HOURS);
191     }
192 
193     public static TimeValue ofMicroseconds(final long microseconds) {
194         return of(microseconds, TimeUnit.MICROSECONDS);
195     }
196 
197     public static TimeValue ofMilliseconds(final long millis) {
198         return of(millis, TimeUnit.MILLISECONDS);
199     }
200 
201     public static TimeValue ofMinutes(final long minutes) {
202         return of(minutes, TimeUnit.MINUTES);
203     }
204 
205     public static TimeValue ofNanoseconds(final long nanoseconds) {
206         return of(nanoseconds, TimeUnit.NANOSECONDS);
207     }
208 
209     public static TimeValue ofSeconds(final long seconds) {
210         return of(seconds, TimeUnit.SECONDS);
211     }
212 
213     /**
214      * Converts a {@link TimeUnit} to the equivalent {@link ChronoUnit}.
215      *
216      * @return the converted equivalent ChronoUnit
217      */
218     static ChronoUnit toChronoUnit(final TimeUnit timeUnit) {
219         switch (Objects.requireNonNull(timeUnit)) {
220         case NANOSECONDS:
221             return ChronoUnit.NANOS;
222         case MICROSECONDS:
223             return ChronoUnit.MICROS;
224         case MILLISECONDS:
225             return ChronoUnit.MILLIS;
226         case SECONDS:
227             return ChronoUnit.SECONDS;
228         case MINUTES:
229             return ChronoUnit.MINUTES;
230         case HOURS:
231             return ChronoUnit.HOURS;
232         case DAYS:
233             return ChronoUnit.DAYS;
234         default:
235             throw new IllegalArgumentException(timeUnit.toString());
236         }
237     }
238 
239     /**
240      * Parses a TimeValue in the format {@code <Long><SPACE><TimeUnit>}, for example {@code "1200 MILLISECONDS"}.
241      * <p>
242      * Parses:
243      * </p>
244      * <ul>
245      * <li>{@code "1200 MILLISECONDS"}.</li>
246      * <li>{@code " 1200 MILLISECONDS "}, spaces are ignored.</li>
247      * <li>{@code "1 MINUTE"}, singular units.</li>
248      * <li></li>
249      * </ul>
250      *
251      *
252      * @param value the TimeValue to parse
253      * @return a new TimeValue
254      * @throws ParseException if the number cannot be parsed
255      */
256     public static TimeValue parse(final String value) throws ParseException {
257         final String split[] = value.trim().split("\\s+");
258         if (split.length < 2) {
259             throw new IllegalArgumentException(
260                     String.format("Expected format for <Long><SPACE><java.util.concurrent.TimeUnit>: %s", value));
261         }
262         final String clean0 = split[0].trim();
263         final String clean1 = split[1].trim().toUpperCase(Locale.ROOT);
264         final String timeUnitStr = clean1.endsWith("S") ? clean1 : clean1 + "S";
265         return TimeValue.of(Long.parseLong(clean0), TimeUnit.valueOf(timeUnitStr));
266     }
267 
268     private final long duration;
269 
270     private final TimeUnit timeUnit;
271 
272     TimeValue(final long duration, final TimeUnit timeUnit) {
273         super();
274         this.duration = duration;
275         this.timeUnit = Args.notNull(timeUnit, "timeUnit");
276     }
277 
278     public long convert(final TimeUnit targetTimeUnit) {
279         Args.notNull(targetTimeUnit, "timeUnit");
280         return targetTimeUnit.convert(duration, timeUnit);
281     }
282 
283     @Override
284     public boolean equals(final Object obj) {
285         if (this == obj) {
286             return true;
287         }
288         if (obj instanceof TimeValue) {
289             final TimeValue that = (TimeValue) obj;
290             final long thisDuration = this.convert(TimeUnit.NANOSECONDS);
291             final long thatDuration = that.convert(TimeUnit.NANOSECONDS);
292             return thisDuration == thatDuration;
293         }
294         return false;
295     }
296 
297     /**
298      * Returns a TimeValue whose value is {@code (this / divisor)}.
299      *
300      * @param divisor
301      *            value by which this TimeValue is to be divided.
302      * @return {@code this / divisor}
303      * @throws ArithmeticException
304      *             if {@code divisor} is zero.
305      */
306     public TimeValue divide(final long divisor) {
307         final long newDuration = duration / divisor;
308         return of(newDuration, timeUnit);
309     }
310 
311     /**
312      * Returns a TimeValue whose value is {@code (this / divisor)}.
313      *
314      * @param divisor
315      *            value by which this TimeValue is to be divided.
316      * @param targetTimeUnit
317      *            the target TimeUnit
318      * @return {@code this / divisor}
319      * @throws ArithmeticException
320      *             if {@code divisor} is zero.
321      */
322     public TimeValue divide(final long divisor, final TimeUnit targetTimeUnit) {
323         return of(convert(targetTimeUnit) / divisor, targetTimeUnit);
324     }
325 
326     public long getDuration() {
327         return duration;
328     }
329 
330     public TimeUnit getTimeUnit() {
331         return timeUnit;
332     }
333 
334     @Override
335     public int hashCode() {
336         int hash = LangUtils.HASH_SEED;
337         hash = LangUtils.hashCode(hash, this.convert(TimeUnit.NANOSECONDS));
338         return hash;
339     }
340 
341     public TimeValue min(final TimeValue other) {
342         return this.compareTo(other) > 0 ? other : this;
343     }
344 
345     private TimeUnit min(final TimeUnit other) {
346         return scale() > scale(other) ? other : getTimeUnit();
347     }
348 
349     private int scale() {
350         return scale(timeUnit);
351     }
352 
353     /**
354      * Returns a made up scale for TimeUnits.
355      *
356      * @param tUnit
357      *            a TimeUnit
358      * @return a number from 1 to 7, where 1 is NANOSECONDS and 7 DAYS.
359      */
360     private int scale(final TimeUnit tUnit) {
361         switch (tUnit) {
362         case NANOSECONDS:
363             return 1;
364         case MICROSECONDS:
365             return 2;
366         case MILLISECONDS:
367             return 3;
368         case SECONDS:
369             return 4;
370         case MINUTES:
371             return 5;
372         case HOURS:
373             return 6;
374         case DAYS:
375             return 7;
376         default:
377             // Should never happens unless Java adds to the enum.
378             throw new IllegalStateException();
379         }
380     }
381 
382     public void sleep() throws InterruptedException {
383         timeUnit.sleep(duration);
384     }
385 
386     public void timedJoin(final Thread thread) throws InterruptedException {
387         timeUnit.timedJoin(thread, duration);
388     }
389 
390     public void timedWait(final Object obj) throws InterruptedException {
391         timeUnit.timedWait(obj, duration);
392     }
393 
394     public long toDays() {
395         return timeUnit.toDays(duration);
396     }
397 
398     /**
399      * Converts this instance of to a Duration.
400      *
401      * @return a Duration.
402      * @since 5.2
403      */
404     public Duration toDuration() {
405         return duration == 0 ? Duration.ZERO : Duration.of(duration, toChronoUnit(timeUnit));
406     }
407 
408     public long toHours() {
409         return timeUnit.toHours(duration);
410     }
411 
412     public long toMicroseconds() {
413         return timeUnit.toMicros(duration);
414     }
415 
416     public long toMilliseconds() {
417         return timeUnit.toMillis(duration);
418     }
419 
420     public int toMillisecondsIntBound() {
421         return asBoundInt(toMilliseconds());
422     }
423 
424     public long toMinutes() {
425         return timeUnit.toMinutes(duration);
426     }
427 
428     public long toNanoseconds() {
429         return timeUnit.toNanos(duration);
430     }
431 
432     public long toSeconds() {
433         return timeUnit.toSeconds(duration);
434     }
435 
436     public int toSecondsIntBound() {
437         return asBoundInt(toSeconds());
438     }
439 
440     @Override
441     public int compareTo(final TimeValue other) {
442         final TimeUnit targetTimeUnit = min(other.getTimeUnit());
443         return Long.compare(convert(targetTimeUnit), other.convert(targetTimeUnit));
444     }
445 
446     @Override
447     public String toString() {
448         return String.format("%d %s", duration, timeUnit);
449     }
450 
451     public Timeout toTimeout() {
452         return Timeout.of(duration, timeUnit);
453     }
454 
455 }