FileDocCategorySizeDatePackage
BaseCache.javaAPI DocGlassfish v2 API32498Fri May 04 22:33:00 BST 2007com.sun.ejb.containers.util.cache

BaseCache.java

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.ejb.containers.util.cache;

import com.sun.appserv.util.cache.Cache;
import com.sun.appserv.util.cache.CacheListener;
import com.sun.appserv.util.cache.Constants;

import java.text.MessageFormat;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Vector;
import java.util.Map;
import java.util.HashMap;
import java.util.Properties;
import java.util.Iterator;
import java.util.ResourceBundle;

import com.sun.logging.LogDomains;

/**
 * BaseCache
 * Generic in-memory, abstract cache 
 */
public class BaseCache implements Cache {
    
    /**
     * The resource bundle containing the localized message strings.
     */
    protected static ResourceBundle _rb = null;

    static final int MAX_ENTRIES = 1 << 30;
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    
    // maximum number of entries this cache may ever hold
    int maxEntries;
    
    // the number of cache entries in this cache
    protected int   entryCount;
    protected Object  entryCountLk = new Object();
    
    /** threshold for the cache; once the threshold is reached
     *  entries are removed to accomodate newer inserts
     */
    protected int threshold = 0;
    
    // the number of cache hits
    protected int     hitCount;
    protected Object  hitCountLk = new Object();
    
    // the number of cache misses
    protected int     missCount;
    protected Object  missCountLk = new Object();

    // the number of cache item removals
    protected int    removalCount;
    protected Object removalCountLk = new Object();

    // the number of cache item refreshes
    protected int     refreshCount;
    protected Object  refreshCountLk = new Object();

    // the number of times an item was added to cache
    protected int     addCount;
    protected Object  addCountLk = new Object();

    // the number of times the cache overflowed
    protected int     overflowCount;
    protected Object  overflowCountLk = new Object();
    
    // table size
    protected int maxBuckets;
    
    // cache entries hash table
    protected CacheItem[] buckets;
    // bucket-wide locks
    protected Object[]    bucketLocks;
    
    // boolean status and locks for item thread-safe refreshes
    protected boolean[]   refreshFlags;
    
    protected ArrayList listeners = new ArrayList();
    
    /**
     * default constructor for the basic cache
     */
    public BaseCache() { }
    
    /**
     * Sets all references to null. This method should be called 
     * at the end of this object's life cycle.
     */
    public void destroy() {
        if ((listeners != null) && (buckets != null) && (bucketLocks != null)){
            clear();
            this.listeners.clear();
        }
        
        this.entryCountLk     = null;
        this.hitCountLk       = null;
        this.missCountLk      = null;
        this.removalCountLk   = null;
        this.refreshCountLk   = null;
        this.addCountLk       = null;
        this.overflowCountLk  = null;
        this.buckets          = null;
        this.bucketLocks      = null;
        this.refreshFlags     = null;
        this.listeners        = null;
    }


    /**
     * initialize the cache
     * @param maxEntries maximum number of entries expected in the cache
     * @param props opaque list of properties for a given cache implementation
     * @throws a generic Exception if the initialization failed
     */
    public void init(int maxEntries, Properties props) throws Exception {
        init(maxEntries, DEFAULT_LOAD_FACTOR, props);
    }
    
    /**
     * initialize the cache
     * @param maxEntries maximum number of entries expected in the cache
     * @param loadFactor the load factor
     * @param props opaque list of properties for a given cache implementation
     * @throws a generic Exception if the initialization failed
     */
    public void init(int maxEntries, float loadFactor, Properties props) {

        // web container logger
        _rb = LogDomains.getLogger(LogDomains.UTIL_LOGGER).getResourceBundle();
        
        if (maxEntries <= 0) {
            String msg = _rb.getString("cache.BaseCache.illegalMaxEntries");
            
            Integer obj = new Integer(maxEntries);
            Object[] params = { obj };
            msg = MessageFormat.format(msg, params);
            
            throw new IllegalArgumentException(msg);
        }
        
        if (maxEntries > MAX_ENTRIES) {
            maxEntries = MAX_ENTRIES;
        }

        this.maxEntries = maxEntries;

        // find a power of 2 >= maxEntries
        maxBuckets = 1;
        while (maxBuckets < maxEntries) {
            maxBuckets <<= 1;
        }

        /** initialize the threshold; a zero value for maxEntries
         *  implies no caching.
         */
        if (maxEntries != 0) {
            threshold = (int)(maxEntries * loadFactor) + 1;
        }

        // create the cache and the bucket locks
        entryCount = 0;
       	buckets = new CacheItem[maxBuckets];
       	bucketLocks = new Object[maxBuckets];
        refreshFlags = new boolean[maxBuckets];

        for (int i=0; i<maxBuckets; i++) {
            buckets[i] = null;
            bucketLocks[i] = new Object();
            refreshFlags[i] = false;
        }
    }

