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
277
278 public class Expirer implements Runnable {
279 private final ReadWriteLock stateLock = new ReentrantReadWriteLock();
280
281 private long timeToLiveMillis;
282
283 private long expirationIntervalMillis;
284
285 private boolean running = false;
286
287 private final Thread expirerThread;
288
289
290
291
292
293 public Expirer() {
294 expirerThread = new Thread(this, "ExpiringMapExpirer-"
295 + expirerCount++);
296 expirerThread.setDaemon(true);
297 }
298
299 public void run() {
300 while (running) {
301 processExpires();
302
303 try {
304 Thread.sleep(expirationIntervalMillis);
305 } catch (InterruptedException e) {
306 }
307 }
308 }
309
310 private void processExpires() {
311 long timeNow = System.currentTimeMillis();
312
313 for (ExpiringObject o : delegate.values()) {
314
315 if (timeToLiveMillis <= 0) {
316 continue;
317 }
318
319 long timeIdle = timeNow - o.getLastAccessTime();
320
321 if (timeIdle >= timeToLiveMillis) {
322 delegate.remove(o.getKey());
323
324 for (ExpirationListener<V> listener : expirationListeners) {
325 listener.expired(o.getValue());
326 }
327 }
328 }
329 }
330
331
332
333
334
335 public void startExpiring() {
336 stateLock.writeLock().lock();
337
338 try {
339 if (!running) {
340 running = true;
341 expirerThread.start();
342 }
343 } finally {
344 stateLock.writeLock().unlock();
345 }
346 }
347
348
349
350
351
352 public void startExpiringIfNotStarted() {
353 stateLock.readLock().lock();
354 try {
355 if (running) {
356 return;
357 }
358 } finally {
359 stateLock.readLock().unlock();
360 }
361
362 stateLock.writeLock().lock();
363 try {
364 if (!running) {
365 running = true;
366 expirerThread.start();
367 }
368 } finally {
369 stateLock.writeLock().unlock();
370 }
371 }
372
373
374
375
376 public void stopExpiring() {
377 stateLock.writeLock().lock();
378
379 try {
380 if (running) {
381 running = false;
382 expirerThread.interrupt();
383 }
384 } finally {
385 stateLock.writeLock().unlock();
386 }
387 }
388
389
390
391
392
393
394
395 public boolean isRunning() {
396 stateLock.readLock().lock();
397
398 try {
399 return running;
400 } finally {
401 stateLock.readLock().unlock();
402 }
403 }
404
405
406
407
408
409
410
411 public int getTimeToLive() {
412 stateLock.readLock().lock();
413
414 try {
415 return (int) timeToLiveMillis / 1000;
416 } finally {
417 stateLock.readLock().unlock();
418 }
419 }
420
421
422
423
424
425
426
427 public void setTimeToLive(long timeToLive) {
428 stateLock.writeLock().lock();
429
430 try {
431 this.timeToLiveMillis = timeToLive * 1000;
432 } finally {
433 stateLock.writeLock().unlock();
434 }
435 }
436
437
438
439
440
441
442
443
444 public int getExpirationInterval() {
445 stateLock.readLock().lock();
446
447 try {
448 return (int) expirationIntervalMillis / 1000;
449 } finally {
450 stateLock.readLock().unlock();
451 }
452 }
453
454
455
456
457
458
459
460
461 public void setExpirationInterval(long expirationInterval) {
462 stateLock.writeLock().lock();
463
464 try {
465 this.expirationIntervalMillis = expirationInterval * 1000;
466 } finally {
467 stateLock.writeLock().unlock();
468 }
469 }
470 }
471 }