org.apache.ojb.broker.util.ReferenceMap.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.ojb.broker.util.ReferenceMap.java

Source

package org.apache.ojb.broker.util;

/* Copyright 2004-2005 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

/**
 * <p>
 * Modified version of {@link org.apache.commons.collections.ReferenceMap}, using
 * object identity for key comparison (). This class
 * simply extended {@link org.apache.commons.collections.ReferenceMap} with an extra field
 * "useSystemIdentity" which is initialized in constructor and is used every time we
 * want to compare (or hash) keys or values.
 * </p>
 * <p>
 * Javadoc of ReferenceMap starts here:
 * <br/>
 * Hashtable-based {@link java.util.Map} implementation that allows
 *  mappings to be removed by the garbage collector.
 * </p>
 * <P>
 *  When you construct a <Code>ReferenceMap</Code>, you can
 *  specify what kind of references are used to store the
 *  map's keys and values.  If non-hard references are
 *  used, then the garbage collector can remove mappings
 *  if a key or value becomes unreachable, or if the
 *  JVM's memory is running low.  For information on how
 *  the different reference types behave, see
 *  {@link java.lang.ref.Reference}.<P>
 *
 *  Different types of references can be specified for keys
 *  and values.  The keys can be configured to be weak but
 *  the values hard, in which case this class will behave
 *  like a <A HREF="http://java.sun.com/j2se/1.4/docs/api/java/util/WeakHashMap.html">
 *  <Code>WeakHashMap</Code></A>.  However, you
 *  can also specify hard keys and weak values, or any other
 *  combination.  The default constructor uses hard keys
 *  and soft values, providing a memory-sensitive cache.<P>
 *
 *  The algorithms used are basically the same as those
 *  in {@link java.util.HashMap}.  In particular, you
 *  can specify a load factor and capacity to suit your
 *  needs.  All optional {@link java.util.Map} operations are
 *  supported.<P>
 *
 *  However, this {@link java.util.Map} implementation does <I>not</I>
 *  allow null elements.  Attempting to add a null key or
 *  or a null value to the map will raise a
 *  <Code>NullPointerException</Code>.<P>
 *
 *  As usual, this implementation is not synchronized.  You
 *  can use {@link java.util.Collections#synchronizedMap} to
 *  provide synchronized access to a <Code>ReferenceMap</Code>.
 *
 * @deprecated use {@link org.apache.commons.collections.map.ReferenceIdentityMap} instead.
 *  @author Andy Malakov
 *  @version $Id: ReferenceMap.java,v 1.6.2.2 2005/12/21 22:27:47 tomdz Exp $
 */
public class ReferenceMap extends AbstractMap {

    /**
     *  For serialization.
     */
    final private static long serialVersionUID = -3370601314380922368L;

    /**
     *  Constant indicating that hard references should be used.
     */
    final public static int HARD = 0;

    /**
     *  Constant indiciating that soft references should be used.
     */
    final public static int SOFT = 1;

    /**
     *  Constant indicating that weak references should be used.
     */
    final public static int WEAK = 2;

    // --- serialized instance variables:

    /**
     *  The reference type for keys.  Must be HARD, SOFT, WEAK.
     *  Note: I originally marked this field as final, but then this class
     *   didn't compile under JDK1.2.2.
     *  @serial
     */
    private int keyType;

    /**
     *  The reference type for values.  Must be HARD, SOFT, WEAK.
     *  Note: I originally marked this field as final, but then this class
     *   didn't compile under JDK1.2.2.
     *  @serial
     */
    private int valueType;

    /**
     *  The threshold variable is calculated by multiplying
     *  table.length and loadFactor.
     *  Note: I originally marked this field as final, but then this class
     *   didn't compile under JDK1.2.2.
     *  @serial
     */
    private float loadFactor;

    // -- Non-serialized instance variables

    /**
     *  ReferenceQueue used to eliminate stale mappings.
     *  @see #purge
     */
    private transient ReferenceQueue queue = new ReferenceQueue();

    /**
     *  The hash table.  Its length is always a power of two.
     */
    private transient Entry[] table;

    /**
     *  Number of mappings in this map.
     */
    private transient int size;

    /**
     *  When size reaches threshold, the map is resized.
     *  @see #resize
     */
    private transient int threshold;

    /**
     *  Number of times this map has been modified.
     */
    private transient volatile int modCount;

    /**
     *  Cached key set.  May be null if key set is never accessed.
     */
    private transient Set keySet;

    /**
     *  Cached entry set.  May be null if entry set is never accessed.
     */
    private transient Set entrySet;

    /**
     *  Cached values.  May be null if values() is never accessed.
     */
    private transient Collection values;

    /**
      *  Note: I originally marked this field as final, but then this class
      *   didn't compile under JDK1.2.2.
      */
    private boolean useSystemIdentity;

    /**
     *  Constructs a new <Code>ReferenceMap</Code> that will
     *  use hard references to keys and soft references to values.
     */
    public ReferenceMap() {
        this(HARD, SOFT);
    }

    /**
     *  Constructs a new <Code>ReferenceMap</Code> that will
     *  use the specified types of references.
     *
     *  @param keyType  the type of reference to use for keys;
     *   must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
     *  @param valueType  the type of reference to use for values;
     *   must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
     */
    public ReferenceMap(int keyType, int valueType) {
        this(keyType, valueType, 16, 0.75f, false);
    }

    /**
     *  Constructs a new <Code>ReferenceMap</Code> with the
     *  specified reference types, load factor and initial
     *  capacity.
     *
     *  @param keyType  the type of reference to use for keys;
     *   must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
     *  @param valueType  the type of reference to use for values;
     *   must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
     *  @param capacity  the initial capacity for the map
     *  @param loadFactor  the load factor for the map
     *  @param useSystemIdentity if true System.identityHashCode() and comparision operator (==) will be used
     *         in place of Object.hashCode and Object.equals (Please read {@link java.util.WeakHashMap}
     *         java doc for more information).
     */
    public ReferenceMap(int keyType, int valueType, int capacity, float loadFactor, boolean useSystemIdentity) {
        super();

        verify("keyType", keyType);
        verify("valueType", valueType);

        this.useSystemIdentity = useSystemIdentity;

        if (capacity <= 0) {
            throw new IllegalArgumentException("capacity must be positive");
        }
        if ((loadFactor <= 0.0f) || (loadFactor >= 1.0f)) {
            throw new IllegalArgumentException("Load factor must be greater than 0 and less than 1.");
        }

        this.keyType = keyType;
        this.valueType = valueType;

        int v = 1;
        while (v < capacity)
            v *= 2;

        this.table = new Entry[v];
        this.loadFactor = loadFactor;
        this.threshold = (int) (v * loadFactor);
    }

    // used by constructor
    private static void verify(String name, int type) {
        if ((type < HARD) || (type > WEAK)) {
            throw new IllegalArgumentException(name + " must be HARD, SOFT, WEAK.");
        }
    }

    /**
     *  Writes this object to the given output stream.
     *
     *  @param out  the output stream to write to
     *  @throws java.io.IOException  if the stream raises it
     */
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        out.writeInt(table.length);

        // Have to use null-terminated list because size might shrink
        // during iteration

