blob: abede2d47a2f5b5bffd78003d556bd39ade05267 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package java.util;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import org.apache.harmony.luni.internal.nls.Messages;
/**
* Hashtable associates keys with values. Both keys and values cannot be null.
* The size of the Hashtable is the number of key/value pairs it contains. The
* capacity is the number of key/value pairs the Hashtable can hold. The load
* factor is a float value which determines how full the Hashtable gets before
* expanding the capacity. If the load factor of the Hashtable is exceeded, the
* capacity is doubled.
*
* @see Enumeration
* @see java.io.Serializable
* @see java.lang.Object#equals
* @see java.lang.Object#hashCode
* @since Android 1.0
*/
public class Hashtable<K, V> extends Dictionary<K, V> implements Map<K, V>,
Cloneable, Serializable {
private static final long serialVersionUID = 1421746759512286392L;
transient int elementCount;
transient Entry<K, V>[] elementData;
private float loadFactor;
private int threshold;
transient int firstSlot;
transient int lastSlot = -1;
transient int modCount;
private static final Enumeration<?> EMPTY_ENUMERATION = new Enumeration<Object>() {
public boolean hasMoreElements() {
return false;
}
public Object nextElement() {
throw new NoSuchElementException();
}
};
private static <K, V> Entry<K, V> newEntry(K key, V value, int hash) {
return new Entry<K, V>(key, value);
}
private static class Entry<K, V> extends MapEntry<K, V> {
Entry<K, V> next;
final int hashcode;
Entry(K theKey, V theValue) {
super(theKey, theValue);
hashcode = theKey.hashCode();
}
@Override
@SuppressWarnings("unchecked")
public Object clone() {
Entry<K, V> entry = (Entry<K, V>) super.clone();
if (next != null) {
entry.next = (Entry<K, V>) next.clone();
}
return entry;
}
@Override
public V setValue(V object) {
if (object == null) {
throw new NullPointerException();
}
V result = value;
value = object;
return result;
}
public int getKeyHash() {
return key.hashCode();
}
public boolean equalsKey(Object aKey, int hash) {
// BEGIN android-changed
// The VM can inline String.equals
return hashcode == aKey.hashCode()
&& (key instanceof String
? ((String) key).equals(aKey) : key.equals(aKey));
// END android-changed
}
@Override
public String toString() {
return key + "=" + value; //$NON-NLS-1$
}
}
private final class HashIterator<E> implements Iterator<E> {
private int position, expectedModCount;
private final MapEntry.Type<E, K, V> type;
private Entry<K, V> lastEntry;
private int lastPosition;
private boolean canRemove = false;
HashIterator(MapEntry.Type<E, K, V> value) {
type = value;
position = lastSlot;
expectedModCount = modCount;
}
public boolean hasNext() {
if (lastEntry != null && lastEntry.next != null) {
return true;
}
while (position >= firstSlot) {
if (elementData[position] == null) {
position--;
} else {
return true;
}
}
return false;
}
public E next() {
if (expectedModCount == modCount) {
if (lastEntry != null) {
lastEntry = lastEntry.next;
}
if (lastEntry == null) {
while (position >= firstSlot
&& (lastEntry = elementData[position]) == null) {
position--;
}
if (lastEntry != null) {
lastPosition = position;
// decrement the position so we don't find the same slot
// next time
position--;
}
}
if (lastEntry != null) {
canRemove = true;
return type.get(lastEntry);
}
throw new NoSuchElementException();
}
throw new ConcurrentModificationException();
}
public void remove() {
if (expectedModCount == modCount) {
if (canRemove) {
canRemove = false;
synchronized (Hashtable.this) {
boolean removed = false;
Entry<K, V> entry = elementData[lastPosition];
if (entry == lastEntry) {
elementData[lastPosition] = entry.next;
removed = true;
} else {
while (entry != null && entry.next != lastEntry) {
entry = entry.next;
}
if (entry != null) {
entry.next = lastEntry.next;
removed = true;
}
}
if (removed) {
modCount++;
elementCount--;
expectedModCount++;
return;
}
// the entry must have been (re)moved outside of the
// iterator
// but this condition wasn't caught by the modCount
// check
// throw ConcurrentModificationException() outside of
// synchronized block
}
} else {
throw new IllegalStateException();
}
}
throw new ConcurrentModificationException();
}
}
private final class HashEnumerator<E> implements Enumeration<E> {
boolean key;
int start;
Entry<K, V> entry;
HashEnumerator(boolean isKey) {
key = isKey;
start = lastSlot + 1;
}
public boolean hasMoreElements() {
if (entry != null) {
return true;
}
while (--start >= firstSlot) {
if (elementData[start] != null) {
entry = elementData[start];
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
public E nextElement() {
if (hasMoreElements()) {
Object result = key ? entry.key : entry.value;
entry = entry.next;
return (E) result;
}
throw new NoSuchElementException();
}
}
/**
* Constructs a new {@code Hashtable} using the default capacity and load
* factor.
*
* @since Android 1.0
*/
public Hashtable() {
this(11);
}
/**
* Constructs a new {@code Hashtable} using the specified capacity and the
* default load factor.
*
* @param capacity
* the initial capacity.
* @since Android 1.0
*/
public Hashtable(int capacity) {
if (capacity >= 0) {
elementCount = 0;
elementData = newElementArray(capacity == 0 ? 1 : capacity);
firstSlot = elementData.length;
loadFactor = 0.75f;
computeMaxSize();
} else {
throw new IllegalArgumentException();
}
}
/**
* Constructs a new {@code Hashtable} using the specified capacity and load
* factor.
*
* @param capacity
* the initial capacity.
* @param loadFactor
* the initial load factor.
* @since Android 1.0
*/
public Hashtable(int capacity, float loadFactor) {
if (capacity >= 0 && loadFactor > 0) {
elementCount = 0;
firstSlot = capacity;
elementData = newElementArray(capacity == 0 ? 1 : capacity);
this.loadFactor = loadFactor;
computeMaxSize();
} else {
throw new IllegalArgumentException();
}
}
/**
* Constructs a new instance of {@code Hashtable} containing the mappings
* from the specified map.
*
* @param map
* the mappings to add.
* @since Android 1.0
*/
public Hashtable(Map<? extends K, ? extends V> map) {
this(map.size() < 6 ? 11 : (map.size() * 4 / 3) + 11);
putAll(map);
}
@SuppressWarnings("unchecked")
private Entry<K, V>[] newElementArray(int size) {
return new Entry[size];
}
/**
* Removes all key/value pairs from this {@code Hashtable}, leaving the
* size zero and the capacity unchanged.
*
* @see #isEmpty
* @see #size
* @since Android 1.0
*/
public synchronized void clear() {
elementCount = 0;
Arrays.fill(elementData, null);
modCount++;
}
/**
* Returns a new {@code Hashtable} with the same key/value pairs, capacity
* and load factor.
*
* @return a shallow copy of this {@code Hashtable}.
* @see java.lang.Cloneable
* @since Android 1.0
*/
@Override
@SuppressWarnings("unchecked")
public synchronized Object clone() {
try {
Hashtable<K, V> hashtable = (Hashtable<K, V>) super.clone();
hashtable.elementData = elementData.clone();
Entry<K, V> entry;
for (int i = elementData.length; --i >= 0;) {
if ((entry = elementData[i]) != null) {
hashtable.elementData[i] = (Entry<K, V>) entry.clone();
}
}
return hashtable;
} catch (CloneNotSupportedException e) {
return null;
}
}
private void computeMaxSize() {
threshold = (int) (elementData.length * loadFactor);
}
/**
* Returns true if this {@code Hashtable} contains the specified object as
* the value of at least one of the key/value pairs.
*
* @param value
* the object to look for as a value in this {@code Hashtable}.
* @return {@code true} if object is a value in this {@code Hashtable},
* {@code false} otherwise.
* @see #containsKey
* @see java.lang.Object#equals
* @since Android 1.0
*/
public synchronized boolean contains(Object value) {
if (value == null) {
throw new NullPointerException();
}
for (int i = elementData.length; --i >= 0;) {
Entry<K, V> entry = elementData[i];
while (entry != null) {
if (value.equals(entry.value)) {
return true;
}
entry = entry.next;
}
}
return false;
}
/**
* Returns true if this {@code Hashtable} contains the specified object as a
* key of one of the key/value pairs.
*
* @param key
* the object to look for as a key in this {@code Hashtable}.
* @return {@code true} if object is a key in this {@code Hashtable},
* {@code false} otherwise.
* @see #contains
* @see java.lang.Object#equals
* @since Android 1.0
*/
public synchronized boolean containsKey(Object key) {
return getEntry(key) != null;
}
/**
* Searches this {@code Hashtable} for the specified value.
*
* @param value
* the object to search for.
* @return {@code true} if {@code value} is a value of this
* {@code Hashtable}, {@code false} otherwise.
* @since Android 1.0
*/
public boolean containsValue(Object value) {
return contains(value);
}
/**
* Returns an enumeration on the values of this {@code Hashtable}. The
* results of the Enumeration may be affected if the contents of this
* {@code Hashtable} are modified.
*
* @return an enumeration of the values of this {@code Hashtable}.
* @see #keys
* @see #size
* @see Enumeration
* @since Android 1.0
*/
@Override
@SuppressWarnings("unchecked")
public synchronized Enumeration<V> elements() {
if (elementCount == 0) {
return (Enumeration<V>) EMPTY_ENUMERATION;
}
return new HashEnumerator<V>(false);
}
/**
* Returns a set of the mappings contained in this {@code Hashtable}. Each
* element in the set is a {@link Map.Entry}. The set is backed by this
* {@code Hashtable} so changes to one are reflected by the other. The set
* does not support adding.
*
* @return a set of the mappings.
* @since Android 1.0
*/
public Set<Map.Entry<K, V>> entrySet() {
return new Collections.SynchronizedSet<Map.Entry<K, V>>(
new AbstractSet<Map.Entry<K, V>>() {
@Override
public int size() {
synchronized (Hashtable.this) {
return elementCount;
}
}
@Override
public void clear() {
Hashtable.this.clear();
}
@Override
@SuppressWarnings("unchecked")
public boolean remove(Object object) {
synchronized (Hashtable.this) {
if (contains(object)) {
Hashtable.this
.remove(((Map.Entry<K, V>) object)
.getKey());
return true;
}
return false;
}
}
@Override
@SuppressWarnings("unchecked")
public boolean contains(Object object) {
synchronized (Hashtable.this) {
Entry<K, V> entry = getEntry(((Map.Entry<K, V>) object)
.getKey());
return object.equals(entry);
}
}
@Override
public Iterator<Map.Entry<K, V>> iterator() {
return new HashIterator<Map.Entry<K, V>>(
new MapEntry.Type<Map.Entry<K, V>, K, V>() {
public Map.Entry<K, V> get(
MapEntry<K, V> entry) {
return entry;
}
});
}
}, this);
}
/**
* Compares this {@code Hashtable} with the specified object and indicates
* if they are equal. In order to be equal, {@code object} must be an
* instance of Map and contain the same key/value pairs.
*
* @param object
* the object to compare with this object.
* @return {@code true} if the specified object is equal to this Map,
* {@code false} otherwise.
* @see #hashCode
* @since Android 1.0
*/
@Override
public synchronized boolean equals(Object object) {
if (this == object) {
return true;
}
if (object instanceof Map) {
Map<?, ?> map = (Map<?, ?>) object;
if (size() != map.size()) {
return false;
}
Set<Map.Entry<K, V>> entries = entrySet();
for (Map.Entry<?, ?> e : map.entrySet()) {
if (!entries.contains(e)) {
return false;
}
}
return true;
}
return false;
}
/**
* Returns the value associated with the specified key in this
* {@code Hashtable}.
*
* @param key
* the key of the value returned.
* @return the value associated with the specified key, or {@code null} if
* the specified key does not exist.
* @see #put
* @since Android 1.0
*/
@Override
public synchronized V get(Object key) {
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % elementData.length;
Entry<K, V> entry = elementData[index];
while (entry != null) {
if (entry.equalsKey(key, hash)) {
return entry.value;
}
entry = entry.next;
}
return null;
}
Entry<K, V> getEntry(Object key) {
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % elementData.length;
Entry<K, V> entry = elementData[index];
while (entry != null) {
if (entry.equalsKey(key, hash)) {
return entry;
}
entry = entry.next;
}
return null;
}
@Override
public synchronized int hashCode() {
int result = 0;
Iterator<Map.Entry<K, V>> it = entrySet().iterator();
while (it.hasNext()) {
Map.Entry<K, V> entry = it.next();
Object key = entry.getKey();
Object value = entry.getValue();
int hash = (key != this ? key.hashCode() : 0)
^ (value != this ? (value != null ? value.hashCode() : 0)
: 0);
result += hash;
}
return result;
}
/**
* Returns true if this {@code Hashtable} has no key/value pairs.
*
* @return {@code true} if this {@code Hashtable} has no key/value pairs,
* {@code false} otherwise.
* @see #size
* @since Android 1.0
*/
@Override
public synchronized boolean isEmpty() {
return elementCount == 0;
}
/**
* Returns an enumeration on the keys of this {@code Hashtable} instance.
* The results of the enumeration may be affected if the contents of this
* {@code Hashtable} are modified.
*
* @return an enumeration of the keys of this {@code Hashtable}.
* @see #elements
* @see #size
* @see Enumeration
* @since Android 1.0
*/
@Override
@SuppressWarnings("unchecked")
public synchronized Enumeration<K> keys() {
if (elementCount == 0) {
return (Enumeration<K>) EMPTY_ENUMERATION;
}
return new HashEnumerator<K>(true);
}
/**
* Returns a set of the keys contained in this {@code Hashtable}. The set
* is backed by this {@code Hashtable} so changes to one are reflected by
* the other. The set does not support adding.
*
* @return a set of the keys.
* @since Android 1.0
*/
public Set<K> keySet() {
return new Collections.SynchronizedSet<K>(new AbstractSet<K>() {
@Override
public boolean contains(Object object) {
synchronized (Hashtable.this) {
return containsKey(object);
}
}
@Override
public int size() {
synchronized (Hashtable.this) {
return elementCount;
}
}
@Override
public void clear() {
Hashtable.this.clear();
}
@Override
public boolean remove(Object key) {
synchronized (Hashtable.this) {
if (containsKey(key)) {
Hashtable.this.remove(key);
return true;
}
return false;
}
}
@Override
public Iterator<K> iterator() {
return new HashIterator<K>(new MapEntry.Type<K, K, V>() {
public K get(MapEntry<K, V> entry) {
return entry.key;
}
});
}
}, this);
}
/**
* Associate the specified value with the specified key in this
* {@code Hashtable}. If the key already exists, the old value is replaced.
* The key and value cannot be null.
*
* @param key
* the key to add.
* @param value
* the value to add.
* @return the old value associated with the specified key, or {@code null}
* if the key did not exist.
* @see #elements
* @see #get
* @see #keys
* @see java.lang.Object#equals
* @since Android 1.0
*/
@Override
public synchronized V put(K key, V value) {
if (key != null && value != null) {
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % elementData.length;
Entry<K, V> entry = elementData[index];
while (entry != null && !entry.equalsKey(key, hash)) {
entry = entry.next;
}
if (entry == null) {
modCount++;
if (++elementCount > threshold) {
rehash();
index = (hash & 0x7FFFFFFF) % elementData.length;
}
if (index < firstSlot) {
firstSlot = index;
}
if (index > lastSlot) {
lastSlot = index;
}
entry = newEntry(key, value, hash);
entry.next = elementData[index];
elementData[index] = entry;
return null;
}
V result = entry.value;
entry.value = value;
return result;
}
throw new NullPointerException();
}
/**
* Copies every mapping to this {@code Hashtable} from the specified map.
*
* @param map
* the map to copy mappings from.
* @since Android 1.0
*/
public synchronized void putAll(Map<? extends K, ? extends V> map) {
for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
/**
* Increases the capacity of this {@code Hashtable}. This method is called
* when the size of this {@code Hashtable} exceeds the load factor.
*
* @since Android 1.0
*/
protected void rehash() {
int length = (elementData.length << 1) + 1;
if (length == 0) {
length = 1;
}
int newFirst = length;
int newLast = -1;
Entry<K, V>[] newData = newElementArray(length);
for (int i = lastSlot + 1; --i >= firstSlot;) {
Entry<K, V> entry = elementData[i];
while (entry != null) {
int index = (entry.getKeyHash() & 0x7FFFFFFF) % length;
if (index < newFirst) {
newFirst = index;
}
if (index > newLast) {
newLast = index;
}
Entry<K, V> next = entry.next;
entry.next = newData[index];
newData[index] = entry;
entry = next;
}
}
firstSlot = newFirst;
lastSlot = newLast;
elementData = newData;
computeMaxSize();
}
/**
* Removes the key/value pair with the specified key from this
* {@code Hashtable}.
*
* @param key
* the key to remove.
* @return the value associated with the specified key, or {@code null} if
* the specified key did not exist.
* @see #get
* @see #put
* @since Android 1.0
*/
@Override
public synchronized V remove(Object key) {
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % elementData.length;
Entry<K, V> last = null;
Entry<K, V> entry = elementData[index];
while (entry != null && !entry.equalsKey(key, hash)) {
last = entry;
entry = entry.next;
}
if (entry != null) {
modCount++;
if (last == null) {
elementData[index] = entry.next;
} else {
last.next = entry.next;
}
elementCount--;
V result = entry.value;
entry.value = null;
return result;
}
return null;
}
/**
* Returns the number of key/value pairs in this {@code Hashtable}.
*
* @return the number of key/value pairs in this {@code Hashtable}.
* @see #elements
* @see #keys
* @since Android 1.0
*/
@Override
public synchronized int size() {
return elementCount;
}
/**
* Returns the string representation of this {@code Hashtable}.
*
* @return the string representation of this {@code Hashtable}.
* @since Android 1.0
*/
@Override
public synchronized String toString() {
if (isEmpty()) {
return "{}"; //$NON-NLS-1$
}
StringBuilder buffer = new StringBuilder(size() * 28);
buffer.append('{');
for (int i = lastSlot; i >= firstSlot; i--) {
Entry<K, V> entry = elementData[i];
while (entry != null) {
if (entry.key != this) {
buffer.append(entry.key);
} else {
// luni.04=this Map
buffer.append("(" + Messages.getString("luni.04") + ")"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
}
buffer.append('=');
if (entry.value != this) {
buffer.append(entry.value);
} else {
// luni.04=this Map
buffer.append("(" + Messages.getString("luni.04") + ")"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
}
buffer.append(", "); //$NON-NLS-1$
entry = entry.next;
}
}
// Remove the last ", "
if (elementCount > 0) {
buffer.setLength(buffer.length() - 2);
}
buffer.append('}');
return buffer.toString();
}
/**
* Returns a collection of the values contained in this {@code Hashtable}.
* The collection is backed by this {@code Hashtable} so changes to one are
* reflected by the other. The collection does not support adding.
*
* @return a collection of the values.
* @since Android 1.0
*/
public Collection<V> values() {
return new Collections.SynchronizedCollection<V>(
new AbstractCollection<V>() {
@Override
public boolean contains(Object object) {
synchronized (Hashtable.this) {
return Hashtable.this.contains(object);
}
}
@Override
public int size() {
synchronized (Hashtable.this) {
return elementCount;
}
}
@Override
public void clear() {
Hashtable.this.clear();
}
@Override
public Iterator<V> iterator() {
return new HashIterator<V>(
new MapEntry.Type<V, K, V>() {
public V get(MapEntry<K, V> entry) {
return entry.value;
}
});
}
}, this);
}
private synchronized void writeObject(ObjectOutputStream stream)
throws IOException {
stream.defaultWriteObject();
stream.writeInt(elementData.length);
stream.writeInt(elementCount);
for (int i = elementData.length; --i >= 0;) {
Entry<K, V> entry = elementData[i];
while (entry != null) {
stream.writeObject(entry.key);
stream.writeObject(entry.value);
entry = entry.next;
}
}
}
@SuppressWarnings("unchecked")
private void readObject(ObjectInputStream stream) throws IOException,
ClassNotFoundException {
stream.defaultReadObject();
int length = stream.readInt();
elementData = newElementArray(length);
elementCount = stream.readInt();
for (int i = elementCount; --i >= 0;) {
Object key = stream.readObject();
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % length;
if (index < firstSlot) {
firstSlot = index;
}
if (index > lastSlot) {
lastSlot = index;
}
Entry<K, V> entry = newEntry((K) key, (V) stream.readObject(), hash);
entry.next = elementData[index];
elementData[index] = entry;
}
}
}