diff --git a/projects/samskivert/src/java/com/samskivert/servlet/user/UserRepository.java b/projects/samskivert/src/java/com/samskivert/servlet/user/UserRepository.java index 7df78e9c..b809808f 100644 --- a/projects/samskivert/src/java/com/samskivert/servlet/user/UserRepository.java +++ b/projects/samskivert/src/java/com/samskivert/servlet/user/UserRepository.java @@ -1,5 +1,5 @@ // -// $Id: UserRepository.java,v 1.15 2001/08/12 01:34:31 mdb Exp $ +// $Id: UserRepository.java,v 1.16 2001/09/15 17:22:11 mdb Exp $ // // samskivert library - useful routines for java programs // Copyright (C) 2001 Michael Bayne @@ -31,7 +31,7 @@ import com.samskivert.Log; import com.samskivert.jdbc.JDBCUtil; import com.samskivert.jdbc.MySQLRepository; import com.samskivert.jdbc.jora.*; -import com.samskivert.util.IntMap; +import com.samskivert.util.HashIntMap; /** * Interfaces with the RDBMS in which the user information is stored. The @@ -334,7 +334,7 @@ public class UserRepository extends MySQLRepository ids.append(userids[i]); } - final IntMap map = new IntMap(); + final HashIntMap map = new HashIntMap(); // do the query execute(new Operation () { diff --git a/projects/samskivert/src/java/com/samskivert/util/Collections.java b/projects/samskivert/src/java/com/samskivert/util/Collections.java new file mode 100644 index 00000000..92adee25 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/util/Collections.java @@ -0,0 +1,256 @@ +// +// $Id: Collections.java,v 1.1 2001/09/15 17:22:11 mdb Exp $ + +package com.samskivert.util; + +import java.util.*; + +/** + * Like the java.util class of the same name, the + * Collections class provides utility functions related to + * the collections provided by the com.samskivert.util + * package. + */ +public class Collections +{ + /** + * Returns a synchronized (thread-safe) int map backed by the + * specified int map. In order to guarantee serial access, it is + * critical that all access to the backing int map is + * accomplished through the returned int map. + * + *

It is imperative that the user manually synchronize on the + * returned int map when iterating over any of its collection views: + * + *

+     *  IntMap m = Collections.synchronizedIntMap(new HashIntMap());
+     *      ...
+     *  Set s = m.keySet();  // Needn't be in synchronized block
+     *      ...
+     *  synchronized(m) {  // Synchronizing on m, not s!
+     *      Iterator i = s.iterator(); // Must be in synchronized block
+     *      while (i.hasNext())
+     *          foo(i.next());
+     *  }
+     * 
+ * + * Failure to follow this advice may result in non-deterministic + * behavior. + * + *

