gw.util.SpaceEfficientHashMap Maven / Gradle / Ivy
/*
* Copyright 2014 Guidewire Software, Inc.
*/
package gw.util;
import java.util.Map;
import java.util.Set;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.AbstractMap;
@SuppressWarnings({"unchecked"})
public class SpaceEfficientHashMap implements Map {
private static final Object EMPTY_KEY = new Object();
private Object _keys = EMPTY_KEY;
private Object _values;
private static final int MAX_LIST_SIZE = 16;
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
transient volatile int modCount;
public SpaceEfficientHashMap() {
}
/**
* Constructs a new HashMap with the same mappings as the
* specified Map. The HashMap is created with
* default load factor (0.75) and an initial capacity sufficient to
* hold the mappings in the specified Map.
*
* @param m the map whose mappings are to be placed in this map
* @throws NullPointerException if the specified map is null
*/
public SpaceEfficientHashMap(Map extends K, ? extends V> m) {
putAll(m);
}
@Override
public int size() {
if (isObject()) {
return _keys == EMPTY_KEY ? 0 : 1;
} else if (isList()) {
return ((List) _keys).size();
}
return ((Map) _keys).size();
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public boolean containsKey(Object key) {
if (isMap()) {
return ((Map) _keys).containsKey(key);
} else if (isList()) {
List keyList = (List) _keys;
return keyList.contains(key);
} else {
return GosuObjectUtil.equals(_keys, key);
}
}
@Override
public boolean containsValue(Object value) {
if (isMap()) {
return ((Map) _keys).containsValue(value);
} else if (isList()) {
List valueList = (List) _values;
return valueList.contains(value);
} else {
return _keys != EMPTY_KEY && GosuObjectUtil.equals(_values, value);
}
}
@Override
public V get(Object key) {
if (isMap()) {
return (V) ((Map) _keys).get(key);
} else if (isList()) {
List keyList = (List) _keys;
for (int i = 0; i < keyList.size(); i++) {
Object o = keyList.get(i);
if (GosuObjectUtil.equals(o, key)) {
return (V) ((List) _values).get(i);
}
}
return null;
} else {
return _keys != EMPTY_KEY && GosuObjectUtil.equals(_keys, key) ? (V) _values : null;
}
}
@Override
public V put(K key, V value) {
if (isMap()) {
return (V) ((Map) _keys).put(key, value);
} else if (isList()) {
ArrayList keyList = (ArrayList) _keys;
ArrayList valueList = (ArrayList) _values;
for (int i = 0; i < keyList.size(); i++) {
Object o = keyList.get(i);
if (GosuObjectUtil.equals(o, key)) {
V oldValue = (V) valueList.get(i);
valueList.set(i, value);
return oldValue;
}
}
if (keyList.size() + 1 > MAX_LIST_SIZE) {
Map map = new HashMap(keyList.size() + 1);
for (int i = 0; i < keyList.size(); i++) {
map.put(keyList.get(i), valueList.get(i));
}
map.put(key, value);
_keys = map;
_values = null;
} else {
keyList.add(key);
valueList.add(value);
}
return null;
} else {
if (GosuObjectUtil.equals(_keys, key)) {
V oldValue = (V) _values;
_values = value;
return oldValue;
} else if (_keys == EMPTY_KEY) {
_keys = key;
_values = value;
return null;
} else {
List keyList = new ArrayList(2);
keyList.add(_keys);
keyList.add(key);
List valueList = new ArrayList(2);
valueList.add(_values);
valueList.add(value);
_keys = keyList;
_values = valueList;
return null;
}
}
}
@Override
public V remove(Object key) {
if (isMap()) {
return (V) ((Map) _keys).remove(key);
} else if (isList()) {
ArrayList keyList = (ArrayList) _keys;
ArrayList valueList = (ArrayList) _values;
for (int i = 0; i < keyList.size(); i++) {
Object o = keyList.get(i);
if (GosuObjectUtil.equals(o, key)) {
V oldValue = (V) valueList.get(i);
// The next two lines are expensive...
keyList.remove(i);
valueList.remove(i);
return oldValue;
}
}
return null;
} else {
if (GosuObjectUtil.equals(_keys, key)) {
V oldValue = (V) _values;
_values = null;
_keys = EMPTY_KEY;
return oldValue;
} else {
return null;
}
}
}
@Override
public void putAll(Map extends K, ? extends V> m) {
if (m.isEmpty()) {
return;
}
if (isMap()) {
((Map) _keys).putAll(m);
} else {
if (m.size() < 10) {
for (Entry extends K, ? extends V> entry : m.entrySet()) {
put(entry.getKey(), entry.getValue());
}
} else {
Map map = new HashMap();
if (isList()) {
ArrayList keyList = (ArrayList) _keys;
ArrayList valueList = (ArrayList) _values;
for (int i = 0; i < keyList.size(); i++) {
map.put(keyList.get(i), valueList.get(i));
}
} else if (_keys != EMPTY_KEY) {
map.put(_keys, _values);
}
map.putAll(m);
_keys = map;
_values = null;
trimToSize();
}
}
}
@Override
public void clear() {
_keys = EMPTY_KEY;
_values = null;
}
@Override
public Set keySet() {
if (isMap()) {
return ((Map) _keys).keySet();
} else {
return new MyListSet(true);
}
}
@Override
public Collection values() {
if (isMap()) {
return ((Map) _keys).values();
} else {
return new MyListSet(false);
}
}
@Override
public Set> entrySet() {
if (isMap()) {
return ((Map) _keys).entrySet();
} else {
return new MyEntrySet();
}
}
/**
* Compares the specified object with this map for equality. Returns
* true if the given object is also a map and the two maps
* represent the same mappings. More formally, two maps m1 and
* m2 represent the same mappings if
* m1.entrySet().equals(m2.entrySet()). This ensures that the
* equals method works properly across different implementations
* of the Map interface.
*
* This implementation first checks if the specified object is this map;
* if so it returns true. Then, it checks if the specified
* object is a map whose size is identical to the size of this map; if
* not, it returns false. If so, it iterates over this map's
* entrySet collection, and checks that the specified map
* contains each mapping that this map contains. If the specified map
* fails to contain such a mapping, false is returned. If the
* iteration completes, true is returned.
*
* @param o object to be compared for equality with this map
* @return true if the specified object is equal to this map
*/
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Map)) {
return false;
}
Map m = (Map) o;
if (m.size() != size()) {
return false;
}
try {
Iterator> i = entrySet().iterator();
while (i.hasNext()) {
Entry e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key) == null && m.containsKey(key))) {
return false;
}
} else {
if (!value.equals(m.get(key))) {
return false;
}
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
/**
* Returns the hash code value for this map. The hash code of a map is
* defined to be the sum of the hash codes of each entry in the map's
* entrySet() view. This ensures that m1.equals(m2)
* implies that m1.hashCode()==m2.hashCode() for any two maps
* m1 and m2, as required by the general contract of
* {@link Object#hashCode}.
*
* This implementation iterates over entrySet(), calling
* {@link Map.Entry#hashCode hashCode()} on each element (entry) in the
* set, and adding up the results.
*
* @return the hash code value for this map
* @see Map.Entry#hashCode()
* @see Object#equals(Object)
* @see Set#equals(Object)
*/
public int hashCode() {
int h = 0;
Iterator> i = entrySet().iterator();
while (i.hasNext()) {
h += i.next().hashCode();
}
return h;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("{");
int i = 0;
for (Entry entry : entrySet()) {
if (i > 0) {
buf.append(", ");
}
buf.append(entry.getKey()).append("=").append(entry.getValue());
i++;
}
buf.append("}");
return buf.toString();
}
private boolean isObject() {
return !isList() && !isMap();
}
private boolean isList() {
return _keys instanceof List;
}
private boolean isMap() {
return _keys instanceof Map;
}
public void trimToSize() {
if (isList()) {
ArrayList keyList = (ArrayList) _keys;
ArrayList valueList = (ArrayList) _values;
if (keyList.size() == 0) {
_keys = EMPTY_KEY;
_values = null;
} else if (keyList.size() == 1) {
_keys = keyList.get(0);
_values = valueList.get(0);
} else {
keyList.trimToSize();
valueList.trimToSize();
}
} else if (isMap()) {
Map map = (Map) _keys;
if (map.size() == 0) {
// compact to object
_keys = EMPTY_KEY;
_values = null;
} else if (map.size() == 1) {
Map.Entry entry = (Entry) map.entrySet().iterator().next();
_keys = entry.getKey();
_values = entry.getValue();
} else if (map.size() <= MAX_LIST_SIZE) {
ArrayList keyList = new ArrayList(map.size());
ArrayList valueList = new ArrayList(map.size());
for (Object o : map.entrySet()) {
Map.Entry entry = (Entry) o;
keyList.add(entry.getKey());
valueList.add(entry.getValue());
}
keyList.trimToSize();
valueList.trimToSize();
_keys = keyList;
_values = valueList;
} else {
// can't compact a map. Too bad, so sad. Or we could reallocate the map...
_keys = new HashMap(map);
}
}
}
private final class MyListSet extends AbstractSet {
boolean _returnKeySet;
MyListSet(boolean returnKeySet) {
_returnKeySet = returnKeySet;
}
public Iterator iterator() {
if (isList()) {
return new Iterator() {
private Iterator _kit = ((List) _keys).iterator();
private Iterator _vit = ((List) _values).iterator();
@Override
public boolean hasNext() {
return _kit.hasNext();
}
@Override
public T next() {
Object key = _kit.next();
Object value = _vit.next();
return (T) (_returnKeySet ? key : value);
}
@Override
public void remove() {
_kit.remove();
_vit.remove();
}
};
}
if (_keys == EMPTY_KEY && _values == null) {
return new Iterator() {
@Override
public boolean hasNext() {
return false;
}
@Override
public T next() {
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new NoSuchElementException();
}
};
} else {
return new Iterator() {
boolean _onFirst = true;
@Override
public boolean hasNext() {
return _onFirst;
}
@Override
public T next() {
if (!_onFirst) {
throw new NoSuchElementException();
}
_onFirst = false;
if (_returnKeySet) {
return (T) _keys;
} else {
return (T) _values;
}
}
@Override
public void remove() {
if (_onFirst) {
throw new IllegalStateException();
}
_keys = EMPTY_KEY;
_values = null;
}
};
}
}
public int size() {
return isList() ? ((List) _keys).size() : (_keys == EMPTY_KEY) ? 0 : 1;
}
public boolean contains(Object o) {
if (_returnKeySet) {
return containsKey(o);
} else {
return containsValue(o);
}
}
public boolean remove(Object o) {
if (_returnKeySet) {
return SpaceEfficientHashMap.this.remove(o) != null;
} else {
Iterator e = iterator();
if (o == null) {
while (e.hasNext()) {
if (e.next() == null) {
e.remove();
return true;
}
}
} else {
while (e.hasNext()) {
if (o.equals(e.next())) {
e.remove();
return true;
}
}
}
return false;
}
}
public void clear() {
SpaceEfficientHashMap.this.clear();
}
}
private final class MyEntrySet extends AbstractSet> {
MyEntrySet() {
}
public Iterator> iterator() {
if (isList()) {
return new Iterator>() {
private Iterator _kit = ((List) _keys).iterator();
private Iterator _vit = ((List) _values).iterator();
@Override
public boolean hasNext() {
return _kit.hasNext();
}
@Override
public Entry next() {
Object key = _kit.next();
Object value = _vit.next();
return new AbstractMap.SimpleEntry((S) key, (T) value);
}
@Override
public void remove() {
_kit.remove();
_vit.remove();
}
};
}
if (_keys == EMPTY_KEY) {
return new Iterator>() {
@Override
public boolean hasNext() {
return false;
}
@Override
public Entry next() {
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new NoSuchElementException();
}
};
} else {
return new Iterator>() {
boolean _onFirst = true;
@Override
public boolean hasNext() {
return _onFirst;
}
@Override
public Entry next() {
if (!_onFirst) {
throw new NoSuchElementException();
}
_onFirst = false;
return new AbstractMap.SimpleEntry((S) _keys, (T) _values);
}
@Override
public void remove() {
_keys = null;
_values = null;
}
};
}
}
public int size() {
return isList() ? ((List) _keys).size() : (_keys == EMPTY_KEY) ? 0 : 1;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return SpaceEfficientHashMap.this.remove(o) != null;
}
public void clear() {
SpaceEfficientHashMap.this.clear();
}
}
}