commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "christopher marshall" <oxbow_la...@hotmail.com>
Subject PATCH : org.apache.commons.collections.SoftRefHashMap
Date Fri, 17 May 2002 16:03:33 GMT
All -

Please let me know if this is OK as I haven't submitted code before.

Patches fix the SoftRefHashMap so that the "entrySet()" and "values()" 
methods are backed by the underlying map and the keys whose referent-values 
have been cleared will be automatically (and performant-ly) purged upon 
writes to the map. I have deprecated the "purge()" method and re-implemented 
it to call "processQueue()", which is a more performant way of purging the 
Map as it goes straight to the cleared values without iterating through the 
whole Map.

The Map also has the new method "getIfAbsentPut(Object key, ObjectFactory 
fac)" which takes a new paramter of type 
org.apache.common.collections.ObjectFactory (an Interface). It aids the use 
of this map as a cache (see below).

I feel that the idea to extend the class with "createReference()" 
overimplemented is a bad one, and have removed the functionality. Among 
other things, it is difficult to see how this would be implemented here 
without unnecessary complications - considering the functionality is 
questionable (to extend SoftRefHashMap to actually be a WeakRefHashMap?) I 
think that this is justified.

I include two files org.apache.commons.collections.SoftRefHashMap and 
org.apache.commons.collections.ObjectFactory.

The idea of Object factory is so that the Map can be used effectively as a 
Cache; suppose that a User wishes to store (expensively retrieved) database 
entries in the Map which must always be retrieved (whether the referent 
value has since been GC-ed or not). The user code looks like

//
public UserObject apiGetUser(final String userId) {
  return (UserObject) map.getIfAbsentPut(userId, new ObjectFactory() {
     public Object create() {
         //expensive database access to return the
         //required Object
         Object user = expensiveDBAccess( userId );
         return user;
     }
  });
}
//

The ObjectFactory.create() will only be invoked (and the expensive operation 
executed) should the value have been cleared from the Map (ie. GC-d). The 
factory method will be invoked and the Object stored in the map for the key.

Anyway here is the source code...


*****************************************


/**
*
* The Apache Software License, Version 1.1
*
* Copyright (c) 1999-2002 The Apache Software Foundation.  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.
*
* 3. The end-user documentation included with the redistribution, if
*    any, must include the following acknowlegement:
*       "This product includes software developed by the
*        Apache Software Foundation (http://www.apache.org/)."
*    Alternately, this acknowlegement may appear in the software itself,
*    if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
*    Foundation" must not be used to endorse or promote products derived
*    from this software without prior written permission. For written
*    permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
*    nor may "Apache" appear in their names without prior written
*    permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR
* ITS 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation.  For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.commons.collections;

/**
* This interface provides an abstract Object factory.
* @author Chris Marshall
* @version 1.0
*/

public interface ObjectFactory {
  /**
   * return an Object.
   */
  public Object create();
}

*******************************************************************


/*
*
* The Apache Software License, Version 1.1
*
* Copyright (c) 1999-2002 The Apache Software Foundation.  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.
*
* 3. The end-user documentation included with the redistribution, if
*    any, must include the following acknowlegement:
*       "This product includes software developed by the
*        Apache Software Foundation (http://www.apache.org/)."
*    Alternately, this acknowlegement may appear in the software itself,
*    if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
*    Foundation" must not be used to endorse or promote products derived
*    from this software without prior written permission. For written
*    permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
*    nor may "Apache" appear in their names without prior written
*    permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR
* ITS 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation.  For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.commons.collections;

import java.lang.ref.*;
import java.lang.reflect.*;
import java.util.*;

/** <p>
  * HashMap with SoftReference links to values which allows the values of 
the Map
  * to be garbage collected by the JVM if it becomes low on memory.
  * Derive from this class and
  * override the factory method <code>createReference()</code> method to 
make
  * a Map wrapped in other types of Reference.
  * </p>
  *
  * <p>
  * A synchronized version can be obtained with:
  * <code>Collections.synchronizedMap( theMapToSynchronize )</code>
  * </p>
  *
  * <p>
  * <b>WARNING</b> the values() and entrySet() methods require optimisation
  * like the standard {@link HashMap} implementations so that iteration
  * over this Map is efficient.
  * </p>
  *
  * @author  James.Dodd
  * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
  */
public class SoftRefHashMap implements Map {

    /** The wrapped HashMap */
    private Map hashMap = new HashMap();
    private ReferenceQueue queue = new ReferenceQueue();

    // Inner Classes
    // ---------------------------------------------------------------------

