blob: 336f86ee119a6355818df00db1a00f46518c8ec8 [file] [log] [blame]
package brooklyn.internal.storage.impl;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nullable;
import brooklyn.internal.storage.Serializer;
/**
* A decorator for a ConcurrentMap that allows null keys and values to be used.
*
* It also accepts a serializer that transforms the underlying keys/values stored
* in the backing map.
*
* However, {@link #values()} and {@link #entrySet()} return immutable snapshots
* of the map's contents. This may be revisited in a future version.
*
* @author aled
*/
public class ConcurrentMapAcceptingNullVals2<K, V> implements ConcurrentMap<K, V> {
private static enum Marker {
NULL;
}
private final ConcurrentMap<K, V> delegate;
private final Serializer<Object,Object> serializer;
public ConcurrentMapAcceptingNullVals2(ConcurrentMap<K,V> delegate, Serializer<Object,Object> serializer) {
this.delegate = checkNotNull(delegate, "delegate");
this.serializer = checkNotNull(serializer, "serializer");
}
@Override
public void clear() {
delegate.clear();
}
@Override
public boolean containsKey(Object key) {
return delegate.containsKey(toKey(key));
}
@Override
public boolean containsValue(Object value) {
return delegate.containsValue(toValue(value));
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
// Note that returns an immutable snapshot
Set<Map.Entry<K, V>> result = new LinkedHashSet<Map.Entry<K, V>>(delegate.size());
for (Map.Entry<K, V> entry : delegate.entrySet()) {
result.add(new AbstractMap.SimpleEntry<K,V>((K)fromKey(entry.getKey()), (V)fromValue(entry.getValue())));
}
return Collections.unmodifiableSet(result);
}
@Override
public Collection<V> values() {
// Note that returns an immutable snapshot
List<V> result = new ArrayList<V>(delegate.size());
for (V v : delegate.values()) {
result.add((V)fromValue(v));
}
return Collections.unmodifiableCollection(result);
}
@Override
public Set<K> keySet() {
return new KeySet<K>(delegate.keySet());
}
@Override
public V get(Object key) {
return (V) fromValue(delegate.get(key));
}
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public V put(K key, V value) {
return (V) fromValue(delegate.put((K)toKey(key), (V) toValue(value)));
}
@Override
public void putAll(Map<? extends K, ? extends V> vals) {
for (Map.Entry<? extends K, ? extends V> entry : vals.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
@Override
public V remove(Object key) {
return (V) fromValue(delegate.remove(toKey(key)));
}
@Override
public int size() {
return delegate.size();
}
@Override
public V putIfAbsent(K key, V value) {
return (V) fromValue(delegate.putIfAbsent((K)toKey(key), (V) toValue(value)));
}
@Override
public boolean remove(Object key, Object value) {
return delegate.remove((K) toKey(key), (V) toValue(value));
}
@Override
public V replace(K key, V value) {
return (V) fromValue(delegate.replace((K) toKey(key), (V) toValue(value)));
}
@Override
public boolean replace(K key, V oldValue, V newValue) {
return delegate.replace((K) toKey(key), (V) toValue(oldValue), (V) toValue(newValue));
}
private class KeySet<T> extends AbstractSet<T> {
private final Set<T> delegate;
KeySet(Set<T> delegate) {
this.delegate = delegate;
}
@Override
public void clear() {
delegate.clear();
}
@Override
public boolean contains(Object o) {
return delegate.contains(toKey(o));
}
@Override
public boolean containsAll(Collection<?> c) {
for (Object e : c) {
if (!delegate.contains(toKey(e))) return false;
}
return true;
}
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public Iterator<T> iterator() {
// Note that returns an immutable snapshot
List<T> result = new ArrayList<T>(delegate.size());
for (T k : delegate) {
result.add((T)fromKey(k));
}
return Collections.unmodifiableList(result).iterator();
}
@Override
public boolean remove(Object o) {
return delegate.remove(toKey(o));
}
@Override
public boolean removeAll(Collection<?> c) {
boolean result = false;
for (Object e : c) {
result = result & delegate.remove(toKey(e));
}
return result;
}
@Override
public boolean retainAll(Collection<?> c) {
boolean result = false;
for (Iterator<T> iter = delegate.iterator(); iter.hasNext();) {
T e = iter.next();
if (!c.contains(fromKey(e))) {
iter.remove();
result = true;
}
}
return result;
}
@Override
public int size() {
return delegate.size();
}
@Override
public Object[] toArray() {
Object[] result = delegate.toArray();
for (int i = 0; i < result.length; i++) {
result[i] = fromKey(result[i]);
}
return result;
}
@Override
public <T> T[] toArray(T[] a) {
// TODO Not type-safe if serializer is making changes
T[] result = delegate.toArray(a);
for (int i = 0; i < result.length; i++) {
result[i] = (T) fromKey(result[i]);
}
return result;
}
}
@Override
public boolean equals(@Nullable Object object) {
// copied from guava's non-public method Maps.equalsImpl
if (this == object) {
return true;
}
if (object instanceof Map) {
Map<?, ?> o = (Map<?, ?>) object;
return entrySet().equals(o.entrySet());
}
return false;
}
@Override
public int hashCode() {
// copied from guava's ImmutableMap.hashCode
return entrySet().hashCode();
}
@Override
public String toString() {
return delegate.toString();
}
private Object toValue(Object value) {
Object v2 = serializer.serialize(value);
return ((v2 != null) ? v2 : Marker.NULL);
}
private Object fromValue(Object value) {
Object v2 = (value == Marker.NULL) ? null : value;
return serializer.deserialize(v2);
}
private Object toKey(Object value) {
Object v2 = serializer.serialize(value);
return ((v2 != null) ? v2 : Marker.NULL);
}
private Object fromKey(Object value) {
Object v2 = (value == Marker.NULL) ? null : value;
return serializer.deserialize(v2);
}
}