View Javadoc

    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 }