X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.utils.datastructures%2Fsrc%2Forg%2Fsimantics%2Futils%2Fdatastructures%2Fcache%2FSoftTimedCache.java;fp=bundles%2Forg.simantics.utils.datastructures%2Fsrc%2Forg%2Fsimantics%2Futils%2Fdatastructures%2Fcache%2FSoftTimedCache.java;h=40f47526417eb6fe592d975e3a65f271b472a1c3;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/cache/SoftTimedCache.java b/bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/cache/SoftTimedCache.java new file mode 100644 index 000000000..40f475264 --- /dev/null +++ b/bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/cache/SoftTimedCache.java @@ -0,0 +1,223 @@ +/******************************************************************************* + * 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; + } + +}