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

org.enhydra.xml.xmlc.deferredparsing.Cache Maven / Gradle / Ivy

The newest version!
/*
 * Cache.java
 *
 * Created: Fri Dec 21 11:38:14 2001
 * Author : Ole Arndt
 */
package org.enhydra.xml.xmlc.deferredparsing;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.AbstractCollection;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * A Cache for arbitrary objects. It holds its objects with soft
 * references. Because broken handling of soft references in
 * current JVM implementations, we also hold additional hard
 * references to the most recently used entries.
 *
 * @author  Ole Arndt
 * @version $Id: Cache.java,v 1.3 2005/01/26 08:29:24 jkjome Exp $
 */
public class Cache implements Map {
    
    /**
     * A simple class for holding references to
     * the most recently used entries.
     */
    protected static class MRUCache {
        
        private static final class Entry {
            public Object ref;
            public Cache.MRUCache.Entry prev;
            public Cache.MRUCache.Entry next;
        }
        
            
        /** an array to hold the references. */
        private Entry[] cache;

        /** list of free entries */
        private Entry freelist;
        
        /** Current index into ref array. */
        private Entry header;

        /**
         * Creates a new MRUCache instance.
         *
         * @param num an int value
         */
        MRUCache(int num) {
            cache = new Entry[num];
            for (int i = 0; i < cache.length; ++i)
                cache[i] = new Entry();
            
            header = new Entry();
            this.clear();
        }

        /**
         * Describe getCapacity method here.
         *
         * @return an int value
         */
        int getCapacity() {
            return cache.length;
        }

        /**
         * Describe add method here.
         *
         * @param obj an Object value
         * @return an Object value
         */
         synchronized Object add(Object obj) {
            // if object is already at front of cache
            // return immediately
            if (obj == null || obj == header.next.ref)
                return obj;
            
            Entry entry = null;

            if ((entry = find(obj)) != null) {
                extractEntry(entry);
            } else if (freelist != null) {
                entry = freelist;
                freelist = entry.next;
            } else {
                // remove from back
                entry = header.prev;
                extractEntry(entry);
            }

            entry.ref = obj;

            // insert to front
            entry.next = header.next;
            entry.prev = header;
            entry.next.prev = entry;
            header.next = entry;
            
            return obj;
        }

        
        /**
         * Remove an object from the cache.
         *
         * @param obj an Object value
         */
        synchronized void remove(Object obj) {
            Entry entry = null;
            if (obj != null && (entry = find(obj)) != null) {
                extractEntry(entry);

                entry.ref = null;
                entry.next = freelist;
                freelist = entry;
            }
        }
        
        /**
         * Clear the MRU cache.
         */
         synchronized void clear() {
            header.next = header.prev = header;
            
            for (int i = 0; i < (cache.length - 1); ++i) {
                cache[i].next = cache[i + 1];
                cache[i].ref = null;
            }
            
            freelist = cache[0];
            cache[cache.length - 1].next = null;
            cache[cache.length - 1].ref  = null;
        }

        /**
         * Find an entry by object.
         *
         * @param obj an Object value
         * @return an Object value
         */
        private Entry find(Object obj) {
            for (Entry e = header.next; e != header; e = e.next) {
                if (e.ref == obj)
                    return e;
            }
            return null;
        }


        /**
         * Extract an entry.
         *
         * @param e an Entry value
         */
        private void extractEntry(Entry entry) {
            entry.next.prev = entry.prev;
            entry.prev.next = entry.next;
        }
        
    }

    /**
     * An inner class to hold the mappings.
     */
    private class MapEntry extends WeakReference implements Map.Entry {
        /** the key */
        private Object key;
        
        /**
         * Creates a new MapEntry instance.
         *
         * @param key an Object value
         * @param value an Object value
         * @param queue the reference queue this entry will be enqueued on.
         */
        MapEntry(Object key, Object value, ReferenceQueue queue) {
            super(value, queue);
            this.key = key;
        }

        /**
         * Get the Key value.
         * @return the Key value.
         */
        public Object getKey() {
            return key;
        }

        /**
         * Get the Value value.
         * @return the Value value.
         */
        public Object getValue() {
            return this.get();
        }

        /**
         * Set the Value value.
         * @param value The new Value value.
         */
        public Object setValue(Object value) {
            return Cache.this.put(key, value);
        }

        public boolean equals(Object o) {
            return (o!=null) &&
                (o instanceof MapEntry) &&
                (key.equals(((MapEntry)o).getKey()));
        }

        public int hashCode() {
            return key.hashCode();
        }
    }
    
    /**
     * An iterator for the entry set.
     */
    private class ValueIterator implements Iterator {
        Iterator it;

        public ValueIterator(Iterator it) {
            this.it = it;
        }
        
        public boolean hasNext() {
            return it.hasNext();
        }

        public Object next() {
            return mru.add(((MapEntry) it.next()).getValue());
        }

        public void remove() {
            it.remove();
        }
    }

    /** the default size for the MRU cache */
    public static final int DEFAULT_NUM_MRU_ENTRIES = 64;
    
    /** the reference queue for collecting old references */
    private ReferenceQueue queue = new ReferenceQueue();

    /** the underlying map */
    private HashMap map = new HashMap();

    /** the  MRU cache */
    private MRUCache mru;

    /**
     * Creates a new Cache instance with the
     * default value for most recently used entries.
     *
     */
    public Cache () {
        this(DEFAULT_NUM_MRU_ENTRIES);
    }

    /**
     * Creates a new Cache instance with an
     * explicit value for most recently used entries cache.
     *
     * @param numMRUEntries an int value
     */
    public Cache (int numMRUEntries) {
        mru = new MRUCache(numMRUEntries);
    }