The returned map will be serializable if the specified map is + * serializable. + * + * @param m the int map to be "wrapped" in a synchronized int map. + * + * @return a synchronized view of the specified int map. + */ + public static IntMap synchronizedIntMap(IntMap m) { + return new SynchronizedIntMap(m); + } + + /** + * Horked from the Java util class and extended for IntMap. + */ + protected static class SynchronizedIntMap implements IntMap + { + private IntMap m; // Backing Map + private Object mutex; // Object on which to synchronize + + SynchronizedIntMap(IntMap m) { + if (m == null) { + throw new NullPointerException(); + } + this.m = m; + mutex = this; + } + + SynchronizedIntMap(IntMap m, Object mutex) { + this.m = m; + this.mutex = mutex; + } + + public int size() { + synchronized(mutex) {return m.size();} + } + + public boolean isEmpty(){ + synchronized(mutex) {return m.isEmpty();} + } + + public boolean containsKey(int key) { + synchronized(mutex) {return m.containsKey(key);} + } + + public boolean containsKey(Object key) { + synchronized(mutex) {return m.containsKey(key);} + } + + public boolean containsValue(Object value){ + synchronized(mutex) {return m.containsValue(value);} + } + + public Object get(int key) { + synchronized(mutex) {return m.get(key);} + } + + public Object get(Object key) { + synchronized(mutex) {return m.get(key);} + } + + public Object put(int key, Object value) { + synchronized(mutex) {return m.put(key, value);} + } + + public Object put(Object key, Object value) { + synchronized(mutex) {return m.put(key, value);} + } + + public Object remove(int key) { + synchronized(mutex) {return m.remove(key);} + } + + public Object remove(Object key) { + synchronized(mutex) {return m.remove(key);} + } + + public void putAll(Map map) { + synchronized(mutex) {m.putAll(map);} + } + + public void clear() { + synchronized(mutex) {m.clear();} + } + + private transient Set keySet = null; + private transient Set entrySet = null; + private transient Collection values = null; + + public Set keySet() { + synchronized(mutex) { + if (keySet==null) + keySet = new SynchronizedSet(m.keySet(), mutex); + return keySet; + } + } + + public Set entrySet() { + synchronized(mutex) { + if (entrySet==null) + entrySet = new SynchronizedSet(m.entrySet(), mutex); + return entrySet; + } + } + + public Collection values() { + synchronized(mutex) { + if (values==null) + values = new SynchronizedCollection(m.values(), mutex); + return values; + } + } + + public boolean equals(Object o) { + synchronized(mutex) {return m.equals(o);} + } + + public int hashCode() { + synchronized(mutex) {return m.hashCode();} + } + + public String toString() { + synchronized(mutex) {return m.toString();} + } + } + + /** + * I wish I could use this from the java.util.Collections + * class, but those crazy kids at Sun are always using private and + * default access and pointlessly preventing people from properly + * reusing their code. Yay! + */ + protected static class SynchronizedSet + extends SynchronizedCollection implements Set { + SynchronizedSet(Set s) { + super(s); + } + SynchronizedSet(Set s, Object mutex) { + super(s, mutex); + } + + public boolean equals(Object o) { + synchronized(mutex) {return c.equals(o);} + } + public int hashCode() { + synchronized(mutex) {return c.hashCode();} + } + } + + /** + * I wish I could use this from the java.util.Collections + * class, but those crazy kids at Sun are always using private and + * default access and pointlessly preventing people from properly + * reusing their code. Yay! + */ + protected static class SynchronizedCollection + implements Collection { + Collection c; // Backing Collection + Object mutex; // Object on which to synchronize + + SynchronizedCollection(Collection c) { + if (c==null) + throw new NullPointerException(); + this.c = c; + mutex = this; + } + SynchronizedCollection(Collection c, Object mutex) { + this.c = c; + this.mutex = mutex; + } + + public int size() { + synchronized(mutex) {return c.size();} + } + public boolean isEmpty() { + synchronized(mutex) {return c.isEmpty();} + } + public boolean contains(Object o) { + synchronized(mutex) {return c.contains(o);} + } + public Object[] toArray() { + synchronized(mutex) {return c.toArray();} + } + public Object[] toArray(Object[] a) { + synchronized(mutex) {return c.toArray(a);} + } + + public Iterator iterator() { + return c.iterator(); // Must be manually synched by user! + } + + public boolean add(Object o) { + synchronized(mutex) {return c.add(o);} + } + public boolean remove(Object o) { + synchronized(mutex) {return c.remove(o);} + } + + public boolean containsAll(Collection coll) { + synchronized(mutex) {return c.containsAll(coll);} + } + public boolean addAll(Collection coll) { + synchronized(mutex) {return c.addAll(coll);} + } + public boolean removeAll(Collection coll) { + synchronized(mutex) {return c.removeAll(coll);} + } + public boolean retainAll(Collection coll) { + synchronized(mutex) {return c.retainAll(coll);} + } + public void clear() { + synchronized(mutex) {c.clear();} + } + public String toString() { + synchronized(mutex) {return c.toString();} + } + } +} diff --git a/projects/samskivert/src/java/com/samskivert/util/HashIntMap.java b/projects/samskivert/src/java/com/samskivert/util/HashIntMap.java index 3bf59c5f..deb71114 100644 --- a/projects/samskivert/src/java/com/samskivert/util/HashIntMap.java +++ b/projects/samskivert/src/java/com/samskivert/util/HashIntMap.java @@ -1,5 +1,5 @@ // -// $Id: HashIntMap.java,v 1.3 2001/08/11 22:43:29 mdb Exp $ +// $Id: HashIntMap.java,v 1.4 2001/09/15 17:22:11 mdb Exp $ // // samskivert library - useful routines for java programs // Copyright (C) 2001 Michael Bayne @@ -20,63 +20,81 @@ package com.samskivert.util; -import java.util.Enumeration; -import java.util.NoSuchElementException; +import java.util.*; /** - * An int map is like a hashmap, but with integers as keys. We avoid the - * annoyance of having to create integer objects every time we want to - * lookup or insert values. + * An int map is like a regular map, but with integers as keys. We avoid + * the annoyance of having to create integer objects every time we want to + * lookup or insert values. The hash int map is an int map that uses a + * hashtable mechanism to store its key/value mappings. */ -public class IntMap +public class HashIntMap + extends AbstractMap implements IntMap { + /** + * The default number of buckets to use for the hash table. + */ public final static int DEFAULT_BUCKETS = 64; - public IntMap (int buckets) + /** + * Constructs an empty hash int map with the specified number of hash + * buckets. + */ + public HashIntMap (int buckets) { _buckets = new Record[buckets]; } - public IntMap () + /** + * Constructs an empty hash int map with the default number of hash + * buckets. + */ + public HashIntMap () { this(DEFAULT_BUCKETS); } - public synchronized int size () + // documentation inherited + public int size () { return _size; } - public synchronized void put (int key, Object value) + // documentation inherited + public boolean containsKey (Object key) { - int index = Math.abs(key)%_buckets.length; - Record rec = _buckets[index]; - - // either we start a new chain - if (rec == null) { - _buckets[index] = new Record(key, value); - _size++; // we're bigger - return; - } - - // or we replace an element in an existing chain - Record prev = rec; - for (; rec != null; rec = rec.next) { - if (rec.key == key) { - rec.value = value; // we're not bigger - return; - } - prev = rec; - } - - // or we append it to this chain - prev.next = new Record(key, value); - _size++; // we're bigger + return containsKey(((Integer)key).intValue()); } - public synchronized Object get (int key) + // documentation inherited + public boolean containsKey (int key) { - int index = Math.abs(key)%_buckets.length; + return get(key) != null; + } + + // documentation inherited + public boolean containsValue (Object o) + { + for (int i = 0; i < _buckets.length; i++) { + for (Record r = _buckets[i]; r != null; r = r.next) { + if (r.value.equals(o)) { + return true; + } + } + } + return false; + } + + // documentation inherited + public Object get (Object key) + { + return get(((Integer)key).intValue()); + } + + // documentation inherited + public Object get (int key) + { + int index = Math.abs(key) % _buckets.length; for (Record rec = _buckets[index]; rec != null; rec = rec.next) { if (rec.key == key) { return rec.value; @@ -85,16 +103,59 @@ public class IntMap return null; } - public boolean contains (int key) + // documentation inherited + public Object put (Object key, Object value) { - return get(key) != null; + return put(((Integer)key).intValue(), value); } - public synchronized Object remove (int key) + // documentation inherited + public Object put (int key, Object value) { + // disallow null values + if (value == null) { + throw new IllegalArgumentException(); + } + int index = Math.abs(key)%_buckets.length; Record rec = _buckets[index]; + // either we start a new chain + if (rec == null) { + _buckets[index] = new Record(key, value); + _size++; // we're bigger + return null; + } + + // or we replace an element in an existing chain + Record prev = rec; + for (; rec != null; rec = rec.next) { + if (rec.key == key) { + Object ovalue = rec.value; + rec.value = value; // we're not bigger + return ovalue; + } + prev = rec; + } + + // or we append it to this chain + prev.next = new Record(key, value); + _size++; // we're bigger + return null; + } + + // documentation inherited + public Object remove (Object key) + { + return remove(((Integer)key).intValue()); + } + + // documentation inherited + public Object remove (int key) + { + int index = Math.abs(key) % _buckets.length; + Record rec = _buckets[index]; + // if there's no chain, there's no object if (rec == null) { return null; @@ -120,7 +181,8 @@ public class IntMap return null; } - public synchronized void clear () + // documentation inherited + public void clear () { // abandon all of our hash chains (the joy of garbage collection) for (int i = 0; i < _buckets.length; i++) { @@ -130,17 +192,99 @@ public class IntMap _size = 0; } - public Enumeration keys () + // documentation inherited + public Set entrySet () { - return new Enumerator(_buckets, true); + return new AbstractSet() { + public int size () + { + return _size; + } + + public Iterator iterator () + { + return new EntryIterator(); + } + }; } - public Enumeration elements () + protected class EntryIterator implements Iterator { - return new Enumerator(_buckets, false); + public boolean hasNext () + { + // if we're pointing to an entry, we've got more entries + if (_record != null) { + return true; + } + + // search backward through the buckets looking for the next + // non-empty hash chain + while (_index-- > 0) { + if ((_record = _buckets[_index]) != null) { + return true; + } + } + + // found no non-empty hash chains, we're done + return false; + } + + public Object next () + { + // if we're not pointing to an entry, search for the next + // non-empty hash chain + if (_record == null) { + while ((_index-- > 0) && + ((_record = _buckets[_index]) == null)); + } + + // keep track of the last thing we returned + _last = _record; + + // if we found a record, return it's value and move our record + // reference to it's successor + if (_record != null) { + _record = _last.next; + return _last; + } + + throw new NoSuchElementException(); + } + + public void remove () + { + if (_last == null) { + throw new IllegalStateException(); + } + + // remove the record the hard way + HashIntMap.this.remove(_last.key); + _last = null; + } + + protected int _index = _buckets.length; + protected Record _record, _last; } - protected static class Record + /** + * Returns an iteration over the keys of this hash int map. The keys + * are returned as Integer objects. + */ + public Iterator keys () + { + return keySet().iterator(); + } + + /** + * Returns an iteration over the elements (values) of this hash int + * map. + */ + public Iterator elements () + { + return values().iterator(); + } + + protected static class Record implements Entry { public Record next; public int key; @@ -151,86 +295,74 @@ public class IntMap this.key = key; this.value = value; } + + public Object getKey () + { + return new Integer(key); + } + + public Object getValue () + { + return value; + } + + public Object setValue (Object value) + { + Object ovalue = this.value; + this.value = value; + return ovalue; + } + + public boolean equals (Object o) + { + if (o instanceof Record) { + Record or = (Record)o; + return (key == or.key) && value.equals(or.value); + } else { + return false; + } + } + + public int hashCode () + { + return key ^ ((value == null) ? 0 : value.hashCode()); + } } - class Enumerator implements Enumeration - { - public Enumerator (Record[] buckets, boolean returnKeys) - { - _buckets = buckets; - _index = buckets.length; - _returnKeys = returnKeys; - } + public static void main (String[] args) + { + HashIntMap table = new HashIntMap(); - public boolean hasMoreElements () - { - // if we're pointing to an entry, we've got more entries - if (_record != null) { - return true; - } + System.out.print("Inserting: "); + for (int i = 10; i < 100; i++) { + Integer value = new Integer(i); + table.put(i, value); + System.out.print("(" + i + "," + value + ")"); + } + System.out.println(""); - // search backward through the buckets looking for the next - // non-empty hash chain - while (_index-- > 0) { - if ((_record = _buckets[_index]) != null) { - return true; - } - } + System.out.print("Looking up: "); + for (int i = 10; i < 100; i++) { + System.out.print("(" + i + "," + table.get(i) + ")"); + } + System.out.println(""); - // found no non-empty hash chains, we're done - return false; - } + System.out.println("Keys: " + + StringUtil.toString(table.keys())); + System.out.println("Elems: " + + StringUtil.toString(table.elements())); - public Object nextElement () - { - // if we're not pointing to an entry, search for the next - // non-empty hash chain - if (_record == null) { - while ((_index-- > 0) && - ((_record = _buckets[_index]) == null)); - } + System.out.print("Removing: "); + for (int i = 10; i < 98; i++) { + System.out.print("(" + i + "," + table.remove(i) + ")"); + } + System.out.println(""); - // if we found a record, return it's value and move our record - // reference to it's successor - if (_record != null) { - Record r = _record; - _record = r.next; - return _returnKeys ? new Integer(r.key) : r.value; - } - - throw new NoSuchElementException("IntMapEnumerator"); - } - - private int _index; - private Record _record; - private Record[] _buckets; - private boolean _returnKeys; - } - -// public static void main (String[] args) -// { -// IntMap table = new IntMap(); - -// System.out.print("Inserting: "); -// for (int i = 10; i < 100; i++) { -// Integer value = new Integer(i); -// table.put(i, value); -// System.out.print("(" + i + "," + value + ")"); -// } -// System.out.println(""); - -// System.out.print("Looking up: "); -// for (int i = 10; i < 100; i++) { -// System.out.print("(" + i + "," + table.get(i) + ")"); -// } -// System.out.println(""); - -// System.out.print("Removing: "); -// for (int i = 10; i < 100; i++) { -// System.out.print("(" + i + "," + table.remove(i) + ")"); -// } -// System.out.println(""); -// } + System.out.println("Keys: " + + StringUtil.toString(table.keys())); + System.out.println("Elems: " + + StringUtil.toString(table.elements())); + } private Record[] _buckets; private int _size; diff --git a/projects/samskivert/src/java/com/samskivert/util/IntMap.java b/projects/samskivert/src/java/com/samskivert/util/IntMap.java new file mode 100644 index 00000000..dff974e9 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/util/IntMap.java @@ -0,0 +1,78 @@ +// +// $Id: IntMap.java,v 1.1 2001/09/15 17:22:11 mdb Exp $ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001 Michael Bayne +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.util; + +import java.util.Map; + +/** + * An int map is a map that uses integers as keys and provides accessors + * that eliminate the need to create and manipulate superfluous + * Integer objects. It extends the Map interface + * and therefore provides all of the standard accessors (for which + * Integer objects should be supplied as keys). + */ +public interface IntMap extends Map +{ + /** + * Returns true if this map contains a mapping for the + * specified key. + * + * @param key key whose presence in this map is to be tested. + * + * @return true if this map contains a mapping for the + * specified key. + */ + public boolean containsKey (int key); + + /** + * Returns the value to which this map maps the specified key. Returns + * null if the map contains no mapping for this key. + * + * @param key key whose associated value is to be returned. + * + * @return the value to which this map maps the specified key, or + * null if the map contains no mapping for this key. + */ + public Object get (int key); + + /** + * Associates the specified value with the specified key in this map. + * If the map previously contained a mapping for this key, the old + * value is replaced. + * + * @param key key with which the specified value is to be associated. + * @param value value to be associated with the specified key. + * + * @return previous value associated with specified key, or + * null if there was no mapping for key. + */ + public Object put (int key, Object value); + + /** + * Removes the mapping for this key from this map if present. + * + * @param key key whose mapping is to be removed from the map. + * + * @return previous value associated with specified key, or + * null if there was no mapping for key. + */ + public Object remove (int key); +} diff --git a/projects/samskivert/src/java/com/samskivert/util/IntervalManager.java b/projects/samskivert/src/java/com/samskivert/util/IntervalManager.java index ebf916fc..5bf725b2 100644 --- a/projects/samskivert/src/java/com/samskivert/util/IntervalManager.java +++ b/projects/samskivert/src/java/com/samskivert/util/IntervalManager.java @@ -1,5 +1,5 @@ // -// $Id: IntervalManager.java,v 1.3 2001/08/11 22:43:29 mdb Exp $ +// $Id: IntervalManager.java,v 1.4 2001/09/15 17:22:11 mdb Exp $ // // samskivert library - useful routines for java programs // Copyright (C) 2001 Michael Bayne @@ -21,6 +21,7 @@ package com.samskivert.util; import java.util.*; +import java.util.Collections; import com.samskivert.Log; /** @@ -245,7 +246,7 @@ public class IntervalManager extends Thread // to rewrite this such that we can add and remove intervaleds much // faster. protected static ArrayList _schedule = new ArrayList(); - protected static IntMap _hash = new IntMap(); + protected static HashIntMap _hash = new HashIntMap(); protected static long _nextwake = Long.MAX_VALUE; protected static int _helpers = 0; // # of created helpers