]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/cache/SoftTimedCache.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.utils.datastructures / src / org / simantics / utils / datastructures / cache / SoftTimedCache.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.utils.datastructures.cache;
13
14 import java.lang.ref.SoftReference;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.HashMap;
19 import java.util.Map;
20 import java.util.Timer;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23
24 import org.simantics.utils.threads.ThreadUtils;
25
26 /**
27  * A timed (Key, Value) cache which disposes the cached entry after the
28  * specified amount of time if it is not removed from the cache. The hold time
29  * is given to the {@link #put(Object, Object, long)} method, separately for
30  * each value.
31  * 
32  * <p>
33  * The cached values are held as soft references, which makes them collectible
34  * under memory pressure, even before their hold time has ran out.
35  * 
36  * @author Tuukka Lehtonen
37  * 
38  * @param <K> key type, held by strong references
39  * @param <V> value type, held by soft references to allow collection of the
40  *        cached elements when under memory pressure
41  */
42 public class SoftTimedCache<K, V> implements ITimedCache<K, V> {
43
44     protected class Entry {
45         final K key;
46         final SoftReference<V> ref;
47         long holdTime;
48         TimeUnit unit;
49
50         ScheduledFuture<?> future;
51
52         Entry(K k, V v, long holdTime, TimeUnit unit) {
53             assert k != null;
54             assert v != null;
55
56             this.key = k;
57             this.ref = new SoftReference<V>(v);
58             this.holdTime = holdTime;
59             this.unit = unit;
60         }
61     }
62
63     private final Map<K, Entry> cache = Collections.synchronizedMap(new HashMap<K, Entry>());
64
65     @SuppressWarnings("unused")
66     private String name;
67
68     private Timer timer;
69
70     public SoftTimedCache() {
71         this("Cache Timer");
72     }
73
74     public SoftTimedCache(String name) {
75         this.name = name;
76     }
77
78     public int size() {
79         return cache.size();
80     }
81
82     @Override
83     protected void finalize() throws Throwable {
84         if (timer != null) {
85             timer.cancel();
86         }
87         clear();
88         super.finalize();
89     }
90
91     public synchronized void clear() {
92         Object[] entries;
93         synchronized (this) {
94             entries = cache.values().toArray();
95             cache.clear();
96         }
97         for (Object o : entries) {
98             @SuppressWarnings("unchecked")
99             Entry e = (Entry) o;
100             V v = e.ref.get();
101             e.ref.clear();
102             cleanup(e);
103             disposeValue(v);
104         }
105     }
106
107     @Override
108     public void put(final K k, V v, long holdTime, TimeUnit unit) {
109         Entry e = new Entry(k, v, holdTime, unit);
110         synchronized (this) {
111             // First dispose of a previous entry.
112             dispose(k);
113             // Then cache the new one.
114             cache.put(k, e);
115
116             if (unit != null && holdTime > 0) {
117                 schedule(e);
118             }
119         }
120     }
121
122     @Override
123     public V release(K k) {
124         Entry e;
125         synchronized (this) {
126             e = cache.remove(k);
127         }
128         if (e == null)
129             return null;
130         return cleanup(e);
131     }
132
133     private V cleanup(Entry e) {
134         if (e.future != null) {
135             if (!e.future.isCancelled()) {
136                 boolean ret = e.future.cancel(false);
137                 if (ret == false)
138                     // Already disposing of this cached entry, let it be.
139                     return null;
140             }
141         }
142         return e.ref.get();
143     }
144
145     private void dispose(K k) {
146         Entry e;
147         synchronized (this) {
148             e = cache.remove(k);
149         }
150         if (e == null)
151             // Has been released.
152             return;
153
154         V v = e.ref.get();
155         if (v != null) {
156             if (e.future != null)
157                 e.future.cancel(false);
158             e.ref.clear();
159             disposeValue(v);
160         }
161     }
162
163     void schedule(final Entry e) {
164         e.future = ThreadUtils.getNonBlockingWorkExecutor().schedule(new Runnable() {
165             @Override
166             public void run() {
167                 // Disposal may block, must transfer to blocking work executor.
168                 ThreadUtils.getBlockingWorkExecutor().execute(new Runnable() {
169                     @Override
170                     public void run() {
171                         dispose(e.key);
172                     }
173                 });
174             }
175         }, e.holdTime, e.unit);
176     }
177
178
179     /**
180      * Override to customize disposal of values when their timer elapses.
181      * 
182      * @param v the value to dispose of
183      */
184     protected void disposeValue(V v) {
185         // Do nothing by default.
186     }
187
188     public class CacheEntry {
189         private final Entry e;
190         private final V value;
191         public CacheEntry(Entry e) {
192             this.e = e;
193             this.value = e.ref.get();
194         }
195         public K getKey() {
196             return e.key;
197         }
198         public V getValue() {
199             return value;
200         }
201         public boolean disposeScheduled() {
202             return e.future != null;
203         }
204         public void schedule(long holdTime, TimeUnit unit) {
205             if (e.future == null) {
206                 e.holdTime = holdTime;
207                 e.unit = unit;
208                 SoftTimedCache.this.schedule(e);
209             }
210         }
211     }
212
213     public Collection<CacheEntry> getEntries() {
214         ArrayList<CacheEntry> result = new ArrayList<CacheEntry>();
215         synchronized (this) {
216             for (Entry e : cache.values()) {
217                 result.add(new CacheEntry(e));
218             }
219         }
220         return result;
221     }
222
223 }