    /**
     * Creates a new Cache instance.
     * The give Map will be copied.
     * 
     * @param other a Map value
     */
    public Cache (Map other) { 
        this();
        putAll(other);
    }
    
    /**
     * Creates a new Cache instance.
     * The give Map will be copied.
     * 
     * @param other a Map value
     */
    public Cache (int numMRUEntries, Map other) { 
        this(numMRUEntries);
        putAll(other);
    }

    /**
     * Get hash code of map.
     *
     * @return an int value
     */
    public int hashCode() {
        return map.hashCode();
    }

    /**
     * Compare two Cache instances.
     *
     * @param cache other cache to compare to.
     * @return true if caches are equal, false otherwise.
     */
    public boolean equals(Object cache) {
        return (cache instanceof Cache) && map.equals(((Cache)cache).map);
    }

    /**
     * Get the current size of the cache.
     *
     * @return the size of the cache.
     */
    public int size() {
        return map.size();
    }

    /**
     * Describe clear method here.
     *
     */
    public void clear() {
        mru.clear();
        map.clear();
    }

    /**
     * Associates the specified value with the specified key in this cache.
     * If the cache previously contained a mapping for this key, the old value is replaced.
     * Note that this cache can not map null values.
     * 
     * @param key key with which the specified value is to be associated.
     * @param value value to be associated with the specified key.
     * @return previous value associated with specified key, or null if there was no mapping for key.
     * @throws NullPointerException if value is null.
     */
    public synchronized Object put(Object key, Object value) {

        // cannot map null values, they do not differ from
        // cleared soft references.
        if (value == null)
            return new UnsupportedOperationException("Cache does not support null values");

        // clean up before us, so we don't fill the memory
        // with keys if user calls only #put and never #get
        cleanup();

        MapEntry entry = (MapEntry) map.put(key, new MapEntry(key, value, queue));

        return (entry != null) ? entry.getValue() : null;
    }

    /**
     * Returns the value to which this cache maps the specified key.
     * If the value is not found, returns null
     *
     * @param key key whose associated value is to be returned.
     * @return the value to which this cache maps the specified key,
     * or null if the cache contains no mapping for
     * this key.
     */
    public synchronized Object get(Object key) {
        Object value = null;
        MapEntry entry = null;

        cleanup();

        if ((entry = (MapEntry) map.get(key)) != null)
            value = entry.getValue();

        return mru.add(value);
    }

    /**
     * Return a collection of all values this cache contains.
     * It is not guaranteed, that an iterator from the collection
     * will return only non null values. It is likely some soft (weak) references
     * will go away while traversing the Collection. The iterator will return
     * null in this case.
     * 
     * @return a Collection containing a values of the cache.
     */
    public Collection values() {
        return new AbstractCollection() {
                public int size() {
                    return Cache.this.size();
                }

                public Iterator iterator() {
                    return getValueIterator();
                }
            };
    }

    /**
     * Removes the mapping for this key from this cache.
     *
     * @param key key whose mapping is to be removed from the cache.
     * @return previous value associated with specified key, or null if there was no mapping for key.
     */
    public synchronized Object remove(Object key) {
        cleanup();
        mru.remove(key);
        MapEntry entry = (MapEntry) map.remove(key);
        return (entry != null) ? entry.getValue() : null;
    }

    /**
     * Return a Set of all keys.
     *
     * @return a Set of all keys.
     */
    public Set keySet() {
        cleanup();
        return map.keySet();
    }

    /**
     * Returns a set view of the mappings contained in this cache.
     * Each element in the returned set is a Map.Entry. The set is backed by the cache,
     * so changes to the cache are reflected in the set, and vice-versa.
     * If the map is modified while an iteration over the set is in progress,
     * the results of the iteration are undefined.
     * The set supports element removal, which removes the corresponding mapping from the map,
     * via the Iterator.remove, Set.remove, removeAll, retainAll and clear operations.
     * It does not support the add or addAll operations.
     *
     * @return a Set view of all values.
     */
    public Set entrySet() {
        cleanup();
        return map.entrySet();
    }

    /**
     * Map interface, isEmpty method.
     *
     * @return true if map is empty, false otherwise
     * @see java.util.Map#isEmpty
     */
    public boolean isEmpty() {
        cleanup();
        return map.isEmpty();
    }

    /**
     * Describe containsValue method here.
     *
     * @param obj an Object value
     * @return a boolean value
     */
    public boolean containsValue(Object obj) {
        for (Iterator it = map.entrySet().iterator(); it.hasNext(); ) {
            MapEntry entry  = (MapEntry) it.next();
            Object value = entry.getValue();
            if (value != null && value.equals(obj))
                return true;
        }
        
        return false;
    }

    /**
     * Describe containsKey method here.
     *
     * @param key an Object value
     * @return a boolean value
     */
    public boolean containsKey(Object key) {
        return map.containsKey(key);
    }

    /**
     * Describe putAll method here.
     *
     * @param cache a Map value
     */
    public void putAll(Map cache) {
        for (Iterator it = cache.keySet().iterator(); it.hasNext(); ) {
            Object key = it.next(); 
            put(key, cache.get(key));
        }
    }

    /**
     * Clean up the expired references from the reference queue.
     */
    private synchronized void cleanup() {
        MapEntry entry;
        while ((entry = (MapEntry)queue.poll()) != null) {
            map.remove(entry.getKey());
	}
    }
    
    private Iterator getValueIterator() {
        cleanup();
        return new ValueIterator(map.entrySet().iterator());
    }
    
} // Cache




© 2015 - 2025 Weber Informatics LLC | Privacy Policy