/* * Copyright (c) 2008, Harald Kuhr * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name "TwelveMonkeys" nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.twelvemonkeys.util; import java.util.*; import java.io.Serializable; /** * AbstractDecoratedMap * <p/> * * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/AbstractDecoratedMap.java#2 $ */ // TODO: The generics in this class looks suspicious.. abstract class AbstractDecoratedMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Serializable, Cloneable { protected Map<K, Entry<K, V>> entries; protected transient volatile int modCount; private transient volatile Set<Entry<K, V>> entrySet = null; private transient volatile Set<K> keySet = null; private transient volatile Collection<V> values = null; /** * Creates a {@code Map} backed by a {@code HashMap}. */ public AbstractDecoratedMap() { this(new HashMap<K, Entry<K, V>>(), null); } /** * Creates a {@code Map} backed by a {@code HashMap}, containing all * key/value mappings from the given {@code Map}. * <p/> * <small>This is constructor is here to comply with the reccomendations for * "standard" constructors in the {@code Map} interface.</small> * * @see #AbstractDecoratedMap(java.util.Map, java.util.Map) * * @param pContents the map whose mappings are to be placed in this map. * May be {@code null}. */ public AbstractDecoratedMap(Map<? extends K, ? extends V> pContents) { this(new HashMap<K, Entry<K, V>>(), pContents); } /** * Creates a {@code Map} backed by the given backing-{@code Map}, * containing all key/value mappings from the given contents-{@code Map}. * <p/> * NOTE: The backing map is structuraly cahnged, and it should NOT be * accessed directly, after the wrapped map is created. * * @param pBacking the backing map of this map. Must be either empty, or * the same map as {@code pContents}. * @param pContents the map whose mappings are to be placed in this map. * May be {@code null}. * * @throws IllegalArgumentException if {@code pBacking} is {@code null} * or if {@code pBacking} differs from {@code pContent} and is not empty. */ public AbstractDecoratedMap(Map<K, Entry<K, V>> pBacking, Map<? extends K, ? extends V> pContents) { if (pBacking == null) { throw new IllegalArgumentException("backing == null"); } Entry<? extends K, ? extends V>[] entries = null; if (pBacking == pContents) { // NOTE: Special treatment to avoid ClassCastExceptions Set<? extends Entry<? extends K, ? extends V>> es = pContents.entrySet(); //noinspection unchecked entries = new Entry[es.size()]; entries = es.toArray(entries); pContents = null; pBacking.clear(); } else if (!pBacking.isEmpty()) { throw new IllegalArgumentException("backing must be empty"); } this.entries = pBacking; init(); if (pContents != null) { putAll(pContents); } else if (entries != null) { // Reinsert entries, this time wrapped for (Entry<? extends K, ? extends V> entry : entries) { put(entry.getKey(), entry.getValue()); } } } /** * Default implementation, does nothing. */ protected void init() { } public int size() { return entries.size(); } public void clear() { entries.clear(); modCount++; init(); } public boolean isEmpty() { return entries.isEmpty(); } public boolean containsKey(Object pKey) { return entries.containsKey(pKey); } /** * Returns {@code true} if this map maps one or more keys to the * specified pValue. More formally, returns {@code true} if and only if * this map contains at least one mapping to a pValue {@code v} such that * {@code (pValue==null ? v==null : pValue.equals(v))}. * <p/> * This implementation requires time linear in the map size for this * operation. * * @param pValue pValue whose presence in this map is to be tested. * @return {@code true} if this map maps one or more keys to the * specified pValue. */ public boolean containsValue(Object pValue) { for (V value : values()) { if (value == pValue || (value != null && value.equals(pValue))) { return true; } } return false; } public Collection<V> values() { Collection<V> values = this.values; return values != null ? values : (this.values = new Values()); } public Set<Entry<K, V>> entrySet() { Set<Entry<K, V>> es = entrySet; return es != null ? es : (entrySet = new EntrySet()); } public Set<K> keySet() { Set<K> ks = keySet; return ks != null ? ks : (keySet = new KeySet()); } /** * Returns a shallow copy of this {@code AbstractMap} instance: the keys * and values themselves are not cloned. * * @return a shallow copy of this map. */ protected Object clone() throws CloneNotSupportedException { AbstractDecoratedMap map = (AbstractDecoratedMap) super.clone(); map.values = null; map.entrySet = null; map.keySet = null; // TODO: Implement: Need to clone the backing map... return map; } // Subclass overrides these to alter behavior of views' iterator() method protected abstract Iterator<K> newKeyIterator(); protected abstract Iterator<V> newValueIterator(); protected abstract Iterator<Entry<K, V>> newEntryIterator(); // TODO: Implement these (get/put/remove)? public abstract V get(Object pKey); public abstract V remove(Object pKey); public abstract V put(K pKey, V pValue); /*protected*/ Entry<K, V> createEntry(K pKey, V pValue) { return new BasicEntry<K, V>(pKey, pValue); } /*protected*/ Entry<K, V> getEntry(K pKey) { return entries.get(pKey); } /** * Removes the given entry from the Map. * * @param pEntry the entry to be removed * * @return the removed entry, or {@code null} if nothing was removed. */ protected Entry<K, V> removeEntry(Entry<K, V> pEntry) { if (pEntry == null) { return null; } // Find candidate entry for this key Entry<K, V> candidate = getEntry(pEntry.getKey()); if (candidate == pEntry || (candidate != null && candidate.equals(pEntry))) { // Remove remove(pEntry.getKey()); return pEntry; } return null; } protected class Values extends AbstractCollection<V> { public Iterator<V> iterator() { return newValueIterator(); } public int size() { return AbstractDecoratedMap.this.size(); } public boolean contains(Object o) { return containsValue(o); } public void clear() { AbstractDecoratedMap.this.clear(); } } protected class EntrySet extends AbstractSet<Entry<K, V>> { public Iterator<Entry<K, V>> iterator() { return newEntryIterator(); } public boolean contains(Object o) { if (!(o instanceof Entry)) return false; Entry e = (Entry) o; //noinspection SuspiciousMethodCalls Entry<K, V> candidate = entries.get(e.getKey()); return candidate != null && candidate.equals(e); } public boolean remove(Object o) { if (!(o instanceof Entry)) { return false; } /* // NOTE: Extra cautions is taken, to only remove the entry if it // equals the entry in the map Object key = ((Entry) o).getKey(); Entry entry = (Entry) entries.get(key); // Same entry? if (entry != null && entry.equals(o)) { return AbstractWrappedMap.this.remove(key) != null; } return false; */ //noinspection unchecked return AbstractDecoratedMap.this.removeEntry((Entry) o) != null; } public int size() { return AbstractDecoratedMap.this.size(); } public void clear() { AbstractDecoratedMap.this.clear(); } } protected class KeySet extends AbstractSet<K> { public Iterator<K> iterator() { return newKeyIterator(); } public int size() { return AbstractDecoratedMap.this.size(); } public boolean contains(Object o) { return containsKey(o); } public boolean remove(Object o) { return AbstractDecoratedMap.this.remove(o) != null; } public void clear() { AbstractDecoratedMap.this.clear(); } } /** * A simple Map.Entry implementaton. */ static class BasicEntry<K, V> implements Entry<K, V>, Serializable { K mKey; V mValue; BasicEntry(K pKey, V pValue) { mKey = pKey; mValue = pValue; } /** * Default implementation does nothing. * * @param pMap the map that is accessed */ protected void recordAccess(Map<K, V> pMap) { } /** * Default implementation does nothing. * @param pMap the map that is removed from */ protected void recordRemoval(Map<K, V> pMap) { } public V getValue() { return mValue; } public V setValue(V pValue) { V oldValue = mValue; mValue = pValue; return oldValue; } public K getKey() { return mKey; } public boolean equals(Object pOther) { if (!(pOther instanceof Map.Entry)) { return false; } Map.Entry entry = (Map.Entry) pOther; Object k1 = mKey; Object k2 = entry.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = mValue; Object v2 = entry.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) { return true; } } return false; } public int hashCode() { return (mKey == null ? 0 : mKey.hashCode()) ^ (mValue == null ? 0 : mValue.hashCode()); } public String toString() { return getKey() + "=" + getValue(); } } }