All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.code.joliratools.cache.ExpiringCache Maven / Gradle / Ivy

/**
 * (C) 2009 jolira (http://www.jolira.com). Licensed under the GNU General Public License, Version 3.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.gnu.org/licenses/gpl-3.0-standalone.html 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.
 */
package com.google.code.joliratools.cache;

import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * A simple thread-safe cache that implements expires entries bases on a time to live.
 * 
 * @author Joachim Kainz
 * @param 
 * @param 
 * @see Map
 */
public final class ExpiringCache implements Map, Serializable {
    private static final long serialVersionUID = -417862913992399059L;
    private static final long FIFTEEN_MINUTES = 1000 * 60 * 15;
    private static final int DEFAULT_MAX_SIZE = 16 * 1024;

    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Map> m = new LinkedHashMap>();
    private final long ttl;
    private final int maxSize;

    /**
     * Create a new cache with a 15 minutes timeout and a 16K size.
     */
    public ExpiringCache() {
        this(FIFTEEN_MINUTES);
    }

    /**
     * Create a new cache.
     * 
     * @param ttl
     *            the time to live in milliseconds
     */
    public ExpiringCache(final long ttl) {
        this(ttl, DEFAULT_MAX_SIZE);
    }

    /**
     * Create a new cache.
     * 
     * @param ttl
     *            the time to live in milliseconds
     * @param maxSize
     *            specify the maximum size of the cache
     */
    public ExpiringCache(final long ttl, final int maxSize) {
        if (ttl <= 0) {
            throw new IllegalArgumentException("time to live must be greater than 0");
        }

        if (maxSize < 0) {
            throw new IllegalArgumentException("maximum size must be greater than or equal to 0");
        }

        this.ttl = ttl;
        this.maxSize = maxSize;
    }

    private void cleanup(final long current, final int offset) {
        for (;;) {
            final Collection>> entries = m.entrySet();
            final Iterator>> it = entries.iterator();

            if (!it.hasNext()) {
                return;
            }

            final Entry> entry = it.next();
            final CacheEntry wrapper = entry.getValue();
            final int futureSize = m.size() + offset;

            if ((maxSize <= 0 || futureSize < maxSize) && !wrapper.hasExpired(current)) {
                return;
            }

            final K key = entry.getKey();
            final CacheEntry removed = m.remove(key);

            assert removed == wrapper;
        }
    }

    @Override
    public void clear() {
        final Lock l = lock.writeLock();

        l.lock();

        try {
            m.clear();
        } finally {
            l.unlock();
        }
    }

    @Override
    public boolean containsKey(final Object key) {
        final Lock l = lock.readLock();

        l.lock();

        try {
            return m.containsKey(key);
        } finally {
            l.unlock();
        }
    }

    @Override
    public boolean containsValue(final Object value) {
        final Collection values = values();

        for (final V val : values) {
            if (val == value) {
                return true;
            }

            if (value == null) {
                continue;
            }

            if (value.equals(val)) {
                return true;
            }
        }

        return false;
    }

    /**
     * This method is not (yet) supported.
     * 
     * @throws UnsupportedOperationException
     */
    @Override
    public Set> entrySet() {
        throw new UnsupportedOperationException();
    }

    @Override
    public V get(final Object key) {
        final Lock l = lock.readLock();

        l.lock();

        try {
            final CacheEntry ref = m.get(key);
            final long current = System.currentTimeMillis();

            if (ref == null || ref.hasExpired(current)) {
                return null;
            }

            return ref.get();
        } finally {
            l.unlock();
        }
    }

    @Override
    public boolean isEmpty() {
        final Lock l = lock.readLock();

        l.lock();

        try {
            return m.isEmpty();
        } finally {
            l.unlock();
        }
    }

    /**
     * This method is not (yet) supported.
     * 
     * @throws UnsupportedOperationException
     */
    @Override
    public Set keySet() {
        throw new UnsupportedOperationException();
    }

    @Override
    public V put(final K key, final V value) {
        final Lock l = lock.writeLock();

        l.lock();

        try {
            final long current = System.currentTimeMillis();

            cleanup(current, 1);

            final CacheEntry wrapper = new CacheEntry(value, current + ttl);
            final CacheEntry overridden = m.put(key, wrapper);

            if (overridden == null) {
                return null;
            }

            return overridden.get();
        } finally {
            l.unlock();
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public void putAll(final Map other) {
        final Set set = other.entrySet();

        if (set == null) {
            return;
        }

        for (final Object o : set) {
            final Map.Entry entry = (Map.Entry) o;
            final K key = entry.getKey();
            final V value = entry.getValue();

            put(key, value);
        }
    }

    /**
     * This put operation only put the value into the map if no other, non-expired value currently exists in the map. If
     * this key is already in the map, the existing value is returned instead.
     * 
     * @param key
     *            the key to be used
     * @param value
     *            the new value
     * @return the new value, if there was no existing entry for this key; otherwise the existing value.
     */
    public V putIfAbsent(final K key, final V value) {
        final V existing = get(key);

        if (existing != null) {
            return existing;
        }

        final Lock l = lock.writeLock();

        l.lock();

        try {
            final long current = System.currentTimeMillis();

            cleanup(current, 1);

            final CacheEntry _existing = m.get(key);

            if (_existing != null && !_existing.hasExpired(current)) {
                final V val = _existing.get();

                if (val != null) {
                    return val;
                }
            }

            final CacheEntry wrapper = new CacheEntry(value, current + ttl);
            final CacheEntry overridden = m.put(key, wrapper);

            assert overridden == _existing;

            return value;
        } finally {
            l.unlock();
        }
    }

    @Override
    public V remove(final Object key) {
        final Lock l = lock.writeLock();

        l.lock();

        try {
            final long current = System.currentTimeMillis();

            cleanup(current, -1);

            final CacheEntry removed = m.remove(key);

            if (removed == null || removed.hasExpired(current)) {
                return null;
            }

            return removed.get();
        } finally {
            l.unlock();
        }
    }

    @Override
    public int size() {
        final Lock l = lock.readLock();

        l.lock();

        try {
            return m.size();
        } finally {
            l.unlock();
        }
    }

    /**
     * This method is not (yet) supported.
     * 
     * @throws UnsupportedOperationException
     */
    @Override
    public Collection values() {
        throw new UnsupportedOperationException();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy