commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From scolebou...@apache.org
Subject cvs commit: jakarta-commons/collections/src/java/org/apache/commons/collections/map AbstractReferenceMap.java
Date Tue, 27 Apr 2004 21:32:52 GMT
scolebourne    2004/04/27 14:32:52

  Modified:    collections/src/java/org/apache/commons/collections/map
                        AbstractReferenceMap.java
  Log:
  Improve subclassability by delegating methods from entry to main map
  Add support for serializable subclasses
  
  Revision  Changes    Path
  1.2       +146 -60   jakarta-commons/collections/src/java/org/apache/commons/collections/map/AbstractReferenceMap.java
  
  Index: AbstractReferenceMap.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/collections/src/java/org/apache/commons/collections/map/AbstractReferenceMap.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- AbstractReferenceMap.java	9 Apr 2004 22:18:18 -0000	1.1
  +++ AbstractReferenceMap.java	27 Apr 2004 21:32:52 -0000	1.2
  @@ -15,6 +15,9 @@
    */
   package org.apache.commons.collections.map;
   
  +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;
  @@ -72,7 +75,7 @@
    * provide synchronized access to a <code>ReferenceMap</code>.
    *
    * @see java.lang.ref.Reference
  - * @since Commons Collections 3.1 (from ReferenceMap in 3.0)
  + * @since Commons Collections 3.1 (extracted from ReferenceMap in 3.0)
    * @version $Revision$ $Date$
    * 
    * @author Paul Jack
  @@ -93,24 +96,24 @@
        * The reference type for keys.  Must be HARD, SOFT, WEAK.
        * @serial
        */
  -    private int keyType;
  +    protected int keyType;
   
       /**
        * The reference type for values.  Must be HARD, SOFT, WEAK.
        * @serial
        */
  -    private int valueType;
  +    protected int valueType;
   
       /**
        * Should the value be automatically purged when the associated key has been collected?
        */
  -    private boolean purgeValues;
  +    protected boolean purgeValues;
   
       /**
        * ReferenceQueue used to eliminate stale mappings.
        * See purge.
        */
  -    private transient ReferenceQueue queue = new ReferenceQueue();
  +    private transient ReferenceQueue queue;
   
       //-----------------------------------------------------------------------
       /**
  @@ -144,6 +147,13 @@
           this.purgeValues = purgeValues;
       }
   
  +    /**
  +     * Initialise this subclass during construction, cloning or deserialization.
  +     */
  +    protected void init() {
  +        queue = new ReferenceQueue();
  +    }
  +
       //-----------------------------------------------------------------------
       /**
        * Checks the type int is a valid value.
  @@ -159,49 +169,6 @@
       }
   
       //-----------------------------------------------------------------------
  -//    /**
  -//     * Writes this object to the given output stream.
  -//     *
  -//     * @param out  the output stream to write to
  -//     * @throws IOException  if the stream raises it
  -//     */
  -//    private void writeObject(ObjectOutputStream out) throws IOException {
  -//        out.defaultWriteObject();
  -//        out.writeInt(data.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 in  the input stream to read from
  -//     * @throws IOException  if the stream raises it
  -//     * @throws ClassNotFoundException  if the stream raises it
  -//     */
  -//    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
{
  -//        in.defaultReadObject();
  -//        data = new HashEntry[in.readInt()];
  -//        threshold = calculateThreshold(data.length, loadFactor);
  -//        queue = new ReferenceQueue();
  -//        Object key = in.readObject();
  -//        while (key != null) {
  -//            Object value = in.readObject();
  -//            put(key, value);
  -//            key = in.readObject();
  -//        }
  -//    }
  -
  -    //-----------------------------------------------------------------------
       /**
        * Gets the size of the map.
        * 
  @@ -439,6 +406,19 @@
       }
   
       /**
  +     * Gets the hash code for a MapEntry.
  +     * Subclasses can override this, for example to use the identityHashCode.
  +     * 
  +     * @param key  the key to get a hash code for, may be null
  +     * @param value  the value to get a hash code for, may be null
  +     * @return the hash code, as per the MapEntry specification
  +     */
  +    protected int hashEntry(Object key, Object value) {
  +        return (key == null ? 0 : key.hashCode()) ^
  +               (value == null ? 0 : value.hashCode()); 
  +    }
  +    
  +    /**
        * Compares two keys, in internal converted form, to see if they are equal.
        * <p>
        * This implementation converts the key from the entry to a real reference
  @@ -447,7 +427,6 @@
        * @param key1  the first key to compare passed in from outside
        * @param key2  the second key extracted from the entry via <code>entry.key</code>
        * @return true if equal
  -     * @since Commons Collections 3.1
        */
       protected boolean isEqualKey(Object key1, Object key2) {
           key2 = (keyType > HARD ? ((Reference) key2).get() : key2);
  @@ -462,7 +441,6 @@
        * @param key  the key to store
        * @param value  the value to store
        * @return the newly created entry
  -     * @since Commons Collections 3.1
        */
       protected HashEntry createEntry(HashEntry next, int hashCode, Object key, Object value)
{
           return new ReferenceEntry(this, next, hashCode, key, value);
  @@ -472,7 +450,6 @@
        * Creates an entry set iterator.
        * 
        * @return the entrySet iterator
  -     * @since Commons Collections 3.1
        */
       protected Iterator createEntrySetIterator() {
           return new ReferenceEntrySetIterator(this);
  @@ -482,7 +459,6 @@
        * Creates an key set iterator.
        * 
        * @return the keySet iterator
  -     * @since Commons Collections 3.1
        */
       protected Iterator createKeySetIterator() {
           return new ReferenceKeySetIterator(this);
  @@ -492,7 +468,6 @@
        * Creates an values iterator.
        * 
        * @return the values iterator
  -     * @since Commons Collections 3.1
        */
       protected Iterator createValuesIterator() {
           return new ReferenceValuesIterator(this);
  @@ -596,17 +571,35 @@
               super(next, hashCode, null, null);
               this.parent = parent;
               this.key = toReference(parent.keyType, key, hashCode);
  -            this.value = toReference(parent.valueType, value, hashCode);
  +            this.value = toReference(parent.valueType, value, hashCode); // the key hashCode
is passed in deliberately
           }
   
  +        /**
  +         * Gets the key from the entry.
  +         * This method dereferences weak and soft keys and thus may return null.
  +         * 
  +         * @return the key, which may be null if it was garbage collected
  +         */
           public Object getKey() {
               return (parent.keyType > HARD) ? ((Reference) key).get() : key;
           }
   
  +        /**
  +         * Gets the value from the entry.
  +         * This method dereferences weak and soft value and thus may return null.
  +         * 
  +         * @return the value, which may be null if it was garbage collected
  +         */
           public Object getValue() {
               return (parent.valueType > HARD) ? ((Reference) value).get() : value;
           }
   
  +        /**
  +         * Sets the value of the entry.
  +         * 
  +         * @param obj  the object to store
  +         * @return the previous value
  +         */
           public Object setValue(Object obj) {
               Object old = getValue();
               if (parent.valueType > HARD) {
  @@ -616,6 +609,15 @@
               return old;
           }
   
  +        /**
  +         * Compares this map entry to another.
  +         * <p>
  +         * This implementation uses <code>isEqualKey</code> and
  +         * <code>isEqualValue</code> on the main map for comparison.
  +         * 
  +         * @param obj  the other map entry to compare to
  +         * @return true if equal, false if not
  +         */
           public boolean equals(Object obj) {
               if (obj == this) {
                   return true;
  @@ -625,12 +627,26 @@
               }
               
               Map.Entry entry = (Map.Entry)obj;
  -            Object key = entry.getKey();
  -            Object value = entry.getValue();
  -            if ((key == null) || (value == null)) {
  +            Object entryKey = entry.getKey();  // convert to hard reference
  +            Object entryValue = entry.getValue();  // convert to hard reference
  +            if ((entryKey == null) || (entryValue == null)) {
                   return false;
               }
  -            return key.equals(getKey()) && value.equals(getValue());
  +            // compare using map methods, aiding identity subclass
  +            // note that key is direct access and value is via method
  +            return parent.isEqualKey(entryKey, key) &&
  +                   parent.isEqualValue(entryValue, getValue());
  +        }
  +
  +        /**
  +         * Gets the hashcode of the entry using temporary hard references.
  +         * <p>
  +         * This implementation uses <code>hashEntry</code> on the main map.
  +         * 
  +         * @return the hashcode of the entry
  +         */
  +        public int hashCode() {
  +            return parent.hashEntry(getKey(), getValue());
           }
   
           /**
  @@ -642,7 +658,6 @@
            * @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
  -         * @since Commons Collections 3.1
            */
           protected Object toReference(int type, Object referent, int hash) {
               switch (type) {
  @@ -860,6 +875,7 @@
        * A soft reference holder.
        */
       static class SoftRef extends SoftReference {
  +        /** the hashCode of the key (even if the reference points to a value) */
           private int hash;
   
           public SoftRef(int hash, Object r, ReferenceQueue q) {
  @@ -876,6 +892,7 @@
        * A weak reference holder.
        */
       static class WeakRef extends WeakReference {
  +        /** the hashCode of the key (even if the reference points to a value) */
           private int hash;
   
           public WeakRef(int hash, Object r, ReferenceQueue q) {
  @@ -888,5 +905,74 @@
           }
       }
   
  +    //-----------------------------------------------------------------------
  +    /**
  +     * Replaces the superclass method to store the state of this class.
  +     * <p>
  +     * Serialization is not one of the JDK's nicest topics. Normal serialization will
  +     * initialise the superclass before the subclass. Sometimes however, this isn't
  +     * what you want, as in this case the <code>put()</code> method on read
can be
  +     * affected by subclass state.
  +     * <p>
  +     * The solution adopted here is to serialize the state data of this class in
  +     * this protected method. This method must be called by the
  +     * <code>writeObject()</code> of the first serializable subclass.
  +     * <p>
  +     * Subclasses may override if they have a specific field that must be present
  +     * on read before this implementation will work. Generally, the read determines
  +     * what must be serialized here, if anything.
  +     * 
  +     * @param out  the output stream
  +     */
  +    protected void doWriteObject(ObjectOutputStream out) throws IOException {
  +        out.writeInt(keyType);
  +        out.writeInt(valueType);
  +        out.writeBoolean(purgeValues);
  +        out.writeFloat(loadFactor);
  +        out.writeInt(data.length);
  +        for (MapIterator it = mapIterator(); it.hasNext();) {
  +            out.writeObject(it.next());
  +            out.writeObject(it.getValue());
  +        }
  +        out.writeObject(null);  // null terminate map
  +        // do not call super.doWriteObject() as code there doesn't work for reference map
  +    }
  +
  +    /**
  +     * Replaces the superclassm method to read the state of this class.
  +     * <p>
  +     * Serialization is not one of the JDK's nicest topics. Normal serialization will
  +     * initialise the superclass before the subclass. Sometimes however, this isn't
  +     * what you want, as in this case the <code>put()</code> method on read
can be
  +     * affected by subclass state.
  +     * <p>
  +     * The solution adopted here is to deserialize the state data of this class in
  +     * this protected method. This method must be called by the
  +     * <code>readObject()</code> of the first serializable subclass.
  +     * <p>
  +     * Subclasses may override if the subclass has a specific field that must be present
  +     * before <code>put()</code> or <code>calculateThreshold()</code>
will work correctly.
  +     * 
  +     * @param in  the input stream
  +     */
  +    protected void doReadObject(ObjectInputStream in) throws IOException, ClassNotFoundException
{
  +        this.keyType = in.readInt();
  +        this.valueType = in.readInt();
  +        this.purgeValues = in.readBoolean();
  +        this.loadFactor = in.readFloat();
  +        int capacity = in.readInt();
  +        init();
  +        data = new HashEntry[capacity];
  +        while (true) {
  +            Object key = in.readObject();
  +            if (key == null) {
  +                break;
  +            }
  +            Object value = in.readObject();
  +            put(key, value);
  +        }
  +        threshold = calculateThreshold(data.length, loadFactor);
  +        // do not call super.doReadObject() as code there doesn't work for reference map
  +    }
   
   }
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org


Mime
View raw message