Package helma.util

Source Code of helma.util.CacheMap

// LruHashtable - a Hashtable that expires least-recently-used objects
//
// Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>.  All rights reserved.
//
// 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.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS 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 THE AUTHOR OR 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.
//
// Visit the ACME Labs Java page for up-to-date versions of this and other
// fine Java utilities: http://www.acme.com/java/

// Moved to helma.util to use java.util.HashMap instead of java.util.Hashtable
package helma.util;

import java.util.Map;
import java.util.HashMap;
import java.util.Properties;

import helma.framework.core.Application;
import helma.objectmodel.ObjectCache;


/// A Hashtable that expires least-recently-used objects.
// <P>
// Use just like java.util.Hashtable, except that the initial-capacity
// parameter is required.  Instead of growing bigger than that size,
// it will throw out objects that haven't been looked at in a while.
// <P>
// <A HREF="/resources/classes/Acme/LruHashtable.java">Fetch the software.</A><BR>
// <A HREF="/resources/classes/Acme.tar.gz">Fetch the entire Acme package.</A>
// <P>
// @see java.util.Hashtable

public class CacheMap implements ObjectCache {

    // Load factor.
    private float loadFactor;

    // When count exceeds this threshold, expires the old table.
    private int threshold;

    // Capacity of each bucket.
    private int eachCapacity;

    // The tables.
    private Map oldTable;
    private Map newTable;

    // the application to output messages to
    private Application app = null;

    /**
     * Zero argument constructor. Creates a CacheMap with capacity of 1000
     * and load factor 0.75
     */
    public CacheMap() {
        this(1000, 0.75f);
    }

    /// Constructs a new, empty hashtable with the specified initial
    // capacity and the specified load factor.
    // Unlike a plain Hashtable, an LruHashtable will never grow or
    // shrink from this initial capacity.
    // @param initialCapacity the initial number of buckets
    // @param loadFactor a number between 0.0 and 1.0, it defines
    //    the threshold for expiring old entries
    // @exception IllegalArgumentException If the initial capacity
    // is less than or equal to zero.
    // @exception IllegalArgumentException If the load factor is
    // less than or equal to zero.
    public CacheMap(int initialCapacity, float loadFactor) {
        // We have to call a superclass constructor, but we're not actually
        // going to use it at all.  The only reason we want to extend Hashtable
        // is for type conformance.  So, make a parent hash table of minimum
        // size and then ignore it.
        if (initialCapacity <= 0 || loadFactor <= 0.0)
            throw new IllegalArgumentException();
        this.loadFactor = loadFactor;
        // table rotation threshold: we allow each table to gain
        // initialCapacity/2 entries.
        threshold = initialCapacity / 2;
        // We deliberately choose the initial capacity of tables large
        // enough that it can hold threshold entries without being rehashed,
        // in other words, make sure our threshold for table rotation is lower
        // than that of the underlying HashMap for table rehashing.
        eachCapacity = (int) (threshold / loadFactor) + 2;
        // create tables - we'll never insert into the initial oldTable,
        // it's a dummy that will be lost on the first cache rotation.
        oldTable = new HashMap();
        newTable = createTable(eachCapacity, loadFactor);
    }

    /// Constructs a new, empty hashtable with the specified initial
    // capacity.
    // Unlike a plain Hashtable, an LruHashtable will never grow or
    // shrink from this initial capacity.
    // @param initialCapacity the initial number of buckets
    public CacheMap(int initialCapacity) {
        this(initialCapacity, 0.75F);
    }

    /// Returns the number of elements contained in the hashtable.
    public int size() {
        return newTable.size() + oldTable.size();
    }

    /// Returns true if the hashtable contains no elements.
    public boolean isEmpty() {
        return size() == 0;
    }


    /// Set the capacity of the CacheMap
    public void setCapacity(int capacity) {
        // table rotation threshold: we allow each table to gain
        // initialCapacity/2 entries.
        int newThreshold = capacity / 2;
        if (newThreshold != threshold) {
            if (app != null)
                app.logEvent("Setting cache capacity to " + capacity);
            updateThreshold(newThreshold);
        }
    }

    private synchronized void updateThreshold(int newThreshold) {
        threshold = newThreshold;
        eachCapacity = (int) (threshold / loadFactor) + 2;
        // if newtable is larger than threshold, rotate.
        if (newTable.size() > threshold) {
            oldTable = newTable;
            newTable = createTable(eachCapacity, loadFactor);
        }
    }

    /// Returns true if the specified object is an element of the hashtable.
    // This operation is more expensive than the containsKey() method.
    // @param value the value that we are looking for
    // @exception NullPointerException If the value being searched
    // for is equal to null.
    // @see LruHashtable#containsKey
    public synchronized boolean containsValue(Object value) {
        if (newTable.containsValue(value))
            return true;
        if (oldTable.containsValue(value)) {
            // We would like to move the object from the old table to the
            // new table.  However, we need keys to re-add the objects, and
            // there's no good way to find all the keys for the given object.
            // We'd have to enumerate through all the keys and check each
            // one.  Yuck.  For now we just punt.  Anyway, contains() is
            // probably not a commonly-used operation.
            return true;
        }
        return false;
    }

