/*
* 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.brooklyn.core.internal.storage.impl;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.AbstractMap;
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;
/**
* A decorator for a ConcurrentMap that allows null values to be used.
*
* 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 ConcurrentMapAcceptingNullVals<K, V> implements ConcurrentMap<K, V> {
private static enum Marker {
NULL;
}
private final ConcurrentMap<K, V> delegate;
public ConcurrentMapAcceptingNullVals(ConcurrentMap<K,V> delegate) {
this.delegate = checkNotNull(delegate, "delegate");
}
@Override
public void clear() {
delegate.clear();
}
@Override
public boolean containsKey(Object key) {
return delegate.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return delegate.containsValue(toNonNullValue(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>(entry.getKey(), (V)fromNonNullValue(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)fromNonNullValue(v));
}
return Collections.unmodifiableCollection(result);
}
@Override
public Set<K> keySet() {
return delegate.keySet();
}
@Override
public V get(Object key) {
return (V) fromNonNullValue(delegate.get(key));
}
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public V put(K key, V value) {
return (V) fromNonNullValue(delegate.put(key, (V) toNonNullValue(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) fromNonNullValue(delegate.remove(key));
}
@Override
public int size() {
return delegate.size();
}
@Override
public V putIfAbsent(K key, V value) {
return (V) fromNonNullValue(delegate.putIfAbsent(key, (V) toNonNullValue(value)));
}
@Override
public boolean remove(Object key, Object value) {
return delegate.remove(key, (V) toNonNullValue(value));
}
@Override
public V replace(K key, V value) {
return (V) fromNonNullValue(delegate.replace(key, (V) toNonNullValue(value)));
}
@Override
public boolean replace(K key, V oldValue, V newValue) {
return delegate.replace(key, (V) toNonNullValue(oldValue), (V) toNonNullValue(newValue));
}
private static class SetWithNullVals<T> implements Set<T> {
private final Set<T> delegate;
public SetWithNullVals(Set<T> delegate) {
this.delegate = delegate;
}
@Override
public boolean add(T e) {
return delegate.add(e); // unsupported; let delegate give exception
}
@Override
public boolean addAll(Collection<? extends T> c) {
return delegate.addAll(c); // unsupported; let delegate give exception
}
@Override
public void clear() {
delegate.clear();
}
@Override
public boolean contains(Object o) {
return delegate.contains(toNonNullValue(o));
}
@Override
public boolean containsAll(Collection<?> c) {
for (Object e : c) {
if (!delegate.contains(toNonNullValue(e))) return false;
}
return true;
}
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public Iterator<T> iterator() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean remove(Object o) {
return delegate.remove(toNonNullValue(o));
}
@Override
public boolean removeAll(Collection<?> c) {
boolean result = false;
for (Object e : c) {
result = result & delegate.remove(toNonNullValue(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(fromNonNullValue(e))) {
iter.remove();
result = true;
}
}
return result;
}
@Override
public int size() {
return delegate.size();
}
@Override
public Object[] toArray() {
// TODO Auto-generated method stub
return null;
}
@Override
public <T> T[] toArray(T[] a) {
// TODO Auto-generated method stub
return null;
}
}
private static Object toNonNullValue(Object value) {
return (value != null) ? value : Marker.NULL;
}
private static Object fromNonNullValue(Object value) {
return (value == Marker.NULL) ? null : value;
}
@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();
}
}