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;
+ }
+
+}