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 public class ExpiringMap<K, V> implements Map<K, V> {
39
40
41
42
43 public static final int DEFAULT_TIME_TO_LIVE = 60;
44
45
46
47
48 public static final int DEFAULT_EXPIRATION_INTERVAL = 1;
49
50 private static volatile int expirerCount = 1;
51
52 private final ConcurrentHashMap<K, ExpiringObject> delegate;
53
54 private final CopyOnWriteArrayList<ExpirationListener<V>> expirationListeners;
55
56 private final Expirer expirer;
57
58
59
60
61
62
63 public ExpiringMap() {
64 this(DEFAULT_TIME_TO_LIVE, DEFAULT_EXPIRATION_INTERVAL);
65 }
66
67
68
69
70
71
72
73
74 public ExpiringMap(int timeToLive) {
75 this(timeToLive, DEFAULT_EXPIRATION_INTERVAL);
76 }
77
78
79
80
81
82
83
84
85
86
87 public ExpiringMap(int timeToLive, int expirationInterval) {
88 this(new ConcurrentHashMap<K, ExpiringObject>(),
89 new CopyOnWriteArrayList<ExpirationListener<V>>(), timeToLive,
90 expirationInterval);
91 }
92
93 private ExpiringMap(ConcurrentHashMap<K, ExpiringObject> delegate,
94 CopyOnWriteArrayList<ExpirationListener<V>> expirationListeners,
95 int timeToLive, int expirationInterval) {
96 this.delegate = delegate;
97 this.expirationListeners = expirationListeners;
98
99 this.expirer = new Expirer();
100 expirer.setTimeToLive(timeToLive);
101 expirer.setExpirationInterval(expirationInterval);
102 }
103
104 public V put(K key, V value) {
105 ExpiringObject answer = delegate.put(key, new ExpiringObject(key,
106 value, System.currentTimeMillis()));
107 if (answer == null) {
108 return null;
109 }
110
111 return answer.getValue();
112 }
113
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 public V remove(Object key) {
127 ExpiringObject answer = delegate.remove(key);
128 if (answer == null) {
129 return null;
130 }
131
132 return answer.getValue();
133 }
134
135 public boolean containsKey(Object key) {
136 return delegate.containsKey(key);
137 }
138
139 public boolean containsValue(Object value) {
140 return delegate.containsValue(value);
141 }
142
143 public int size() {
144 return delegate.size();
145 }
146
147 public boolean isEmpty() {
148 return delegate.isEmpty();
149 }
150
151 public void clear() {
152 delegate.clear();
153 }
154
155 @Override
156 public int hashCode() {
157 return delegate.hashCode();
158 }
159
160 public Set<K> keySet() {
161 return delegate.keySet();
162 }
163
164 @Override
165 public boolean equals(Object obj) {
166 return delegate.equals(obj);
167 }
168
169 public void putAll(Map<? extends K, ? extends V> inMap) {
170 for (Entry<? extends K, ? extends V> e : inMap.entrySet()) {
171 this.put(e.getKey(), e.getValue());
172 }
173 }
174
175 public Collection<V> values() {
176 throw new UnsupportedOperationException();
177 }
178
179 public Set<Map.Entry<K, V>> entrySet() {
180 throw new UnsupportedOperationException();
181 }
182
183 public void addExpirationListener(ExpirationListener<V> listener) {
184 expirationListeners.add(listener);
185 }
186
187 public void removeExpirationListener(
188 ExpirationListener<V> listener) {
189 expirationListeners.remove(listener);
190 }
191
192 public Expirer getExpirer() {
193 return expirer;
194 }
195
196 public int getExpirationInterval() {
197 return expirer.getExpirationInterval();
198 }
199
200 public int getTimeToLive() {
201 return expirer.getTimeToLive();
202 }
203
204 public void setExpirationInterval(int expirationInterval) {
205 expirer.setExpirationInterval(expirationInterval);
206 }
207
208 public void setTimeToLive(int timeToLive) {
209 expirer.setTimeToLive(timeToLive);
210 }
211
212 private class ExpiringObject {
213 private K key;
214
215 private V value;
216
217 private long lastAccessTime;
218
219 private final ReadWriteLock lastAccessTimeLock = new ReentrantReadWriteLock();
220
221 ExpiringObject(K key, V value, long lastAccessTime) {
222 if (value == null) {
223 throw new IllegalArgumentException(
224 "An expiring object cannot be null.");
225 }
226
227 this.key = key;
228 this.value = value;
229 this.lastAccessTime = lastAccessTime;
230 }
231
232 public long getLastAccessTime() {
233 lastAccessTimeLock.readLock().lock();
234
235 try {
236 return lastAccessTime;
237 } finally {
238 lastAccessTimeLock.readLock().unlock();
239 }
240 }
241
242 public void setLastAccessTime(long lastAccessTime) {
243 lastAccessTimeLock.writeLock().lock();
244
245 try {
246 this.lastAccessTime = lastAccessTime;
247 } finally {
248 lastAccessTimeLock.writeLock().unlock();
249 }
250 }
251
252 public K getKey() {
253 return key;
254 }
255
256 public V getValue() {
257 return value;
258 }
259
260 @Override
261 public boolean equals(Object obj) {
262 return value.equals(obj);
263 }
264
265 @Override
266 public int hashCode() {
267 return value.hashCode();
268 }
269 }
270
271
272
273
274
275
276 public class Expirer implements Runnable {
277 private final ReadWriteLock stateLock = new ReentrantReadWriteLock();
278
279 private long timeToLiveMillis;
280
281 private long expirationIntervalMillis;
282
283 private boolean running = false;
284
285 private final Thread expirerThread;
286
287
288
289
290
291 public Expirer() {
292 expirerThread = new Thread(this, "ExpiringMapExpirer-"
293 + expirerCount++);
294 expirerThread.setDaemon(true);
295 }
296
297 public void run() {
298 while (running) {
299 processExpires();
300
301 try {
302 Thread.sleep(expirationIntervalMillis);
303 } catch (InterruptedException e) {
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 }