blob: 576d87527562917de6c1423681cc971c5d25b073 [file] [log] [blame]
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.core5.util;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
/**
* Represents a time value as a {@code long} time and a {@link TimeUnit}.
*
* @since 5.0
*/
@Contract(threading = ThreadingBehavior.IMMUTABLE)
public class TimeValue implements Comparable<TimeValue> {
static final int INT_UNDEFINED = -1;
/**
* A constant holding the maximum value a {@code TimeValue} can have: <code>Long.MAX_VALUE</code> days.
*/
public static final TimeValue MAX_VALUE = ofDays(Long.MAX_VALUE);
/**
* A negative one millisecond {@link TimeValue}.
*/
public static final TimeValue NEG_ONE_MILLISECOND = TimeValue.of(INT_UNDEFINED, TimeUnit.MILLISECONDS);
/**
* A negative one second {@link TimeValue}.
*/
public static final TimeValue NEG_ONE_SECOND = TimeValue.of(INT_UNDEFINED, TimeUnit.SECONDS);
/**
* A zero milliseconds {@link TimeValue}.
*/
public static final TimeValue ZERO_MILLISECONDS = TimeValue.of(0, TimeUnit.MILLISECONDS);
/**
* Returns the given {@code long} value as an {@code int} where long values out of int range are returned as
* {@link Integer#MIN_VALUE} and {@link Integer#MAX_VALUE}.
*
* <p>
* For example: {@code TimeValue.asBoundInt(Long.MAX_VALUE)} returns {@code Integer.MAX_VALUE}.
* </p>
*
* @param value a long value to convert
* @return an int value bound within {@link Integer#MIN_VALUE} and {@link Integer#MAX_VALUE}.
*/
public static int asBoundInt(final long value) {
if (value > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
} else if (value < Integer.MIN_VALUE) {
return Integer.MIN_VALUE;
}
return (int) value;
}
/**
* Returns the given {@code timeValue} if it is not {@code null}, if {@code null} then returns the given
* {@code defaultValue}.
*
* @param timeValue may be {@code null}
* @param defaultValue may be {@code null}
* @return {@code timeValue} or {@code defaultValue}
*/
public static <T extends TimeValue> T defaultsTo(final T timeValue, final T defaultValue) {
return timeValue != null ? timeValue : defaultValue;
}
/**
* Returns the given {@code timeValue} if it is not {@code null}, if {@code null} then returns
* {@link #NEG_ONE_SECOND}.
*
* @param timeValue may be {@code null}
* @return {@code timeValue} or {@link #NEG_ONE_SECOND}
*/
public static TimeValue defaultsToNegativeOneMillisecond(final TimeValue timeValue) {
return defaultsTo(timeValue, NEG_ONE_MILLISECOND);
}
/**
* Returns the given {@code timeValue} if it is not {@code null}, if {@code null} then returns
* {@link #NEG_ONE_SECOND}.
*
* @param timeValue may be {@code null}
* @return {@code timeValue} or {@link #NEG_ONE_SECOND}
*/
public static TimeValue defaultsToNegativeOneSecond(final TimeValue timeValue) {
return defaultsTo(timeValue, NEG_ONE_SECOND);
}
/**
* Returns the given {@code timeValue} if it is not {@code null}, if {@code null} then returns
* {@link #ZERO_MILLISECONDS}.
*
* @param timeValue may be {@code null}
* @return {@code timeValue} or {@link #ZERO_MILLISECONDS}
*/
public static TimeValue defaultsToZeroMilliseconds(final TimeValue timeValue) {
return defaultsTo(timeValue, ZERO_MILLISECONDS);
}
public static boolean isNonNegative(final TimeValue timeValue) {
return timeValue != null && timeValue.getDuration() >= 0;
}
public static boolean isPositive(final TimeValue timeValue) {
return timeValue != null && timeValue.getDuration() > 0;
}
/**
* Creates a TimeValue.
*
* @param duration the time duration in the given {@code timeUnit}.
* @param timeUnit the time unit for the given durarion.
* @return a Timeout
*/
public static TimeValue of(final long duration, final TimeUnit timeUnit) {
return new TimeValue(duration, timeUnit);
}
public static TimeValue ofDays(final long days) {
return of(days, TimeUnit.DAYS);
}
public static TimeValue ofHours(final long hours) {
return of(hours, TimeUnit.HOURS);
}
public static TimeValue ofMicroseconds(final long microseconds) {
return of(microseconds, TimeUnit.MICROSECONDS);
}
public static TimeValue ofMilliseconds(final long millis) {
return of(millis, TimeUnit.MILLISECONDS);
}
public static TimeValue ofMinutes(final long minutes) {
return of(minutes, TimeUnit.MINUTES);
}
public static TimeValue ofNanoseconds(final long nanoseconds) {
return of(nanoseconds, TimeUnit.NANOSECONDS);
}
public static TimeValue ofSeconds(final long seconds) {
return of(seconds, TimeUnit.SECONDS);
}
/**
* Parses a TimeValue in the format {@code <Long><SPACE><TimeUnit>}, for example {@code "1,200 MILLISECONDS"}.
* <p>
* Parses:
* </p>
* <ul>
* <li>{@code "1,200 MILLISECONDS"} Note the comma.</li>
* <li>{@code "1200 MILLISECONDS"} Without a comma.</li>
* <li>{@code " 1,200 MILLISECONDS "} Spaces are ignored.</li>
* <li></li>
* </ul>
*
*
* @param value the TimeValue to parse
* @return a new TimeValue
* @throws ParseException if the number cannot be parsed
*/
public static TimeValue parse(final String value) throws ParseException {
final Locale locale = Locale.ROOT;
final String split[] = value.trim().split("\\s+");
if (split.length < 2) {
throw new IllegalArgumentException(
String.format("Expected format for <Long><SPACE><java.util.concurrent.TimeUnit>: %s", value));
}
final String clean0 = split[0].trim();
final String clean1 = split[1].trim().toUpperCase(Locale.ROOT);
final String timeUnitStr = clean1.endsWith("S") ? clean1 : clean1 + "S";
return TimeValue.of(NumberFormat.getInstance(locale).parse(clean0).longValue(), TimeUnit.valueOf(timeUnitStr));
}
private final long duration;
private final TimeUnit timeUnit;
TimeValue(final long duration, final TimeUnit timeUnit) {
super();
this.duration = duration;
this.timeUnit = Args.notNull(timeUnit, "timeUnit");
}
public long convert(final TimeUnit targetTimeUnit) {
Args.notNull(targetTimeUnit, "timeUnit");
return targetTimeUnit.convert(duration, timeUnit);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof TimeValue) {
final TimeValue that = (TimeValue) obj;
return this.duration == that.duration && LangUtils.equals(this.timeUnit, that.timeUnit);
}
return false;
}
/**
* Returns a TimeValue whose value is {@code (this / divisor)}.
*
* @param divisor
* value by which this TimeValue is to be divided.
* @return {@code this / divisor}
* @throws ArithmeticException
* if {@code divisor} is zero.
*/
public TimeValue divide(final long divisor) {
final long newDuration = duration / divisor;
return of(newDuration, timeUnit);
}
/**
* Returns a TimeValue whose value is {@code (this / divisor)}.
*
* @param divisor
* value by which this TimeValue is to be divided.
* @param targetTimeUnit
* the target TimeUnit
* @return {@code this / divisor}
* @throws ArithmeticException
* if {@code divisor} is zero.
*/
public TimeValue divide(final long divisor, final TimeUnit targetTimeUnit) {
return of(convert(targetTimeUnit) / divisor, targetTimeUnit);
}
public long getDuration() {
return duration;
}
public TimeUnit getTimeUnit() {
return timeUnit;
}
@Override
public int hashCode() {
int hash = LangUtils.HASH_SEED;
hash = LangUtils.hashCode(hash, duration);
hash = LangUtils.hashCode(hash, timeUnit);
return hash;
}
public TimeValue min(final TimeValue other) {
return this.compareTo(other) > 0 ? other : this;
}
private TimeUnit min(final TimeUnit other) {
return scale() > scale(other) ? other : getTimeUnit();
}
private int scale() {
return scale(timeUnit);
}
/**
* Returns a made up scale for TimeUnits.
*
* @param tUnit
* a TimeUnit
* @return a number from 1 to 7, where 1 is NANOSECONDS and 7 DAYS.
*/
private int scale(final TimeUnit tUnit) {
switch (tUnit) {
case NANOSECONDS:
return 1;
case MICROSECONDS:
return 2;
case MILLISECONDS:
return 3;
case SECONDS:
return 4;
case MINUTES:
return 5;
case HOURS:
return 6;
case DAYS:
return 7;
default:
// Should never happens unless Java adds to the enum.
throw new IllegalStateException();
}
}
public void sleep() throws InterruptedException {
timeUnit.sleep(duration);
}
public void timedJoin(final Thread thread) throws InterruptedException {
timeUnit.timedJoin(thread, duration);
}
public void timedWait(final Object obj) throws InterruptedException {
timeUnit.timedWait(obj, duration);
}
public long toDays() {
return timeUnit.toDays(duration);
}
public long toHours() {
return timeUnit.toHours(duration);
}
public long toMicroseconds() {
return timeUnit.toMicros(duration);
}
public long toMilliseconds() {
return timeUnit.toMillis(duration);
}
public int toMillisecondsIntBound() {
return asBoundInt(toMilliseconds());
}
public long toMinutes() {
return timeUnit.toMinutes(duration);
}
public long toNanoseconds() {
return timeUnit.toNanos(duration);
}
public long toSeconds() {
return timeUnit.toSeconds(duration);
}
public int toSecondsIntBound() {
return asBoundInt(toSeconds());
}
@Override
public int compareTo(final TimeValue other) {
final TimeUnit targetTimeUnit = min(other.getTimeUnit());
return Long.compare(convert(targetTimeUnit), other.convert(targetTimeUnit));
}
@Override
public String toString() {
return String.format(Locale.ROOT, "%,d %s", duration, timeUnit);
}
public Timeout toTimeout() {
return Timeout.of(duration, timeUnit);
}
}