/* 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 * @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() // { // } }