    /**
	* Internal factory class to return <tt>Reference</tt>
	* implementation.
	*/
   private static class RefFactory {
	  private static Reference reference(
		 Object o,
		 Object key,
		 ReferenceQueue queue) {
		 return SoftKey.create(o, key, queue);
	  }
   }
/**
	* Internal extension of <tt>SoftReference</tt>
	* to be stored as the <tt>value</tt> in the
	* underlying map.
	*/
   static private class SoftKey extends SoftReference {
	  private int hash;
	  private Object key;
	  /* Hashcode of key, stored here since the key
	  may be tossed by the GC */

	  private SoftKey(Object k, Object key, ReferenceQueue queue) {
		 super(k, queue);
		 this.key = key;
		 hash = k.hashCode();
	  }

	  private static SoftKey create(Object k, Object key, ReferenceQueue queue) 
{
		 if (k == null)
			return null;
		 else
			return new SoftKey(k, key, queue);
	  }

	  /* A SoftKey is equal to another WeakKey iff they both refer to objects
	  that are, in turn, equal according to their own equals methods */
	  public boolean equals(Object o) {
		 if (this == o)
			return true;
		 if (!(o instanceof SoftKey))
			return false;
		 Object t = this.get();
		 Object u = ((SoftKey) o).get();
		 if ((t == null) || (u == null))
			return false;
		 if (t == u)
			return true;
		 return t.equals(u);
	  }
	  public Object getKey() {
		 return this.key;
	  }

	  public int hashCode() {
		 return hash;
	  }

	  public String toString() {
		 return "SoftKey(" + get() + ")";
	  }
   }

   /**
	* Internal implementation of <tt>Map.Entry</tt>
	* for presentation through the <tt>EntryIterator</tt>
	*/
   private class SoftKeyEntry implements Map.Entry {
	  private Object key;
	  private Reference value;

	  private Map.Entry underlying;
	  private SoftKeyEntry(Object key, Reference value) {
		 this.key = key;
		 this.value = value;
	  }

	  private SoftKeyEntry(Map.Entry e) {
		 this.underlying = e;
		 this.key = e.getKey();
		 if (e instanceof SoftKeyEntry)
			this.value = ((SoftKeyEntry) e).getRef();
		 else if (e.getValue() instanceof Reference)
			this.value = (Reference) e.getValue();
		 else
		 	this.value = RefFactory.reference(e.getValue(), e.getKey(), queue);
	  }
	  public Object getKey() {
		 return this.key;
	  }
	  public Object getValue() {
		 return this.value.get();
	  }
	  public Object setValue(Object o) {
		 if (underlying != null)
			underlying.setValue(o);
		 Object oldValue = value.get();
		 this.value = RefFactory.reference(o, getKey(), queue);
		 return oldValue;
	  }

	  private Reference getRef() {
		 return this.value;
	  }
   }

   /**
	* Internal <tt>Iterator</tt> representation to iterate
	* Through the underlying <tt>values()</tt>
	*/
   private class ValueIterator implements Iterator {
	  private Iterator iterator;
	  private ValueIterator() {
		 this.iterator = getMap().values().iterator();
	  }
	  public boolean hasNext() {
		 return this.iterator.hasNext();
	  }

	  public Object next() {
		 if (!this.iterator.hasNext())
			throw new NoSuchElementException();
		 Object o = null;
		 while (o == null && this.iterator.hasNext()) {
			Reference ref = (Reference) this.iterator.next();
			if ((o = ref.get()) == null)
			   this.iterator.remove();
		 }
		 return o;
	  }

	  public void remove() {
		 this.iterator.remove();
	  }
   }

   /**
	* Internal <tt>Iterator</tt> representation to
	* iterate through the underlying <tt>entrySet()</tt>.
	*/
   private class EntryIterator implements Iterator {
	  private Iterator iterator;
	  private EntryIterator() {
		 this.iterator = getMap().entrySet().iterator();
	  }

	  public boolean hasNext() {
		 return this.iterator.hasNext();
	  }

	  public Object next() {
		 if (!this.iterator.hasNext())
			throw new NoSuchElementException();
		 Map.Entry entry = null;
		 while (entry == null && this.iterator.hasNext()) {
			Map.Entry e = (Map.Entry) this.iterator.next();
			Reference ref = (Reference) e.getValue();
			if (ref.get() == null)
			   this.iterator.remove();
			else
			   entry = new SoftKeyEntry(e);
		 }
		 return entry;
	  }

	  public void remove() {
		 this.iterator.remove();
	  }
   }
    public SoftRefHashMap() {
    }


    /**
     * Removes References that have had their referents garbage collected
     * @deprecated
     */
    public void purge() {
        processQueue();
    }

