查看本类的 API文档回源码主页即时通讯网 - 即时通讯开发者社区!
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
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   * A map with expiration.
32   *
33   * @author The Apache Directory Project (mina-dev@directory.apache.org)
34   */
35  public class ExpiringMap<K, V> implements Map<K, V> {
36      public static final int DEFAULT_TIME_TO_LIVE = 60;
37  
38      public static final int DEFAULT_EXPIRATION_INTERVAL = 1;
39  
40      private static volatile int expirerCount = 1;
41  
42      private final ConcurrentHashMap<K, ExpiringObject> delegate;
43  
44      private final CopyOnWriteArrayList<ExpirationListener> expirationListeners;
45  
46      private final Expirer expirer;
47  
48      public ExpiringMap() {
49          this(DEFAULT_TIME_TO_LIVE, DEFAULT_EXPIRATION_INTERVAL);
50      }
51  
52      public ExpiringMap(int timeToLive) {
53          this(timeToLive, DEFAULT_EXPIRATION_INTERVAL);
54      }
55  
56      public ExpiringMap(int timeToLive, int expirationInterval) {
57          this(new ConcurrentHashMap<K, ExpiringObject>(),
58                  new CopyOnWriteArrayList<ExpirationListener>(), timeToLive,
59                  expirationInterval);
60      }
61  
62      private ExpiringMap(ConcurrentHashMap<K, ExpiringObject> delegate,
63              CopyOnWriteArrayList<ExpirationListener> expirationListeners,
64              int timeToLive, int expirationInterval) {
65          this.delegate = delegate;
66          this.expirationListeners = expirationListeners;
67  
68          this.expirer = new Expirer();
69          expirer.setTimeToLive(timeToLive);
70          expirer.setExpirationInterval(expirationInterval);
71      }
72  
73      public V put(K key, V value) {
74          ExpiringObject answer = delegate.put(key, new ExpiringObject(key,
75                  value, System.currentTimeMillis()));
76          if (answer == null) {
77              return null;
78          }
79  
80          return answer.getValue();
81      }
82  
83      public V get(Object key) {
84          ExpiringObject object = delegate.get(key);
85  
86          if (object != null) {
87              object.setLastAccessTime(System.currentTimeMillis());
88  
89              return object.getValue();
90          }
91  
92          return null;
93      }
94  
95      public V remove(Object key) {
96          ExpiringObject answer = delegate.remove(key);
97          if (answer == null) {
98              return null;
99          }
100 
101         return answer.getValue();
102     }
103 
104     public boolean containsKey(Object key) {
105         return delegate.containsKey(key);
106     }
107 
108     public boolean containsValue(Object value) {
109         return delegate.containsValue(value);
110     }
111 
112     public int size() {
113         return delegate.size();
114     }
115 
116     public boolean isEmpty() {
117         return delegate.isEmpty();
118     }
119 
120     public void clear() {
121         delegate.clear();
122     }
123 
124     @Override
125     public int hashCode() {
126         return delegate.hashCode();
127     }
128 
129     public Set<K> keySet() {
130         return delegate.keySet();
131     }
132 
133     @Override
134     public boolean equals(Object obj) {
135         return delegate.equals(obj);
136     }
137 
138     public void putAll(Map<? extends K, ? extends V> inMap) {
139         for (Entry<? extends K, ? extends V> e : inMap.entrySet()) {
140             this.put(e.getKey(), e.getValue());
141         }
142     }
143 
144     public Collection<V> values() {
145         throw new UnsupportedOperationException();
146     }
147 
148     public Set<Map.Entry<K, V>> entrySet() {
149         throw new UnsupportedOperationException();
150     }
151 
152     public void addExpirationListener(ExpirationListener<? extends V> listener) {
153         expirationListeners.add(listener);
154     }
155 
156     public void removeExpirationListener(
157             ExpirationListener<? extends V> listener) {
158         expirationListeners.remove(listener);
159     }
160 
161     public Expirer getExpirer() {
162         return expirer;
163     }
164 
165     public int getExpirationInterval() {
166         return expirer.getExpirationInterval();
167     }
168 
169     public int getTimeToLive() {
170         return expirer.getTimeToLive();
171     }
172 
173     public void setExpirationInterval(int expirationInterval) {
174         expirer.setExpirationInterval(expirationInterval);
175     }
176 
177     public void setTimeToLive(int timeToLive) {
178         expirer.setTimeToLive(timeToLive);
179     }
180 
181     private class ExpiringObject {
182         private K key;
183 
184         private V value;
185 
186         private long lastAccessTime;
187 
188         private ReadWriteLock lastAccessTimeLock = new ReentrantReadWriteLock();
189 
190         ExpiringObject(K key, V value, long lastAccessTime) {
191             if (value == null) {
192                 throw new IllegalArgumentException(
193                         "An expiring object cannot be null.");
194             }
195 
196             this.key = key;
197             this.value = value;
198             this.lastAccessTime = lastAccessTime;
199         }
200 
201         public long getLastAccessTime() {
202             lastAccessTimeLock.readLock().lock();
203 
204             try {
205                 return lastAccessTime;
206             } finally {
207                 lastAccessTimeLock.readLock().unlock();
208             }
209         }
210 
211         public void setLastAccessTime(long lastAccessTime) {
212             lastAccessTimeLock.writeLock().lock();
213 
214             try {
215                 this.lastAccessTime = lastAccessTime;
216             } finally {
217                 lastAccessTimeLock.writeLock().unlock();
218             }
219         }
220 
221         public K getKey() {
222             return key;
223         }
224 
225         public V getValue() {
226             return value;
227         }
228 
229         @Override
230         public boolean equals(Object obj) {
231             return value.equals(obj);
232         }
233 
234         @Override
235         public int hashCode() {
236             return value.hashCode();
237         }
238     }
239 
240     public class Expirer implements Runnable {
241         private ReadWriteLock stateLock = new ReentrantReadWriteLock();
242 
243         private long timeToLiveMillis;
244 
245         private long expirationIntervalMillis;
246 
247         private boolean running = false;
248 
249         private final Thread expirerThread;
250 
251         public Expirer() {
252             expirerThread = new Thread(this, "ExpiringMapExpirer-"
253                     + (expirerCount++));
254             expirerThread.setDaemon(true);
255         }
256 
257         public void run() {
258             while (running) {
259                 processExpires();
260 
261                 try {
262                     Thread.sleep(expirationIntervalMillis);
263                 } catch (InterruptedException e) {
264                 }
265             }
266         }
267 
268         private void processExpires() {
269             long timeNow = System.currentTimeMillis();
270 
271             for (ExpiringObject o : delegate.values()) {
272 
273                 if (timeToLiveMillis <= 0)
274                     continue;
275 
276                 long timeIdle = timeNow - o.getLastAccessTime();
277 
278                 if (timeIdle >= timeToLiveMillis) {
279                     delegate.remove(o.getKey());
280 
281                     for (ExpirationListener<V> listener : expirationListeners) {
282                         listener.expired(o.getValue());
283                     }
284                 }
285             }
286         }
287 
288         public void startExpiring() {
289             stateLock.writeLock().lock();
290 
291             try {
292                 if (!running) {
293                     running = true;
294                     expirerThread.start();
295                 }
296             } finally {
297                 stateLock.writeLock().unlock();
298             }
299         }
300 
301         public void startExpiringIfNotStarted() {
302             stateLock.readLock().lock();
303             try {
304                 if (running) {
305                     return;
306                 }
307             } finally {
308                 stateLock.readLock().unlock();
309             }
310 
311             stateLock.writeLock().lock();
312             try {
313                 if (!running) {
314                     running = true;
315                     expirerThread.start();
316                 }
317             } finally {
318                 stateLock.writeLock().unlock();
319             }
320         }
321 
322         public void stopExpiring() {
323             stateLock.writeLock().lock();
324 
325             try {
326                 if (running) {
327                     running = false;
328                     expirerThread.interrupt();
329                 }
330             } finally {
331                 stateLock.writeLock().unlock();
332             }
333         }
334 
335         public boolean isRunning() {
336             stateLock.readLock().lock();
337 
338             try {
339                 return running;
340             } finally {
341                 stateLock.readLock().unlock();
342             }
343         }
344 
345         public int getTimeToLive() {
346             stateLock.readLock().lock();
347 
348             try {
349                 return (int) timeToLiveMillis / 1000;
350             } finally {
351                 stateLock.readLock().unlock();
352             }
353         }
354 
355         public void setTimeToLive(long timeToLive) {
356             stateLock.writeLock().lock();
357 
358             try {
359                 this.timeToLiveMillis = timeToLive * 1000;
360             } finally {
361                 stateLock.writeLock().unlock();
362             }
363         }
364 
365         public int getExpirationInterval() {
366             stateLock.readLock().lock();
367 
368             try {
369                 return (int) expirationIntervalMillis / 1000;
370             } finally {
371                 stateLock.readLock().unlock();
372             }
373         }
374 
375         public void setExpirationInterval(long expirationInterval) {
376             stateLock.writeLock().lock();
377 
378             try {
379                 this.expirationIntervalMillis = expirationInterval * 1000;
380             } finally {
381                 stateLock.writeLock().unlock();
382             }
383         }
384     }
385 }