All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.ratis.util.TimeDuration Maven / Gradle / Ivy

There is a newer version: 3.1.2
Show newest version
/*
 * 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.
 */
package org.apache.ratis.util;

import org.apache.ratis.util.function.CheckedBiFunction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.LongUnaryOperator;
import java.util.stream.Stream;

/**
 * Time duration is represented by a long together with a {@link TimeUnit}.
 * 

* This is a value-based class. */ public final class TimeDuration implements Comparable { static final Logger LOG = LoggerFactory.getLogger(TimeDuration.class); public static final TimeDuration ZERO = valueOf(0, TimeUnit.NANOSECONDS); public static final TimeDuration ONE_MILLISECOND = TimeDuration.valueOf(1, TimeUnit.MILLISECONDS); public static final TimeDuration ONE_SECOND = TimeDuration.valueOf(1, TimeUnit.SECONDS); public static final TimeDuration ONE_MINUTE = TimeDuration.valueOf(1, TimeUnit.MINUTES); public static final TimeDuration ONE_DAY = TimeDuration.valueOf(1, TimeUnit.DAYS); static final double ERROR_THRESHOLD = 0.001; // accept 0.1% error /** @return the next lower {@link TimeUnit}. If the unit is already the lowest, return it. */ public static TimeUnit lowerUnit(TimeUnit unit) { final int ordinal = unit.ordinal(); return ordinal == 0? unit: TimeUnit.values()[ordinal - 1]; } /** @return the next higher {@link TimeUnit}. If the unit is already the highest, return it. */ public static TimeUnit higherUnit(TimeUnit unit) { final int ordinal = unit.ordinal(); final TimeUnit[] timeUnits = TimeUnit.values(); return ordinal == timeUnits.length - 1? unit: timeUnits[ordinal + 1]; } /** @return the minimum of the given parameters. */ public static TimeDuration min(TimeDuration left, TimeDuration right) { Objects.requireNonNull(left, "left == null"); Objects.requireNonNull(right, "right == null"); return Stream.of(left, right).min(TimeDuration::compareTo).orElseThrow( () -> new IllegalStateException("Failed to compute min(" + left + ", " + right + ")")); } /** @return the maximum of the given parameters. */ public static TimeDuration max(TimeDuration left, TimeDuration right) { Objects.requireNonNull(left, "left == null"); Objects.requireNonNull(right, "right == null"); return Stream.of(left, right).max(TimeDuration::compareTo).orElseThrow( () -> new IllegalStateException("Failed to compute max(" + left + ", " + right + ")")); } /** Abbreviations of {@link TimeUnit}. */ public enum Abbreviation { NANOSECONDS("ns", "nanos"), MICROSECONDS("μs", "us", "micros"), MILLISECONDS("ms", "msec", "millis"), SECONDS("s", "sec"), MINUTES("min", "m"), HOURS("hr", "h"), DAYS("day", "d"); private final TimeUnit unit = TimeUnit.valueOf(name()); private final List symbols; Abbreviation(String... symbols) { final List input = Arrays.asList(symbols); final List all = new ArrayList<>(input.size() + 2); input.forEach(s -> all.add(s.toLowerCase())); final String s = unit.name().toLowerCase(); addIfAbsent(all, s); addIfAbsent(all, s.substring(0, s.length() - 1)); this.symbols = Collections.unmodifiableList(all); } static void addIfAbsent(List list, String toAdd) { for(String s : list) { if (toAdd.equals(s)) { return; } } list.add(toAdd); } /** @return the corresponding {@link TimeUnit}. */ public TimeUnit unit() { return unit; } /** @return the default abbreviation. */ String getDefault() { return symbols.get(0); } /** @return the entire abbreviation list for this unit. */ public List getSymbols() { return symbols; } /** @return the corresponding {@link Abbreviation}. */ public static Abbreviation valueOf(TimeUnit unit) { return valueOf(unit.name()); } } /** The same as valueOf(timeString, targetUnit).toLong(targetUnit). */ public static long parse(String timeString, TimeUnit targetUnit) { return valueOf(timeString, targetUnit).toLong(targetUnit); } /** * Parse the given string. * The string is first be trimmed and underscore removed. * * @param defaultUnit Use it if no unit is specified. * @return a {@link TimeDuration} in the target unit. */ public static TimeDuration valueOf(String timeString, TimeUnit defaultUnit) { Objects.requireNonNull(timeString, "timeString = null"); final String lower = timeString.trim().replace("_", "").toLowerCase(); for(Abbreviation a : Abbreviation.values()) { for(String s : a.getSymbols()) { if (lower.endsWith(s)) { final String value = lower.substring(0, lower.length()-s.length()).trim(); try { return valueOf(Long.parseLong(value), a.unit()); } catch(NumberFormatException e) { // failed with current symbol; ignore and try next symbol. } } } } Objects.requireNonNull(defaultUnit, "defaultUnit == null"); return valueOf(Long.parseLong(lower), defaultUnit); } /** @return a {@link TimeDuration} representing the given duration and unit. */ public static TimeDuration valueOf(long duration, TimeUnit unit) { return new TimeDuration(duration, unit); } private final long duration; private final TimeUnit unit; private TimeDuration(long duration, TimeUnit unit) { this.duration = duration; this.unit = Objects.requireNonNull(unit, "unit = null"); } /** @return the duration value. */ public long getDuration() { return duration; } /** @return the {@link TimeUnit}. */ public TimeUnit getUnit() { return unit; } /** * Convert this {@link TimeDuration} to a long in the target unit. * Note that the returned value may be truncated or saturated; see {@link TimeUnit#convert(long, TimeUnit)}.* * * @return the value in the target unit. */ public long toLong(TimeUnit targetUnit) { return targetUnit.convert(duration, unit); } /** * The same as Math.toIntExact(toLong(targetUnit)); * Similar to {@link #toLong(TimeUnit)}, the returned value may be truncated. * However, the returned value is never saturated. The method throws {@link ArithmeticException} if it overflows. * * @return the value in the target unit. * @throws ArithmeticException if it overflows. */ public int toIntExact(TimeUnit targetUnit) { return Math.toIntExact(toLong(targetUnit)); } /** @return the {@link TimeDuration} in the target unit. */ public TimeDuration to(TimeUnit targetUnit) { if (this.unit == targetUnit) { return this; } final TimeDuration t = valueOf(toLong(targetUnit), targetUnit); LOG.debug("{}.to({}) = {}", this, targetUnit, t); return t; } /** @return (this + that) in the minimum unit among them. */ public TimeDuration add(TimeDuration that) { Objects.requireNonNull(that, "that == null"); final TimeUnit minUnit = CollectionUtils.min(this.unit, that.unit); return valueOf(this.toLong(minUnit) + that.toLong(minUnit), minUnit); } /** @return (this + (thatDuration, thatUnit)) in the minimum unit among them. */ public TimeDuration add(long thatDuration, TimeUnit thatUnit) { return add(TimeDuration.valueOf(thatDuration, thatUnit)); } /** @return (this - that) in the minimum unit among them. */ public TimeDuration subtract(TimeDuration that) { Objects.requireNonNull(that, "that == null"); final TimeUnit minUnit = CollectionUtils.min(this.unit, that.unit); return valueOf(this.toLong(minUnit) - that.toLong(minUnit), minUnit); } private static boolean isMagnitudeLarge(long n) { return n > 1_000_000_000_000L || n < -1_000_000_000_000L; } /** @return this * multiplier; the result unit may be changed in order to reduce rounding/overflow error. */ public TimeDuration multiply(double multiplier) { final double product = duration * multiplier; final long rounded = Math.round(product); if (unit.ordinal() != TimeUnit.values().length - 1) { // check overflow error if (rounded == Long.MAX_VALUE || rounded == Long.MIN_VALUE) { if (Math.abs(multiplier) > 2) { return multiply(2).multiply(multiplier / 2); } else { return to(higherUnit(unit)).multiply(multiplier); } } } if (unit.ordinal() != 0) { // check round off error if (Math.abs(product - rounded) > Math.abs(product) * ERROR_THRESHOLD) { if (isMagnitudeLarge(duration) && Math.abs(multiplier) < 0.5d) { return multiply(0.5).multiply(multiplier * 2); } else { return to(lowerUnit(unit)).multiply(multiplier); } } } final TimeDuration t = valueOf(rounded, unit); LOG.debug("{} * {} = {}", this, multiplier, t); return t; } /** @return -this. */ public TimeDuration negate() { if (duration == Long.MIN_VALUE) { return valueOf(Long.MAX_VALUE, unit); } return valueOf(Math.negateExact(duration), unit); } /** Round up to the given nanos to nearest multiple (in nanoseconds) of this {@link TimeDuration}. */ public long roundUpNanos(long nanos) { if (duration <= 0) { throw new ArithmeticException( "Rounding up to a non-positive " + JavaUtils.getClassSimpleName(getClass()) + " (=" + this + ")"); } final long divisor = toLong(TimeUnit.NANOSECONDS); if (nanos == 0 || divisor == 1) { return nanos; } long remainder = nanos % divisor; // In Java, the sign of remainder is the same as the dividend. if (remainder > 0) { remainder -= divisor; } return nanos - remainder; } /** * Apply the given operator to the duration value of this object. * * @return a new object with the new duration value and the same unit of this object. */ public TimeDuration apply(LongUnaryOperator operator) { return valueOf(operator.applyAsLong(duration), unit); } /** Apply the given function to the (duration, unit) of this object. */ public OUTPUT apply( CheckedBiFunction function) throws THROWABLE { return function.apply(getDuration(), getUnit()); } /** @return Is this {@link TimeDuration} less than zero? */ public boolean isNegative() { return duration < 0; } /** @return Is this {@link TimeDuration} greater than or equal to zero? */ public boolean isNonNegative() { return duration >= 0; } /** @return Is this {@link TimeDuration} greater than zero? */ public boolean isPositive() { return duration > 0; } /** @return Is this {@link TimeDuration} less than or equal to zero? */ public boolean isNonPositive() { return duration <= 0; } /** The same as sleep(null). */ public TimeDuration sleep() throws InterruptedException { return sleep(null); } /** * Performs a {@link TimeUnit#sleep(long)} using this {@link TimeDuration}. * * @param log If not null, use it to print log messages. * @return the difference of the actual sleep time duration and this {@link TimeDuration}. */ public TimeDuration sleep(Consumer log) throws InterruptedException { if (log != null) { log.accept(StringUtils.stringSupplierAsObject(() -> "Start sleeping " + this)); } final Timestamp start = Timestamp.currentTime(); try { unit.sleep(duration); if (log != null) { log.accept(StringUtils.stringSupplierAsObject(() -> "Completed sleeping " + this)); } } catch (InterruptedException ie) { Thread.currentThread().interrupt(); if (log != null) { log.accept(StringUtils.stringSupplierAsObject(() -> "Interrupted sleeping " + this)); } throw ie; } return start.elapsedTime().subtract(this); } @Override public int compareTo(TimeDuration that) { if (this.unit.compareTo(that.unit) > 0) { return Integer.compare(0, that.compareTo(this)); } if (this.unit == that.unit) { return Long.compare(this.duration, that.duration); } // this.unit < that.unit final long thisDurationInThatUnit = this.toLong(that.unit); if (thisDurationInThatUnit == that.duration) { // check for overflow final long thatDurationInThisUnit = that.toLong(this.unit); return Long.compare(this.duration, thatDurationInThisUnit); } else { return Long.compare(thisDurationInThatUnit, that.duration); } } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (!(obj instanceof TimeDuration)) { return false; } final TimeDuration that = (TimeDuration)obj; return this.compareTo(that) == 0; } @Override public int hashCode() { return Long.hashCode(toLong(TimeUnit.NANOSECONDS)); } @Override public String toString() { return duration + Abbreviation.valueOf(unit).getDefault(); } /** @return a representation of this object in the given target unit and decimal places. */ public String toString(TimeUnit targetUnit, int decimalPlaces) { Objects.requireNonNull(targetUnit, "targetUnit == null"); if (targetUnit.compareTo(unit) <= 0) { return to(targetUnit).toString(); } final double divisor = unit.convert(1, targetUnit); if (duration % divisor == 0) { return to(targetUnit).toString(); } final String decimal = StringUtils.format("%." + decimalPlaces + "f", duration/divisor); final String s = decimal + Abbreviation.valueOf(targetUnit).getDefault(); LOG.debug("{}.to({}) = {}", this, targetUnit, s); return s; } }