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