    /**
     * add the cache module listener
     * @param listener <code>CacheListener</code> implementation
     */
    public void addCacheListener(CacheListener listener) {
        listeners.add(listener);
    }
    
    /**
     * Returns a hash code for non-null Object x.
     * @See also <code>HashMap</code>
     */
    protected int hash(Object x) {
        int h = x.hashCode();
        return h - (h << 7);  // i.e., -127 * h
    }
    
    /** 
     * Check for equality of non-null reference x and possibly-null y. 
     */
    protected boolean eq(Object x, Object y) {
        return x == y || x.equals(y);
    }
    
    /**
     * increase the threshold
     */
    protected void handleOverflow() {
        // just double the threshold; this may degenerate the cache.
        threshold = (threshold * 2);
        incrementOverflowCount();
    }
    
    /**
     * this item is just added to the cache
     * @param item <code>CacheItem</code> that was created
     * @return a overflow item; may be null
     * Cache bucket is already synchronized by the caller
     *
     * Here, if cache is overflowing (i.e. reached threshold); this class 
     * simply makes the cache unbounded by raising the threshold. Subclasses 
     * are expected to provide a robust cache replacement algorithm.
     *
     * Subclasses should enhance this implemntation. 
     */
    protected CacheItem itemAdded(CacheItem item) {
        if (isThresholdReached()) {
            handleOverflow();
        }
        return null;
    }

    /**
     * this item is accessed 
     * @param item <code>CacheItem</code> accessed
     *
     * Cache bucket is already synchronized by the caller
     */
    protected void itemAccessed(CacheItem item) { }

    /**
     * item value has been refreshed
     * @param item <code>CacheItem</code> that was refreshed
     * @param oldSize size of the previous value that was refreshed
     * Cache bucket is already synchronized by the caller
     */
    protected void itemRefreshed(CacheItem item, int oldSize) { }

    /**
     * item value has been removed from the cache
     * @param item <code>CacheItem</code> that was just removed
     *
     * Cache bucket is already synchronized by the caller
     */
    protected void itemRemoved(CacheItem item) { }

    /**
     * Cannot find an item with the given key and hashCode
     * @param key <code>Object</code> that is not found
     * @param hashCode <code>int</code> its hashCode
     *
     * @returns the Object value associated with the item
     * Cache bucket is already synchronized by the caller
     */
    protected Object loadValue(Object key, int hashCode) { 
        return null;
    }

    /**
     * create new item
     * @param hashCode for the entry
     * @param key <code>Object</code> key 
     * @param value <code>Object</code> value
     * @param size size in bytes of the item
     * subclasses may override to provide their own CacheItem extensions
     * e.g. one that permits persistence.
     */
    protected CacheItem createItem(int hashCode, Object key, 
                                        Object value, int size) {
        return new CacheItem(hashCode, key, value, size);
    }
 
    /**
     * has cache reached its threshold
     * @return true when the cache reached its threshold
     */
    protected boolean isThresholdReached() {
        return (entryCount > threshold);
    }

    /** 
     * get the index of the item in the cache
     * @param hashCode of the entry
     * @return the index to be used in the cache
     */
    protected final int getIndex(int hashCode) {
        return (hashCode & (maxBuckets - 1));
    }

    /** 
     * get the index of the item given a key
     * @param key of the entry
     * @return the index to be used in the cache
     */
    public final int getIndex(Object key) {
        return getIndex(hash(key));
    }

    /**
     * get the item stored at the key.
     * @param key lookup key
     * @returns the item stored at the key; null if not found.
     */
    public Object get(Object key) {
        int hashCode = hash(key);

        return get(hashCode, key);
    }

