blob: be02eafc8a47347be1a6dc9ba585a842bfef6edf [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2015 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.core.internal.utils;
import org.eclipse.core.runtime.Assert;
/**
* A cache that keeps a collection of at most maximumCapacity+threshold entries.
* When the number of entries exceeds that limit, least recently used entries are removed
* so the current size is the same as the maximum capacity.
*/
public class Cache {
public class Entry implements KeyedHashSet.KeyedElement {
Object cached;
Object key;
Entry next;
Entry previous;
long timestamp;
public Entry(Object key, Object cached, long timestamp) {
this.key = key;
this.cached = cached;
this.timestamp = timestamp;
}
@Override
public boolean compare(KeyedHashSet.KeyedElement other) {
if (!(other instanceof Entry))
return false;
Entry otherEntry = (Entry) other;
return key.equals(otherEntry.key);
}
/* Removes this entry from the cache */
public void discard() {
unchain();
cached = null;
entries.remove(this);
}
public Object getCached() {
return cached;
}
@Override
public Object getKey() {
return key;
}
@Override
public int getKeyHashCode() {
return key.hashCode();
}
public Entry getNext() {
return next;
}
public Entry getPrevious() {
return previous;
}
public long getTimestamp() {
return timestamp;
}
public boolean isHead() {
return previous == null;
}
public boolean isTail() {
return next == null;
}
/* Inserts into the head of the list */
void makeHead() {
Entry oldHead = head;
head = this;
next = oldHead;
previous = null;
if (oldHead == null)
tail = this;
else
oldHead.previous = this;
}
public void setCached(Object cached) {
this.cached = cached;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
@Override
public String toString() {
return key + " -> " + cached + " [" + timestamp + ']'; //$NON-NLS-1$ //$NON-NLS-2$
}
/* Removes from the linked list, but not from the entries collection */
void unchain() {
// it may be in the tail
if (tail == this)
tail = previous;
else
next.previous = previous;
// it may be in the head
if (head == this)
head = next;
else
previous.next = next;
}
}
KeyedHashSet entries;
Entry head;
private int maximumCapacity;
Entry tail;
private double threshold;
public Cache(int maximumCapacity) {
this(Math.min(KeyedHashSet.MINIMUM_SIZE, maximumCapacity), maximumCapacity, 0.25);
}
public Cache(int initialCapacity, int maximumCapacity, double threshold) {
Assert.isTrue(maximumCapacity >= initialCapacity, "maximum capacity < initial capacity"); //$NON-NLS-1$
Assert.isTrue(threshold >= 0 && threshold <= 1, "threshold should be between 0 and 1"); //$NON-NLS-1$
Assert.isTrue(initialCapacity > 0, "initial capacity must be greater than zero"); //$NON-NLS-1$
entries = new KeyedHashSet(initialCapacity);
this.maximumCapacity = maximumCapacity;
this.threshold = threshold;
}
public void addEntry(Object key, Object toCache) {
addEntry(key, toCache, 0);
}
public Entry addEntry(Object key, Object toCache, long timestamp) {
Entry newHead = (Entry) entries.getByKey(key);
if (newHead == null)
entries.add(newHead = new Entry(key, toCache, timestamp));
newHead.cached = toCache;
newHead.timestamp = timestamp;
newHead.makeHead();
int extraEntries = entries.size() - maximumCapacity;
if (extraEntries > maximumCapacity * threshold)
// we have reached our limit - ensure we are under the maximum capacity
// by discarding older entries
packEntries(extraEntries);
return newHead;
}
public Entry getEntry(Object key) {
return getEntry(key, true);
}
public Entry getEntry(Object key, boolean update) {
Entry existing = (Entry) entries.getByKey(key);
if (existing == null)
return null;
if (!update)
return existing;
existing.unchain();
existing.makeHead();
return existing;
}
public Entry getHead() {
return head;
}
public Entry getTail() {
return tail;
}
private void packEntries(int extraEntries) {
// should remove in an ad-hoc way to get better performance
Entry current = tail;
for (; current != null && extraEntries > 0; extraEntries--) {
current.discard();
current = current.previous;
}
}
public long size() {
return entries.size();
}
public void discardAll() {
entries.clear();
head = tail = null;
}
public void dispose() {
discardAll();
entries = null;
head = tail = null;
}
}