/*******************************************************************************
* Copyright (c) 2007, 2010 Association for Decentralized Information Management
* in Industry THTH ry.
* 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:
* VTT Technical Research Centre of Finland - initial API and implementation
*******************************************************************************/
package org.simantics.utils.datastructures.cache;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.simantics.utils.threads.ThreadUtils;
/**
* A timed (Key, Value) cache which disposes the cached entry after the
* specified amount of time if it is not removed from the cache. The hold time
* is given to the {@link #put(Object, Object, long)} method, separately for
* each value.
*
*
* The cached values are held as soft references, which makes them collectible
* under memory pressure, even before their hold time has ran out.
*
* @author Tuukka Lehtonen
*
* @param key type, held by strong references
* @param value type, held by soft references to allow collection of the
* cached elements when under memory pressure
*/
public class SoftTimedCache implements ITimedCache {
protected class Entry {
final K key;
final SoftReference ref;
long holdTime;
TimeUnit unit;
ScheduledFuture> future;
Entry(K k, V v, long holdTime, TimeUnit unit) {
assert k != null;
assert v != null;
this.key = k;
this.ref = new SoftReference(v);
this.holdTime = holdTime;
this.unit = unit;
}
}
private final Map cache = Collections.synchronizedMap(new HashMap());
@SuppressWarnings("unused")
private String name;
private Timer timer;
public SoftTimedCache() {
this("Cache Timer");
}
public SoftTimedCache(String name) {
this.name = name;
}
public int size() {
return cache.size();
}
@Override
protected void finalize() throws Throwable {
if (timer != null) {
timer.cancel();
}
clear();
super.finalize();
}
public synchronized void clear() {
Object[] entries;
synchronized (this) {
entries = cache.values().toArray();
cache.clear();
}
for (Object o : entries) {
@SuppressWarnings("unchecked")
Entry e = (Entry) o;
V v = e.ref.get();
e.ref.clear();
cleanup(e);
disposeValue(v);
}
}
@Override
public void put(final K k, V v, long holdTime, TimeUnit unit) {
Entry e = new Entry(k, v, holdTime, unit);
synchronized (this) {
// First dispose of a previous entry.
dispose(k);
// Then cache the new one.
cache.put(k, e);
if (unit != null && holdTime > 0) {
schedule(e);
}
}
}
@Override
public V release(K k) {
Entry e;
synchronized (this) {
e = cache.remove(k);
}
if (e == null)
return null;
return cleanup(e);
}
private V cleanup(Entry e) {
if (e.future != null) {
if (!e.future.isCancelled()) {
boolean ret = e.future.cancel(false);
if (ret == false)
// Already disposing of this cached entry, let it be.
return null;
}
}
return e.ref.get();
}
private void dispose(K k) {
Entry e;
synchronized (this) {
e = cache.remove(k);
}
if (e == null)
// Has been released.
return;
V v = e.ref.get();
if (v != null) {
if (e.future != null)
e.future.cancel(false);
e.ref.clear();
disposeValue(v);
}
}
void schedule(final Entry e) {
e.future = ThreadUtils.getNonBlockingWorkExecutor().schedule(new Runnable() {
@Override
public void run() {
// Disposal may block, must transfer to blocking work executor.
ThreadUtils.getBlockingWorkExecutor().execute(new Runnable() {
@Override
public void run() {
dispose(e.key);
}
});
}
}, e.holdTime, e.unit);
}
/**
* Override to customize disposal of values when their timer elapses.
*
* @param v the value to dispose of
*/
protected void disposeValue(V v) {
// Do nothing by default.
}
public class CacheEntry {
private final Entry e;
private final V value;
public CacheEntry(Entry e) {
this.e = e;
this.value = e.ref.get();
}
public K getKey() {
return e.key;
}
public V getValue() {
return value;
}
public boolean disposeScheduled() {
return e.future != null;
}
public void schedule(long holdTime, TimeUnit unit) {
if (e.future == null) {
e.holdTime = holdTime;
e.unit = unit;
SoftTimedCache.this.schedule(e);
}
}
}
public Collection getEntries() {
ArrayList result = new ArrayList();
synchronized (this) {
for (Entry e : cache.values()) {
result.add(new CacheEntry(e));
}
}
return result;
}
}