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