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
36
37 public class ExpiringMap<K, V> implements Map<K, V> {
38
39
40
41
42 public static final int DEFAULT_TIME_TO_LIVE = 60;
43
44
45
46
47 public static final int DEFAULT_EXPIRATION_INTERVAL = 1;
48
49 private static volatile int expirerCount = 1;
50
51 private final ConcurrentHashMap<K, ExpiringObject> delegate;
52
53 private final CopyOnWriteArrayList<ExpirationListener<V>> expirationListeners;
54
55 private final Expirer expirer;
56
57
58
59
60
61
62 public ExpiringMap() {
63 this(DEFAULT_TIME_TO_LIVE, DEFAULT_EXPIRATION_INTERVAL);
64 }
65
66
67
68
69
70
71
72
73 public ExpiringMap(int timeToLive) {
74 this(timeToLive, DEFAULT_EXPIRATION_INTERVAL);
75 }
76
77
78
79
80
81
82
83
84
85
86 public ExpiringMap(int timeToLive, int expirationInterval) {
87 this(new ConcurrentHashMap<K, ExpiringObject>(),
88 new CopyOnWriteArrayList<ExpirationListener<V>>(), timeToLive,
89 expirationInterval);
90 }
91
92 private ExpiringMap(ConcurrentHashMap<K, ExpiringObject> delegate,
93 CopyOnWriteArrayList<ExpirationListener<V>> expirationListeners,
94 int timeToLive, int expirationInterval) {
95 this.delegate = delegate;
96 this.expirationListeners = expirationListeners;
97
98 this.expirer = new Expirer();
99 expirer.setTimeToLive(timeToLive);
100 expirer.setExpirationInterval(expirationInterval);
101 }
102
103 public V put(K key, V value) {
104 ExpiringObject answer = delegate.put(key, new ExpiringObject(key,
105 value, System.currentTimeMillis()));
106 if (answer == null) {
107 return null;
108 }
109
110 return answer.getValue();
111 }
112
113 public V get(Object key) {
114 ExpiringObject object = delegate.get(key);
115
116 if (object != null) {
117 object.setLastAccessTime(System.currentTimeMillis());
118
119 return object.getValue();
120 }
121
122 return null;
123 }
124
125 public V remove(Object key) {
126 ExpiringObject answer = delegate.remove(key);
127 if (answer == null) {
128 return null;
129 }
130
131 return answer.getValue();
132 }
133
134 public boolean containsKey(Object key) {
135 return delegate.containsKey(key);
136 }
137
138 public boolean containsValue(Object value) {
139 return delegate.containsValue(value);
140 }
141
142 public int size() {
143 return delegate.size();
144 }
145
146 public boolean isEmpty() {
147 return delegate.isEmpty();
148 }
149
150 public void clear() {
151 delegate.clear();
152 }
153
154 @Override
155 public int hashCode() {
156 return delegate.hashCode();
157 }
158
159 public Set<K> keySet() {
160 return delegate.keySet();
161 }
162
163 @Override
164 public boolean equals(Object obj) {
165 return delegate.equals(obj);
166 }
167
168 public void putAll(Map<? extends K, ? extends V> inMap) {
169 for (Entry<? extends K, ? extends V> e : inMap.entrySet()) {
170 this.put(e.getKey(), e.getValue());
171 }
172 }
173
174 public Collection<V> values() {
175 throw new UnsupportedOperationException();
176 }
177
178 public Set<Map.Entry<K, V>> entrySet() {
179 throw new UnsupportedOperationException();
180 }
181
182 public void addExpirationListener(ExpirationListener<V> listener) {
183 expirationListeners.add(listener);
184 }
185
186 public void removeExpirationListener(
187 ExpirationListener<V> listener) {
188 expirationListeners.remove(listener);
189 }
190
191 public Expirer getExpirer() {
192 return expirer;
193 }
194
195 public int getExpirationInterval() {
196 return expirer.getExpirationInterval();
197 }
198
199 public int getTimeToLive() {
200 return expirer.getTimeToLive();
201 }
202
203 public void setExpirationInterval(int expirationInterval) {
204 expirer.setExpirationInterval(expirationInterval);
205 }
206
207 public void setTimeToLive(int timeToLive) {
208 expirer.setTimeToLive(timeToLive);
209 }
210
211 private class ExpiringObject {
212 private K key;
213
214 private V value;
215
216 private long lastAccessTime;
217
218 private final ReadWriteLock lastAccessTimeLock = new ReentrantReadWriteLock();
219
220 ExpiringObject(K key, V value, long lastAccessTime) {
221 if (value == null) {
222 throw new IllegalArgumentException(
223 "An expiring object cannot be null.");
224 }
225
226 this.key = key;
227 this.value = value;
228 this.lastAccessTime = lastAccessTime;
229 }
230
231 public long getLastAccessTime() {
232 lastAccessTimeLock.readLock().lock();
233
234 try {
235 return lastAccessTime;
236 } finally {
237 lastAccessTimeLock.readLock().unlock();
238 }
239 }
240
241 public void setLastAccessTime(long lastAccessTime) {
242 lastAccessTimeLock.writeLock().lock();
243
244 try {
245 this.lastAccessTime = lastAccessTime;
246 } finally {
247 lastAccessTimeLock.writeLock().unlock();
248 }
249 }
250
251 public K getKey() {
252 return key;
253 }
254
255 public V getValue() {
256 return value;
257 }
258
259 @Override
260 public boolean equals(Object obj) {
261 return value.equals(obj);
262 }
263
264 @Override
265 public int hashCode() {
266 return value.hashCode();
267 }
268 }
269
270
271
272
273
274
275 public class Expirer implements Runnable {
276 private final ReadWriteLock stateLock = new ReentrantReadWriteLock();
277
278 private long timeToLiveMillis;
279
280 private long expirationIntervalMillis;
281
282 private boolean running = false;
283
284 private final Thread expirerThread;
285
286
287
288
289
290 public Expirer() {
291 expirerThread = new Thread(this, "ExpiringMapExpirer-"
292 + expirerCount++);
293 expirerThread.setDaemon(true);
294 }
295
296 public void run() {
297 while (running) {
298 processExpires();
299
300 try {
301 Thread.sleep(expirationIntervalMillis);
302 } catch (InterruptedException e) {
303
304 }
305 }
306 }
307
308 private void processExpires() {
309 long timeNow = System.currentTimeMillis();
310
311 for (ExpiringObject o : delegate.values()) {
312
313 if (timeToLiveMillis <= 0) {
314 continue;
315 }
316
317 long timeIdle = timeNow - o.getLastAccessTime();
318
319 if (timeIdle >= timeToLiveMillis) {
320 delegate.remove(o.getKey());
321
322 for (ExpirationListener<V> listener : expirationListeners) {
323 listener.expired(o.getValue());
324 }
325 }
326 }
327 }
328
329
330
331
332
333 public void startExpiring() {
334 stateLock.writeLock().lock();
335
336 try {
337 if (!running) {
338 running = true;
339 expirerThread.start();
340 }
341 } finally {
342 stateLock.writeLock().unlock();
343 }
344 }
345
346
347
348
349
350 public void startExpiringIfNotStarted() {
351 stateLock.readLock().lock();
352 try {
353 if (running) {
354 return;
355 }
356 } finally {
357 stateLock.readLock().unlock();
358 }
359
360 stateLock.writeLock().lock();
361 try {
362 if (!running) {
363 running = true;
364 expirerThread.start();
365 }
366 } finally {
367 stateLock.writeLock().unlock();
368 }
369 }
370
371
372
373
374 public void stopExpiring() {
375 stateLock.writeLock().lock();
376
377 try {
378 if (running) {
379 running = false;
380 expirerThread.interrupt();
381 }
382 } finally {
383 stateLock.writeLock().unlock();
384 }
385 }
386
387
388
389
390
391
392
393 public boolean isRunning() {
394 stateLock.readLock().lock();
395
396 try {
397 return running;
398 } finally {
399 stateLock.readLock().unlock();
400 }
401 }
402
403
404
405
406
407
408
409 public int getTimeToLive() {
410 stateLock.readLock().lock();
411
412 try {
413 return (int) timeToLiveMillis / 1000;
414 } finally {
415 stateLock.readLock().unlock();
416 }
417 }
418
419
420
421
422
423
424
425 public void setTimeToLive(long timeToLive) {
426 stateLock.writeLock().lock();
427
428 try {
429 this.timeToLiveMillis = timeToLive * 1000;
430 } finally {
431 stateLock.writeLock().unlock();
432 }
433 }
434
435
436
437
438
439
440
441
442 public int getExpirationInterval() {
443 stateLock.readLock().lock();
444
445 try {
446 return (int) expirationIntervalMillis / 1000;
447 } finally {
448 stateLock.readLock().unlock();
449 }
450 }
451
452
453
454
455
456
457
458
459 public void setExpirationInterval(long expirationInterval) {
460 stateLock.writeLock().lock();
461
462 try {
463 this.expirationIntervalMillis = expirationInterval * 1000;
464 } finally {
465 stateLock.writeLock().unlock();
466 }
467 }
468 }
469 }