    /**
     * Retrieves the referent of the Referenced value
     * @param key The key with which to retrieve the value
     */
    public Object get(Object key) {
	SoftReference ref = (SoftReference) getMap().get(key);
	if (ref == null)
		return null;
	Object o = null;
	if ((o = ref.get()) == null)
		remove(key);
	return o;
    }

    /**
     * Adds a key-value mapping, wrapping the value in a Reference
     */
    public Object put(Object key, Object value) {
	processQueue();
	return getMap().put(key, RefFactory.reference(value, key, queue));
    }

    /**
     * Process the reference queue to clean up.
     * Creation date: (07/05/02 10:13:59)
     */
    private void processQueue() {
       Reference r = null;
       while ((r = queue.poll()) != null)
              getMap().remove(((SoftKey) r).getKey());
    }
    /**
      * Returns a collection of the Referenced values
      */
    public java.util.Collection values() {
            return new AbstractCollection() {
                    public int size() {
                            return getMap().size();
                    }

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

                    }
            };

    }

    /**
     * Returns the value to which this map maps the specified key.  If the
     * map does not contain the key, or if the value has been cleared by the
     * GarbageCollector, the supplied <tt>ObjectFactory</tt> is used to 
place
     * a new value in the map foir the key. This new value is returned.
     *
     * @param key key whose associated value is to be returned.
     * @param objectFactory factory to create the Object with which to put
     *			the <tt>Map</tt>
     * @return the value to which this map maps the specified key, or
     *	       <tt>objectFactory.create()</tt> if the map contains no
     *		   mapping (or the mapping has been cleared) for this key.
     *
     * @throws ClassCastException if the key is of an inappropriate type for
     * 		   this map.
     * @throws NullPointerException key is <tt>null</tt> and this map does 
not
     *		  not permit <tt>null</tt> keys.
     * @throws Exception if the <tt>ObjectFactory</tt>'s
     *		   <tt>create()</tt> method throws an Exception
     *
     * @see #containsKey(Object)
     */
    public Object getIfAbsentPut(Object key, 
org.apache.commons.collections.ObjectFactory objectFactory)
       throws Exception {
       Object o = get(key);
       if (o == null)
              put(key, o = objectFactory.create());
       return o;
    }
    /**
     * Answers whether the argument is in the domain of the mappings
     */
    public boolean containsKey( Object key ) {
        return getMap().containsKey( key );
    }

    /**
     * Answers whether the argument is a Referenced value
     */
    public boolean containsValue( Object value ) {
        Collection values = (Collection) getMap().values();
        if ( values == null ) {
            return false;
        }
        for ( Iterator i = values.iterator(); i.hasNext(); ) {
            Reference ref = (Reference) i.next();
            if ( ref == null ) {
                continue;
            }
            Object target = ref.get();
            if ( target == value ) {
                return true;
            }
        }
        return false;
    }

    /**
      * Put all of the mappings in the argument into this wrapped map
      */
    public void putAll(java.util.Map map) {
        if ( map == null || map.size() == 0 ) {
            return;
        }
        for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
	    Map.Entry e = (Map.Entry) it.next();
	    put(e.getKey(), e.getValue());
        }
    }

    /**
      * Returns a set view of the mappings in the wrapped map
      */
    public java.util.Set entrySet() {
	return new AbstractSet() {

		public int size() {
			return getMap().size();
		}

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

    /**
      * Removes a mapping from this map
      */
    public Object remove(Object key) {
	SoftReference ref = (SoftReference) getMap().remove(key);
        if (ref != null) {
	    return ref.get();
        }
        return null;
    }

    /**
      * Clears all  mappings
      */
    public void clear() {
        getMap().clear();
    }

    /**
      * Calculates the hash code for this map
      */
    public int hashCode() {
        return getMap().hashCode();
    }

    /**
      * Returns the domain of the mappings
      */
    public Set keySet() {
        return getMap().keySet();
    }

    /**
      * Answers whether there are any mappings
      */
    public boolean isEmpty() {
        return getMap().isEmpty();
    }

    /**
      * Answers whether this map and the argument are 'the same'
      */
    public boolean equals( final Object object ) {
        return getMap().equals( object );
    }

    /**
      * Returns the number of mappings in this map
      */
    public int size() {
        return getMap().size();
    }


    /**
     * Retrieves the wrapped HashMap
     * @return The wrapped HashMap
     */
    protected Map getMap() {
        return hashMap;
    }
}

_________________________________________________________________
Chat with friends online, try MSN Messenger: http://messenger.msn.com


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


Mime
View raw message