    /**
     * get the item stored at the given pre-computed hash code and the key.
     * @param key lookup key
     * @returns the item stored at the key; null if not found.
     */
    public Object get(int hashCode, Object key) {

        int index = getIndex(hashCode);
        Object value;
        CacheItem item = null;

        synchronized (bucketLocks[index]) {
            item = buckets[index];
            
            for (; item != null; item = item.next) {
                if ( (hashCode == item.hashCode) && eq(key, item.key) ) {
                    break;
                }
            }
            
            // update the stats in line
            if (item != null) {
                value = item.getValue();
                itemAccessed(item);
            }
            else {
                value = loadValue(key, hashCode);
            }
        }
        
        if (item != null) {
            incrementHitCount();
        } else {
            incrementMissCount();
        }

        return value;
    }
        
    /**
     * check if the cache contains the item at the key
     * @param key lookup key
     * @returns true if there is an item stored at the key; false if not.
     */
    public boolean contains(Object key) {
    	return (get(key) != null);
    }
    
    /**
     * get all the items stored at the key.
     * @param key lookup key
     * @returns an Iterator over the items with the given key.
     */
    public Iterator getAll(Object key) {
        int hashCode = hash(key);
        int index = getIndex(hashCode);

        ArrayList valueList = new ArrayList(entryCount);
        synchronized (bucketLocks[index]) {
            CacheItem item = buckets[index];
            
            for (; item != null; item = item.next) {
                if ( (hashCode == item.hashCode) && eq(key, item.key) ) {
                    incrementHitCount();
                    valueList.add(item.getValue());
                }
            }
            
        }

        return valueList.iterator();
    }

    /**
     * get an Iterator for the keys stored in the cache
     * @returns an Iterator
     */
    public Iterator keys() {
        ArrayList keyList = new ArrayList(entryCount);

        for (int index=0; index < maxBuckets; index++) {
            synchronized (bucketLocks[index]) {
                for (CacheItem item = buckets[index]; item != null; 
                     item = item.next) {
                    keyList.add(item.key);
                }
            }
        }
        
        return keyList.iterator();
    }
    
    /**
     * get an Enumeration for the keys stored in the cache
     * @returns an Enumeration
     * XXX: should use Iterator which is based on Collections
     */
    public Enumeration elements() {
        Vector keyList = new Vector();
        
        for (int index=0; index < maxBuckets; index++) {
            synchronized (bucketLocks[index]) {
                for (CacheItem item = buckets[index]; item != null; 
                     item = item.next) {
                    keyList.addElement(item.value);
                }
            }
        }
        
        return keyList.elements();
    }
    
    /**
     * get an Iterator for the values stored in the cache
     * @returns an Iterator
     */
    public Iterator values() {
        ArrayList valueList = new ArrayList(entryCount);
        
        for (int index=0; index < maxBuckets; index++) {
            synchronized (bucketLocks[index]) {
                for (CacheItem item = buckets[index]; item != null; 
                     item = item.next) {
                    valueList.add(item.value);
                }
            }
        }
        
        return valueList.iterator();
    }
    
    /**
     * cache the given value at the specified key and return previous value
     * @param key lookup key
     * @param object item value to be stored
     * @returns the previous item stored at the key; null if not found.
     */
    public Object put(Object key, Object value) {
        int hashCode = hash(key);
        
        return _put(hashCode, key, value, -1, false);
    }
    
    /**
     * cache the given value at the specified key and return previous value
     * @param key lookup key
     * @param object item value to be stored
     * @param size in bytes of the value being cached
     * @returns the previous item stored at the key; null if not found.
     */
    public Object put(Object key, Object value, int size) {
        int hashCode = hash(key);

        return _put(hashCode, key, value, size, false);
    }

    /**
     * add the given value to the cache at the specified key
     * @param key lookup key
     * @param object item value to be stored
     */
    public void add(Object key, Object value) {
        int hashCode = hash(key);

        _put(hashCode, key, value, -1, true);
    }

    /**
     * add the given value with specified size to the cache at specified key
     * @param key lookup key
     * @param object item value to be stored
     * @param size in bytes of the value being added
     *
     * This function is suitable for multi-valued keys.
     */
    public void add(Object key, Object value, int size) {
        int hashCode = hash(key);

        _put(hashCode, key, value, size, true);
    }