        for (Iterator iter = entrySet().iterator(); iter.hasNext();) {
            Map.Entry entry = (Map.Entry) iter.next();
            out.writeObject(entry.getKey());
            out.writeObject(entry.getValue());
        }
        out.writeObject(null);
    }

    /**
     *  Reads the contents of this object from the given input stream.
     *
     *  @param inp  the input stream to read from
     *  @throws java.io.IOException  if the stream raises it
     *  @throws java.lang.ClassNotFoundException  if the stream raises it
     */
    private void readObject(ObjectInputStream inp) throws IOException, ClassNotFoundException {
        inp.defaultReadObject();
        table = new Entry[inp.readInt()];
        threshold = (int) (table.length * loadFactor);
        queue = new ReferenceQueue();
        Object key = inp.readObject();
        while (key != null) {
            Object value = inp.readObject();
            put(key, value);
            key = inp.readObject();
        }
    }

    /**
     *  Constructs a reference of the given type to the given
     *  referent.  The reference is registered with the queue
     *  for later purging.
     *
     *  @param type  HARD, SOFT or WEAK
     *  @param referent  the object to refer to
     *  @param hash  the hash code of the <I>key</I> of the mapping;
     *    this number might be different from referent.hashCode() if
     *    the referent represents a value and not a key
     */
    private Object toReference(int type, Object referent, int hash) {
        switch (type) {
        case HARD:
            return referent;
        case SOFT:
            return new SoftRef(hash, referent, queue);
        case WEAK:
            return new WeakRef(hash, referent, queue);
        default:
            throw new Error();
        }
    }

    /**
     *  Returns the entry associated with the given key.
     *
     *  @param key  the key of the entry to look up
     *  @return  the entry associated with that key, or null
     *    if the key is not in this map
     */
    private Entry getEntry(Object key) {
        if (key == null)
            return null;
        int hash = hashCode(key);
        int index = indexFor(hash);
        for (Entry entry = table[index]; entry != null; entry = entry.next) {
            if ((entry.hash == hash) && equals(key, entry.getKey())) {
                return entry;
            }
        }
        return null;
    }

    /**
     *  Converts the given hash code into an index into the
     *  hash table.
     */
    private int indexFor(int hash) {
        // mix the bits to avoid bucket collisions...
        hash += ~(hash << 15);
        hash ^= (hash >>> 10);
        hash += (hash << 3);
        hash ^= (hash >>> 6);
        hash += ~(hash << 11);
        hash ^= (hash >>> 16);
        return hash & (table.length - 1);
    }

    /**
     *  Resizes this hash table by doubling its capacity.
     *  This is an expensive operation, as entries must
     *  be copied from the old smaller table to the new
     *  bigger table.
     */
    private void resize() {
        Entry[] old = table;
        table = new Entry[old.length * 2];

        for (int i = 0; i < old.length; i++) {
            Entry next = old[i];
            while (next != null) {
                Entry entry = next;
                next = next.next;
                int index = indexFor(entry.hash);
                entry.next = table[index];
                table[index] = entry;
            }
            old[i] = null;
        }
        threshold = (int) (table.length * loadFactor);
    }

    /**
     *  Purges stale mappings from this map.<P>
     *
     *  Ordinarily, stale mappings are only removed during
     *  a write operation; typically a write operation will
     *  occur often enough that you'll never need to manually
     *  invoke this method.<P>
     *
     *  Note that this method is not synchronized!  Special
     *  care must be taken if, for instance, you want stale
     *  mappings to be removed on a periodic basis by some
     *  background thread.
     */
    private void purge() {
        Reference ref = queue.poll();
        while (ref != null) {
            purge(ref);
            ref = queue.poll();
        }
    }

    private void purge(Reference ref) {
        // The hashCode of the reference is the hashCode of the
        // mapping key, even if the reference refers to the
        // mapping value...
        int hash = ref.hashCode(); // note: hashCode() is referined
        int index = indexFor(hash);
        Entry previous = null;
        Entry entry = table[index];
        while (entry != null) {
            if (entry.purge(ref)) {
                if (previous == null)
                    table[index] = entry.next;
                else
                    previous.next = entry.next;
                this.size--;
                return;
            }
            previous = entry;
            entry = entry.next;
        }

    }

    /**
     *  Returns the size of this map.
     *
     *  @return  the size of this map
     */
    public int size() {
        purge();
        return size;
    }

    /**
     *  Returns <Code>true</Code> if this map is empty.
     *
     *  @return <Code>true</Code> if this map is empty
     */
    public boolean isEmpty() {
        purge();
        return size == 0;
    }

    /**
     *  Returns <Code>true</Code> if this map contains the given key.
     *
     *  @return true if the given key is in this map
     */
    public boolean containsKey(Object key) {
        purge();
        Entry entry = getEntry(key);
        if (entry == null)
            return false;
        return entry.getValue() != null;
    }

    /**
     *  Returns the value associated with the given key, if any.
     *
     *  @return the value associated with the given key, or <Code>null</Code>
     *   if the key maps to no value
     */
    public Object get(Object key) {
        purge();
        Entry entry = getEntry(key);
        if (entry == null)
            return null;
        return entry.getValue();
    }

    /**
     *  Associates the given key with the given value.<P>
     *  Neither the key nor the value may be null.
     *
     *  @param key  the key of the mapping
     *  @param value  the value of the mapping
     *  @return  the last value associated with that key, or
     *   null if no value was associated with the key
     *  @throws java.lang.NullPointerException if either the key or value
     *   is null
     */
    public Object put(Object key, Object value) {
        if (key == null)
            throw new NullPointerException("null keys not allowed");
        if (value == null)
            throw new NullPointerException("null values not allowed");

        purge();
        if (size + 1 > threshold)
            resize();

        int hash = hashCode(key);
        int index = indexFor(hash);
        Entry entry = table[index];
        while (entry != null) {
            if ((hash == entry.hash) && equals(key, entry.getKey())) {
                Object result = entry.getValue();
                entry.setValue(value);
                return result;
            }
            entry = entry.next;
        }
        this.size++;
        modCount++;
        key = toReference(keyType, key, hash);
        value = toReference(valueType, value, hash);
        table[index] = new Entry(key, hash, value, table[index]);
        return null;
    }

    /**
     *  Removes the key and its associated value from this map.
     *
     *  @param key  the key to remove
     *  @return the value associated with that key, or null if
     *   the key was not in the map
     */
    public Object remove(Object key) {
        if (key == null)
            return null;
        purge();
        int hash = hashCode(key);
        int index = indexFor(hash);
        Entry previous = null;
        Entry entry = table[index];
        while (entry != null) {
            if ((hash == entry.hash) && equals(key, entry.getKey())) {
                if (previous == null)
                    table[index] = entry.next;
                else
                    previous.next = entry.next;
                this.size--;
                modCount++;
                return entry.getValue();
            }
            previous = entry;
            entry = entry.next;
        }
        return null;
    }

    /**
     *  Clears this map.
     */
    public void clear() {
        Arrays.fill(table, null);
        size = 0;
        while (queue.poll() != null) {
            // drain the queue
        }
    }

    /**
     *  Returns a set view of this map's entries.
     *
     *  @return a set view of this map's entries
     */
    public Set entrySet() {
        if (entrySet != null)
            return entrySet;
        entrySet = new AbstractSet() {
            public int size() {
                return ReferenceMap.this.size();
            }

            public void clear() {
                ReferenceMap.this.clear();
            }

            public boolean contains(Object o) {
                if (o == null)
                    return false;
                if (!(o instanceof Map.Entry))
                    return false;
                Map.Entry e = (Map.Entry) o;
                Entry e2 = getEntry(e.getKey());
                return (e2 != null) && e.equals(e2);
            }

            public boolean remove(Object o) {
                boolean r = contains(o);
                if (r) {
                    Map.Entry e = (Map.Entry) o;
                    ReferenceMap.this.remove(e.getKey());
                }
                return r;
            }

            public Iterator iterator() {
                return new EntryIterator();
            }

            public Object[] toArray() {
                return toArray(new Object[0]);
            }

            public Object[] toArray(Object[] arr) {
                ArrayList list = new ArrayList();
                Iterator iterator = iterator();
                while (iterator.hasNext()) {
                    Entry e = (Entry) iterator.next();
                    list.add(new DefaultMapEntry(e.getKey(), e.getValue()));
                }
                return list.toArray(arr);
            }
        };
        return entrySet;
    }

    /**
     *  Returns a set view of this map's keys.
     *
     *  @return a set view of this map's keys
     */
    public Set keySet() {
        if (keySet != null)
            return keySet;
        keySet = new AbstractSet() {
            public int size() {
                return size;
            }

            public Iterator iterator() {
                return new KeyIterator();
            }

            public boolean contains(Object o) {
                return containsKey(o);
            }

            public boolean remove(Object o) {
                Object r = ReferenceMap.this.remove(o);
                return r != null;
            }

            public void clear() {
                ReferenceMap.this.clear();
            }

        };
        return keySet;
    }

    /**
     *  Returns a collection view of this map's values.
     *
     *  @return a collection view of this map's values.
     */
    public Collection values() {
        if (values != null)
            return values;
        values = new AbstractCollection() {
            public int size() {
                return size;
            }

            public void clear() {
                ReferenceMap.this.clear();
            }

            public Iterator iterator() {
                return new ValueIterator();
            }
        };
        return values;
    }

    // If getKey() or getValue() returns null, it means
    // the mapping is stale and should be removed.
    private class Entry implements Map.Entry {

        Object key;
        Object value;
        int hash;
        Entry next;

        public Entry(Object key, int hash, Object value, Entry next) {
            this.key = key;
            this.hash = hash;
            this.value = value;
            this.next = next;
        }

        public Object getKey() {
            return (keyType > HARD) ? ((Reference) key).get() : key;
        }

        public Object getValue() {
            return (valueType > HARD) ? ((Reference) value).get() : value;
        }

        public Object setValue(Object object) {
            Object old = getValue();
            if (valueType > HARD)
                ((Reference) value).clear();
            value = toReference(valueType, object, hash);
            return old;
        }

        public boolean equals(Object o) {
            if (o == null)
                return false;
            if (o == this)
                return true;
            if (!(o instanceof Map.Entry))
                return false;

            Map.Entry entry = (Map.Entry) o;
            Object _key = entry.getKey();
            Object _value = entry.getValue();
            if ((_key == null) || (_value == null))
                return false;
            return ReferenceMap.this.equals(_key, getKey()) && ReferenceMap.this.equals(_value, getValue());
        }

        public int hashCode() {
            Object v = getValue();
            return hash ^ ((v == null) ? 0 : v.hashCode());
        }

        public String toString() {
            return getKey() + "=" + getValue();
        }

        boolean purge(Reference ref) {
            boolean r = (keyType > HARD) && (key == ref);
            r = r || ((valueType > HARD) && (value == ref));
            if (r) {
                if (keyType > HARD)
                    ((Reference) key).clear();
                if (valueType > HARD)
                    ((Reference) value).clear();
            }
            return r;
        }
    }

    private class EntryIterator implements Iterator {
        // These fields keep track of where we are in the table.
        int index;
        Entry entry;
        Entry previous;

        // These Object fields provide hard references to the
        // current and next entry; this assures that if hasNext()
        // returns true, next() will actually return a valid element.
        Object nextKey, nextValue;
        Object currentKey, currentValue;

        int expectedModCount;

        public EntryIterator() {
            index = (size() != 0 ? table.length : 0);
            // have to do this here!  size() invocation above
            // may have altered the modCount.
            expectedModCount = modCount;
        }

        public boolean hasNext() {
            checkMod();
            while (nextNull()) {
                Entry e = entry;
                int i = index;
                while ((e == null) && (i > 0)) {
                    i--;
                    e = table[i];
                }
                entry = e;
                index = i;
                if (e == null) {
                    currentKey = null;
                    currentValue = null;
                    return false;
                }
                nextKey = e.getKey();
                nextValue = e.getValue();
                if (nextNull())
                    entry = entry.next;
            }
            return true;
        }

        private void checkMod() {
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
        }

        private boolean nextNull() {
            return (nextKey == null) || (nextValue == null);
        }

        protected Entry nextEntry() {
            checkMod();
            if (nextNull() && !hasNext())
                throw new NoSuchElementException();
            previous = entry;
            entry = entry.next;
            currentKey = nextKey;
            currentValue = nextValue;
            nextKey = null;
            nextValue = null;
            return previous;
        }

        public Object next() {
            return nextEntry();
        }

        public void remove() {
            checkMod();
            if (previous == null)
                throw new IllegalStateException();
            ReferenceMap.this.remove(currentKey);
            previous = null;
            currentKey = null;
            currentValue = null;
            expectedModCount = modCount;
        }

    }

    private class ValueIterator extends EntryIterator {
        public Object next() {
            return nextEntry().getValue();
        }
    }

    private class KeyIterator extends EntryIterator {
        public Object next() {
            return nextEntry().getKey();
        }
    }

    // These two classes store the hashCode of the key of
    // of the mapping, so that after they're dequeued a quick
    // lookup of the bucket in the table can occur.

    private static class SoftRef extends SoftReference {
        private int hash;

        public SoftRef(int hash, Object r, ReferenceQueue q) {
            super(r, q);
            this.hash = hash;
        }

        public int hashCode() {
            return hash;
        }
    }

    private static class WeakRef extends WeakReference {
        private int hash;

        public WeakRef(int hash, Object r, ReferenceQueue q) {
            super(r, q);
            this.hash = hash;
        }

        public int hashCode() {
            return hash;
        }
    }

    private int hashCode(Object obj) {
        return useSystemIdentity ? System.identityHashCode(obj) : obj.hashCode(); // null keys|values are not supported
    }

    private boolean equals(Object obj1, Object obj2) {
        return (obj1 == obj2) || (!useSystemIdentity && obj1.equals(obj2)); // null keys|values are not supported
    }

    //***********************************************
    // inner class
    //***********************************************
    /** A default implementation of {@link java.util.Map.Entry}
      *
      * @since 1.0
      * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
      * @author <a href="mailto:mas@apache.org">Michael A. Smith</a>
      */

    public class DefaultMapEntry implements Map.Entry {

        private Object key;
        private Object value;

        /**
         *  Constructs a new <Code>DefaultMapEntry</Code> with a null key
         *  and null value.
         */
        public DefaultMapEntry() {
        }

        /**
         *  Constructs a new <Code>DefaultMapEntry</Code> with the given
         *  key and given value.
         *
         *  @param key  the key for the entry, may be null
         *  @param value  the value for the entyr, may be null
         */
        public DefaultMapEntry(Object key, Object value) {
            this.key = key;
            this.value = value;
        }

        /**
         *  Implemented per API documentation of
         *  {@link java.util.Map.Entry#equals(java.lang.Object)}
         **/
        public boolean equals(Object o) {
            if (o == null)
                return false;
            if (o == this)
                return true;

            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e2 = (Map.Entry) o;
            return ((getKey() == null ? e2.getKey() == null : getKey().equals(e2.getKey()))
                    && (getValue() == null ? e2.getValue() == null : getValue().equals(e2.getValue())));
        }

        /**
         *  Implemented per API documentation of
         *  {@link java.util.Map.Entry#hashCode()}
         **/
        public int hashCode() {
            return ((getKey() == null ? 0 : getKey().hashCode())
                    ^ (getValue() == null ? 0 : getValue().hashCode()));
        }

        // Map.Entry interface
        //-------------------------------------------------------------------------

        /**
         *  Returns the key.
         *
         *  @return the key
         */
        public Object getKey() {
            return key;
        }

        /**
         *  Returns the value.
         *
         *  @return the value
         */
        public Object getValue() {
            return value;
        }

        // Properties
        //-------------------------------------------------------------------------

        /**
         *  Sets the key.  This method does not modify any map.
         *
         *  @param key  the new key
         */
        public void setKey(Object key) {
            this.key = key;
        }

        /** Note that this method only sets the local reference inside this object and
          * does not modify the original Map.
          *
          * @return the old value of the value
          * @param value the new value
          */
        public Object setValue(Object value) {
            Object answer = this.value;
            this.value = value;
            return answer;
        }

    }
}