    /// Returns true if the collection contains an element for the key.
    // @param key the key that we are looking for
    // @see LruHashtable#contains
    public synchronized boolean containsKey(Object key) {
        if (newTable.containsKey(key))
            return true;
        if (oldTable.containsKey(key)) {
            // Move object from old table to new table.
            Object value = oldTable.get(key);
            newTable.put(key, value);
            oldTable.remove(key);
            return true;
        }
        return false;
    }

    /// Returns the number of keys in object array <code>keys</code> that
    //  were not found in the Map.
    //  Those keys that are contained in the Map are nulled out in the array.
    // @param keys an array of key objects we are looking for
    // @see LruHashtable#contains
    public synchronized int containsKeys(Object[] keys) {
        int notfound = 0;
        for (int i = 0; i < keys.length; i++) {
            if (newTable.containsKey(keys[i]))
                keys[i] = null;
            else if (oldTable.containsKey(keys[i])) {
                // Move object from old table to new table.
                Object value = oldTable.get(keys[i]);
                newTable.put(keys[i], value);
                oldTable.remove(keys[i]);
                keys[i] = null;
            } else
                notfound++;
        }
        return notfound;
    }

    /// Gets the object associated with the specified key in the
    // hashtable.
    // @param key the specified key
    // @returns the element for the key or null if the key
    //     is not defined in the hash table.
    // @see LruHashtable#put
    public synchronized Object get(Object key) {
        Object value;
        value = newTable.get(key);
        if (value != null)
            return value;
        value = oldTable.get(key);
        if (value != null) {
            // Move object from old table to new table.
            newTable.put(key, value);
            oldTable.remove(key);
            return value;
        }
        return null;
    }

    /// Puts the specified element into the hashtable, using the specified
    // key.  The element may be retrieved by doing a get() with the same key.
    // The key and the element cannot be null.
    // @param key the specified key in the hashtable
    // @param value the specified element
    // @exception NullPointerException If the value of the element
    // is equal to null.
    // @see LruHashtable#get
    // @return the old value of the key, or null if it did not have one.
    public synchronized Object put(Object key, Object value) {

        Object oldValue = newTable.put(key, value);
        if (oldValue != null)
            return oldValue;
        oldValue = oldTable.get(key);
        if (oldValue != null)
            oldTable.remove(key);
        // we put a key into newtable that wasn't there before. check if it
        // grew beyond the threshold
        if (newTable.size() >= threshold) {
            // Rotate the tables.
            if (app != null)
                app.logEvent("Rotating Cache tables at " + newTable.size() +
                        "/" + oldTable.size() + " (new/old)");
            oldTable = newTable;
            newTable = createTable(eachCapacity, loadFactor);
        }
        return oldValue;
    }

    /// Removes the element corresponding to the key. Does nothing if the
    // key is not present.
    // @param key the key that needs to be removed
    // @return the value of key, or null if the key was not found.
    public synchronized Object remove(Object key) {
        Object oldValue = newTable.remove(key);
        if (oldValue == null)
            oldValue = oldTable.remove(key);
        return oldValue;
    }

    /// Clears the hash table so that it has no more elements in it.
    public synchronized boolean clear() {
        newTable.clear();
        oldTable.clear();
        return true;
    }

    /// Called when the application using this cache is stopped. We
    //  simply clear out our cache contents.
    public synchronized void shutdown() {
        clear();
    }

    /// Set the application to use for debug and profiling output
    public void init(Application app) {
        this.app = app;
        if (app != null) {
            updateProperties(app.getProperties());
        }
    }

    /// The app properties have been modified, reload settings
    public void updateProperties(Properties props) {
        try {
            int cacheSize = Integer.parseInt(props.getProperty("cachesize", "1000"));
            setCapacity(cacheSize);
        } catch (Exception x) {
            String message = "Invalid cachesize setting: " + props.getProperty("cachesize");
            if (app != null) {
                app.logError(message);
            } else {
                System.err.println(message);
            }
        }
    }

    public synchronized Object[] getCachedObjects() {
        Object[] k1 = newTable.keySet().toArray();
        Object[] k2 = oldTable.keySet().toArray();
        Object[] k = new Object[k1.length + k2.length];
        System.arraycopy(k1, 0, k, 0, k1.length);
        System.arraycopy(k2, 0, k, k1.length, k2.length);
        return k;
    }

    public String toString() {
        return newTable.toString() + oldTable.toString() + hashCode();
    }

    /**
     * Override this method to use custom Map implementations. The
     * default implementation returns a java.util.HashMap instance.
     *
     * @param capacity the initial capacity
     * @param loadFactor the load factor
     * @return a new Map used for internal caching
     */
    protected Map createTable(int capacity, float loadFactor) {
        return new HashMap(capacity, loadFactor);
    }

}

TOP

Related Classes of helma.util.CacheMap

TOP
Copyright © 2015 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.