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