/*********************************************************************************
* TotalCross Software Development Kit *
* Copyright (C) 2000-2001 Arthur van Hoff *
* Copyright (C) 2000-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;
/*
* This class is almost identical to java.util.Hashtable, with some
modifications.
*/
/**
* This class implements a hash table, which maps keys to values. Any
* non-<code>null</code> object can be used as a key or as a value.
* <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>
* Hashtable numbers = new Hashtable(3);
* numbers.put("one", Convert.toString(1));
* numbers.put("two", Convert.toString(2));
* numbers.put("three", Convert.toString(3));
* </pre>
* <p>
* To retrieve a number, use the following code:
* <pre>
* String n = (String)numbers.get("two");
* if (n != null)
* // "two = " + Convert.toInt(n);
* </pre>
* This Hashtable class does not support Generics; use the HashMap class instead.
*/
public class Hashtable
{
/** Hashtable collision list. */
protected static class Entry
{
public int hash;
public Object key;
public Object value;
public Entry next;
}
/** The hash table data. */
protected Entry table[];
/** The total number of entries in the hash table. */
private transient int count;
/** Rehashes the table when count exceeds this threshold. */
private int threshold;
/** The load factor for the hashtable. */
private 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;
/** Setting this to true will allow the hashtable to have more than one key with the same value.
* In this case, the methods will always return the first matching key.
* @since TotalCross 1.24
*/
public boolean allowDuplicateKeys;
/**
* 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 Hashtable(int initialCapacity)
{
init(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 Hashtable(int initialCapacity, double loadFactor)
{
init(initialCapacity, loadFactor);
}
/** Constructs a new hashtable, parsing the elements from the given String.
* Each string must be in the form: key = value, splitted in lines.
* This aids the task of creating resource bundles to add localization to your
* application.
* <br>You can include txt files in your application's pdb file using /t,
* where each txt will hold the strings for a language. For example:
* <pre>
* // save these two lines in a file named EN.txt:
* Message = Message
* TestMsg = This is a test
* Exit = Exit
* // save these other two in a file named PT.txt:
* Message = Mensagem
* TestMsg = Isso � um teste
* Exit = Sair
* </pre>
* The TotalCross deployer will include the two files referenced below in the tcz file.
* <br>
* Now, when your program starts, you can do:
* <pre>
* String txt = idiom == EN ? "EN.txt" : "PT.txt";
* byte[] b = Vm.getFile(txt);
* Hashtable res = new Hashtable(new String(b,0,b.length));
* new MessageBox(res.get("Message"), res.get("TestMsg"), new String[]{res.get("Exit")}).popupNonBlocking();
* </pre>
* Note that the keys are <i>case sensitive</i>, and that all strings are trimmed.
*
* @since SuperWaba 5.72
*/
public Hashtable(String res) // guich@572_17
{
String[] items = totalcross.sys.Convert.tokenizeString(res, '\n');
init(items.length, 0.75); // guich@tc114_27
for (int i =0; i < items.length; i++)
{
String s = items[i];
int eq = s.indexOf('=',0);
if (eq < 0)
continue;
put(s.substring(0,eq).trim(), s.substring(eq+1).trim());
}
}
/** Creates a Hashtable with the given keys and values.
* The values can be two things:
* <ol>
* <li> An Object array (<code>Object[]</code>). In this case, the number of keys and values must match.
* <li> A single Object. This object is set as value to all keys.
* </ol>
* The values parameter cannot be null.
* @since TotalCross 1.5
*/
public Hashtable(Object[] keys, Object values)
{
this(keys.length);
Object[] objArray = values instanceof Object[] ? (Object[])values : null;
for (int i = 0; i < keys.length; i++)
put(keys[i], objArray != null ? objArray[i] : values);
}
private void init(int initialCapacity, double loadFactor) // guich@tc114_27
{
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()
{
Entry tab[] = table;
if (count < 100)
for (int i = tab.length; --i >= 0;) tab[i] = null; // faster for tables with few elements
else
totalcross.sys.Convert.fill(tab, 0, tab.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;
* <code>null</code> if the key is not mapped to any value in
* this hashtable.
*/
public Object get(Object key)
{
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % table.length;
for (Entry e = table[index] ; e != null ; e = e.next)
if ((e.hash == hash) && e.key.equals(key))
return e.value;
return null;
}
/**
* Returns the value to which the specified key is mapped in this hashtable as a String.
* If the item is a String, a cast is made, otherwise, the toString method is called.
* @param key a key in the hashtable.
* @return the value to which the key is mapped in this hashtable;
* <code>null</code> if the key is not mapped to any value in
* this hashtable.
* @since TotalCross 1.24
*/
public String getString(Object key) // guich@tc124_25
{
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % table.length;
for (Entry e = table[index] ; e != null ; e = e.next)
if ((e.hash == hash) && e.key.equals(key))
return e.value instanceof String ? (String)e.value : e.value.toString();
return null;
}
/**
* Returns the value to which the specified key is mapped in this hashtable as a String.
* If the item is a String, a cast is made, otherwise, the toString method is called.
* @param key a key in the hashtable.
* @return the value to which the key is mapped in this hashtable;
* <code>defaultValue</code> if the key is not mapped to any value in
* this hashtable.
* @since TotalCross 1.24
*/
public String getString(Object key, String defaultValue) // guich@tc124_25
{
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % table.length;
for (Entry e = table[index] ; e != null ; e = e.next)
if ((e.hash == hash) && e.key.equals(key))
return e.value instanceof String ? (String)e.value : e.value.toString();
return defaultValue;
}
/**
* Returns the value to which the specified key is mapped in this hashtable.
* @param key a key in the hashtable.
* @param defaultValue The default value to be returned if none is found.
* @return the value to which the key is mapped in this hashtable;
* <code>defaultValue</code> if the key is not mapped to any value in
* this hashtable.
* @since TotalCross 1.15
*/
public Object get(Object key, Object defaultValue) // guich@tc115_47
{
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % table.length;
for (Entry e = table[index] ; e != null ; e = e.next)
if ((e.hash == hash) && e.key.equals(key))
return e.value;
return defaultValue;
}
/**
* Returns the value to which the specified key is mapped in this hashtable.
* @param hash The key hash in the hashtable.
* @param defaultValue The default value to be returned if none is found.
* @return the value to which the key is mapped in this hashtable;
* <code>defaultValue</code> if the key is not mapped to any value in
* this hashtable.
* @since TotalCross 1.15
*/
public Object get(int hash, Object defaultValue) // guich@tc115_47
{
Object r = get(hash);
return r == null ? defaultValue : r;
}
/**
* Returns the value to which the specified hash is mapped in this hashtable.
* <p>
* <b>Caution</b>: since you're passing an integer instead of an object, if there are two
* objects that map to the same key, this method will always return the first one only.
*
* @param hash The key hash in the hashtable.
* @return the value to which the key is mapped in this hashtable;
* <code>null</code> if the key is not mapped to any value in
* this hashtable.
*/
public Object get(int hash)
{
int index = (hash & 0x7FFFFFFF) % table.length;
for (Entry e = table[index] ; e != null ; e = e.next)
if (e.hash == hash)
return e.value;
return null;
}
/**
* 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(Object key) // guich@580_29
{
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % table.length;
for (Entry e = table[index] ; e != null ; e = e.next)
if ((e.hash == hash) && e.key.equals(key))
return true;
return false;
}
/**
* Return a Vector of the keys in the Hashtable. The order is the same of the getValues method.
* @see #getValues
* @see #getKeyValuePairs
*/
public Vector getKeys()
{
return getKeysValues(GET_KEYS,null);
}
/**
* Return a Vector of the values in the Hashtable. The order is the same of the getKeys method.
* @since SuperWaba 5.1
* @see #getKeys
* @see #getKeyValuePairs
*/
public Vector getValues() // guich@510_8
{
return getKeysValues(GET_VALUES,null);
}
/**
* Return a Vector with pairs in the form <code>key=value</code> from the Hashtable.
* Each vector's element can safely be casted to a String.
* @param separator the separator between the key and the value. Should be ": ","=", etc.
* @since SuperWaba 5.1
*/
public Vector getKeyValuePairs(String separator) // guich@510_8
{
return getKeysValues(GET_BOTH,separator);
}
private static final int GET_KEYS = 0;
private static final int GET_VALUES = 1;
private static final int GET_BOTH = 2;
private Vector getKeysValues(int getType, String sep)
{
// 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
Object[] v = new Object[count];
if (table != null)
for (int i = 0,n=0; i < table.length; i++)
for (Entry entry = table[i]; entry != null; entry = entry.next)
v[n++] = getType==GET_KEYS?entry.key:getType==GET_VALUES?entry.value:(entry.key+sep+entry.value);
return new Vector(v);
}
/** Copies the keys and values of this Hashtable into the given Hashtable.
* Note that the target Hashtable is not cleared; you should do that by yourself.
* @since TotalCross 1.15
*/
public void copyInto(Hashtable target) // guich@tc115_17
{
if (table != null)
for (int i = 0; i < table.length; i++)
for (Entry entry = table[i]; entry != null; entry = entry.next)
{
if (entry.key != null)
target.put(entry.key, entry.value);
else
target.put(entry.hash, entry.value);
}
}
/**
* Maps the specified <code>key</code> to the specified
* <code>value</code> in this hashtable. Neither the key nor the
* value can be <code>null</code>.
* <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 <code>null</code> if it did not have one.
* @see java.lang.Object#equals(java.lang.Object)
*/
public Object put(Object key, Object value)
{
// Make sure the value is not null
if (value == null) // flsobral@tc100b4_23: throw NPE instead of returning null
throw new NullPointerException("Argument 'value' cannot have a null value");
int hash = key.hashCode(); // flsobral@tc100b4_23: this operation throws NPE if key is null, no need to explicitly test that.
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int index = (hash & 0x7FFFFFFF) % tab.length;
if (!allowDuplicateKeys)
for (Entry e = tab[index] ; e != null ; e = e.next)
if ((e.hash == hash) && e.key.equals(key))
{
Object 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.hash = hash;
e.key = key;
e.value = value;
e.next = tab[index];
if (e.next != null) // guich@571_11
collisions++;
tab[index] = e;
count++;
return null;
}
/**
* Maps the specified <code>key</code> to the specified
* <code>value</code> in this hashtable. Neither the key nor the
* value can be <code>null</code>.
* <p>
* The value can be retrieved by calling the <code>get</code> method
* with a key that is equal to the original key.
* <p>
* This method receives a hashcode instead of the object. You MUST use the get(int)
* method to retrieve the value, otherwise you will get a NullPointerException, because
* no key is stored using this method.
*
* @param hash the hashtable key's hash.
* @param value the value.
* @return the previous value of the specified key in this hashtable,
* or <code>null</code> if it did not have one.
* @see java.lang.Object#equals(java.lang.Object)
*/
public Object put(int hash, Object value)
{
// Make sure the value is not null
if (value == null) // flsobral@tc100b4_23: throw NPE instead of returning null
throw new NullPointerException("Argument 'value' cannot have a null value");
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int index = (hash & 0x7FFFFFFF) % tab.length;
if (!allowDuplicateKeys)
for (Entry e = tab[index] ; e != null ; e = e.next)
if (e.hash == hash)
{
Object old = e.value;
e.value = value;
return old;
}
if (count >= threshold)
{
// Rehash the table if the threshold is exceeded
rehash();
return put(hash, value);
}
// Creates the new entry.
Entry e = new Entry();
e.hash = hash;
e.value = value;
e.next = tab[index];
if (e.next != null) // guich@571_11
collisions++;
tab[index] = e;
count++;
return null;
}
/**
* 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.hash & 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>null</code> if the key did not have a mapping.
*/
public Object remove(Object key)
{
Entry tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index], prev = null ; e != null ; prev = e, e = e.next)
if ((e.hash == hash) && e.key.equals(key))
{
if (prev != null)
prev.next = e.next;
else
tab[index] = e.next;
count--;
return e.value;
}
return null;
}
/**
* Removes the key (and its corresponding value) from this
* hashtable. This method does nothing if the key is not in the hashtable.
* @param hash the hash code of the key that needs to be removed.
* @return the value to which the key had been mapped in this hashtable,
* or <code>null</code> if the key did not have a mapping.
*/
public Object remove(int hash) // flsobral@tc100b4: Added method remove(int hash)
{
Entry tab[] = table;
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index], prev = null ; e != null ; prev = e, e = e.next)
if (e.hash == hash)
{
if (prev != null)
prev.next = e.next;
else
tab[index] = e.next;
count--;
return e.value;
}
return null;
}
/** Returns the number of keys in this hashtable. */
public int size()
{
return count;
}
/** Dumps the keys and values into the given StringBuffer.
* @param sb The StringBuffer where the data will be dumped to
* @param keyvalueSeparator The separator between the key and the value (E.G.: ": ")
* @param lineSeparator The separator placed after each key+value pair (E.G.: "\r\n"). The last separator is cut from the StringBuffer.
* @since TotalCross 1.23
*/
public StringBuffer dumpKeysValues(StringBuffer sb, String keyvalueSeparator, String lineSeparator)
{
if (table != null)
for (int i = 0; i < table.length; i++)
for (Entry entry = table[i]; entry != null; entry = entry.next)
sb.append(entry.key).append(keyvalueSeparator).append(entry.value).append(lineSeparator);
int l = sb.length();
if (l > 0) // cut the last separator
sb.setLength(l-lineSeparator.length());
return sb;
}
}