package org.ovirt.engine.api.extensions;
import java.io.Serializable;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Type safe map.
* Keys are bundle of uuid and type, each value added is checked against key
* type.
*/
public class ExtMap implements ConcurrentMap<ExtKey, Object>, Cloneable, Serializable {
private static final long serialVersionUID = 4065309872012801647L;
/**
* Wrapped map.
* Wrapper and not inhertence per assumption of future
* exposure if interface changes.
*/
private ConcurrentMap<ExtKey, Object> map;
/*
* Object
*/
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ExtMap)) {
return false;
}
ExtMap other = (ExtMap) obj;
return Objects.equals(map, other.map);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return Objects.hashCode(map);
}
/**
* {@inheritDoc}
*/
@Override
public ExtMap clone() {
return new ExtMap(map);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
boolean first = true;
StringBuilder ret = new StringBuilder(1024);
ret.append("{");
for (Map.Entry<ExtKey, Object> entry : map.entrySet()) {
if (first) {
first = false;
} else {
ret.append(", ");
}
ret.append(entry.getKey());
ret.append("=");
if ((entry.getKey().getFlags() & ExtKey.Flags.SENSITIVE) != 0) {
ret.append("***");
} else if ((entry.getKey().getFlags() & ExtKey.Flags.SKIP_DUMP) != 0) {
ret.append("*skip*");
} else {
ret.append(entry.getValue());
}
}
ret.append("}");
return ret.toString();
}
/*
* Map Interface
*/
/**
* {@inheritDoc}
*/
@Override
public void clear() {
map.clear();
}
/**
* {@inheritDoc}
*/
@Override
public boolean containsKey(Object key) {
return map.containsKey(key);
}
/**
* {@inheritDoc}
*/
@Override
public boolean containsValue(Object value) {
return map.containsValue(value);
}
/**
* {@inheritDoc}
*/
@Override
public Set<Map.Entry<ExtKey, Object>> entrySet() {
return map.entrySet();
}
/**
* {@inheritDoc}
*/
@Override
public Object get(Object key) {
return map.get(key);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isEmpty() {
return map.isEmpty();
}
/**
* {@inheritDoc}
*/
@Override
public Set<ExtKey> keySet() {
return map.keySet();
}
/**
* {@inheritDoc}
*/
@Override
public Object put(ExtKey key, Object value) {
if (value == null) {
return map.remove(key);
}
else {
checkKeyValue(key, value);
return map.put(key, value);
}
}
/**
* {@inheritDoc}
*/
@Override
public void putAll(Map<? extends ExtKey, ? extends Object> m) {
for (Map.Entry<? extends ExtKey, ? extends Object> entry : m.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
/**
* {@inheritDoc}
*/
@Override
public Object remove(Object key) {
return map.remove(key);
}
/**
* {@inheritDoc}
*/
@Override
public int size() {
return map.size();
}
/**
* {@inheritDoc}
*/
@Override
public Collection<Object> values() {
return map.values();
}
/*
* ConcurrentMap interface
*/
/**
* {@inheritDoc}
*/
@Override
public Object putIfAbsent(ExtKey key, Object value) {
if (value == null) {
return null;
} else {
checkKeyValue(key, value);
return map.putIfAbsent(key, value);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean remove(Object key, Object value) {
return map.remove(key, value);
}
/**
* {@inheritDoc}
*/
@Override
public Object replace(ExtKey key, Object value) {
if (value == null) {
return map.remove(key);
} else {
checkKeyValue(key, value);
return map.replace(key, value);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean replace(ExtKey key, Object oldValue, Object newValue) {
if (newValue == null) {
return map.remove(key, oldValue);
} else {
checkKeyValue(key, newValue);
return map.replace(key, oldValue, newValue);
}
}
/*
* ExtMap
*/
/**
* Constructs an empty ExtMap with the specified initial capacity and load factor.
* @param initialCapacity the initial capacity.
* @param loadFactor the load factor.
* @throws IllegalArgumentException if the initial capacity is negative or the load factor is nonpositive.
*
*/
public ExtMap(int initialCapacity, float loadFactor) {
map = new ConcurrentHashMap<>(initialCapacity, loadFactor);
}
/**
* Constructs an empty ExtMap with the specified initial capacity and the default load factor (0.75).
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative or the load factor is nonpositive.
*/
public ExtMap(int initialCapacity) {
this(initialCapacity, (float)0.75);
}
/**
* Constructs an empty ExtMap with the default initial capacity (16) and the default load factor (0.75).
*/
public ExtMap() {
this(16);
}
/**
* Constructs a new ExtMap with the same mappings as the specified Map.
* The ExtMap 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 ExtMap(Map<ExtKey, Object> m) {
this(m.size());
map.putAll(m);
}
/**
* Multiple put.
* Usable for adding multiple entries:
* <pre>{@code
* ExtMap = new ExtMap().mput(key1, value1).mput(key2, value2);
* }</pre>
* @param key key with which the specified value is to be associated.
* @param value value to be associated with the specified key.
* @return this.
*/
public ExtMap mput(ExtKey key, Object value) {
put(key, value);
return this;
}
/**
* Multiple putAll.
* Usable for adding multiple entries:
* <pre>{@code
* ExtMap = new ExtMap().mputAll(map1).mputAll(map2);
* }</pre>
* @param m map to add.
* @return this.
*/
public ExtMap mput(Map<? extends ExtKey, ? extends Object> m) {
putAll(m);
return this;
}
/**
* Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.
* Safe version of get().
* <pre>{@code
* Integer i = <Integer> map.get(key1, Integer);
* }</pre>
* @param key key with which the specified value is to be associated.
* @param type expected type.
* @param defaultValue default value to return.
* @param <T> type of return and default value, inferred
* @return Value.
*/
public <T> T get(ExtKey key, Class<T> type, T defaultValue) {
if (!key.getType().isAssignableFrom(type)) {
throw new IllegalArgumentException(
String.format(
"Cannnot assign key '%s' into type '%s'",
key,
type
)
);
}
if (defaultValue != null && !key.getType().isAssignableFrom(defaultValue.getClass())) {
throw new IllegalArgumentException(
String.format(
"Cannnot assign default value of '%s' into type '%s'",
defaultValue.getClass(),
key
)
);
}
T value = type.cast(map.get(key));
if (value == null) {
value = defaultValue;
}
return value;
}
/**
* Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.
* Safe version of get().
* @param key key with which the specified value is to be associated.
* @param type expected type.
* @param <T> type of return value, inferred
* @return Value.
* @see #get(ExtKey key, Class type, Object defaultValue)
*/
public <T> T get(ExtKey key, Class<T> type) {
return get(key, type, null);
}
/**
* Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.
* Unsafe method of get with cast.
* <pre>{@code
* Integer i = <Integer> map.get(key1);
* }</pre>
* @param key key.
* @param <T> type of return value
* @return Value.
*/
@SuppressWarnings("unchecked")
public <T> T get(ExtKey key) {
return (T)map.get(key);
}
/**
* Returns the value to which the specified key is mapped, or default.
* Unsafe method of get with cast.
* <pre>{@code
* Integer i = <Integer> map.get(key1, 5);
* }</pre>
* @param key key.
* @param defaultValue default value.
* @param <T> type of return and default value, inferred
* @return Value.
*/
@SuppressWarnings("unchecked")
public <T> T get(ExtKey key, Object defaultValue) {
if (defaultValue != null && !key.getType().isAssignableFrom(defaultValue.getClass())) {
throw new IllegalArgumentException(
String.format(
"Cannnot assign default value of '%s' into type '%s'",
defaultValue.getClass(),
key
)
);
}
Object value = get(key);
if (value == null) {
value = defaultValue;
}
return (T)value;
}
/**
* Check if value matches key type.
*/
private void checkKeyValue(ExtKey key, Object value) {
if (!key.getType().isInstance(value)) {
throw new IllegalArgumentException(
String.format(
"Cannnot assign type '%s' into key '%s'",
value.getClass(),
key
)
);
}
}
}