/********************************************************************************* * TotalCross Software Development Kit * * Copyright (C) 2000, 2001 Arthur van Hoff * * Copyright (C) 2001-2012 SuperWaba Ltda. * * All Rights Reserved * * * * This library and virtual machine 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. * * * * This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 * * A copy of this license is located in file license.txt at the root of this * * SDK or can be downloaded here: * * http://www.gnu.org/licenses/lgpl-3.0.txt * * * *********************************************************************************/ package totalcross.util; import totalcross.sys.*; /* * This class is almost identical to java.util.Hashtable, with some modifications. */ /** * This class implements a hashtable, which maps keys to values. Both * key and value must be integer values. * If an error occurs, an Exception is returned. * <p> * An instance of <code>Hashtable</code> has two parameters that * affect its efficiency: its <i>capacity</i> and its <i>load * factor</i>. The load factor should be between 0.0 and 1.0. When * the number of entries in the hashtable exceeds the product of the * load factor and the current capacity, the capacity is increased by * calling the <code>rehash</code> method. Larger load factors use * memory more efficiently, at the expense of larger expected time * per lookup. * <p> * If many entries are to be made into a <code>Hashtable</code>, * creating it with a sufficiently large capacity may allow the * entries to be inserted more efficiently than letting it perform * automatic rehashing as needed to grow the table. * <p> * This example creates a hashtable of numbers. It uses the names of * the numbers as keys: * <pre> * IntHashtable numbers = new IntHashtable(10); * numbers.put(1, 1000); * numbers.put(2, 2000); * numbers.put(3, 3000); * </pre> * <p> * To retrieve a number, use the following code: * <pre> * int i = numbers.get(2); // "two = " + i; * </pre> * Don't forget to catch the ElementNotFoundException in the methods it is thrown. */ public class IntHashtable { /** Exception thrown when allowDuplicateKeys is set to false. */ public static class DuplicatedKeyException extends RuntimeException { public DuplicatedKeyException(String s) { super(s); } } /** Hashtable collision list. */ protected static class Entry { public int key; // == hash public int value; public Entry next; } /** The hash table data. */ protected Entry table[]; /** The total number of entries in the hash table. */ protected transient int count; /** Rehashes the table when count exceeds this threshold. */ protected int threshold; /** The load factor for the hashtable. */ protected double loadFactor; /** Computes the number of collisions for a set of inserts. You must zero this each time you want to compute it. * Here's a sample of how to determine the best values. Keep in mind that the lower collisions is better, but don't * waste too much memory if its too high. * <pre> * int max = 0xFFFFFFF; * for (int h = 5; ; h++) * { * IntHashtable ht = new IntHashtable(h); * ht.put("nbsp".hashCode(),' '); * ht.put("shy".hashCode(),'�'); * ht.put("quot".hashCode(),'"'); * ... * if (ht.collisions < max) * { * Vm.debug("h: "+h+" colli: "+ht.collisions); * max = ht.collisions; * if (max == 0) * break; * } * } * </pre> * @since SuperWaba 5.71. */ public int collisions; /** Set to false to throw a IntHashtable.DuplicatedKeyException if you add a key that already exists. Its very rare to have * two Objects with same key, but it could occur. This is good to improve program's correctness. * @since TotalCross 1.14 */ public boolean allowDuplicateKeys = true; // guich@tc114_67 /** * Constructs a new, empty hashtable with the specified initial capacity * and default load factor of 0.75f. * * @param initialCapacity The number of elements you think the hashtable will end with. The hashtable will grow if necessary, but using * a number near or above the final size can improve performance. */ public IntHashtable(int initialCapacity) { this(initialCapacity, 0.75f); } /** * Constructs a new, empty hashtable with the specified initial * capacity and the specified load factor. * If initialCapacity is zero, it is changed to 5. * * @param initialCapacity The number of elements you think the hashtable will end with. The hashtable will grow if necessary, but using * a number near or above the final size can improve performance. * @param loadFactor a number between 0.0 and 1.0. */ public IntHashtable(int initialCapacity, double loadFactor) { if (initialCapacity <= 0) initialCapacity = 5; // guich@310_6 initialCapacity = (int)(initialCapacity / loadFactor + 1); // guich@tc100: since most users just pass the number of element, compute the desired initial capacity based in the load factor this.loadFactor = loadFactor; table = new Entry[initialCapacity]; threshold = (int)(initialCapacity * loadFactor); } /** * Clears this hashtable so that it contains no keys. */ public void clear() { totalcross.sys.Convert.fill(table, 0, table.length, null); count = 0; } /** * Returns the value to which the specified key is mapped in this hashtable. * @param key a key in the hashtable. * @return the value to which the key is mapped in this hashtable. * @throws totalcross.util.ElementNotFoundException When the key was not found. * @see #get(int, int) */ public int get(int key) throws ElementNotFoundException { int index = (key & 0x7FFFFFFF) % table.length; for (Entry e = table[index] ; e != null ; e = e.next) if (e.key == key) return e.value; throw new ElementNotFoundException("Key not found: "+key); } /** * Returns the value to which the specified key is mapped in this hashtable. * @param key an Object who's hashcode is the key in the hashtable. * @return the value to which the key is mapped in this hashtable. * @throws totalcross.util.ElementNotFoundException When the key was not found. * @throws NullPointerException If the key is null * @see #get(int, int) */ public int get(Object key) throws ElementNotFoundException { return get(key.hashCode()); } /** * Checks if the value with the specified key is mapped in this hashtable. * @param key a key in the hashtable. * @return True if the key exists, false otherwise. * @since SuperWaba 5.8 */ public boolean exists(int key) // guich@580_29 { int index = (key & 0x7FFFFFFF) % table.length; for (Entry e = table[index] ; e != null ; e = e.next) if (e.key == key) return true; return false; } /** * Return a Vector of the values in the Hashtable. The order is the same of the getKeys method. * @since SuperWaba 5.11 */ public IntVector getValues() // guich@511_10 { return getKeysOrValues(false); } /** * Return an IntVector of the keys in the IntHashtable. The order is the same of the getValues method. * Added ds@120. * corrected by dgecawich@200 */ public IntVector getKeys() { return getKeysOrValues(true); } private IntVector getKeysOrValues(boolean isKeys) { // dgecawich 5/16/01 - fix so that all keys are returned rather than just the last one // the sympton for this was that getCount() always returned 1 regardless of how many items were added int[] array = new int[count]; // guich@511_10: optimized to avoid method calls int len = table.length,n=0; for ( int i = 0; i < len; i++ ) { Entry entry = table[i]; while (entry != null) // guich@566_30 { array[n++] = isKeys?entry.key:entry.value; entry = entry.next; } } return new IntVector(array); } /** Takes out the hashCode from the given key object and calls put(int,int). * * To increase safeness, set <code>allowDuplicateKeys</code> to false. * @see #put(int, int) */ public int put(Object key, int value) { try { return put(key.hashCode(), value); } catch (DuplicatedKeyException dke) { throw new DuplicatedKeyException(key.toString()); } } /** * Maps the specified <code>key</code> to the specified * <code>value</code> in this hashtable. * <p> * The value can be retrieved by calling the <code>get</code> method * with a key that is equal to the original key. * * @param key the hashtable key. * @param value the value. * @return the previous value of the specified key in this hashtable, * or the given value if it did not have one. * @see java.lang.Object#equals(java.lang.Object) * @see #allowDuplicateKeys * @throws IntHashtable.DuplicatedKeyException if allowDuplicateKeys is set to false and another key is already added. */ public int put(int key, int value) { // Makes sure the key is not already in the hashtable. Entry tab[] = table; int index = (key & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index] ; e != null ; e = e.next) if (e.key == key) { if (!allowDuplicateKeys) // guich@tc114_67 throw new DuplicatedKeyException(Convert.toString(key)); int old = e.value; e.value = value; return old; } if (count >= threshold) { // Rehash the table if the threshold is exceeded rehash(); return put(key, value); } // Creates the new entry. Entry e = new Entry(); e.key = key; e.value = value; e.next = tab[index]; if (e.next != null) // guich@571_11 collisions++; tab[index] = e; count++; return value; // guich@tc100: returns the given value instead of INVALID } /** * Rehashes the contents of the hashtable into a hashtable with a * larger capacity. This method is called automatically when the * number of keys in the hashtable exceeds this hashtable's capacity * and load factor. */ protected void rehash() { int oldCapacity = table.length; Entry oldTable[] = table; int newCapacity = (((oldCapacity << 1) + oldCapacity) >> 1) + 1; // guich@120 - grows 50% instead of 100% - guich@200b4_198: added Peter Dickerson and Andrew Chitty changes to correct the optimization i made with << and >> Entry newTable[] = new Entry[newCapacity]; threshold = (int)(newCapacity * loadFactor); table = newTable; for (int i = oldCapacity ; i-- > 0 ;) for (Entry old = oldTable[i] ; old != null ; ) { Entry e = old; old = old.next; int index = (e.key & 0x7FFFFFFF) % newCapacity; e.next = newTable[index]; newTable[index] = e; } } /** * Removes the key (and its corresponding value) from this * hashtable. This method does nothing if the key is not in the hashtable. * @param key the key that needs to be removed. * @return the value to which the key had been mapped in this hashtable, * or <code>INVALID</code> if the key did not have a mapping. * @throws totalcross.util.ElementNotFoundException When the key was not found. */ public int remove(int key) throws ElementNotFoundException { Entry tab[] = table; int index = (key & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index], prev = null ; e != null ; prev = e, e = e.next) if (e.key == key) { if (prev != null) prev.next = e.next; else tab[index] = e.next; count--; return e.value; } throw new ElementNotFoundException("Key not found: "+key); } /** * Returns the number of keys in this hashtable. * * @return the number of keys in this hashtable. */ public int size() { return count; } /** * Returns the value to which the specified key is mapped in this hashtable. * @param key a key in the hashtable. * @return the value to which the key is mapped in this hashtable; * or <code>defaultValue</code> if the key is not mapped to any value in * this hashtable. * @since TotalCross 1.0 */ public int get(int key, int defaultValue) { int index = (key & 0x7FFFFFFF) % table.length; for (Entry e = table[index] ; e != null ; e = e.next) if (e.key == key) return e.value; return defaultValue; } /** Returns the key at the given position, or throws ArrayIndexOutOfBounds if the given position does not exist. * Note that the first key has no relation with the smallest key. * @since TotalCross 1.0 * @throws ArrayIndexOutOfBoundsException If the position is out of range */ public int getKey(int pos) { int len = table.length; int t = pos; for ( int i = 0; i < len; i++ ) { Entry entry = table[i]; while (entry != null) // guich@566_30 { if (t-- <= 0) return entry.key; entry = entry.next; } } throw new ArrayIndexOutOfBoundsException("Position " + pos + " out of range"); } /** Increments the value of a key by the given amount. If the key doesn't exist, a new one is created with * the amount. Otherwise, its value is changed by the amount. This method is useful to use an IntHashtable * as a multi counter. * @return The current value. * @since TotalCross 1.2 */ public int incrementValue(int key, int amount) { int current = get(key, 0); current += amount; put(key, current); return current; } }