Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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 org.apache.openjpa.lib.util.concurrent;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.Set;
import org.apache.openjpa.lib.util.ReferenceMap;
import org.apache.openjpa.lib.util.SizedMap;
/**
* This class implements a HashMap which has limited synchronization
* and reference keys or values(but not both). In particular mutators are
* generally synchronized while accessors are generally not. Additionally the
* Iterators returned by this class are not "fail-fast", but instead try to
* continue to iterate over the data structure after changes have been
* made. Finally purging of the reference queue is only done inside mutators.
* Null keys are not supported if keys use references. Null values are not
* supported if values use references.
* This class is based heavily on the WeakHashMap class in the Java
* collections package.
*/
public class ConcurrentReferenceHashMap extends AbstractMap
implements ConcurrentMap, ReferenceMap, SizedMap, Cloneable {
/**
* Cache of random numbers used in "random" methods, since generating them
* is expensive. We hope each map changes enough between cycling through
* this list that the overall effect is random enough.
*/
static final double[] RANDOMS = new double[1000];
static {
Random random = new Random();
for (int i = 0; i < RANDOMS.length; i++)
RANDOMS[i] = random.nextDouble();
}
/**
* The hash table data.
*/
private transient 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 HashMap.
*/
private float loadFactor;
/**
* The key reference type.
*/
private int keyType;
/**
* The value reference type.
*/
private int valueType;
/**
* Reference queue for cleared Entries
*/
private final ReferenceQueue queue = new ReferenceQueue();
/**
* Spread "random" removes and iteration.
*/
private int randomEntry = 0;
/**
* Maximum entries.
*/
private int maxSize = Integer.MAX_VALUE;
/**
* Compare two objects. These might be keys, values, or Entry instances.
* This implementation uses a normal null-safe object equality algorithm.
*
* @since 1.0.0
*/
protected boolean eq(Object x, Object y) {
return x == y || (x != null && x.equals(y));
}
/**
* Obtain the hashcode of an object. The object might be a key, a value,
* or an Entry. This implementation just delegates to
* {@link Object#hashCode}
*
* @since 1.0.0
*/
protected int hc(Object o) {
return o == null ? 0 : o.hashCode();
}
/**
* Constructs a new, empty HashMap with the specified initial
* capacity and the specified load factor.
*
* @param keyType the reference type of map keys
* @param valueType the reference type of map values
* @param initialCapacity the initial capacity of the HashMap.
* @param loadFactor a number between 0.0 and 1.0.
* @throws IllegalArgumentException if neither keys nor values use hard
* references, if the initial capacity is less than or equal to zero, or if
* the load factor is less than or equal to zero
*/
public ConcurrentReferenceHashMap(int keyType, int valueType,
int initialCapacity, float loadFactor) {
if (initialCapacity < 0) {
throw new IllegalArgumentException("Illegal Initial Capacity: " +
initialCapacity);
}
if ((loadFactor > 1) || (loadFactor <= 0)) {
throw new IllegalArgumentException("Illegal Load factor: " +
loadFactor);
}
if (keyType != HARD && valueType != HARD) {
throw new IllegalArgumentException("Either keys or values must " +
"use hard references.");
}
this.keyType = keyType;
this.valueType = valueType;
this.loadFactor = loadFactor;
table = new Entry[initialCapacity];
threshold = (int) (initialCapacity * loadFactor);
}
/**
* Constructs a new, empty HashMap with the specified initial capacity
* and default load factor.
*
* @param keyType the reference type of map keys
* @param valueType the reference type of map values
* @param initialCapacity the initial capacity of the HashMap.
*/
public ConcurrentReferenceHashMap(int keyType, int valueType,
int initialCapacity) {
this(keyType, valueType, initialCapacity, 0.75f);
}
/**
* Constructs a new, empty HashMap with a default capacity and load factor.
*
* @param keyType the reference type of map keys
* @param valueType the reference type of map values
*/
public ConcurrentReferenceHashMap(int keyType, int valueType) {
this(keyType, valueType, 11, 0.75f);
}
/**
* Constructs a new HashMap with the same mappings as the given
* Map. The HashMap is created with a capacity of thrice the number
* of entries in the given Map or 11 (whichever is greater), and a
* default load factor.
*
* @param keyType the reference type of map keys
* @param valueType the reference type of map values
*/
public ConcurrentReferenceHashMap(int keyType, int valueType, Map t) {
this(keyType, valueType, Math.max(3 * t.size(), 11), 0.75f);
putAll(t);
}
public int getMaxSize() {
return maxSize;
}
public void setMaxSize(int maxSize) {
this.maxSize = (maxSize < 0) ? Integer.MAX_VALUE : maxSize;
if (this.maxSize != Integer.MAX_VALUE)
removeOverflow(this.maxSize);
}
public boolean isFull() {
return maxSize != Integer.MAX_VALUE && size() >= maxSize;
}
public void overflowRemoved(Object key, Object value) {
}
/**
* Returns the number of key-value mappings in this Map. This
* result is a snapshot, and may not reflect unprocessed entries
* that will be removed before next attempted access because they
* are no longer referenced.
*/
public int size() {
return count;
}
/**
* Returns true if this Map contains no key-value mappings. This
* result is a snapshot, and may not reflect unprocessed entries
* that will be removed before next attempted access because they
* are no longer referenced.
*/
public boolean isEmpty() {
return count == 0;
}
/**
* Returns true if this HashMap maps one or more keys to the specified
* value.
*
* @param value value whose presence in this Map is to be tested.
*/
public boolean containsValue(Object value) {
Entry[] tab = table;
if (value == null) {
if (valueType != HARD)
return false;
for (int i = tab.length; i-- > 0;)
for (Entry e = tab[i]; e != null; e = e.getNext())
if (e.getValue() == null)
return true;
} else {
for (int i = tab.length; i-- > 0;)
for (Entry e = tab[i]; e != null; e = e.getNext())
if (eq(value, e.getValue()))
return true;
}
return false;
}
/**
* Returns true if this HashMap contains a mapping for the specified key.
*
* @param key key whose presence in this Map is to be tested.
*/
public boolean containsKey(Object key) {
if (key == null && keyType != HARD)
return false;
Entry[] tab = table;
int hash = hc(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index]; e != null; e = e.getNext())
if (e.getHash() == hash && eq(key, e.getKey()))
return true;
return false;
}
/**
* Returns the value to which this HashMap maps the specified key.
* Returns null if the HashMap contains no mapping for this key.
*
* @param key key whose associated value is to be returned.
*/
public Object get(Object key) {
if (key == null && keyType != HARD)
return null;
Entry[] tab = table;
int hash = hc(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index]; e != null; e = e.getNext())
if ((e.getHash() == hash) && eq(key, e.getKey()))
return e.getValue();
return null;
}
/**
* Rehashes the contents of the HashMap into a HashMap with a
* larger capacity. This method is called automatically when the
* number of keys in the HashMap exceeds this HashMap's capacity
* and load factor.
*/
private void rehash() {
int oldCapacity = table.length;
Entry oldMap[] = table;
int newCapacity = oldCapacity * 2 + 1;
Entry newMap[] = new Entry[newCapacity];
for (int i = oldCapacity; i-- > 0;) {
for (Entry old = oldMap[i]; old != null;) {
if ((keyType != HARD && old.getKey() == null)
|| valueType != HARD && old.getValue() == null) {
Entry e = old;
old = old.getNext();
e.setNext(null);
count--;
} else {
Entry e = (Entry) old.clone(queue);
old = old.getNext();
int index = (e.getHash() & 0x7FFFFFFF) % newCapacity;
e.setNext(newMap[index]);
newMap[index] = e;
}
}
}
threshold = (int) (newCapacity * loadFactor);
table = newMap;
}
/**
* Associates the specified value with the specified key in this HashMap.
* If the HashMap previously contained a mapping for this key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated.
* @param value value to be associated with the specified key.
* @return previous value associated with specified key, or null if there
* was no mapping for key. A null return can also indicate that
* the HashMap previously associated null with the specified key.
*/
public Object put(Object key, Object value) {
if ((key == null && keyType != HARD)
|| (value == null && valueType != HARD))
throw new IllegalArgumentException("Null references not supported");
int hash = hc(key);
synchronized (this) {
expungeStaleEntries();
Entry[] tab = table;
int index = 0;
index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index], prev = null; e != null; prev = e,
e = e.getNext()) {
if ((e.getHash() == hash) && eq(key, e.getKey())) {
Object old = e.getValue();
if (valueType == HARD)
e.setValue(value);
else {
e = newEntry(hash, e.getKey(), value, e.getNext());
if (prev == null)
tab[index] = e;
else
prev.setNext(e);
}
return old;
}
}
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
index = (hash & 0x7FFFFFFF) % tab.length;
}
if (maxSize != Integer.MAX_VALUE)
removeOverflow(maxSize - 1);
tab[index] = newEntry(hash, key, value, tab[index]);
count++;
}
return null;
}
/**
* Creates a new entry.
*/
private Entry newEntry(int hash, Object key, Object value, Entry next) {
int refType = (keyType != HARD) ? keyType : valueType;
switch (refType) {
case WEAK:
return new WeakEntry(hash, key, value, refType == keyType, next,
queue);
case SOFT:
return new SoftEntry(hash, key, value, refType == keyType, next,
queue);
default:
return new HardEntry(hash, key, value, next);
}
}
/**
* Remove any entries equal to or over the max size.
*/
private void removeOverflow(int maxSize) {
while (count > maxSize) {
Map.Entry entry = removeRandom();
if (entry == null)
break;
overflowRemoved(entry.getKey(), entry.getValue());
}
}
/**
* Removes the mapping for this key from this HashMap if present.
*
* @param key key whose mapping is to be removed from the Map.
* @return previous value associated with specified key, or null if there
* was no mapping for key. A null return can also indicate that
* the HashMap previously associated null with the specified key.
*/
public Object remove(Object key) {
if (key == null && keyType != HARD)
return null;
int hash = hc(key);
synchronized (this) {
expungeStaleEntries();
Entry[] tab = table;
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index], prev = null; e != null;
prev = e, e = e.getNext()) {
if ((e.getHash() == hash) && eq(key, e.getKey())) {
if (prev != null)
prev.setNext(e.getNext());
// otherwise put the bucket after us
else
tab[index] = e.getNext();
count--;
return e.getValue();
}
}
}
return null;
}
public void removeExpired() {
synchronized (this) {
expungeStaleEntries();
}
}
public void keyExpired(Object value) {
}
public void valueExpired(Object key) {
}
/**
* Return an arbitrary entry index.
*/
private int randomEntryIndex() {
if (randomEntry == RANDOMS.length)
randomEntry = 0;
return (int) (RANDOMS[randomEntry++] * table.length);
}
public Map.Entry removeRandom() {
synchronized (this) {
expungeStaleEntries();
if (count == 0)
return null;
int random = randomEntryIndex();
int index = findEntry(random, random % 2 == 0, false);
if (index == -1)
return null;
Entry rem = table[index];
table[index] = rem.getNext();
count--;
return rem;
}
}
/**
* Find the index of the entry nearest the given index, starting in the
* given direction.
*/
private int findEntry(int start, boolean forward, boolean searchedOther) {
if (forward) {
for (int i = start; i < table.length; i++)
if (table[i] != null)
return i;
return (searchedOther || start == 0) ? -1
: findEntry(start - 1, false, true);
} else {
for (int i = start; i >= 0; i--)
if (table[i] != null)
return i;
return (searchedOther || start == table.length - 1) ? -1
: findEntry(start + 1, true, true);
}
}
public Iterator randomEntryIterator() {
// pass index so calculated before iterator refs table, in case table
// gets replace with a larger one
return new HashIterator(ENTRIES, randomEntryIndex());
}
/**
* Copies all of the mappings from the specified Map to this HashMap
* These mappings will replace any mappings that this HashMap had for any
* of the keys currently in the specified Map.
*
* @param t Mappings to be stored in this Map.
*/
public void putAll(Map t) {
Iterator i = t.entrySet().iterator();
while (i.hasNext()) {
Map.Entry e = (Map.Entry) i.next();
put(e.getKey(), e.getValue());
}
}
/**
* Removes all mappings from this HashMap.
*/
public synchronized void clear() {
// clear out ref queue. We don't need to expunge entries
// since table is getting cleared.
while (queue.poll() != null)
;
table = new Entry[table.length];
count = 0;
// Allocation of array may have caused GC, which may have caused
// additional entries to go stale. Removing these entries from
// the reference queue will make them eligible for reclamation.
while (queue.poll() != null)
;
}
/**
* Returns a shallow copy of this HashMap. The keys and values
* themselves are not cloned.
*/
public synchronized Object clone() {
try {
expungeStaleEntries();
ConcurrentReferenceHashMap t = (ConcurrentReferenceHashMap)
super.clone();
t.table = new Entry[table.length];
for (int i = table.length; i-- > 0;) {
Entry e = table[i];
if (e != null) {
t.table[i] = (Entry) e.clone(t.queue);
e = e.getNext();
for (Entry k = t.table[i]; e != null; e = e.getNext()) {
k.setNext((Entry) e.clone(t.queue));
k = k.getNext();
}
}
}
t.keySet = null;
t.entrySet = null;
t.values = null;
return t;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
// Views
private transient Set keySet = null;
private transient Set entrySet = null;
private transient Collection values = null;
/**
* Returns a Set view of the keys contained in this HashMap. The Set is
* backed by the HashMap, so changes to the HashMap are reflected in the
* Set, and vice-versa. The Set supports element removal, which removes
* the corresponding mapping from the HashMap, via the Iterator.remove,
* Set.remove, removeAll retainAll, and clear operations. It does not
* support the add or addAll operations.
*/
public Set keySet() {
if (keySet == null) {
keySet = new java.util.AbstractSet() {
public Iterator iterator() {
return new HashIterator(KEYS, table.length - 1);
}
public int size() {
return count;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return ConcurrentReferenceHashMap.this.remove(o) != null;
}
public void clear() {
ConcurrentReferenceHashMap.this.clear();
}
};
}
return keySet;
}
/**
* Returns a Collection view of the values contained in this HashMap.
* The Collection is backed by the HashMap, so changes to the HashMap are
* reflected in the Collection, and vice-versa. The Collection supports
* element removal, which removes the corresponding mapping from the
* HashMap, via the Iterator.remove, Collection.remove, removeAll,
* retainAll and clear operations. It does not support the add or addAll
* operations.
*/
public Collection values() {
if (values == null) {
values = new java.util.AbstractCollection() {
public Iterator iterator() {
return new HashIterator(VALUES, table.length - 1);
}
public int size() {
return count;
}
public boolean contains(Object o) {
return containsValue(o);
}
public void clear() {
ConcurrentReferenceHashMap.this.clear();
}
};
}
return values;
}
/**
* Returns a Collection view of the mappings contained in this HashMap.
* Each element in the returned collection is a Map.Entry. The Collection
* is backed by the HashMap, so changes to the HashMap are reflected in the
* Collection, and vice-versa. The Collection supports element removal,
* which removes the corresponding mapping from the HashMap, via the
* Iterator.remove, Collection.remove, removeAll, retainAll and clear
* operations. It does not support the add or addAll operations.
*
* @see Map.Entry
*/
public Set entrySet() {
if (entrySet == null) {
entrySet = new java.util.AbstractSet() {
public Iterator iterator() {
return new HashIterator(ENTRIES, table.length - 1);
}
public boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry entry = (Map.Entry) o;
Object key = entry.getKey();
Entry[] tab = table;
int hash = hc(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index]; e != null; e = e.getNext())
if (e.getHash() == hash && eq(e, entry))
return true;
return false;
}
public boolean remove(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry entry = (Map.Entry) o;
Object key = entry.getKey();
synchronized (ConcurrentReferenceHashMap.this) {
Entry[] tab = table;
int hash = hc(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index], prev = null; e != null;
prev = e, e = e.getNext()) {
if (e.getHash() == hash && eq(e, entry)) {
if (prev != null)
prev.setNext(e.getNext());
else
tab[index] = e.getNext();
count--;
return true;
}
}
return false;
}
}
public int size() {
return count;
}
public void clear() {
ConcurrentReferenceHashMap.this.clear();
}
};
}
return entrySet;
}
/**
* Expunge stale entries from the table.
*/
private void expungeStaleEntries() {
Object r;
while ((r = queue.poll()) != null) {
Entry entry = (Entry) r;
int hash = entry.getHash();
Entry[] tab = table;
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index], prev = null; e != null;
prev = e, e = e.getNext()) {
if (e == entry) {
if (prev != null)
prev.setNext(e.getNext());
// otherwise put the bucket after us
else
tab[index] = e.getNext();
count--;
if (keyType == HARD)
valueExpired(e.getKey());
else
keyExpired(e.getValue());
}
}
}
}
/**
* HashMap collision list entry.
*/
private static interface Entry extends Map.Entry {
public int getHash();
public Entry getNext();
public void setNext(Entry next);
public Object clone(ReferenceQueue queue);
}
/**
* Hard entry.
*/
private class HardEntry implements Entry {
private int hash;
private Object key;
private Object value;
private Entry next;
HardEntry(int hash, Object key, Object value, Entry next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public int getHash() {
return hash;
}
public Entry getNext() {
return next;
}
public void setNext(Entry next) {
this.next = next;
}
public Object clone(ReferenceQueue queue) {
// It is the callers responsibility to set the next field
// correctly.
return new HardEntry(hash, key, value, null);
}
// Map.Entry Ops
public Object getKey() {
return key;
}
public Object getValue() {
return value;
}
public Object setValue(Object value) {
Object oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry)) return false;
Map.Entry e = (Map.Entry) o;
Object k1 = key;
Object k2 = e.getKey();
return (k1 == null ? k2 == null : eq(k1, k2)) &&
(value == null ? e.getValue() == null
: eq(value, e.getValue()));
}
public int hashCode() {
return hash ^ (value == null ? 0 : value.hashCode());
}
public String toString() {
return key + "=" + value.toString();
}
}
/**
* Weak entry.
*/
private class WeakEntry extends WeakReference implements Entry {
private int hash;
private Object hard;
private boolean keyRef;
private Entry next;
WeakEntry(int hash, Object key, Object value, boolean keyRef,
Entry next, ReferenceQueue queue) {
super((keyRef) ? key : value, queue);
this.hash = hash;
this.hard = (keyRef) ? value : key;
this.keyRef = keyRef;
this.next = next;
}
public int getHash() {
return hash;
}
public Entry getNext() {
return next;
}
public void setNext(Entry next) {
this.next = next;
}
public Object clone(ReferenceQueue queue) {
// It is the callers responsibility to set the next field
// correctly.
return new WeakEntry(hash, getKey(), getValue(), keyRef, null,
queue);
}
// Map.Entry Ops
public Object getKey() {
return (keyRef) ? super.get() : hard;
}
public Object getValue() {
return (keyRef) ? hard : super.get();
}
public Object setValue(Object value) {
if (!keyRef)
throw new Error("Attempt to reset reference value.");
Object oldValue = hard;
hard = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry)) return false;
Map.Entry e = (Map.Entry) o;
return eq(getKey(), e.getKey()) && eq(getValue(), e.getValue());
}
public int hashCode() {
Object val = getValue();
return hash ^ (val == null ? 0 : val.hashCode());
}
public String toString() {
return getKey() + "=" + getValue();
}
}
/**
* Soft entry.
*/
private class SoftEntry extends SoftReference implements Entry {
private int hash;
private Object hard;
private boolean keyRef;
private Entry next;
SoftEntry(int hash, Object key, Object value, boolean keyRef,
Entry next, ReferenceQueue queue) {
super((keyRef) ? key : value, queue);
this.hash = hash;
this.hard = (keyRef) ? value : key;
this.keyRef = keyRef;
this.next = next;
}
public int getHash() {
return hash;
}
public Entry getNext() {
return next;
}
public void setNext(Entry next) {
this.next = next;
}
public Object clone(ReferenceQueue queue) {
// It is the callers responsibility to set the next field
// correctly.
return new SoftEntry(hash, getKey(), getValue(), keyRef, null,
queue);
}
// Map.Entry Ops
public Object getKey() {
return (keyRef) ? super.get() : hard;
}
public Object getValue() {
return (keyRef) ? hard : super.get();
}
public Object setValue(Object value) {
if (!keyRef)
throw new Error("Attempt to reset reference value.");
Object oldValue = hard;
hard = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry)) return false;
Map.Entry e = (Map.Entry) o;
return eq(getKey(), e.getKey()) && eq(getValue(), e.getValue());
}
public int hashCode() {
Object val = getValue();
return hash ^ (val == null ? 0 : val.hashCode());
}
public String toString() {
return getKey() + "=" + getValue();
}
}
// Types of Enumerations/Iterations
private static final int KEYS = 0;
private static final int VALUES = 1;
private static final int ENTRIES = 2;
/**
* Map iterator.
*/
private class HashIterator implements Iterator {
final Entry[] table = ConcurrentReferenceHashMap.this.table;
final int type;
int startIndex;
int stopIndex = 0;
int index;
Entry entry = null;
Entry lastReturned = null;
HashIterator(int type, int startIndex) {
this.type = type;
this.startIndex = startIndex;
index = startIndex;
}
public boolean hasNext() {
if (entry != null) {
return true;
}
while (index >= stopIndex) {
if ((entry = table[index--]) != null) {
return true;
}
}
if (stopIndex == 0) {
index = table.length - 1;
stopIndex = startIndex + 1;
while (index >= stopIndex) {
if ((entry = table[index--]) != null) {
return true;
}
}
}
return false;
}
public Object next() {
if (!hasNext())
throw new NoSuchElementException();
Entry e = lastReturned = entry;
entry = e.getNext();
return type == KEYS ? e.getKey()
: (type == VALUES ? e.getValue() : e);
}
public void remove() {
if (lastReturned == null)
throw new IllegalStateException();
synchronized (ConcurrentReferenceHashMap.this) {
Entry[] tab = ConcurrentReferenceHashMap.this.table;
int index = (lastReturned.getHash() & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index], prev = null; e != null;
prev = e, e = e.getNext()) {
if (e == lastReturned) {
if (prev == null)
tab[index] = e.getNext();
else
prev.setNext(e.getNext());
count--;
lastReturned = null;
return;
}
}
throw new Error("Iterated off table when doing remove");
}
}
}
int capacity() {
return table.length;
}
float loadFactor() {
return loadFactor;
}
}