Collections Data Structure Java

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 *
 */
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
 * A map with expiration.  This class contains a worker thread that will 
 * periodically check this class in order to determine if any objects 
 * should be removed based on the provided time-to-live value.
 *
 * @author The Apache MINA Project (dev@mina.apache.org)
 * @version $Rev: 662890 $, $Date: 2008-06-03 23:14:21 +0200 (Tue, 03 Jun 2008) $
 */
public class ExpiringMap implements Map {
    
    /**
     * The default value, 60
     */
    public static final int DEFAULT_TIME_TO_LIVE = 60;
    /**
     * The default value, 1
     */
    public static final int DEFAULT_EXPIRATION_INTERVAL = 1;
    private static volatile int expirerCount = 1;
    private final ConcurrentHashMap delegate;
    private final CopyOnWriteArrayList> expirationListeners;
    private final Expirer expirer;
    /**
     * Creates a new instance of ExpiringMap using the default values 
     * DEFAULT_TIME_TO_LIVE and DEFAULT_EXPIRATION_INTERVAL
     *
     */
    public ExpiringMap() {
        this(DEFAULT_TIME_TO_LIVE, DEFAULT_EXPIRATION_INTERVAL);
    }
    /**
     * Creates a new instance of ExpiringMap using the supplied 
     * time-to-live value and the default value for DEFAULT_EXPIRATION_INTERVAL
     *
     * @param timeToLive
     *  The time-to-live value (seconds)
     */
    public ExpiringMap(int timeToLive) {
        this(timeToLive, DEFAULT_EXPIRATION_INTERVAL);
    }
    /**
     * Creates a new instance of ExpiringMap using the supplied values and 
     * a {@link ConcurrentHashMap} for the internal data structure.
     *
     * @param timeToLive
     *  The time-to-live value (seconds)
     * @param expirationInterval
     *  The time between checks to see if a value should be removed (seconds)
     */
    public ExpiringMap(int timeToLive, int expirationInterval) {
        this(new ConcurrentHashMap(),
                new CopyOnWriteArrayList>(), timeToLive,
                expirationInterval);
    }
    private ExpiringMap(ConcurrentHashMap delegate,
            CopyOnWriteArrayList> expirationListeners,
            int timeToLive, int expirationInterval) {
        this.delegate = delegate;
        this.expirationListeners = expirationListeners;
        this.expirer = new Expirer();
        expirer.setTimeToLive(timeToLive);
        expirer.setExpirationInterval(expirationInterval);
    }
    public V put(K key, V value) {
        ExpiringObject answer = delegate.put(key, new ExpiringObject(key,
                value, System.currentTimeMillis()));
        if (answer == null) {
            return null;
        }
        return answer.getValue();
    }
    public V get(Object key) {
        ExpiringObject object = delegate.get(key);
        if (object != null) {
            object.setLastAccessTime(System.currentTimeMillis());
            return object.getValue();
        }
        return null;
    }
    public V remove(Object key) {
        ExpiringObject answer = delegate.remove(key);
        if (answer == null) {
            return null;
        }
        return answer.getValue();
    }
    public boolean containsKey(Object key) {
        return delegate.containsKey(key);
    }
    public boolean containsValue(Object value) {
        return delegate.containsValue(value);
    }
    public int size() {
        return delegate.size();
    }
    public boolean isEmpty() {
        return delegate.isEmpty();
    }
    public void clear() {
        delegate.clear();
    }
    @Override
    public int hashCode() {
        return delegate.hashCode();
    }
    public Set keySet() {
        return delegate.keySet();
    }
    @Override
    public boolean equals(Object obj) {
        return delegate.equals(obj);
    }
    public void putAll(Map inMap) {
        for (Entry e : inMap.entrySet()) {
            this.put(e.getKey(), e.getValue());
        }
    }
    public Collection values() {
        throw new UnsupportedOperationException();
    }
    public Set> entrySet() {
        throw new UnsupportedOperationException();
    }
    public void addExpirationListener(ExpirationListener listener) {
        expirationListeners.add(listener);
    }
    public void removeExpirationListener(
            ExpirationListener listener) {
        expirationListeners.remove(listener);
    }
    public Expirer getExpirer() {
        return expirer;
    }
    public int getExpirationInterval() {
        return expirer.getExpirationInterval();
    }
    public int getTimeToLive() {
        return expirer.getTimeToLive();
    }
    public void setExpirationInterval(int expirationInterval) {
        expirer.setExpirationInterval(expirationInterval);
    }
    public void setTimeToLive(int timeToLive) {
        expirer.setTimeToLive(timeToLive);
    }
    private class ExpiringObject {
        private K key;
        private V value;
        private long lastAccessTime;
        private final ReadWriteLock lastAccessTimeLock = new ReentrantReadWriteLock();
        ExpiringObject(K key, V value, long lastAccessTime) {
            if (value == null) {
                throw new IllegalArgumentException(
                        "An expiring object cannot be null.");
            }
            this.key = key;
            this.value = value;
            this.lastAccessTime = lastAccessTime;
        }
        public long getLastAccessTime() {
            lastAccessTimeLock.readLock().lock();
            try {
                return lastAccessTime;
            } finally {
                lastAccessTimeLock.readLock().unlock();
            }
        }
        public void setLastAccessTime(long lastAccessTime) {
            lastAccessTimeLock.writeLock().lock();
            try {
                this.lastAccessTime = lastAccessTime;
            } finally {
                lastAccessTimeLock.writeLock().unlock();
            }
        }
        public K getKey() {
            return key;
        }
        public V getValue() {
            return value;
        }
        @Override
        public boolean equals(Object obj) {
            return value.equals(obj);
        }
        @Override
        public int hashCode() {
            return value.hashCode();
        }
    }
    /**
     * A Thread that monitors an {@link ExpiringMap} and will remove
     * elements that have passed the threshold.
     *
     */ 
    public class Expirer implements Runnable {
        private final ReadWriteLock stateLock = new ReentrantReadWriteLock();
        private long timeToLiveMillis;
        private long expirationIntervalMillis;
        private boolean running = false;
        private final Thread expirerThread;
        /**
         * Creates a new instance of Expirer.  
         *
         */
        public Expirer() {
            expirerThread = new Thread(this, "ExpiringMapExpirer-"
                    + expirerCount++);
            expirerThread.setDaemon(true);
        }
        public void run() {
            while (running) {
                processExpires();
                try {
                    Thread.sleep(expirationIntervalMillis);
                } catch (InterruptedException e) {
                }
            }
        }
        private void processExpires() {
            long timeNow = System.currentTimeMillis();
            for (ExpiringObject o : delegate.values()) {
                if (timeToLiveMillis <= 0) {
                    continue;
                }
                long timeIdle = timeNow - o.getLastAccessTime();
                if (timeIdle >= timeToLiveMillis) {
                    delegate.remove(o.getKey());
                    for (ExpirationListener listener : expirationListeners) {
                        listener.expired(o.getValue());
                    }
                }
            }
        }
        /**
         * Kick off this thread which will look for old objects and remove them.
         *
         */
        public void startExpiring() {
            stateLock.writeLock().lock();
            try {
                if (!running) {
                    running = true;
                    expirerThread.start();
                }
            } finally {
                stateLock.writeLock().unlock();
            }
        }
        /**
         * If this thread has not started, then start it.  
         * Otherwise just return;
         */
        public void startExpiringIfNotStarted() {
            stateLock.readLock().lock();
            try {
                if (running) {
                    return;
                }
            } finally {
                stateLock.readLock().unlock();
            }
            stateLock.writeLock().lock();
            try {
                if (!running) {
                    running = true;
                    expirerThread.start();
                }
            } finally {
                stateLock.writeLock().unlock();
            }
        }
        /**
         * Stop the thread from monitoring the map.
         */
        public void stopExpiring() {
            stateLock.writeLock().lock();
            try {
                if (running) {
                    running = false;
                    expirerThread.interrupt();
                }
            } finally {
                stateLock.writeLock().unlock();
            }
        }
        /**
         * Checks to see if the thread is running
         *
         * @return
         *  If the thread is running, true.  Otherwise false.
         */
        public boolean isRunning() {
            stateLock.readLock().lock();
            try {
                return running;
            } finally {
                stateLock.readLock().unlock();
            }
        }
        /**
         * Returns the Time-to-live value.
         *
         * @return
         *  The time-to-live (seconds)
         */
        public int getTimeToLive() {
            stateLock.readLock().lock();
            try {
                return (int) timeToLiveMillis / 1000;
            } finally {
                stateLock.readLock().unlock();
            }
        }
        /**
         * Update the value for the time-to-live
         *
         * @param timeToLive
         *  The time-to-live (seconds)
         */
        public void setTimeToLive(long timeToLive) {
            stateLock.writeLock().lock();
            try {
                this.timeToLiveMillis = timeToLive * 1000;
            } finally {
                stateLock.writeLock().unlock();
            }
        }
        /**
         * Get the interval in which an object will live in the map before
         * it is removed.
         *
         * @return
         *  The time in seconds.
         */
        public int getExpirationInterval() {
            stateLock.readLock().lock();
            try {
                return (int) expirationIntervalMillis / 1000;
            } finally {
                stateLock.readLock().unlock();
            }
        }
        /**
         * Set the interval in which an object will live in the map before
         * it is removed.
         *
         * @param expirationInterval
         *  The time in seconds
         */
        public void setExpirationInterval(long expirationInterval) {
            stateLock.writeLock().lock();
            try {
                this.expirationIntervalMillis = expirationInterval * 1000;
            } finally {
                stateLock.writeLock().unlock();
            }
        }
    }
}
/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 *
 */
/**
 * A listener for expired object events.
 *
 * @author The Apache MINA Project (dev@mina.apache.org)
 * @version $Rev: 589932 $, $Date: 2007-10-30 02:50:39 +0100 (Tue, 30 Oct 2007) $
 * TODO Make this a inner interface of ExpiringMap
 */
 interface ExpirationListener {
    void expired(E expiredObject);
}