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