/******************************************************************************* * 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; } }