/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.util;
import java.util.Map;
import java.util.LinkedHashMap;
import org.opengis.util.Cloneable;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;
/**
* A {@linkplain Collections#checkedMap checked} and {@linkplain Collections#synchronizedMap
* synchronized} {@link java.util.Map}. Type checks are performed at run-time in addition of
* compile-time checks. The synchronization lock can be modified at runtime by overriding the
* {@link #getLock} method.
* <p>
* This class is similar to using the wrappers provided in {@link Collections}, minus the cost
* of indirection levels and with the addition of overrideable methods.
*
* @todo Current implementation do not synchronize the {@linkplain #entrySet entry set},
* {@linkplain #keySet key set} and {@linkplain #values values} collection.
*
* @param <K> The type of keys in the map.
* @param <V> The type of values in the map.
*
* @since 2.1
*
* @source $URL$
* @version $Id$
* @author Jody Garnett (Refractions Research)
* @author Martin Desruisseaux (IRD)
*
* @see Collections#checkedMap
* @see Collections#synchronizedMap
*/
public class CheckedHashMap<K,V> extends LinkedHashMap<K,V> implements Cloneable {
/**
* Serial version UID for compatibility with different versions.
*/
private static final long serialVersionUID = -7777695267921872849L;
/**
* The class type for keys.
*/
private final Class<K> keyType;
/**
* The class type for values.
*/
private final Class<V> valueType;
/**
* Constructs a map of the specified type.
*
* @param keyType The key type (should not be null).
* @param valueType The value type (should not be null).
*/
public CheckedHashMap(final Class<K> keyType, final Class<V> valueType) {
this.keyType = keyType;
this.valueType = valueType;
ensureNonNull( keyType, "keyType");
ensureNonNull(valueType, "valueType");
}
/**
* Ensure that the given argument is non-null.
*/
private static void ensureNonNull(final Class<?> type, final String name) {
if (type == null) {
throw new NullPointerException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1, name));
}
}
/**
* Checks the type of the specified object. The default implementation ensure
* that the object is assignable to the type specified at construction time.
*
* @param element the object to check, or {@code null}.
* @throws IllegalArgumentException if the specified element is not of the expected type.
*/
private static <E> void ensureValidType(final E element, final Class<E> type)
throws IllegalArgumentException
{
if (element!=null && !type.isInstance(element)) {
throw new IllegalArgumentException(Errors.format(
ErrorKeys.ILLEGAL_CLASS_$2, element.getClass(), type));
}
}
/**
* Checks if changes in this collection are allowed. This method is automatically invoked
* after this collection got the {@linkplain #getLock lock} and before any operation that
* may change the content. The default implementation does nothing (i.e. this collection
* is modifiable). Subclasses should override this method if they want to control write
* access.
*
* @throws UnsupportedOperationException if this collection is unmodifiable.
*
* @since 2.5
*/
protected void checkWritePermission() throws UnsupportedOperationException {
assert Thread.holdsLock(getLock());
}
/**
* Returns the synchronization lock. The default implementation returns {@code this}.
* Subclasses that override this method should be careful to update the lock reference
* when this set is {@linkplain #clone cloned}.
*
* @return The synchronization lock.
*
* @since 2.5
*/
protected Object getLock() {
return this;
}
/**
* Returns the number of elements in this map.
*/
@Override
public int size() {
synchronized (getLock()) {
return super.size();
}
}
/**
* Returns {@code true} if this map contains no elements.
*/
@Override
public boolean isEmpty() {
synchronized (getLock()) {
return super.isEmpty();
}
}
/**
* Returns {@code true} if this map contains the specified key.
*/
@Override
public boolean containsKey(final Object key) {
synchronized (getLock()) {
return super.containsKey(key);
}
}
/**
* Returns {@code true} if this map contains the specified value.
*/
@Override
public boolean containsValue(final Object value) {
synchronized (getLock()) {
return super.containsValue(value);
}
}
/**
* Returns the value to which the specified key is mapped, or {@code null} if none.
*/
@Override
public V get(Object key) {
synchronized (getLock()) {
return super.get(key);
}
}
/**
* Associates the specified value with the specified key in this map.
* If the map 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 {@code null}.
* @throws IllegalArgumentException if the key or the value is not of the expected type.
* @throws UnsupportedOperationException if this collection is unmodifiable.
*/
@Override
public V put(final K key, final V value)
throws IllegalArgumentException, UnsupportedOperationException
{
ensureValidType(key, keyType);
ensureValidType(value, valueType);
synchronized (getLock()) {
checkWritePermission();
return super.put(key, value);
}
}
/**
* Copies all of the mappings from the specified map to this map.
*
* @throws UnsupportedOperationException if this collection is unmodifiable.
*/
@Override
public void putAll(Map<? extends K, ? extends V> m) throws UnsupportedOperationException {
for (final Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
ensureValidType(entry.getKey(), keyType);
ensureValidType(entry.getValue(), valueType);
}
synchronized (getLock()) {
checkWritePermission();
super.putAll(m);
}
}
/**
* Removes the mapping for the specified key from this map if present.
*
* @throws UnsupportedOperationException if this collection is unmodifiable.
*/
@Override
public V remove(Object key) throws UnsupportedOperationException {
synchronized (getLock()) {
checkWritePermission();
return super.remove(key);
}
}
/**
* Removes all of the elements from this map.
*
* @throws UnsupportedOperationException if this collection is unmodifiable.
*/
@Override
public void clear() throws UnsupportedOperationException {
synchronized (getLock()) {
checkWritePermission();
super.clear();
}
}
/**
* Returns a string representation of this map.
*/
@Override
public String toString() {
synchronized (getLock()) {
return super.toString();
}
}
/**
* Compares the specified object with this map for equality.
*/
@Override
public boolean equals(Object o) {
synchronized (getLock()) {
return super.equals(o);
}
}
/**
* Returns the hash code value for this map.
*/
@Override
public int hashCode() {
synchronized (getLock()) {
return super.hashCode();
}
}
/**
* Returns a shallow copy of this map.
*
* @return A shallow copy of this map.
*/
@Override
@SuppressWarnings("unchecked")
public CheckedHashMap<K,V> clone() {
synchronized (getLock()) {
return (CheckedHashMap) super.clone();
}
}
}