/* * Copyright (c) 2002-2007 Sun Microsystems, Inc. All rights reserved. * * The Sun Project JXTA(TM) Software License * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The end-user documentation included with the redistribution, if any, must * include the following acknowledgment: "This product includes software * developed by Sun Microsystems, Inc. for JXTA(TM) technology." * Alternately, this acknowledgment may appear in the software itself, if * and wherever such third-party acknowledgments normally appear. * * 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must * not be used to endorse or promote products derived from this software * without prior written permission. For written permission, please contact * Project JXTA at http://www.jxta.org. * * 5. Products derived from this software may not be called "JXTA", nor may * "JXTA" appear in their name, without prior written permission of Sun. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN * MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * JXTA is a registered trademark of Sun Microsystems, Inc. in the United * States and other countries. * * Please see the license information page at : * <http://www.jxta.org/project/www/license.html> for instructions on use of * the license in source files. * * ==================================================================== * * This software consists of voluntary contributions made by many individuals * on behalf of Project JXTA. For more information on Project JXTA, please see * http://www.jxta.org. * * This license is based on the BSD license adopted by the Apache Foundation. */ package net.jxta.impl.util; import java.util.HashMap; import java.util.Map; /** * A Cache which is similar to {@link java.util.LinkedHashMap} * * <p/>LinkedList cannot be used efficiently because it * cannot remove an element efficiently from the middle. For that, we need * the externally referenced element (the thing to be removed) to * be the list entry itself, rather than referenced by an invisible * list entry. That is why we use the DLink/Dlist family. */ public class Cache { /** * CacheEntryImpl objects are both part of a doubly linked list and * inserted in a HashMap. They refer to the thing mapped which is what * users of this class want to get, and to the key. The reason is * that we need the key to remove from the map * an entry that we found in list. The otherway around is made easy by * the nature of the dlinked structure. **/ class CacheEntryImpl extends Dlink implements CacheEntry { private final Object value; private final Object key; // The application interface. public CacheEntryImpl(Object k, Object v) { key = k; value = v; } /** * {@inheritDoc} **/ public Object getKey() { return key; } /** * {@inheritDoc} **/ public Object getValue() { return value; } } private final long maxSize; private long size; private final Map map = new HashMap(); private final Dlist lru = new Dlist(); private final CacheEntryListener listener; /** * Creates a cache whih will keep at most maxSize purgeable entries. * Every new entry is purgeable by default. * * <p/>Entries that are not purgeable are not counted and are never removed * unless clear() or remove() is called. Purgeable entries are removed * silently as needed to make room for new entries so that the number * of purgeable entries remains < maxSize. * * <p/>Entries prugeability is controlled by invoking the sticky() method * or the stickyCacheEntry() method. * * <p/>For now, purged entries are abandonned to the GC which is probably not * so bad. To permit acceleration of the collection of resources, a * purge listener will be added soon. */ public Cache(long maxSize, CacheEntryListener listener) { this.maxSize = maxSize; this.size = 0; this.listener = listener; } /** * Empties the cache completely. * The entries are abandonned to the GC. */ public void clear() { lru.clear(); map.clear(); } /** * Purges some of the cache. * The entries are cleaned-up properly. */ public void purge(int fraction) { if (size == 0) { return; } if (fraction == 0) { fraction = 1; } long nbToPurge = size / fraction; if (nbToPurge == 0) { nbToPurge = 1; } while (nbToPurge-- > 0) { CacheEntryImpl toRm = (CacheEntryImpl) lru.next(); map.remove(toRm.getKey()); toRm.unlink(); --size; if (listener != null) { listener.purged(toRm); } } } /** * Inserts the given cache entry directly. * Returns the previous cache entry associated with the given key, if any. * Not exposed yet. Should not be a problem to expose it, but it is not * needed yet. */ protected CacheEntry putCacheEntry(Object key, CacheEntry value) { if (size == maxSize) { CacheEntryImpl toRm = (CacheEntryImpl) lru.next(); map.remove(toRm.getKey()); toRm.unlink(); --size; if (listener != null) { listener.purged(toRm); } } lru.putLast((CacheEntryImpl) value); ++size; CacheEntryImpl oldEntry = (CacheEntryImpl) map.put(key, value); if (oldEntry == null) { return null; } if (oldEntry.isLinked()) { oldEntry.unlink(); --size; } return oldEntry; } /** * Create a cache entry to hold the given value, and insert it. * Returns the previous value associated with the given key, if any. */ public Object put(Object key, Object value) { CacheEntry oldEntry = putCacheEntry(key, new CacheEntryImpl(key, value)); if (oldEntry == null) { return null; } return oldEntry.getValue(); } /** * Remove the value, if any, and cacheEntry associated with the given key. * return the cacheEntry that has been removed. * Not exposed yet. Should not be a problem to expose it, but it is not * needed yet. */ protected CacheEntry removeCacheEntry(Object key) { CacheEntryImpl oldEntry = (CacheEntryImpl) map.remove(key); if (oldEntry == null) { return null; } if (oldEntry.isLinked()) { oldEntry.unlink(); --size; } return oldEntry; } /** * Remove the value, if any, and cacheEntry associated with the given key. * returns the value that has been removed. */ public Object remove(Object key) { CacheEntry oldEntry = removeCacheEntry(key); if (oldEntry == null) { return null; } return oldEntry.getValue(); } /** * Return the cache entry, if any, associated with the given key. * This is public; it improves performance by letting the application * do a single lookup instead of two when it needs to find an object in * the cache and then change its purgeability. */ public CacheEntry getCacheEntry(Object key) { CacheEntryImpl foundEntry = (CacheEntryImpl) map.get(key); if (foundEntry == null) { return null; } // Leave the purgeability status alone but manage lru position if // purgeable. if (foundEntry.isLinked()) { lru.putLast(foundEntry); } return foundEntry; } /** * Return the value, if any associated with the given key. */ public Object get(Object key) { CacheEntry foundEntry = getCacheEntry(key); if (foundEntry == null) { return null; } return foundEntry.getValue(); } /** * Change the purgeability of the given cacheEntry. * If sticky is true, the entry cannot be purged. * Note: if the CacheEntry is known, it is more efficient to use this * method than sticky(), since sticky will preform a hashmap lookup * to locate the cache entry. */ public void stickyCacheEntry(CacheEntry ce, boolean sticky) { CacheEntryImpl target = (CacheEntryImpl) ce; if (sticky) { // Stiky => not purgeable. if (!target.isLinked()) { return; } target.unlink(); --size; } else { // ! Sticky => purgeable. if (target.isLinked()) { return; } if (size == maxSize) { CacheEntryImpl toRm = (CacheEntryImpl) lru.next(); map.remove(toRm.getKey()); toRm.unlink(); if (listener != null) { listener.purged(toRm); } --size; } lru.putLast(target); ++size; } } /** * Force the value associated with the given key to be purgeable or * non-purgeable from the cache (non-sticky vs. sticky). * Note: Most often, a call to the get() method will be performed * before it can be decided to invoke sticky(). Whenever this is the case * it is better to invoke getCacheEntry() + getValue() and then * stickyCacheEntry() since that eliminates one hashmap lookup. */ public void sticky(Object key, boolean sticky) { CacheEntry foundEntry = (CacheEntry) map.get(key); if (foundEntry == null) { return; } stickyCacheEntry(foundEntry, sticky); } }