/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2004-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.io.Serializable;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
/**
* A map whose keys are derived from an other map. The keys are derived only when
* requested, which make it possible to backup potentially large maps. Implementations
* need only to overrides {@link #baseToDerived} and {@link #derivedToBase} methods.
* This set do not supports {@code null} key, since {@code null} is used
* when no mapping from {@linkplain #base} to {@code this} exists.
* This class is serializable if the underlying {@linkplain #base} set is serializable
* too.
* <p>
* This class is <strong>not</strong> thread-safe. Synchronizations (if wanted) are user's
* reponsability.
*
* @param <BK> The type of keys in the backing map.
* @param <K> The type of keys in this map.
* @param <V> The type of values in both this map and the underlying map.
*
* @since 2.0
*
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public abstract class DerivedMap<BK,K,V> extends AbstractMap<K,V> implements Serializable {
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = -6994867383669885934L;
/**
* The base map whose keys are derived from.
*
* @see #baseToDerived
* @see #derivedToBase
*/
protected final Map<BK,V> base;
/**
* Key set. Will be constructed only when first needed.
*
* @see #keySet
*/
private transient Set<K> keySet;
/**
* Entry set. Will be constructed only when first needed.
*
* @see #entrySet
*/
private transient Set<Map.Entry<K,V>> entrySet;
/**
* The derived key type.
*/
private final Class<K> keyType;
/**
* Creates a new derived map from the specified base map.
*
* @param base The base map.
*
* @deprecated Use {@link #DerivedMap(Map, Class} instead.
*/
@SuppressWarnings("unchecked")
public DerivedMap(final Map<BK,V> base) {
this(base, (Class) Object.class);
}
/**
* Creates a new derived map from the specified base map.
*
* @param base The base map.
* @param keyType the type of keys in the derived map.
*
* @since 2.5
*/
public DerivedMap(final Map<BK,V> base, final Class<K> keyType) {
this.base = base;
this.keyType = keyType;
}
/**
* Transforms a key from the {@linkplain #base} map to a key in this map.
* If there is no key in the derived map for the specified base key,
* then this method returns {@code null}.
*
* @param key A ley from the {@linkplain #base} map.
* @return The key that this view should contains instead of {@code key},
* or {@code null}.
*/
protected abstract K baseToDerived(final BK key);
/**
* Transforms a key from this derived map to a key in the {@linkplain #base} map.
*
* @param key A key in this map.
* @return The key stored in the {@linkplain #base} map.
*/
protected abstract BK derivedToBase(final K key);
/**
* Returns the number of key-value mappings in this map.
*
* @return the number of key-value mappings in this map.
*/
@Override
public int size() {
return super.size();
}
/**
* Returns {@code true} if this map contains no key-value mappings.
*
* @return {@code true} if this map contains no key-value mappings.
*/
@Override
public boolean isEmpty() {
return base.isEmpty() || super.isEmpty();
}
/**
* Returns {@code true} if this map maps one or more keys to this value.
* The default implementation invokes
* <code>{@linkplain #base}.containsValue(value)</code>.
*
* @return {@code true} if this map maps one or more keys to this value.
*/
@Override
public boolean containsValue(final Object value) {
return base.containsValue(value);
}
/**
* Returns {@code true} if this map contains a mapping for the specified key.
* The default implementation invokes
* <code>{@linkplain #base}.containsKey({@linkplain #derivedToBase derivedToBase}(key))</code>.
*
* @param key key whose presence in this map is to be tested.
* @return {@code true} if this map contains a mapping for the specified key.
*/
@Override
public boolean containsKey(final Object key) {
if (keyType.isInstance(key)) {
return base.containsKey(derivedToBase(keyType.cast(key)));
} else {
return false;
}
}
/**
* Returns the value to which this map maps the specified key.
* The default implementation invokes
* <code>{@linkplain #base}.get({@linkplain #derivedToBase derivedToBase}(key))</code>.
*
* @param key key whose associated value is to be returned.
* @return the value to which this map maps the specified key.
*/
@Override
public V get(final Object key) {
if (keyType.isInstance(key)) {
return base.get(derivedToBase(keyType.cast(key)));
} else {
return null;
}
}
/**
* Associates the specified value with the specified key in this map.
* The default implementation invokes
* <code>{@linkplain #base}.put({@linkplain #derivedToBase derivedToBase}(key), value)</code>.
*
* @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}
* if there was no mapping for key.
* @throws UnsupportedOperationException if the {@linkplain #base} map doesn't
* supports the {@code put} operation.
*/
@Override
public V put(final K key, final V value) throws UnsupportedOperationException {
return base.put(derivedToBase(key), value);
}
/**
* Removes the mapping for this key from this map if present.
* The default implementation invokes
* <code>{@linkplain #base}.remove({@linkplain #derivedToBase derivedToBase}(key))</code>.
*
* @param key key whose mapping is to be removed from the map.
* @return previous value associated with specified key, or {@code null}
* if there was no entry for key.
* @throws UnsupportedOperationException if the {@linkplain #base} map doesn't
* supports the {@code remove} operation.
*/
@Override
public V remove(final Object key) throws UnsupportedOperationException {
if (keyType.isInstance(key)) {
return base.remove(derivedToBase(keyType.cast(key)));
} else {
return null;
}
}
/**
* Returns a set view of the keys contained in this map.
*
* @return a set view of the keys contained in this map.
*/
@Override
public Set<K> keySet() {
if (keySet == null) {
keySet = new KeySet(base.keySet());
}
return keySet;
}
/**
* Returns a collection view of the values contained in this map.
*
* @return a collection view of the values contained in this map.
*/
@Override
public Collection<V> values() {
return base.values();
}
/**
* Returns a set view of the mappings contained in this map.
*
* @return a set view of the mappings contained in this map.
*/
@Override
@SuppressWarnings("unchecked")
public Set<Map.Entry<K,V>> entrySet() {
if (entrySet == null) {
entrySet = (Set) new EntrySet(base.entrySet());
}
return entrySet;
}
/**
* The key set.
*/
private final class KeySet extends DerivedSet<BK,K> {
private static final long serialVersionUID = -2931806200277420177L;
public KeySet(final Set<BK> base) {
super(base, keyType);
}
protected K baseToDerived(final BK element) {
return DerivedMap.this.baseToDerived(element);
}
protected BK derivedToBase(final K element) {
return DerivedMap.this.derivedToBase(element);
}
}
/**
* The entry set.
*/
private final class EntrySet extends DerivedSet<Map.Entry<BK,V>, Entry<BK,K,V>> {
private static final long serialVersionUID = -2931806200277420177L;
@SuppressWarnings("unchecked")
public EntrySet(final Set<Map.Entry<BK,V>> base) {
super(base, (Class) Entry.class);
}
protected Entry<BK,K,V> baseToDerived(final Map.Entry<BK,V> entry) {
final K derived = DerivedMap.this.baseToDerived(entry.getKey());
return derived!=null ? new Entry<BK,K,V>(entry, derived) : null;
}
protected Map.Entry<BK,V> derivedToBase(final Entry<BK,K,V> element) {
return element.entry;
}
}
/**
* The entry element.
*/
private static final class Entry<BK,K,V> implements Map.Entry<K,V> {
public final Map.Entry<BK,V> entry;
private final K derived;
public Entry(final Map.Entry<BK,V> entry, final K derived) {
this.entry = entry;
this.derived = derived;
}
public K getKey() {
return derived;
}
public V getValue() {
return entry.getValue();
}
public V setValue(V value) {
return entry.setValue(value);
}
}
}