package org.postgresql.util;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
public class LruCache<Key, Value extends CanEstimateSize> implements Gettable<Key, Value> {
public interface EvictAction<Value> {
void evict(Value value) throws SQLException;
}
public interface CreateAction<Key, Value> {
Value create(Key key) throws SQLException;
}
private final EvictAction<Value> onEvict;
private final CreateAction<Key, Value> createAction;
private final int maxSizeEntries;
private final long maxSizeBytes;
private long currentSize;
private final Map<Key, Value> cache;
private class LimitedMap extends LinkedHashMap<Key, Value> {
LimitedMap(int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor, accessOrder);
}
@Override
protected boolean removeEldestEntry(Map.Entry<Key, Value> eldest) {
if (size() <= maxSizeEntries && currentSize <= maxSizeBytes) {
return false;
}
Iterator<Map.Entry<Key, Value>> it = entrySet().iterator();
while (it.hasNext()) {
if (size() <= maxSizeEntries && currentSize <= maxSizeBytes) {
return false;
}
Map.Entry<Key, Value> entry = it.next();
evictValue(entry.getValue());
long valueSize = entry.getValue().getSize();
if (valueSize > 0) {
currentSize -= valueSize;
}
it.remove();
}
return false;
}
}
private void evictValue(Value value) {
try {
onEvict.evict(value);
} catch (SQLException e) {
}
}
public LruCache(int maxSizeEntries, long maxSizeBytes, boolean accessOrder) {
this(maxSizeEntries, maxSizeBytes, accessOrder, NOOP_CREATE_ACTION, NOOP_EVICT_ACTION);
}
public LruCache(int maxSizeEntries, long maxSizeBytes, boolean accessOrder,
CreateAction<Key, Value> createAction,
EvictAction<Value> onEvict) {
this.maxSizeEntries = maxSizeEntries;
this.maxSizeBytes = maxSizeBytes;
this.createAction = createAction;
this.onEvict = onEvict;
this.cache = new LimitedMap(16, 0.75f, accessOrder);
}
public synchronized Value get(Key key) {
return cache.get(key);
}
public synchronized Value borrow(Key key) throws SQLException {
Value value = cache.remove(key);
if (value == null) {
return createAction.create(key);
}
currentSize -= value.getSize();
return value;
}
public synchronized void put(Key key, Value value) {
long valueSize = value.getSize();
if (maxSizeBytes == 0 || maxSizeEntries == 0 || valueSize * 2 > maxSizeBytes) {
evictValue(value);
return;
}
currentSize += valueSize;
Value prev = cache.put(key, value);
if (prev == null) {
return;
}
currentSize -= prev.getSize();
if (prev != value) {
evictValue(prev);
}
}
public synchronized void putAll(Map<Key, Value> m) {
for (Map.Entry<Key, Value> entry : m.entrySet()) {
this.put(entry.getKey(), entry.getValue());
}
}
public static final CreateAction NOOP_CREATE_ACTION = new CreateAction() {
@Override
public Object create(Object o) throws SQLException {
return null;
}
};
public static final EvictAction NOOP_EVICT_ACTION = new EvictAction() {
@Override
public void evict(Object o) throws SQLException {
return;
}
};
}