    /**
     * cache the given value at the specified key and return previous value
     * @param hashCode previously computed hashCode for the key
     * @param key lookup key
     * @param object item value to be stored
     * @param size in bytes of the value being cached
     * @param addValue treate this operation to add (default is to replace)
     * @returns the previous item stored at the key; null if not found.
     *
     * Note: This can be used just to refresh the cached item as well, altho
     * it may call trimCache() if the cache reached its threshold -- which is
     * is probably not very intuitive.
     */
    protected Object _put(int hashCode, Object key,
                          Object value, int size, boolean addValue) {
        int index = getIndex(hashCode);
        
        CacheItem item, newItem = null, oldItem = null, overflow = null;
        Object oldValue;
        int oldSize = 0;

        // lookup the item
        synchronized (bucketLocks[index]) {
            for (item = buckets[index]; item != null; item = item.next) {
                if ((hashCode == item.hashCode) && eq(key, item.key)) {

                    oldItem = item;
                    break;
                }
            }

            // if there was no item in the cache, insert the given item
            if (addValue || oldItem == null) {
                newItem = createItem(hashCode, key, value, size); 
                
                // add the item at the head of the bucket list
                newItem.next = buckets[index];
                buckets[index] = newItem;
                
                oldValue = null;
                overflow = itemAdded(newItem);
            } else {
                oldSize = oldItem.getSize();
                oldValue = oldItem.refreshValue(value, size);
                itemRefreshed(oldItem, oldSize);
            }
        }

        if (newItem != null) {
            incrementEntryCount();
            incrementAddCount();
            
            // make sure we are are not crossing the threshold
            if (overflow != null) {
                trimItem(overflow);
            }
        } else {
            incrementRefreshCount();
        }

        return oldValue;
    }

    /**
     * remove the item stored at the key.
     * @param key lookup key
     * @returns the item stored at the key; null if not found.
     */
    public Object remove(Object key) {
        int hashCode = hash(key);

        Object retVal  = null;
        CacheItem removed = _remove( hashCode, key, null);
        
        if (removed != null) {
            retVal = removed.getValue();
        }
        return retVal;
    }

    /**
     * remove the item stored at the key.
     * @param hashCode a precomputed hashCode
     * @param key lookup key
     * @returns the item stored at the key; null if not found.
     */
    public Object remove(int hashCode, Object key) {
        Object retVal  = null;
        CacheItem removed = _remove( hashCode, key, null);
        
        if (removed != null) {
            retVal = removed.getValue();
        }
        return retVal;
    }

    /**
     * remove the given value stored at the key; value-specific removals.
     * @param key lookup key
     * @param value to match (for a multi-valued keys)
     * @returns the item stored at the key; null if not found.
     */
    public Object remove(Object key, Object value) {
        int hashCode = hash(key);
        
        Object retVal  = null;
        CacheItem removed = _remove( hashCode, key, value);
        
        if (removed != null) {
            retVal = removed.getValue();
        }
        return retVal;
    }

    /**
     * remove the item stored at the key.
     * @param hashCode a precomputed hashCode
     * @param key lookup key
     * @param value of the item to be matched 
     * @returns the item stored at the key; null if not found.
     */
    protected CacheItem _remove(int hashCode, Object key, Object value) {
        int index = getIndex(hashCode);

        CacheItem prev = null, item = null;

        synchronized (bucketLocks[index]) {
            for (item = buckets[index]; item != null; item = item.next) {
                if (hashCode == item.hashCode && key.equals(item.key)) {

                    if (value == null || value == item.value) {

                        if (prev == null) {
                            buckets[index] = item.next;
                        } else  {
                            prev.next = item.next;
                        }
                        item.next = null;
                        
                        itemRemoved(item);
                        break;
                    }
                }
                prev = item;
            }
        }
        
        if (item != null) {
            decrementEntryCount();
            incrementRemovalCount();
            
            incrementHitCount();
        } else {
            incrementMissCount();
        }

        return item;
    }

    /**
     * remove the item stored at the key.
     * @param item CacheItem to be removed
     * @return the item stored at the key; null if not found.
     */
    protected CacheItem _removeItem(CacheItem ritem) {

        int index = getIndex(ritem.hashCode);

        CacheItem prev = null, item = null;
        
        synchronized (bucketLocks[index]) {
            for (item = buckets[index]; item != null; item = item.next) {
                if (item == ritem) {
                    if (prev == null) {
                        buckets[index] = item.next;
                    } else {
                        prev.next = item.next;
                    }
                    item.next = null;
                    break;
                }
                prev = item;
            }
        }
        
        if (item != null) {
            decrementEntryCount();
        }

        return item;
    }

