blob: ad88bd29c22d772b4fd051df4a4eb74e5b93be61 [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.ParseException;
import java.text.SimpleDateFormat;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* A deadline based on a UNIX time, the elapsed since 00:00:00 Coordinated Universal Time (UTC), Thursday, 1 January
* 1970.
*
* @since 5.0
*/
public class Deadline {
/**
* The format used for parsing and formatting dates.
*/
public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
/**
* A special internal value that marks a deadline as the longest possible.
*/
private static final long INTERNAL_MAX_VALUE = Long.MAX_VALUE;
/**
* A special internal value that marks a deadline as the shortest possible.
*/
private static final long INTERNAL_MIN_VALUE = 0;
/**
* The maximum (longest-lived) deadline.
*/
public static Deadline MAX_VALUE = new Deadline(INTERNAL_MAX_VALUE);
/**
* The minimum (shortest-lived) deadline.
*/
public static Deadline MIN_VALUE = new Deadline(INTERNAL_MIN_VALUE);
private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_FORMAT);
/**
* Calculates a deadline with a given time in milliseconds plus a given time value. Non-positive time values
* represent an indefinite timeout without a deadline.
*
* @param timeMillis A time in UNIX milliseconds, usually the current time.
* @param timeValue time value to add to {@code timeMillis}.
* @return a deadline representing the current time plus the given time value.
*/
public static Deadline calculate(final long timeMillis, final TimeValue timeValue) {
if (TimeValue.isPositive(timeValue)) {
// TODO handle unlikely overflow
final long deadline = timeMillis + timeValue.toMilliseconds();
return deadline < 0 ? Deadline.MAX_VALUE : Deadline.fromUnixMilliseconds(deadline);
}
return Deadline.MAX_VALUE;
}
/**
* Calculates a deadline from now plus a given time value. Non-positive time values
* represent an indefinite timeout without a deadline.
*
* @param timeValue time value to add to {@code timeMillis}.
* @return a deadline representing the current time plus the given time value.
*/
public static Deadline calculate(final TimeValue timeValue) {
return calculate(System.currentTimeMillis(), timeValue);
}
/**
* Creates a deadline from a UNIX time in milliseconds.
*
* @param value a UNIX time in milliseconds.
* @return a new deadline.
*/
public static Deadline fromUnixMilliseconds(final long value) {
if (value == INTERNAL_MAX_VALUE) {
return MAX_VALUE;
}
if (value == INTERNAL_MIN_VALUE) {
return MIN_VALUE;
}
return new Deadline(value);
}
/**
* Creates a deadline from a string in the format {@value #DATE_FORMAT}.
*
* @param source a string in the format {@value #DATE_FORMAT}.
* @return a deadline from a string in the format {@value #DATE_FORMAT}.
* @throws ParseException if the specified source string cannot be parsed.
*/
public static Deadline parse(final String source) throws ParseException {
return fromUnixMilliseconds(simpleDateFormat.parse(source).getTime());
}
private volatile boolean frozen;
private volatile long lastCheck;
/*
* Internal representation is a UNIX time.
*/
private final long value;
/**
* Constructs a new instance with the given UNIX time in milliseconds.
*
* @param deadlineMillis UNIX time in milliseconds.
*/
private Deadline(final long deadlineMillis) {
super();
this.value = deadlineMillis;
setLastCheck();
}
@Override
public boolean equals(final Object obj) {
// Only take into account the deadline value.
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Deadline other = (Deadline) obj;
return value == other.value;
}
/**
* Formats this deadline.
*
* @param overdueTimeUnit the time unit to show how much over the deadline we are.
* @return a formatted string.
*/
public String format(final TimeUnit overdueTimeUnit) {
return String.format("Deadline: %s, %s overdue", formatTarget(), remainingTimeValue());
}
/**
* Formats the deadline value as a string in the format {@value #DATE_FORMAT}.
*
* @return a formatted string in the format {@value #DATE_FORMAT}.
*/
public String formatTarget() {
return simpleDateFormat.format(value);
}
public Deadline freeze() {
frozen = true;
return this;
}
/**
* Package private for testing.
*
* @return the last time we checked the current time.
*/
long getLastCheck() {
return lastCheck;
}
/**
* Gets the UNIX time deadline value.
*
* @return the UNIX time deadline value.
*/
public long getValue() {
return value;
}
@Override
public int hashCode() {
// Only take into account the deadline value.
return Objects.hash(value);
}
/**
* Returns whether this deadline occurs before the given time in milliseconds.
*
* @param millis the time to compare.
* @return whether this deadline occurs before the given time in milliseconds.
*/
public boolean isBefore(final long millis) {
return value < millis;
}
/**
* Returns whether the deadline has expired.
*
* @return whether the deadline has expired.
*/
public boolean isExpired() {
setLastCheck();
return value < this.lastCheck;
}
/**
* Returns whether this deadline is the maximum deadline.
*
* @return whether this deadline is the maximum deadline.
*/
public boolean isMax() {
return value == INTERNAL_MAX_VALUE;
}
/**
* Returns whether this deadline is the minimum deadline.
*
* @return whether this deadline is the minimum deadline.
*/
public boolean isMin() {
return value == INTERNAL_MIN_VALUE;
}
/**
* Returns whether this deadline has not expired.
*
* @return whether this deadline has not expired.
*/
public boolean isNotExpired() {
setLastCheck();
return value >= this.lastCheck;
}
/**
* Returns the smaller of this and another {@code Deadline}.
*
* @param other another deadline.
* @return the smaller of {@code this} and {@code other}.
*/
public Deadline min(final Deadline other) {
return value <= other.value ? this : other;
}
/**
* Returns the difference in milliseconds between the deadline and now.
*
* @return the different in milliseconds between the deadline and now.
*/
public long remaining() {
setLastCheck();
return value - lastCheck;
}
/**
* Returns the difference as a TimeValue between the deadline and now.
*
* @return Returns the different as a TimeValue between the deadline and now.
*/
public TimeValue remainingTimeValue() {
return TimeValue.of(remaining(), TimeUnit.MILLISECONDS);
}
private void setLastCheck() {
if (!frozen) {
this.lastCheck = System.currentTimeMillis();
}}
@Override
public String toString() {
return formatTarget();
}
}