| /* Copyright (C) 1998, 1999 Cygnus Solutions |
| |
| This file is part of libgcj. |
| |
| This software is copyrighted work licensed under the terms of the |
| Libgcj License. Please consult the file "LIBGCJ_LICENSE" for |
| details. */ |
| |
| package java.util; |
| |
| import java.io.Serializable; |
| |
| /** |
| * @author Warren Levy <warrenl@cygnus.com> |
| * @date September 24, 1998. |
| */ |
| /* Written using "Java Class Libraries", 2nd edition, ISBN 0-201-31002-3 |
| * "The Java Language Specification", ISBN 0-201-63451-1 |
| * plus online API docs for JDK 1.2 beta from http://www.javasoft.com. |
| * Status: Believed complete and correct |
| */ |
| |
| class HashtableEntry |
| { |
| public Object key; |
| public Object value; |
| public HashtableEntry nextEntry = null; |
| |
| public HashtableEntry(Object key, Object value) |
| { |
| this.key = key; |
| this.value = value; |
| } |
| } |
| |
| class HashtableEnumeration implements Enumeration |
| { |
| // TBD: Enumeration is not safe if new elements are put in the table as |
| // this could cause a rehash and we'd completely lose our place. Even |
| // without a rehash, it is undetermined if a new element added would |
| // appear in the enumeration. The spec says nothing about this, but |
| // the "Java Class Libraries" book infers that modifications to the |
| // hashtable during enumeration causes indeterminate results. Don't do it! |
| // A safer way would be to make a copy of the table (e.g. into a vector) |
| // but this is a fair bit more expensive. |
| private HashtableEntry[] bucket; |
| private int bucketIndex; |
| private HashtableEntry elem; |
| private int enumCount; |
| private int size; |
| private boolean values; |
| |
| public HashtableEnumeration(HashtableEntry[] bkt, int sz, boolean isValues) |
| { |
| bucket = bkt; |
| bucketIndex = -1; |
| enumCount = 0; |
| elem = null; |
| size = sz; |
| values = isValues; |
| } |
| |
| public boolean hasMoreElements() |
| { |
| return enumCount < size; |
| } |
| |
| public Object nextElement() |
| { |
| if (!hasMoreElements()) |
| throw new NoSuchElementException(); |
| |
| // Find next element |
| if (elem != null) // In the middle of a bucket |
| elem = elem.nextEntry; |
| while (elem == null) // Find the next non-empty bucket |
| elem = bucket[++bucketIndex]; |
| |
| enumCount++; |
| return values ? elem.value : elem.key; |
| } |
| } |
| |
| // TBD: The algorithm used here closely reflects what is described in |
| // the "Java Class Libraries" book. The "Java Language Spec" is much |
| // less specific about the implementation. Because of this freedom |
| // provided by the actual spec, hash table algorithms should be |
| // investigated to see if there is a better alternative to this one. |
| |
| // TODO12: |
| // public class Hashtable extends Dictionary |
| // implements Map, Cloneable, Serializable |
| |
| public class Hashtable extends Dictionary implements Cloneable, Serializable |
| { |
| private HashtableEntry bucket[]; |
| private float loadFactor; |
| private int hsize = 0; |
| |
| public Hashtable() |
| { |
| // The "Java Class Libraries" book (p. 919) says that initial size in this |
| // case is 101 (a prime number to increase the odds of even distribution). |
| this(101, 0.75F); |
| } |
| |
| public Hashtable(int initialSize) |
| { |
| this(initialSize, 0.75F); |
| } |
| |
| public Hashtable(int initialSize, float loadFactor) |
| { |
| if (initialSize < 0 || loadFactor <= 0.0 || loadFactor > 1.0) |
| throw new IllegalArgumentException(); |
| |
| bucket = new HashtableEntry[initialSize]; |
| this.loadFactor = loadFactor; |
| } |
| |
| // TODO12: |
| // public Hashtable(Map t) |
| // { |
| // } |
| |
| public synchronized void clear() |
| { |
| // Aid the GC by nulling out the entries in the hash table. |
| for (int i = 0; i < bucket.length; i++) |
| { |
| HashtableEntry elem = bucket[i]; |
| bucket[i] = null; // May already be null. |
| while (elem != null) |
| { |
| HashtableEntry next = elem.nextEntry; |
| elem.nextEntry = null; // May already be null. |
| elem = next; |
| } |
| } |
| hsize = 0; |
| } |
| |
| public synchronized Object clone() |
| { |
| // New hashtable will have same initialCapacity and loadFactor. |
| Hashtable newTable = new Hashtable(bucket.length, loadFactor); |
| |
| HashtableEntry newElem, prev = null; |
| for (int i = 0; i < bucket.length; i++) |
| for (HashtableEntry elem = bucket[i]; elem != null; elem = elem.nextEntry) |
| { |
| // An easy but expensive method is newTable.put(elem.key, elem.value); |
| // Since the hash tables are the same size, the buckets and collisions |
| // will be the same in the new one, so we can just clone directly. |
| // This is much cheaper than using put. |
| newElem = new HashtableEntry(elem.key, elem.value); |
| if (newTable.bucket[i] == null) |
| prev = newTable.bucket[i] = newElem; |
| else |
| prev = prev.nextEntry = newElem; |
| } |
| |
| newTable.hsize = this.hsize; |
| return newTable; |
| } |
| |
| public synchronized boolean contains(Object value) throws NullPointerException |
| { |
| // An exception is thrown here according to the JDK 1.2 doc. |
| if (value == null) |
| throw new NullPointerException(); |
| |
| for (int i = 0; i < bucket.length; i++) |
| for (HashtableEntry elem = bucket[i]; elem != null; elem = elem.nextEntry) |
| if (elem.value.equals(value)) |
| return true; |
| |
| return false; |
| } |
| |
| public synchronized boolean containsKey(Object key) |
| { |
| // The Map interface mandates that we throw this. |
| if (key == null) |
| throw new NullPointerException (); |
| |
| for (HashtableEntry elem = bucket[Math.abs(key.hashCode() |
| % bucket.length)]; |
| elem != null; elem = elem.nextEntry) |
| if (elem.key.equals(key)) |
| return true; |
| |
| return false; |
| } |
| |
| public synchronized Enumeration elements() |
| { |
| return new HashtableEnumeration(bucket, hsize, true); |
| } |
| |
| public synchronized Object get(Object key) |
| { |
| // The Dictionary interface mandates that get() throw a |
| // NullPointerException if key is null. |
| if (key == null) |
| throw new NullPointerException (); |
| |
| for (HashtableEntry elem = bucket[Math.abs (key.hashCode() |
| % bucket.length)]; |
| elem != null; elem = elem.nextEntry) |
| if (elem.key.equals(key)) |
| return elem.value; |
| |
| return null; |
| } |
| |
| public boolean isEmpty() |
| { |
| return this.hsize <= 0; |
| } |
| |
| public synchronized Enumeration keys() |
| { |
| return new HashtableEnumeration(bucket, hsize, false); |
| } |
| |
| public synchronized Object put(Object key, Object value) |
| throws NullPointerException |
| { |
| if (key == null || value == null) |
| throw new NullPointerException(); |
| |
| HashtableEntry prevElem = null; |
| final int index = Math.abs(key.hashCode() % bucket.length); |
| |
| for (HashtableEntry elem = bucket[index]; elem != null; |
| prevElem = elem, elem = elem.nextEntry) |
| if (elem.key.equals(key)) |
| { |
| // Update with the new value and then return the old one. |
| Object oldVal = elem.value; |
| elem.value = value; |
| return oldVal; |
| } |
| |
| // At this point, we know we need to add a new element. |
| HashtableEntry newElem = new HashtableEntry(key, value); |
| if (bucket[index] == null) |
| bucket[index] = newElem; |
| else |
| prevElem.nextEntry = newElem; |
| |
| if (++hsize > loadFactor * bucket.length) |
| rehash(); |
| |
| return null; |
| } |
| |
| protected void rehash() |
| { |
| // Create a new table which is twice the size (plus one) of the old. |
| // One is added to make the new array length odd so it thus has at least |
| // a (small) possibility of being a prime number. |
| HashtableEntry oldBucket[] = bucket; |
| bucket = new HashtableEntry[bucket.length * 2 + 1]; |
| |
| // Copy over each entry into the new table |
| HashtableEntry elem; |
| for (int i = 0; i < oldBucket.length; i++) |
| for (elem = oldBucket[i]; elem != null; elem = elem.nextEntry) |
| { |
| // Calling put(elem.key, elem.value); would seem like the easy way |
| // but it is dangerous since put increases 'hsize' and calls rehash! |
| // This could become infinite recursion under the right |
| // circumstances. Instead, we'll add the element directly; this is a |
| // bit more efficient than put since the data is already verified. |
| final int index = Math.abs(elem.key.hashCode() % bucket.length); |
| HashtableEntry newElem = new HashtableEntry(elem.key, elem.value); |
| if (bucket[index] == null) |
| bucket[index] = newElem; |
| else |
| { |
| // Since this key can't already be in the table, just add this |
| // in at the top of the bucket. |
| newElem.nextEntry = bucket[index]; |
| bucket[index] = newElem; |
| } |
| } |
| } |
| |
| public synchronized Object remove(Object key) |
| { |
| // TBD: Hmm, none of the various docs say to throw an exception here. |
| if (key == null) |
| return null; |
| |
| Object retval; |
| HashtableEntry prevElem = null; |
| final int index = Math.abs(key.hashCode() % bucket.length); |
| |
| for (HashtableEntry elem = bucket[index]; elem != null; |
| prevElem = elem, elem = elem.nextEntry) |
| if (elem.key.equals(key)) |
| { |
| retval = elem.value; |
| if (prevElem == null) |
| bucket[index] = elem.nextEntry; |
| else |
| prevElem.nextEntry = elem.nextEntry; |
| --hsize; |
| return retval; |
| } |
| |
| return null; |
| } |
| |
| public int size() |
| { |
| return this.hsize; |
| } |
| |
| public synchronized String toString() |
| { |
| // Following the Java Lang Spec 21.5.4 (p. 636). |
| |
| Enumeration keys = keys(); |
| Enumeration values = elements(); |
| |
| // Prepend first element with open bracket |
| StringBuffer result = new StringBuffer("{"); |
| |
| // add first element if one exists |
| // TBD: Seems like it is more efficient to catch the exception than |
| // to call hasMoreElements each time around. |
| try |
| { |
| result.append(keys.nextElement().toString() + "=" + |
| values.nextElement().toString()); |
| } |
| catch (NoSuchElementException ex) |
| { |
| } |
| |
| // Prepend subsequent elements with ", " |
| try |
| { |
| while (true) |
| result.append(", " + keys.nextElement().toString() + "=" + |
| values.nextElement().toString()); |
| } |
| catch (NoSuchElementException ex) |
| { |
| } |
| |
| // Append last element with closing bracket |
| result.append("}"); |
| return result.toString(); |
| } |
| |
| // TODO12: |
| // public Set entrySet() |
| // { |
| // } |
| |
| // TODO12: |
| // public Set keySet() |
| // { |
| // } |
| |
| // Since JDK 1.2: |
| // This method is identical to contains but is part of the 1.2 Map interface. |
| // TBD: Should contains return containsValue instead? Depends on which |
| // will be called more typically. |
| public synchronized boolean containsValue(Object value) |
| { |
| return this.contains(value); |
| } |
| |
| // TODO12: |
| // public boolean equals(Object o) |
| // { |
| // } |
| |
| // TODO12: |
| // public boolean hashCode() |
| // { |
| // } |
| |
| // TODO12: |
| // public void putAll(Map t) |
| // { |
| // } |
| |
| // TODO12: |
| // public Collection values() |
| // { |
| // } |
| } |