    /**
     * remove all the item with the given key.
     * @param key lookup key
     */
    public void removeAll(Object key) {
        int hashCode = hash(key);
        int index = getIndex(hashCode);

        CacheItem prev = null, item = null;
        ArrayList items = new ArrayList(entryCount);

        synchronized (bucketLocks[index]) {
            for (item = buckets[index]; item != null;
                 item = item.next) {
                if (hashCode == item.hashCode && key.equals(item.key)) {
                    if (prev == null) {
                        buckets[index] = item.next;
                    } else  {
                        prev.next = item.next;
                    }
                    item.next = null;
                    
                    decrementEntryCount();
                    incrementRemovalCount();
                    
                    items.add(item);
                }
                prev = item;
            }
        }

        // notify subclasses
        for (int i = 0; i < items.size(); i++) {
            itemRemoved((CacheItem)items.get(i));
        }
    }

    /**
     * trim the item from the cache and notify listeners
     * @param item to be trimmed
     */
    protected void trimItem(CacheItem item) {
        CacheItem removed = _removeItem(item);

        if (removed != null) {
            for (int i = 0; i < listeners.size(); i++) {
                CacheListener listener = (CacheListener) listeners.get(i);
                listener.trimEvent(removed.key, removed.value);
            }
        }
    }

    /**
     * wait for a refresh on the object associated with the key
     * @param key lookup key
     * @returns true on successful notification, or false if there is
     *  no thread refreshing this entry.
     */
    public boolean waitRefresh(int index) {
        synchronized (bucketLocks[index]) {
            if (refreshFlags[index] == false) {
                refreshFlags[index] = true;
                return false;
            }

            // wait till refresh is finished
            try {
                bucketLocks[index].wait();
            } catch (InterruptedException ie) {}
        }
        return true;
    }

    /**
     * notify threads waiting for a refresh on the object associated 
     * with the key
     * @param key lookup key
     */
    public void notifyRefresh(int index) {
        // notify other threads waiting for refresh
        synchronized (bucketLocks[index]) {
            refreshFlags[index] = false;
            bucketLocks[index].notifyAll();
        }
    }
    
    /**
     * clear all the entries from the cache.
     * @returns the number of entries cleared from the cache
     */
    public int clear() {
        
        CacheItem item=null, next=null;
        int count = 0;
        
        for (int index = 0; index < maxBuckets; index++) {
            synchronized (bucketLocks[index]) {
                for (item = buckets[index]; item != null; 
                     item = item.next) {
                    next = item.next;
                    item.next = null;
                    
                    count++;
                    decrementEntryCount();
                    itemRemoved(item);
                    
                    if (entryCount == 0) {
                        break;
                    }
			    }
                buckets[index] = null;
            }
        }
        
        return count;
    }
    
    /**
     * trim the expired entries from the cache.
     * @param maxCount maximum number of invalid entries to trim
     *        specify Integer.MAX_VALUE to trim all timedout entries
     *
     * This call is to be scheduled by a thread managed by the container.
     */
    public void trimExpiredEntries(int maxCount) {}

    /**
     * get the number of entries in the cache
     * @return the number of entries the cache currently holds
     */
    public int getEntryCount() {
        return entryCount;
    }

    /*** methods for monitoring the cache          ***/

    /**
     * is this cache empty?
     * @returns true if the cache is empty; false otherwise.
     */
    public boolean isEmpty() {
        return (entryCount == 0);
    }

    /**
     * synchronized counter updates
     */
    protected final void incrementEntryCount() {
        synchronized(entryCountLk) {
            entryCount++;
        }
    }

    protected final void decrementEntryCount() {
        synchronized(entryCountLk) {
            entryCount--;
        }
    }

    protected final void incrementHitCount() {
        synchronized (hitCountLk) {
            hitCount++;
        }
    }

    protected final void incrementMissCount() {
        synchronized (missCountLk) {
            missCount++;
        }
    }

    protected final void incrementRemovalCount() {
        synchronized (removalCountLk) {
            removalCount++;
        }
    }

    protected final void incrementRefreshCount() {
        synchronized (refreshCountLk) {
            refreshCount++;
        }
    }

    protected final void incrementAddCount() {
        synchronized (addCountLk) {
            addCount++;
        }
    }

    protected final void incrementOverflowCount() {
        synchronized (overflowCountLk) {
            overflowCount++;
        }
    }

