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