/*
* Copyright Terracotta, Inc.
*
* 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 org.ehcache.core.spi.store;
import org.ehcache.expiry.Duration;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import static java.lang.String.format;
/**
* @author Ludovic Orban
*/
public abstract class AbstractValueHolder<V> implements Store.ValueHolder<V> {
private static final AtomicLongFieldUpdater<AbstractValueHolder> HITS_UPDATER = AtomicLongFieldUpdater.newUpdater(AbstractValueHolder.class, "hits");
private final long id;
private final long creationTime;
private volatile long lastAccessTime;
private volatile long expirationTime;
private volatile long hits;
private static final AtomicLongFieldUpdater<AbstractValueHolder> ACCESSTIME_UPDATER = AtomicLongFieldUpdater.newUpdater(AbstractValueHolder.class, "lastAccessTime");
private static final AtomicLongFieldUpdater<AbstractValueHolder> EXPIRATIONTIME_UPDATER = AtomicLongFieldUpdater.newUpdater(AbstractValueHolder.class, "expirationTime");
protected AbstractValueHolder(long id, long creationTime) {
this(id, creationTime, NO_EXPIRE);
}
protected AbstractValueHolder(long id, long creationTime, long expirationTime) {
this.id = id;
this.creationTime = creationTime;
this.expirationTime = expirationTime;
this.lastAccessTime = creationTime;
}
protected abstract TimeUnit nativeTimeUnit();
@Override
public long creationTime(TimeUnit unit) {
return unit.convert(creationTime, nativeTimeUnit());
}
public void setExpirationTime(long expirationTime, TimeUnit unit) {
if (expirationTime == NO_EXPIRE) {
updateExpirationTime(NO_EXPIRE);
} else if (expirationTime <= 0) {
throw new IllegalArgumentException("invalid expiration time: " + expirationTime);
} else {
updateExpirationTime(nativeTimeUnit().convert(expirationTime, unit));
}
}
private void updateExpirationTime(long update) {
while (true) {
long current = this.expirationTime;
if (current >= update) {
break;
}
if (EXPIRATIONTIME_UPDATER.compareAndSet(this, current, update)) {
break;
}
};
}
public void accessed(long now, Duration expiration) {
final TimeUnit timeUnit = nativeTimeUnit();
if (expiration != null) {
if (expiration.isInfinite()) {
setExpirationTime(Store.ValueHolder.NO_EXPIRE, null);
} else {
long millis = timeUnit.convert(expiration.getLength(), expiration.getTimeUnit());
long newExpirationTime ;
if (millis == Long.MAX_VALUE) {
newExpirationTime = Long.MAX_VALUE;
} else {
newExpirationTime = now + millis;
if (newExpirationTime < 0) {
newExpirationTime = Long.MAX_VALUE;
}
}
setExpirationTime(newExpirationTime, timeUnit);
}
}
setLastAccessTime(now, timeUnit);
HITS_UPDATER.getAndIncrement(this);
}
@Override
public long expirationTime(TimeUnit unit) {
final long expire = this.expirationTime;
if (expire == NO_EXPIRE) {
return NO_EXPIRE;
}
return unit.convert(expire, nativeTimeUnit());
}
@Override
public boolean isExpired(long expirationTime, TimeUnit unit) {
final long expire = this.expirationTime;
if (expire == NO_EXPIRE) {
return false;
}
return expire <= nativeTimeUnit().convert(expirationTime, unit);
}
@Override
public long lastAccessTime(TimeUnit unit) {
return unit.convert(lastAccessTime, nativeTimeUnit());
}
public void setLastAccessTime(long lastAccessTime, TimeUnit unit) {
long update = unit.convert(lastAccessTime, nativeTimeUnit());
while (true) {
long current = this.lastAccessTime;
if (current >= update) {
break;
}
if (ACCESSTIME_UPDATER.compareAndSet(this, current, update)) {
break;
}
};
}
@Override
public int hashCode() {
int result = 1;
result = 31 * result + (int)(creationTime ^ (creationTime >>> 32));
result = 31 * result + (int)(lastAccessTime ^ (lastAccessTime >>> 32));
result = 31 * result + (int)(expirationTime ^ (expirationTime >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof AbstractValueHolder) {
AbstractValueHolder<?> other = (AbstractValueHolder<?>) obj;
return
other.creationTime(nativeTimeUnit()) == creationTime && creationTime(other.nativeTimeUnit()) == other.creationTime &&
other.expirationTime(nativeTimeUnit()) == expirationTime && expirationTime(other.nativeTimeUnit()) == other.expirationTime &&
other.lastAccessTime(nativeTimeUnit()) == lastAccessTime && lastAccessTime(other.nativeTimeUnit()) == other.lastAccessTime;
}
return false;
}
@Override
public float hitRate(long now, TimeUnit unit) {
final long endTime = TimeUnit.NANOSECONDS.convert(now, TimeUnit.MILLISECONDS);
final long startTime = TimeUnit.NANOSECONDS.convert(creationTime, nativeTimeUnit());
float duration = (endTime - startTime)/(float)TimeUnit.NANOSECONDS.convert(1, unit);
return (hits/duration);
}
@Override
public long hits() {
return this.hits;
}
protected void setHits(long hits) {
HITS_UPDATER.set(this, hits);
}
@Override
public long getId() {
return id;
}
@Override
public String toString() {
return format("%s", value());
}
}