1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.mina.util;
21
22 import java.util.Collection;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.concurrent.ConcurrentHashMap;
26 import java.util.concurrent.CopyOnWriteArrayList;
27 import java.util.concurrent.locks.ReadWriteLock;
28 import java.util.concurrent.locks.ReentrantReadWriteLock;
29
30
31
32
33
34
35 public class ExpiringMap<K, V> implements Map<K, V> {
36 public static final int DEFAULT_TIME_TO_LIVE = 60;
37
38 public static final int DEFAULT_EXPIRATION_INTERVAL = 1;
39
40 private static volatile int expirerCount = 1;
41
42 private final ConcurrentHashMap<K, ExpiringObject> delegate;
43
44 private final CopyOnWriteArrayList<ExpirationListener> expirationListeners;
45
46 private final Expirer expirer;
47
48 public ExpiringMap() {
49 this(DEFAULT_TIME_TO_LIVE, DEFAULT_EXPIRATION_INTERVAL);
50 }
51
52 public ExpiringMap(int timeToLive) {
53 this(timeToLive, DEFAULT_EXPIRATION_INTERVAL);
54 }
55
56 public ExpiringMap(int timeToLive, int expirationInterval) {
57 this(new ConcurrentHashMap<K, ExpiringObject>(),
58 new CopyOnWriteArrayList<ExpirationListener>(), timeToLive,
59 expirationInterval);
60 }
61
62 private ExpiringMap(ConcurrentHashMap<K, ExpiringObject> delegate,
63 CopyOnWriteArrayList<ExpirationListener> expirationListeners,
64 int timeToLive, int expirationInterval) {
65 this.delegate = delegate;
66 this.expirationListeners = expirationListeners;
67
68 this.expirer = new Expirer();
69 expirer.setTimeToLive(timeToLive);
70 expirer.setExpirationInterval(expirationInterval);
71 }
72
73 public V put(K key, V value) {
74 ExpiringObject answer = delegate.put(key, new ExpiringObject(key,
75 value, System.currentTimeMillis()));
76 if (answer == null) {
77 return null;
78 }
79
80 return answer.getValue();
81 }
82
83 public V get(Object key) {
84 ExpiringObject object = delegate.get(key);
85
86 if (object != null) {
87 object.setLastAccessTime(System.currentTimeMillis());
88
89 return object.getValue();
90 }
91
92 return null;
93 }
94
95 public V remove(Object key) {
96 ExpiringObject answer = delegate.remove(key);
97 if (answer == null) {
98 return null;
99 }
100
101 return answer.getValue();
102 }
103
104 public boolean containsKey(Object key) {
105 return delegate.containsKey(key);
106 }
107
108 public boolean containsValue(Object value) {
109 return delegate.containsValue(value);
110 }
111
112 public int size() {
113 return delegate.size();
114 }
115
116 public boolean isEmpty() {
117 return delegate.isEmpty();
118 }
119
120 public void clear() {
121 delegate.clear();
122 }
123
124 @Override
125 public int hashCode() {
126 return delegate.hashCode();
127 }
128
129 public Set<K> keySet() {
130 return delegate.keySet();
131 }
132
133 @Override
134 public boolean equals(Object obj) {
135 return delegate.equals(obj);
136 }
137
138 public void putAll(Map<? extends K, ? extends V> inMap) {
139 for (Entry<? extends K, ? extends V> e : inMap.entrySet()) {
140 this.put(e.getKey(), e.getValue());
141 }
142 }
143
144 public Collection<V> values() {
145 throw new UnsupportedOperationException();
146 }
147
148 public Set<Map.Entry<K, V>> entrySet() {
149 throw new UnsupportedOperationException();
150 }
151
152 public void addExpirationListener(ExpirationListener<? extends V> listener) {
153 expirationListeners.add(listener);
154 }
155
156 public void removeExpirationListener(
157 ExpirationListener<? extends V> listener) {
158 expirationListeners.remove(listener);
159 }
160
161 public Expirer getExpirer() {
162 return expirer;
163 }
164
165 public int getExpirationInterval() {
166 return expirer.getExpirationInterval();
167 }
168
169 public int getTimeToLive() {
170 return expirer.getTimeToLive();
171 }
172
173 public void setExpirationInterval(int expirationInterval) {
174 expirer.setExpirationInterval(expirationInterval);
175 }
176
177 public void setTimeToLive(int timeToLive) {
178 expirer.setTimeToLive(timeToLive);
179 }
180
181 private class ExpiringObject {
182 private K key;
183
184 private V value;
185
186 private long lastAccessTime;
187
188 private ReadWriteLock lastAccessTimeLock = new ReentrantReadWriteLock();
189
190 ExpiringObject(K key, V value, long lastAccessTime) {
191 if (value == null) {
192 throw new IllegalArgumentException(
193 "An expiring object cannot be null.");
194 }
195
196 this.key = key;
197 this.value = value;
198 this.lastAccessTime = lastAccessTime;
199 }
200
201 public long getLastAccessTime() {
202 lastAccessTimeLock.readLock().lock();
203
204 try {
205 return lastAccessTime;
206 } finally {
207 lastAccessTimeLock.readLock().unlock();
208 }
209 }
210
211 public void setLastAccessTime(long lastAccessTime) {
212 lastAccessTimeLock.writeLock().lock();
213
214 try {
215 this.lastAccessTime = lastAccessTime;
216 } finally {
217 lastAccessTimeLock.writeLock().unlock();
218 }
219 }
220
221 public K getKey() {
222 return key;
223 }
224
225 public V getValue() {
226 return value;
227 }
228
229 @Override
230 public boolean equals(Object obj) {
231 return value.equals(obj);
232 }
233
234 @Override
235 public int hashCode() {
236 return value.hashCode();
237 }
238 }
239
240 public class Expirer implements Runnable {
241 private ReadWriteLock stateLock = new ReentrantReadWriteLock();
242
243 private long timeToLiveMillis;
244
245 private long expirationIntervalMillis;
246
247 private boolean running = false;
248
249 private final Thread expirerThread;
250
251 public Expirer() {
252 expirerThread = new Thread(this, "ExpiringMapExpirer-"
253 + (expirerCount++));
254 expirerThread.setDaemon(true);
255 }
256
257 public void run() {
258 while (running) {
259 processExpires();
260
261 try {
262 Thread.sleep(expirationIntervalMillis);
263 } catch (InterruptedException e) {
264 }
265 }
266 }
267
268 private void processExpires() {
269 long timeNow = System.currentTimeMillis();
270
271 for (ExpiringObject o : delegate.values()) {
272
273 if (timeToLiveMillis <= 0)
274 continue;
275
276 long timeIdle = timeNow - o.getLastAccessTime();
277
278 if (timeIdle >= timeToLiveMillis) {
279 delegate.remove(o.getKey());
280
281 for (ExpirationListener<V> listener : expirationListeners) {
282 listener.expired(o.getValue());
283 }
284 }
285 }
286 }
287
288 public void startExpiring() {
289 stateLock.writeLock().lock();
290
291 try {
292 if (!running) {
293 running = true;
294 expirerThread.start();
295 }
296 } finally {
297 stateLock.writeLock().unlock();
298 }
299 }
300
301 public void startExpiringIfNotStarted() {
302 stateLock.readLock().lock();
303 try {
304 if (running) {
305 return;
306 }
307 } finally {
308 stateLock.readLock().unlock();
309 }
310
311 stateLock.writeLock().lock();
312 try {
313 if (!running) {
314 running = true;
315 expirerThread.start();
316 }
317 } finally {
318 stateLock.writeLock().unlock();
319 }
320 }
321
322 public void stopExpiring() {
323 stateLock.writeLock().lock();
324
325 try {
326 if (running) {
327 running = false;
328 expirerThread.interrupt();
329 }
330 } finally {
331 stateLock.writeLock().unlock();
332 }
333 }
334
335 public boolean isRunning() {
336 stateLock.readLock().lock();
337
338 try {
339 return running;
340 } finally {
341 stateLock.readLock().unlock();
342 }
343 }
344
345 public int getTimeToLive() {
346 stateLock.readLock().lock();
347
348 try {
349 return (int) timeToLiveMillis / 1000;
350 } finally {
351 stateLock.readLock().unlock();
352 }
353 }
354
355 public void setTimeToLive(long timeToLive) {
356 stateLock.writeLock().lock();
357
358 try {
359 this.timeToLiveMillis = timeToLive * 1000;
360 } finally {
361 stateLock.writeLock().unlock();
362 }
363 }
364
365 public int getExpirationInterval() {
366 stateLock.readLock().lock();
367
368 try {
369 return (int) expirationIntervalMillis / 1000;
370 } finally {
371 stateLock.readLock().unlock();
372 }
373 }
374
375 public void setExpirationInterval(long expirationInterval) {
376 stateLock.writeLock().lock();
377
378 try {
379 this.expirationIntervalMillis = expirationInterval * 1000;
380 } finally {
381 stateLock.writeLock().unlock();
382 }
383 }
384 }
385 }