mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 06:45:07 +02:00
785 lines
No EOL
23 KiB
Java
785 lines
No EOL
23 KiB
Java
/*
|
|
* Copyright (c) 2002, 2024, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
package sun.security.util;
|
|
|
|
import javax.net.ssl.SSLSession;
|
|
import java.lang.ref.ReferenceQueue;
|
|
import java.lang.ref.SoftReference;
|
|
import java.util.*;
|
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
/**
|
|
* Abstract base class and factory for caches. A cache is a key-value mapping.
|
|
* It has properties that make it more suitable for caching than a Map.
|
|
*
|
|
* The factory methods can be used to obtain two different implementations.
|
|
* They have the following properties:
|
|
*
|
|
* . keys and values reside in memory
|
|
*
|
|
* . keys and values must be non-null
|
|
*
|
|
* . maximum size. Replacements are made in LRU order.
|
|
*
|
|
* . optional lifetime, specified in seconds.
|
|
*
|
|
* . safe for concurrent use by multiple threads
|
|
*
|
|
* . values are held by either standard references or via SoftReferences.
|
|
* SoftReferences have the advantage that they are automatically cleared
|
|
* by the garbage collector in response to memory demand. This makes it
|
|
* possible to simple set the maximum size to a very large value and let
|
|
* the GC automatically size the cache dynamically depending on the
|
|
* amount of available memory.
|
|
*
|
|
* However, note that because of the way SoftReferences are implemented in
|
|
* HotSpot at the moment, this may not work perfectly as it clears them fairly
|
|
* eagerly. Performance may be improved if the Java heap size is set to larger
|
|
* value using e.g. java -Xms64M -Xmx128M foo.Test
|
|
*
|
|
* Cache sizing: the memory cache is implemented on top of a LinkedHashMap.
|
|
* In its current implementation, the number of buckets (NOT entries) in
|
|
* (Linked)HashMaps is always a power of two. It is recommended to set the
|
|
* maximum cache size to value that uses those buckets fully. For example,
|
|
* if a cache with somewhere between 500 and 1000 entries is desired, a
|
|
* maximum size of 750 would be a good choice: try 1024 buckets, with a
|
|
* load factor of 0.75f, the number of entries can be calculated as
|
|
* buckets / 4 * 3. As mentioned above, with a SoftReference cache, it is
|
|
* generally reasonable to set the size to a fairly large value.
|
|
*
|
|
* @author Andreas Sterbenz
|
|
*/
|
|
public abstract class Cache<K,V> {
|
|
|
|
protected Cache() {
|
|
// empty
|
|
}
|
|
|
|
/**
|
|
* Return the number of currently valid entries in the cache.
|
|
*/
|
|
public abstract int size();
|
|
|
|
/**
|
|
* Remove all entries from the cache.
|
|
*/
|
|
public abstract void clear();
|
|
|
|
/**
|
|
* Add an entry to the cache.
|
|
*/
|
|
public abstract void put(K key, V value);
|
|
|
|
/**
|
|
* Add V to the cache with the option to use a QueueCacheEntry if the
|
|
* cache is configured for it. If the cache is not configured for a queue,
|
|
* V will silently add the entry directly.
|
|
*/
|
|
public void put(K key, V value, boolean canQueue) {
|
|
put(key, value);
|
|
}
|
|
|
|
/**
|
|
* Get a value from the cache.
|
|
*/
|
|
public abstract V get(Object key);
|
|
|
|
/**
|
|
* Remove an entry from the cache.
|
|
*/
|
|
public abstract void remove(Object key);
|
|
|
|
/**
|
|
* Pull an entry from the cache.
|
|
*/
|
|
public abstract V pull(Object key);
|
|
|
|
/**
|
|
* Set the maximum size.
|
|
*/
|
|
public abstract void setCapacity(int size);
|
|
|
|
/**
|
|
* Set the timeout(in seconds).
|
|
*/
|
|
public abstract void setTimeout(int timeout);
|
|
|
|
/**
|
|
* accept a visitor
|
|
*/
|
|
public abstract void accept(CacheVisitor<K,V> visitor);
|
|
|
|
/**
|
|
* Return a new memory cache with the specified maximum size, unlimited
|
|
* lifetime for entries, with the values held by SoftReferences.
|
|
*/
|
|
public static <K,V> Cache<K,V> newSoftMemoryCache(int size) {
|
|
return new MemoryCache<>(true, size);
|
|
}
|
|
|
|
/**
|
|
* Return a new memory cache with the specified maximum size, the
|
|
* specified maximum lifetime (in seconds), with the values held
|
|
* by SoftReferences.
|
|
*/
|
|
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> newSoftMemoryQueue(int size, int timeout,
|
|
int maxQueueSize) {
|
|
return new MemoryCache<>(true, size, timeout, maxQueueSize);
|
|
}
|
|
|
|
/**
|
|
* Return a new memory cache with the specified maximum size, unlimited
|
|
* lifetime for entries, with the values held by standard references.
|
|
*/
|
|
public static <K,V> Cache<K,V> newHardMemoryCache(int size) {
|
|
return new MemoryCache<>(false, size);
|
|
}
|
|
|
|
/**
|
|
* Return a dummy cache that does nothing.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public static <K,V> Cache<K,V> newNullCache() {
|
|
return (Cache<K,V>) NullCache.INSTANCE;
|
|
}
|
|
|
|
/**
|
|
* Return a new memory cache with the specified maximum size, the
|
|
* specified maximum lifetime (in seconds), with the values held
|
|
* by standard references.
|
|
*/
|
|
public static <K,V> Cache<K,V> newHardMemoryCache(int size, int timeout) {
|
|
return new MemoryCache<>(false, size, timeout);
|
|
}
|
|
|
|
/**
|
|
* Utility class that wraps a byte array and implements the equals()
|
|
* and hashCode() contract in a way suitable for Maps and caches.
|
|
*/
|
|
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 other)) {
|
|
return false;
|
|
}
|
|
return Arrays.equals(this.b, other.b);
|
|
}
|
|
}
|
|
|
|
public interface CacheVisitor<K,V> {
|
|
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> {
|
|
|
|
// Debugging
|
|
private static final boolean DEBUG = false;
|
|
|
|
private final Map<K, CacheEntry<K,V>> cacheMap;
|
|
private int maxSize;
|
|
final private int maxQueueSize;
|
|
private long lifetime;
|
|
private long nextExpirationTime = Long.MAX_VALUE;
|
|
|
|
// ReferenceQueue is of type V instead of Cache<K,V>
|
|
// to allow SoftCacheEntry to extend SoftReference<V>
|
|
private final ReferenceQueue<V> queue;
|
|
|
|
public MemoryCache(boolean soft, int maxSize) {
|
|
this(soft, maxSize, 0, 0);
|
|
}
|
|
|
|
public MemoryCache(boolean soft, int maxSize, int lifetime) {
|
|
this(soft, maxSize, lifetime, 0);
|
|
}
|
|
|
|
public MemoryCache(boolean soft, int maxSize, int lifetime, int qSize) {
|
|
this.maxSize = maxSize;
|
|
this.maxQueueSize = qSize;
|
|
this.lifetime = lifetime * 1000L;
|
|
if (soft) {
|
|
this.queue = new ReferenceQueue<>();
|
|
} else {
|
|
this.queue = null;
|
|
}
|
|
// LinkedHashMap is needed for its access order. 0.75f load factor is
|
|
// default.
|
|
cacheMap = new LinkedHashMap<>(1, 0.75f, true);
|
|
}
|
|
|
|
/**
|
|
* Empty the reference queue and remove all corresponding entries
|
|
* from the cache.
|
|
*
|
|
* This method should be called at the beginning of each public
|
|
* method.
|
|
*/
|
|
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) {
|
|
// key is null, entry has already been removed
|
|
continue;
|
|
}
|
|
CacheEntry<K,V> currentEntry = cacheMap.remove(key);
|
|
// check if the entry in the map corresponds to the expired
|
|
// entry. If not, re-add the entry
|
|
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");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scan all entries and remove all expired ones.
|
|
*/
|
|
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)) {
|
|
t.remove();
|
|
cnt++;
|
|
} else if (nextExpirationTime > entry.getExpirationTime()) {
|
|
nextExpirationTime = entry.getExpirationTime();
|
|
// If this is a queue, check for some expired entries
|
|
if (entry instanceof QueueCacheEntry<K,V> qe) {
|
|
qe.getQueue().removeIf(e -> !e.isValid(time));
|
|
}
|
|
}
|
|
}
|
|
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
|
|
// entries so that GC does not have to enqueue them
|
|
for (CacheEntry<K,V> entry : cacheMap.values()) {
|
|
entry.invalidate();
|
|
}
|
|
while (queue.poll() != null) {
|
|
// empty
|
|
}
|
|
}
|
|
cacheMap.clear();
|
|
}
|
|
|
|
public void put(K key, V value) {
|
|
put(key, value, false);
|
|
}
|
|
|
|
/**
|
|
* This puts an entry into the cacheMap.
|
|
*
|
|
* If canQueue is true, V will be added using a QueueCacheEntry which
|
|
* is added to cacheMap. If false, V is added to the cacheMap directly.
|
|
* The caller must keep a consistent canQueue value, mixing them can
|
|
* result in a queue being replaced with a single entry.
|
|
*
|
|
* This method is synchronized to avoid multiple QueueCacheEntry
|
|
* overwriting the same key.
|
|
*
|
|
* @param key key to the cacheMap
|
|
* @param value value to be stored
|
|
* @param canQueue can the value be put into a QueueCacheEntry
|
|
*/
|
|
public synchronized void put(K key, V value, boolean canQueue) {
|
|
emptyQueue();
|
|
long expirationTime =
|
|
(lifetime == 0) ? 0 : System.currentTimeMillis() + lifetime;
|
|
if (expirationTime < nextExpirationTime) {
|
|
nextExpirationTime = expirationTime;
|
|
}
|
|
CacheEntry<K,V> newEntry = newEntry(key, value, expirationTime, queue);
|
|
if (maxQueueSize == 0 || !canQueue) {
|
|
CacheEntry<K,V> oldEntry = cacheMap.put(key, newEntry);
|
|
if (oldEntry != null) {
|
|
oldEntry.invalidate();
|
|
}
|
|
} else {
|
|
CacheEntry<K, V> entry = cacheMap.get(key);
|
|
switch (entry) {
|
|
case QueueCacheEntry<K, V> qe -> {
|
|
qe.putValue(newEntry);
|
|
if (DEBUG) {
|
|
System.out.println("QueueCacheEntry= " + qe);
|
|
final AtomicInteger i = new AtomicInteger(1);
|
|
qe.queue.stream().forEach(e ->
|
|
System.out.println(i.getAndIncrement() + "= " + e));
|
|
}
|
|
}
|
|
case null, default ->
|
|
cacheMap.put(key, new QueueCacheEntry<>(key, newEntry,
|
|
expirationTime, maxQueueSize));
|
|
}
|
|
|
|
if (DEBUG) {
|
|
System.out.println("Cache entry added: key=" +
|
|
key.toString() + ", class=" +
|
|
(entry != null ? entry.getClass().getName() : null));
|
|
}
|
|
}
|
|
if (maxSize > 0 && cacheMap.size() > maxSize) {
|
|
expungeExpiredEntries();
|
|
if (cacheMap.size() > maxSize) { // still too large?
|
|
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;
|
|
}
|
|
|
|
if (lifetime > 0 && !entry.isValid(System.currentTimeMillis())) {
|
|
cacheMap.remove(key);
|
|
if (DEBUG) {
|
|
System.out.println("Ignoring expired entry");
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// If the value is a queue, return a queue entry.
|
|
if (entry instanceof QueueCacheEntry<K, V> qe) {
|
|
V result = qe.getValue(lifetime);
|
|
if (qe.isEmpty()) {
|
|
removeImpl(key);
|
|
}
|
|
return result;
|
|
}
|
|
return entry.getValue();
|
|
}
|
|
|
|
public synchronized void remove(Object key) {
|
|
emptyQueue();
|
|
removeImpl(key);
|
|
}
|
|
|
|
private void removeImpl(Object key) {
|
|
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 = Math.max(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);
|
|
}
|
|
}
|
|
|
|
// it is a heavyweight method.
|
|
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 = HashMap.newHashMap(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 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) {
|
|
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) {
|
|
invalidate();
|
|
}
|
|
return valid;
|
|
}
|
|
|
|
public void invalidate() {
|
|
clear();
|
|
key = null;
|
|
expirationTime = -1;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
if (get() instanceof SSLSession se)
|
|
return HexFormat.of().formatHex(se.getId());
|
|
return super.toString();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This CacheEntry<K,V> type allows multiple V entries to be stored in
|
|
* one key in the cacheMap.
|
|
*
|
|
* This implementation is need for TLS clients that receive multiple
|
|
* PSKs or NewSessionTickets for server resumption.
|
|
*/
|
|
private static class QueueCacheEntry<K,V> implements CacheEntry<K,V> {
|
|
|
|
// Limit the number of queue entries.
|
|
private final int MAXQUEUESIZE;
|
|
|
|
final boolean DEBUG = false;
|
|
private K key;
|
|
private long expirationTime;
|
|
final Queue<CacheEntry<K,V>> queue = new ConcurrentLinkedQueue<>();
|
|
|
|
QueueCacheEntry(K key, CacheEntry<K,V> entry, long expirationTime,
|
|
int maxSize) {
|
|
this.key = key;
|
|
this.expirationTime = expirationTime;
|
|
this.MAXQUEUESIZE = maxSize;
|
|
queue.add(entry);
|
|
}
|
|
|
|
public K getKey() {
|
|
return key;
|
|
}
|
|
|
|
public V getValue() {
|
|
return getValue(0);
|
|
}
|
|
|
|
public V getValue(long lifetime) {
|
|
long time = (lifetime == 0) ? 0 : System.currentTimeMillis();
|
|
do {
|
|
var entry = queue.poll();
|
|
if (entry == null) {
|
|
return null;
|
|
}
|
|
if (entry.isValid(time)) {
|
|
return entry.getValue();
|
|
}
|
|
entry.invalidate();
|
|
} while (!queue.isEmpty());
|
|
|
|
return null;
|
|
}
|
|
|
|
public long getExpirationTime() {
|
|
return expirationTime;
|
|
}
|
|
|
|
public void setExpirationTime(long time) {
|
|
expirationTime = time;
|
|
}
|
|
|
|
public void putValue(CacheEntry<K,V> entry) {
|
|
if (DEBUG) {
|
|
System.out.println("Added to queue (size=" + queue.size() +
|
|
"): " + entry.getKey().toString() + ", " + entry);
|
|
}
|
|
// Update the cache entry's expiration time to the latest entry.
|
|
// The getValue() calls will remove expired tickets.
|
|
expirationTime = entry.getExpirationTime();
|
|
// Limit the number of queue entries, removing the oldest.
|
|
if (queue.size() >= MAXQUEUESIZE) {
|
|
queue.remove();
|
|
}
|
|
queue.add(entry);
|
|
}
|
|
|
|
public boolean isValid(long currentTime) {
|
|
boolean valid = (currentTime <= expirationTime) && !queue.isEmpty();
|
|
if (!valid) {
|
|
invalidate();
|
|
}
|
|
return valid;
|
|
}
|
|
|
|
public boolean isValid() {
|
|
return isValid(System.currentTimeMillis());
|
|
}
|
|
|
|
public void invalidate() {
|
|
clear();
|
|
key = null;
|
|
expirationTime = -1;
|
|
}
|
|
|
|
public void clear() {
|
|
queue.forEach(CacheEntry::invalidate);
|
|
queue.clear();
|
|
}
|
|
|
|
public boolean isEmpty() {
|
|
return queue.isEmpty();
|
|
}
|
|
|
|
public Queue<CacheEntry<K,V>> getQueue() {
|
|
return queue;
|
|
}
|
|
}
|
|
} |