You are viewing a plain text version of this content. The canonical link for it is here.
Posted to ojb-dev@db.apache.org by ar...@apache.org on 2004/01/27 20:42:46 UTC

cvs commit: db-ojb/src/test/org/apache/ojb/broker AllTests.java

arminw      2004/01/27 11:42:46

  Modified:    src/java/org/apache/ojb/broker/metadata/fieldaccess
                        AnonymousPersistentField.java
               src/test/org/apache/ojb/broker AllTests.java
  Added:       src/java/org/apache/ojb/broker/util ReferenceMap.java
               src/test/org/apache/ojb/broker/metadata
                        ReferenceMapTest.java
  Log:
  patch by Andy Malakov
  
  - new attempt to fix object identity problem in conjunction
  of anonymous keys (persistence capable objects used as key in weak
  HashMap can cause problems when user override hashcode/equals methods)
  
  - add new Map implementation supports identity weak Map
  
  - add test case for ReferenceMap
  
  Revision  Changes    Path
  1.10      +21 -10    db-ojb/src/java/org/apache/ojb/broker/metadata/fieldaccess/AnonymousPersistentField.java
  
  Index: AnonymousPersistentField.java
  ===================================================================
  RCS file: /home/cvs/db-ojb/src/java/org/apache/ojb/broker/metadata/fieldaccess/AnonymousPersistentField.java,v
  retrieving revision 1.9
  retrieving revision 1.10
  diff -u -r1.9 -r1.10
  --- AnonymousPersistentField.java	24 Jan 2004 19:09:24 -0000	1.9
  +++ AnonymousPersistentField.java	27 Jan 2004 19:42:45 -0000	1.10
  @@ -55,9 +55,11 @@
    */
   
   import java.util.Map;
  -import java.util.WeakHashMap;
   
   import org.apache.ojb.broker.metadata.MetadataException;
  +import org.apache.ojb.broker.util.ReferenceMap;
  +
  +//import org.apache.commons.collections.ReferenceMap;
   
   /**
    * This class handle an anonymous persistent fiels for 1-1 association,
  @@ -69,6 +71,8 @@
   {
       private static final long serialVersionUID = 3989863424358352941L;
   
  +    private static final String NULL_VALUE = "ojbNullValue";
  +
       private transient Map fkCache;
       private String fieldname;
   
  @@ -88,8 +92,7 @@
       }
   
   /*
  -    TODO: Use WeakIdentityHashMap instead WeakHashMap to hold anonymous field values
  -
  +    Use WeakIdentityHashMap instead WeakHashMap to hold anonymous field values
       Here is an snip of the mail from Andy Malakov:
   
   I found that usage of database identity in Java produces quite interesting problem in OJB:
  @@ -113,19 +116,27 @@
   3)
   If Full GC happens after that moment it will dispose fcCache entry if the FIRST reference becomes
   soft-referenced only.
  -    */
  -    protected void  putToFieldCache(Object key, Object value)
  +*/
  +    protected void putToFieldCache(Object key, Object value)
       {
  -        if(fkCache == null)
  +        if (key != null)
           {
  -            fkCache = new WeakHashMap();
  +            if (fkCache == null)
  +            {
  +                fkCache = new ReferenceMap (ReferenceMap.WEAK, ReferenceMap.HARD, 10, 0.75f, true);
  +                // fkCache = new WeakHashMap();
  +            }
  +            // thus we can't put 'null' as value, we use a replacement
  +            if(value == null) value = NULL_VALUE;
  +            fkCache.put(key, value);
           }
  -        fkCache.put(key, value);
       }
   
       protected Object getFromFieldCache(Object key)
       {
  -        return fkCache != null ? fkCache.get(key) : null;
  +        Object obj = (key != null && fkCache != null) ? fkCache.get(key) : null;
  +        if(obj == NULL_VALUE) obj = null;
  +        return obj;
       }
   
       /**
  
  
  
  1.1                  db-ojb/src/java/org/apache/ojb/broker/util/ReferenceMap.java
  
  Index: ReferenceMap.java
  ===================================================================
  package org.apache.ojb.broker.util;
  
  /* ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999-2001 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/>.
   *
   */
  
  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>.
   *
   *  @author Andy Malakov
   *  @version $Id: ReferenceMap.java,v 1.1 2004/01/27 19:42:46 arminw 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 final 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 final 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 final 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;
  
  
      private final 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;
          }
  
      }
  }
  
  
  
  1.1                  db-ojb/src/test/org/apache/ojb/broker/metadata/ReferenceMapTest.java
  
  Index: ReferenceMapTest.java
  ===================================================================
  package org.apache.ojb.broker.metadata;
  
  import junit.framework.TestCase;
  import org.apache.ojb.broker.util.ReferenceMap;
  
  
  public class ReferenceMapTest extends TestCase
  {
      private ReferenceMap referenceMap = null;
  
      public ReferenceMapTest(String name)
      {
          super(name);
      }
  
      public static void main(String[] args)
      {
          String[] arr = {ReferenceMapTest.class.getName()};
          junit.textui.TestRunner.main(arr);
      }
  
  
      protected void setUp()
              throws Exception
      {
          super.setUp();
          /*
          todo: verify the constructors
          */
          referenceMap = new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.HARD, 10, 0.75f, true);
      }
  
      protected void tearDown()
              throws Exception
      {
          referenceMap = null;
          super.tearDown();
      }
  
      public void testWeakIdentityMap()
      {
          String key1 = new String("abc");
          String key2 = new String("abc");
          String value1 = new String("abc");
          String value2 = new String("abc");
  
          assertNotSame("different references", key1, key2);
          assertEquals("identical strings", key1, key2);
  
          referenceMap.put(key1, "nonsence");
          assertEquals("size", 1, referenceMap.size());
  
          //if we put the same value size will remain the same
          referenceMap.put(key1, value1);
          assertEquals("size", 1, referenceMap.size());
  
          //if we put equal value size will increase
          referenceMap.put(key2, value2);
          assertEquals("size", 2, referenceMap.size());
  
          // test containsKey
          assertTrue("ref1 is there", referenceMap.containsKey(key1));
          assertTrue("ref2 is there", referenceMap.containsKey(key2));
  
          // test remove
          assertSame("key1=>value1", value1, referenceMap.remove(key1));
          assertEquals("size", 1, referenceMap.size());
          referenceMap.put(key1, value1); // put it back
          assertEquals("size", 2, referenceMap.size());
  
  
          key2 = ""; // will weaken ref2 key in referenceMap
          gc();
  
          assertEquals("GC didn't release weak references", 1, referenceMap.size());
  
          assertFalse("ref2 is not there", referenceMap.containsKey(key2));
          assertTrue("ref1 is there", referenceMap.containsKey(key1));
  
          if (key1.length() < 0) // lets do something with ref1 to avoid GC
              fail(key1); // never
  
  
          // check NULL
          //referenceMap.put(key1, null);
  
      }
  
      public static void gc()
      {
          try
          {
              // trigger GC
              byte[][] tooLarge = new byte[1000000000][1000000000];
              fail("you have too much RAM");
          }
          catch (OutOfMemoryError ex)
          {
              System.gc(); // ignore
          }
      }
  
  
  }
  
  
  
  1.38      +2 -0      db-ojb/src/test/org/apache/ojb/broker/AllTests.java
  
  Index: AllTests.java
  ===================================================================
  RCS file: /home/cvs/db-ojb/src/test/org/apache/ojb/broker/AllTests.java,v
  retrieving revision 1.37
  retrieving revision 1.38
  diff -u -r1.37 -r1.38
  --- AllTests.java	9 Dec 2003 13:46:46 -0000	1.37
  +++ AllTests.java	27 Jan 2004 19:42:46 -0000	1.38
  @@ -8,6 +8,7 @@
   import org.apache.ojb.broker.metadata.RepositoryElementsTest;
   import org.apache.ojb.broker.metadata.MetadataMultithreadedTest;
   import org.apache.ojb.broker.metadata.ReadonlyTest;
  +import org.apache.ojb.broker.metadata.ReferenceMapTest;
   import org.apache.ojb.broker.sequence.SequenceManagerTest;
   import org.apache.ojb.broker.sequence.NativeIdentifierTest;
   import org.apache.ojb.broker.sequence.SMMultiThreadedTest;
  @@ -84,6 +85,7 @@
           suite.addTestSuite(MtoNTest.class);
           suite.addTestSuite(NestedFieldsTest.class);
           suite.addTestSuite(ReadonlyTest.class);
  +        suite.addTestSuite(ReferenceMapTest.class);
           return suite;
       }
   
  
  
  

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