/*
* Copyright (C) 2011-2014 Chris Vest (mr.chrisvest@gmail.com)
*
* Licensed 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 stormpot;
import java.util.concurrent.TimeUnit;
/**
* This is the standard time based {@link Expiration}. It will invalidate
* objects based on about how long ago they were allocated.
*
* @author Chris Vest <mr.chrisvest@gmail.com>
* @since 2.2
*/
public final class TimeSpreadExpiration<T extends Poolable> implements Expiration<T> {
private final long lowerBoundMillis;
private final long upperBoundMillis;
private final TimeUnit unit;
/**
* Construct a new Expiration that will invalidate objects that are older
* than the given lower bound, before they get older than the upper bound,
* in the given time unit.
*
* If the `lowerBound` is less than 1, the `upperBound` is less than the
* `lowerBound`, or the `unit` is `null`, then an
* {@link java.lang.IllegalArgumentException} will be thrown.
*
* @param lowerBound Poolables younger than this, in the given unit, are not
* considered expired. This value must be at least 1.
* @param upperBound Poolables older than this, in the given unit, are always
* considered expired. This value must be greater than the
* lowerBound.
* @param unit The {@link TimeUnit} of the bounds values. Never `null`.
*/
public TimeSpreadExpiration(
long lowerBound,
long upperBound,
TimeUnit unit) {
if (lowerBound < 1) {
throw new IllegalArgumentException(
"The lower bound cannot be less than 1.");
}
if (upperBound <= lowerBound) {
throw new IllegalArgumentException(
"The upper bound must be greater than the lower bound.");
}
if (unit == null) {
throw new IllegalArgumentException("The TimeUnit cannot be null.");
}
this.lowerBoundMillis = unit.toMillis(lowerBound);
this.upperBoundMillis = unit.toMillis(upperBound);
this.unit = unit;
}
/**
* Returns `true`, with uniformly increasing probability, if the
* {@link stormpot.Poolable} represented by the given {@link SlotInfo} is
* older than the lower bound, eventually returning `true` with
* 100% certainty when the age of the Poolable is equal to or greater than
* the upper bound.
*
* The uniformity of the random expiration holds regardless of how often a
* Poolable is checked. That is to say, checking a Poolable more times within
* an interval of time, does _not_ increase its chances of being
* declared expired.
*/
@Override
public boolean hasExpired(SlotInfo<? extends T> info) {
long expirationAge = info.getStamp();
if (expirationAge == 0) {
long maxDelta = upperBoundMillis - lowerBoundMillis;
expirationAge = lowerBoundMillis + Math.abs(info.randomInt() % maxDelta);
info.setStamp(expirationAge);
}
long age = info.getAgeMillis();
return age >= expirationAge;
}
/**
* Produces a String representation of this TimeSpreadExpiration.
*/
@Override
public String toString() {
long lower = unit.convert(lowerBoundMillis, TimeUnit.MILLISECONDS);
long upper = unit.convert(upperBoundMillis, TimeUnit.MILLISECONDS);
return "TimeSpreadExpiration(" + lower + " to " + upper + " " + unit + ")";
}
}