/* * Copyright 2007-2010 Sun Microsystems, Inc. * * This file is part of Project Darkstar Server. * * Project Darkstar Server is free software: you can redistribute it * and/or modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation and * distributed hereunder to you. * * Project Darkstar Server is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * -- */ package com.sun.sgs.impl.util; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.util.HashMap; import java.util.Map; /** * A map with values that are softly-referenced. An entry is removed if its * associated value gets garbage-collected, or, if the cache was constructed * with a timeout, and entry is removed (lazily) when the timeout expires for * that entry. A timeout is typically used to have the implementation * remove values that become stale after a certain period of time. The * implementation ensures that a caller will not have access to stale * entries (i.e., entries whose timeout has expired). * * @param <K> the key type * @param <V> the value type */ public class CacheMap<K, V> { /** The underlying map. */ private final Map<K, Value<K, V>> map = new HashMap<K, Value<K, V>>(); /** The reference queue, for detecting weak references removals. */ private final ReferenceQueue<V> queue = new ReferenceQueue<V>(); /** The entry timeout. */ private final long entryTimeout; /** Creates an instance of this class. */ public CacheMap() { this(0); } /** * Creates an instance of this class with the specified {@code * entryTimeout}. * * @param entryTimeout an entry timeout, in milliseconds * @throws IllegalArgumentException if {@code entryTimeout} is negative */ public CacheMap(long entryTimeout) { if (entryTimeout < 0) { throw new IllegalArgumentException("entryTimeout is negative"); } this.entryTimeout = entryTimeout; } /** * Associates a value with given key, returning the value previously * associated with key, or {@code null} if none is found. * * @param key the key * @param value the value * @return the previous value or {@code null} */ public V put(K key, V value) { processQueue(); Value<K, V> oldValue = map.put(key, new Value<K, V>(key, value, queue, entryTimeout)); return oldValue != null ? oldValue.get() : null; } /** * Checks if the map contains the specified key. * * @param key the key * @return {@code true} if the map contains the key, else {@code false} */ public boolean containsKey(K key) { processQueue(); return get(key) != null; } /** * Returns the value associated with given key, or {@code null} if the key * is not found. * * @param key the key * @return the value associated with the key or {@code null} */ public V get(K key) { processQueue(); Value<K, V> value = map.get(key); if (value == null) { return null; } else if (value.isExpired()) { map.remove(key); return null; } else { return value.get(); } } /** * Removes the association for the given key, returning the value * previously associated with the key, or {@code null} if the key is not * found. * * @param key the key * @return the value previously associated with the key or {@code null} */ public V remove(K key) { processQueue(); Value<K, V> oldValue = map.remove(key); return oldValue != null ? oldValue.get() : null; } /** Removes all associations from this map. */ public void clear() { processQueue(); map.clear(); } /** * Removes all entries from the map whose keys have been determined to be * no longer referenced. */ private void processQueue() { while (true) { /* No way to say that the queue holds Values */ @SuppressWarnings("unchecked") Value<K, V> value = (Value<K, V>) queue.poll(); if (value != null) { map.remove(value.getKey()); } else { break; } } } /** * A key that maintains a weak reference to an object which should be * compared by object identity. * * @param <K> the type of the referenced object */ private static final class Value<K, V> extends SoftReference<V> { /** The value's associated key. */ private final K key; /** The value's expiration time. */ private final long expirationTime; /** * Creates an instance of this class and registers it with the * specified reference queue. * * @param key the key * @param value the value (held softly) * @param queue the reference queue * @param timeout a timeout (0 = infinite) */ Value(K key, V value, ReferenceQueue<V> queue, long timeout) { super(value, queue); this.key = key; this.expirationTime = timeout > 0 ? System.currentTimeMillis() + timeout : Long.MAX_VALUE; } /** * Returns {@code true} if this value has expired, otherwise * returns {@code false}. */ boolean isExpired() { return expirationTime <= System.currentTimeMillis(); } /** * Returns this value's associated key. */ K getKey() { return key; } } }