    /**
     * get generic stats from subclasses 
     */

    /**
     * get the desired statistic counter
     * @param key to corresponding stat
     * @return an Object corresponding to the stat
     * See also: Constant.java for the key
     */
    public Object getStatByName(String key) {
        Object stat = null;

        if (key == null)
            return null;

        if (key.equals(Constants.STAT_BASECACHE_MAX_ENTRIES))
            stat = new Integer(maxEntries);
        else if (key.equals(Constants.STAT_BASECACHE_THRESHOLD))
            stat = new Integer(threshold);
        else if (key.equals(Constants.STAT_BASECACHE_TABLE_SIZE))
            stat = new Integer(maxBuckets);
        else if (key.equals(Constants.STAT_BASECACHE_ENTRY_COUNT))
            stat = new Integer(entryCount);
        else if (key.equals(Constants.STAT_BASECACHE_HIT_COUNT))
            stat = new Integer(hitCount);
        else if (key.equals(Constants.STAT_BASECACHE_MISS_COUNT))
            stat = new Integer(missCount);
        else if (key.equals(Constants.STAT_BASECACHE_REMOVAL_COUNT))
            stat = new Integer(removalCount);
        else if (key.equals(Constants.STAT_BASECACHE_REFRESH_COUNT))
            stat = new Integer(refreshCount);
        else if (key.equals(Constants.STAT_BASECACHE_OVERFLOW_COUNT))
            stat = new Integer(overflowCount);
        else if (key.equals(Constants.STAT_BASECACHE_ADD_COUNT))
            stat = new Integer(addCount);

        return stat;
    }

    /**
     * get the stats snapshot
     * @return a Map of stats
     * See also: Constant.java for the keys
     */
    public Map getStats() {
        HashMap stats = new HashMap();

        stats.put(Constants.STAT_BASECACHE_MAX_ENTRIES, 
                  new Integer(maxEntries));
        stats.put(Constants.STAT_BASECACHE_THRESHOLD, 
                  new Integer(threshold));
        stats.put(Constants.STAT_BASECACHE_TABLE_SIZE, 
                  new Integer(maxBuckets));
        stats.put(Constants.STAT_BASECACHE_ENTRY_COUNT, 
                  new Integer(entryCount));
        stats.put(Constants.STAT_BASECACHE_HIT_COUNT, 
                  new Integer(hitCount));
        stats.put(Constants.STAT_BASECACHE_MISS_COUNT, 
                  new Integer(missCount));
        stats.put(Constants.STAT_BASECACHE_REMOVAL_COUNT, 
                  new Integer(removalCount));
        stats.put(Constants.STAT_BASECACHE_REFRESH_COUNT, 
                  new Integer(refreshCount));
        stats.put(Constants.STAT_BASECACHE_OVERFLOW_COUNT, 
                  new Integer(overflowCount));
        stats.put(Constants.STAT_BASECACHE_ADD_COUNT, 
                  new Integer(addCount));

        return stats;
    }

    /**
     * clear the stats
     */
    public void clearStats() {
        hitCount = 0;
        missCount = 0;
        removalCount = 0;
        refreshCount = 0;
        overflowCount = 0;
        addCount = 0;
    }

    /** default CacheItem class implementation  ***/
    protected static class CacheItem {
        int hashCode;
        Object key;
        Object value;
        int size;
    
        CacheItem next;
        
        protected CacheItem(int hashCode, Object key, Object value, int size) {
            this.hashCode = hashCode;
            this.key = key;
            this.value = value;
            this.size = size;
        }
        
        /**
         * get the item's hashCode
         */
        protected int getHashCode() {
            return hashCode;
        }
        
        /**
         * get the item's key
         */
        protected Object getKey() {
            return key;
        }
        
        /**
         * get the item's value
         */
        protected Object getValue() {
            return value;
        }
        
        /**
         * @return size of the entry in bytes
         * a value of -1 indicates unknown size
         */
        protected int getSize() {
            return size;
        }
    
        /**
         * refresh the item's value
         * @param value value to be updated
         * @param newSize of the field
         */
        protected Object refreshValue(Object value, int newSize) {
            Object oldValue = this.value;
            this.value = value;
            this.size = newSize;
    
            return oldValue;
        }
    
        public String toString() {
            return "key: " + key + "; value: " + value.toString();
        }
    }
}