|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.util; |
|
|
|
import java.util.*; |
|
import java.lang.ref.*; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public abstract class Cache<K,V> { |
|
|
|
protected Cache() { |
|
// empty |
|
} |
|
|
|
|
|
|
|
*/ |
|
public abstract int size(); |
|
|
|
|
|
|
|
*/ |
|
public abstract void clear(); |
|
|
|
|
|
|
|
*/ |
|
public abstract void put(K key, V value); |
|
|
|
|
|
|
|
*/ |
|
public abstract V get(Object key); |
|
|
|
|
|
|
|
*/ |
|
public abstract void remove(Object key); |
|
|
|
|
|
|
|
*/ |
|
public abstract V pull(Object key); |
|
|
|
|
|
|
|
*/ |
|
public abstract void setCapacity(int size); |
|
|
|
|
|
|
|
*/ |
|
public abstract void setTimeout(int timeout); |
|
|
|
|
|
|
|
*/ |
|
public abstract void accept(CacheVisitor<K,V> visitor); |
|
|
|
|
|
|
|
|
|
*/ |
|
public static <K,V> Cache<K,V> newSoftMemoryCache(int size) { |
|
return new MemoryCache<>(true, size); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static <K,V> Cache<K,V> newSoftMemoryCache(int size, int timeout) { |
|
return new MemoryCache<>(true, size, timeout); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public static <K,V> Cache<K,V> newHardMemoryCache(int size) { |
|
return new MemoryCache<>(false, size); |
|
} |
|
|
|
|
|
|
|
*/ |
|
@SuppressWarnings("unchecked") |
|
public static <K,V> Cache<K,V> newNullCache() { |
|
return (Cache<K,V>) NullCache.INSTANCE; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static <K,V> Cache<K,V> newHardMemoryCache(int size, int timeout) { |
|
return new MemoryCache<>(false, size, timeout); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public static class EqualByteArray { |
|
|
|
private final byte[] b; |
|
private int hash; |
|
|
|
public EqualByteArray(byte[] b) { |
|
this.b = b; |
|
} |
|
|
|
public int hashCode() { |
|
int h = hash; |
|
if (h == 0 && b.length > 0) { |
|
hash = h = Arrays.hashCode(b); |
|
} |
|
return h; |
|
} |
|
|
|
public boolean equals(Object obj) { |
|
if (this == obj) { |
|
return true; |
|
} |
|
if (obj instanceof EqualByteArray == false) { |
|
return false; |
|
} |
|
EqualByteArray other = (EqualByteArray)obj; |
|
return Arrays.equals(this.b, other.b); |
|
} |
|
} |
|
|
|
public interface CacheVisitor<K,V> { |
|
public void visit(Map<K,V> map); |
|
} |
|
|
|
} |
|
|
|
class NullCache<K,V> extends Cache<K,V> { |
|
|
|
static final Cache<Object,Object> INSTANCE = new NullCache<>(); |
|
|
|
private NullCache() { |
|
// empty |
|
} |
|
|
|
public int size() { |
|
return 0; |
|
} |
|
|
|
public void clear() { |
|
// empty |
|
} |
|
|
|
public void put(K key, V value) { |
|
// empty |
|
} |
|
|
|
public V get(Object key) { |
|
return null; |
|
} |
|
|
|
public void remove(Object key) { |
|
// empty |
|
} |
|
|
|
public V pull(Object key) { |
|
return null; |
|
} |
|
|
|
public void setCapacity(int size) { |
|
// empty |
|
} |
|
|
|
public void setTimeout(int timeout) { |
|
// empty |
|
} |
|
|
|
public void accept(CacheVisitor<K,V> visitor) { |
|
// empty |
|
} |
|
|
|
} |
|
|
|
class MemoryCache<K,V> extends Cache<K,V> { |
|
|
|
private static final float LOAD_FACTOR = 0.75f; |
|
|
|
|
|
private static final boolean DEBUG = false; |
|
|
|
private final Map<K, CacheEntry<K,V>> cacheMap; |
|
private int maxSize; |
|
private long lifetime; |
|
private long nextExpirationTime = Long.MAX_VALUE; |
|
|
|
// ReferenceQueue is of type V instead of Cache<K,V> |
|
|
|
private final ReferenceQueue<V> queue; |
|
|
|
public MemoryCache(boolean soft, int maxSize) { |
|
this(soft, maxSize, 0); |
|
} |
|
|
|
public MemoryCache(boolean soft, int maxSize, int lifetime) { |
|
this.maxSize = maxSize; |
|
this.lifetime = lifetime * 1000; |
|
if (soft) |
|
this.queue = new ReferenceQueue<>(); |
|
else |
|
this.queue = null; |
|
|
|
cacheMap = new LinkedHashMap<>(1, LOAD_FACTOR, true); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void emptyQueue() { |
|
if (queue == null) { |
|
return; |
|
} |
|
int startSize = cacheMap.size(); |
|
while (true) { |
|
@SuppressWarnings("unchecked") |
|
CacheEntry<K,V> entry = (CacheEntry<K,V>)queue.poll(); |
|
if (entry == null) { |
|
break; |
|
} |
|
K key = entry.getKey(); |
|
if (key == null) { |
|
|
|
continue; |
|
} |
|
CacheEntry<K,V> currentEntry = cacheMap.remove(key); |
|
// check if the entry in the map corresponds to the expired |
|
|
|
if ((currentEntry != null) && (entry != currentEntry)) { |
|
cacheMap.put(key, currentEntry); |
|
} |
|
} |
|
if (DEBUG) { |
|
int endSize = cacheMap.size(); |
|
if (startSize != endSize) { |
|
System.out.println("*** Expunged " + (startSize - endSize) |
|
+ " entries, " + endSize + " entries left"); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private void expungeExpiredEntries() { |
|
emptyQueue(); |
|
if (lifetime == 0) { |
|
return; |
|
} |
|
int cnt = 0; |
|
long time = System.currentTimeMillis(); |
|
if (nextExpirationTime > time) { |
|
return; |
|
} |
|
nextExpirationTime = Long.MAX_VALUE; |
|
for (Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator(); |
|
t.hasNext(); ) { |
|
CacheEntry<K,V> entry = t.next(); |
|
if (entry.isValid(time) == false) { |
|
t.remove(); |
|
cnt++; |
|
} else if (nextExpirationTime > entry.getExpirationTime()) { |
|
nextExpirationTime = entry.getExpirationTime(); |
|
} |
|
} |
|
if (DEBUG) { |
|
if (cnt != 0) { |
|
System.out.println("Removed " + cnt |
|
+ " expired entries, remaining " + cacheMap.size()); |
|
} |
|
} |
|
} |
|
|
|
public synchronized int size() { |
|
expungeExpiredEntries(); |
|
return cacheMap.size(); |
|
} |
|
|
|
public synchronized void clear() { |
|
if (queue != null) { |
|
// if this is a SoftReference cache, first invalidate() all |
|
|
|
for (CacheEntry<K,V> entry : cacheMap.values()) { |
|
entry.invalidate(); |
|
} |
|
while (queue.poll() != null) { |
|
// empty |
|
} |
|
} |
|
cacheMap.clear(); |
|
} |
|
|
|
public synchronized void put(K key, V value) { |
|
emptyQueue(); |
|
long expirationTime = (lifetime == 0) ? 0 : |
|
System.currentTimeMillis() + lifetime; |
|
if (expirationTime < nextExpirationTime) { |
|
nextExpirationTime = expirationTime; |
|
} |
|
CacheEntry<K,V> newEntry = newEntry(key, value, expirationTime, queue); |
|
CacheEntry<K,V> oldEntry = cacheMap.put(key, newEntry); |
|
if (oldEntry != null) { |
|
oldEntry.invalidate(); |
|
return; |
|
} |
|
if (maxSize > 0 && cacheMap.size() > maxSize) { |
|
expungeExpiredEntries(); |
|
if (cacheMap.size() > maxSize) { |
|
Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator(); |
|
CacheEntry<K,V> lruEntry = t.next(); |
|
if (DEBUG) { |
|
System.out.println("** Overflow removal " |
|
+ lruEntry.getKey() + " | " + lruEntry.getValue()); |
|
} |
|
t.remove(); |
|
lruEntry.invalidate(); |
|
} |
|
} |
|
} |
|
|
|
public synchronized V get(Object key) { |
|
emptyQueue(); |
|
CacheEntry<K,V> entry = cacheMap.get(key); |
|
if (entry == null) { |
|
return null; |
|
} |
|
long time = (lifetime == 0) ? 0 : System.currentTimeMillis(); |
|
if (entry.isValid(time) == false) { |
|
if (DEBUG) { |
|
System.out.println("Ignoring expired entry"); |
|
} |
|
cacheMap.remove(key); |
|
return null; |
|
} |
|
return entry.getValue(); |
|
} |
|
|
|
public synchronized void remove(Object key) { |
|
emptyQueue(); |
|
CacheEntry<K,V> entry = cacheMap.remove(key); |
|
if (entry != null) { |
|
entry.invalidate(); |
|
} |
|
} |
|
|
|
public synchronized V pull(Object key) { |
|
emptyQueue(); |
|
CacheEntry<K,V> entry = cacheMap.remove(key); |
|
if (entry == null) { |
|
return null; |
|
} |
|
|
|
long time = (lifetime == 0) ? 0 : System.currentTimeMillis(); |
|
if (entry.isValid(time)) { |
|
V value = entry.getValue(); |
|
entry.invalidate(); |
|
return value; |
|
} else { |
|
if (DEBUG) { |
|
System.out.println("Ignoring expired entry"); |
|
} |
|
return null; |
|
} |
|
} |
|
|
|
public synchronized void setCapacity(int size) { |
|
expungeExpiredEntries(); |
|
if (size > 0 && cacheMap.size() > size) { |
|
Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator(); |
|
for (int i = cacheMap.size() - size; i > 0; i--) { |
|
CacheEntry<K,V> lruEntry = t.next(); |
|
if (DEBUG) { |
|
System.out.println("** capacity reset removal " |
|
+ lruEntry.getKey() + " | " + lruEntry.getValue()); |
|
} |
|
t.remove(); |
|
lruEntry.invalidate(); |
|
} |
|
} |
|
|
|
maxSize = size > 0 ? size : 0; |
|
|
|
if (DEBUG) { |
|
System.out.println("** capacity reset to " + size); |
|
} |
|
} |
|
|
|
public synchronized void setTimeout(int timeout) { |
|
emptyQueue(); |
|
lifetime = timeout > 0 ? timeout * 1000L : 0L; |
|
|
|
if (DEBUG) { |
|
System.out.println("** lifetime reset to " + timeout); |
|
} |
|
} |
|
|
|
|
|
public synchronized void accept(CacheVisitor<K,V> visitor) { |
|
expungeExpiredEntries(); |
|
Map<K,V> cached = getCachedEntries(); |
|
|
|
visitor.visit(cached); |
|
} |
|
|
|
private Map<K,V> getCachedEntries() { |
|
Map<K,V> kvmap = new HashMap<>(cacheMap.size()); |
|
|
|
for (CacheEntry<K,V> entry : cacheMap.values()) { |
|
kvmap.put(entry.getKey(), entry.getValue()); |
|
} |
|
|
|
return kvmap; |
|
} |
|
|
|
protected CacheEntry<K,V> newEntry(K key, V value, |
|
long expirationTime, ReferenceQueue<V> queue) { |
|
if (queue != null) { |
|
return new SoftCacheEntry<>(key, value, expirationTime, queue); |
|
} else { |
|
return new HardCacheEntry<>(key, value, expirationTime); |
|
} |
|
} |
|
|
|
private static interface CacheEntry<K,V> { |
|
|
|
boolean isValid(long currentTime); |
|
|
|
void invalidate(); |
|
|
|
K getKey(); |
|
|
|
V getValue(); |
|
|
|
long getExpirationTime(); |
|
} |
|
|
|
private static class HardCacheEntry<K,V> implements CacheEntry<K,V> { |
|
|
|
private K key; |
|
private V value; |
|
private long expirationTime; |
|
|
|
HardCacheEntry(K key, V value, long expirationTime) { |
|
this.key = key; |
|
this.value = value; |
|
this.expirationTime = expirationTime; |
|
} |
|
|
|
public K getKey() { |
|
return key; |
|
} |
|
|
|
public V getValue() { |
|
return value; |
|
} |
|
|
|
public long getExpirationTime() { |
|
return expirationTime; |
|
} |
|
|
|
public boolean isValid(long currentTime) { |
|
boolean valid = (currentTime <= expirationTime); |
|
if (valid == false) { |
|
invalidate(); |
|
} |
|
return valid; |
|
} |
|
|
|
public void invalidate() { |
|
key = null; |
|
value = null; |
|
expirationTime = -1; |
|
} |
|
} |
|
|
|
private static class SoftCacheEntry<K,V> |
|
extends SoftReference<V> |
|
implements CacheEntry<K,V> { |
|
|
|
private K key; |
|
private long expirationTime; |
|
|
|
SoftCacheEntry(K key, V value, long expirationTime, |
|
ReferenceQueue<V> queue) { |
|
super(value, queue); |
|
this.key = key; |
|
this.expirationTime = expirationTime; |
|
} |
|
|
|
public K getKey() { |
|
return key; |
|
} |
|
|
|
public V getValue() { |
|
return get(); |
|
} |
|
|
|
public long getExpirationTime() { |
|
return expirationTime; |
|
} |
|
|
|
public boolean isValid(long currentTime) { |
|
boolean valid = (currentTime <= expirationTime) && (get() != null); |
|
if (valid == false) { |
|
invalidate(); |
|
} |
|
return valid; |
|
} |
|
|
|
public void invalidate() { |
|
clear(); |
|
key = null; |
|
expirationTime = -1; |
|
} |
|
} |
|
|
|
} |