All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tangosol.util.AbstractKeyBasedMap Maven / Gradle / Ivy

There is a newer version: 24.09
Show newest version
/*
 * Copyright (c) 2000, 2020, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * http://oss.oracle.com/licenses/upl.
 */

package com.tangosol.util;


import com.tangosol.net.cache.CacheEvent;

import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;


/**
* AbstractKeyBasedMap is a base class for Map implementations. The primary
* difference between the {@link java.util.AbstractMap} abstract class and
* this abstract class is this: that AbstractMap requires a sub-class to
* provide an Entry Set implementation, while AbstractKeyBasedMap requires a
* read-only sub-class to implement only get() and iterateKeys(), and a
* read-write sub-class to additionally implement only put() and remove().
* 

* Read-only implementations must implement {@link #iterateKeys()} and * {@link #get(Object)}. Read/write implementations must additionally * implement {@link #put(Object, Object)} and {@link #remove(Object)}. A * number of the methods have implementations provided, but are extremely * inefficient for Maps that contain large amounts of data, including * {@link #clear()}, {@link #containsKey(Object)}, {@link #size()} (and by * extension {@link #isEmpty()}). Furthermore, if any of a number of method * implementations has any cost of returning an "old value", such as is done * by the {@link #put} and {@link #remove(Object)} methods, then the * {@link #putAll(java.util.Map)} and {@link #removeBlind(Object)} * methods should also be implemented. * * @author 2005.07.13 cp */ public abstract class AbstractKeyBasedMap extends Base implements Map { // ----- Map interface -------------------------------------------------- /** * Clear all key/value mappings. */ public void clear() { // this begs for sub-class optimization for (Iterator iter = iterateKeys(); iter.hasNext(); ) { iter.next(); iter.remove(); } } /** * Returns true if this map contains a mapping for the specified * key. * * @return true if this map contains a mapping for the specified * key, false otherwise. */ public boolean containsKey(Object oKey) { // this begs for sub-class optimization for (Iterator iter = iterateKeys(); iter.hasNext(); ) { if (equals(oKey, iter.next())) { return true; } } return false; } /** * Returns true if this Map maps one or more keys to the * specified value. * * @return true if this Map maps one or more keys to the * specified value, false otherwise */ public boolean containsValue(Object oValue) { return values().contains(oValue); } /** * Returns a set view of the mappings contained in this map. Each element * in the returned set is an {@link Entry}. The set is backed by the * map, so changes to the map are reflected in the set, and vice-versa. * If the map is modified while an iteration over the set is in progress * (except by the iterator's own remove operation, or by the * setValue operation on a map entry returned by the iterator) * the results of the iteration are undefined. The set supports element * removal, which removes the corresponding mapping from the map, via the * Iterator.remove, Set.remove, removeAll, * retainAll and clear operations. It is not expected to * support the add or addAll operations. * * @return a set view of the mappings contained in this map */ public Set> entrySet() { // no need to synchronize; it is acceptable that two threads would // instantiate an entry set Set> set = m_setEntries; if (set == null) { m_setEntries = set = instantiateEntrySet(); } return set; } /** * Returns the value to which this map maps the specified key. * * @param oKey the key object * * @return the value to which this map maps the specified key, * or null if the map contains no mapping for this key */ public abstract V get(Object oKey); /** * Returns true if this map contains no key-value mappings. * * @return true if this map contains no key-value mappings */ public boolean isEmpty() { return size() == 0; } /** * Returns a set view of the keys contained in this map. The set is * backed by the map, so changes to the map are reflected in the set, and * vice-versa. If the map is modified while an iteration over the set is * in progress (except through the iterator's own remove * operation), the results of the iteration are undefined. The set * supports element removal, which removes the corresponding mapping from * the map, via the Iterator.remove, Set.remove, * removeAll retainAll, and clear operations. * It is not expected to support the add or addAll operations. * * @return a set view of the keys contained in this map */ public Set keySet() { // no need to synchronize; it is acceptable that two threads would // instantiate a key set Set set = m_setKeys; if (set == null) { m_setKeys = set = instantiateKeySet(); } return set; } /** * Associates the specified value with the specified key in this map. * * @param oKey key with which the specified value is to be associated * @param oValue value to be associated with the specified key * * @return previous value associated with specified key, or null * if there was no mapping for key */ public V put(K oKey, V oValue) { throw new UnsupportedOperationException(); } /** * Copies all of the mappings from the specified map to this map. The * effect of this call is equivalent to that of calling {@link #put} * on this map once for each mapping in the passed map. The behavior of * this operation is unspecified if the passed map is modified while the * operation is in progress. * * @param map the Map containing the key/value pairings to put into this * Map */ public void putAll(Map map) { for (Map.Entry entry : map.entrySet()) { put(entry.getKey(), entry.getValue()); } } /** * Removes the mapping for this key from this map if present. * Expensive: updates both the underlying cache and the local cache. * * @param oKey key whose mapping is to be removed from the map * * @return previous value associated with specified key, or null * if there was no mapping for key. A null return can * also indicate that the map previously associated null * with the specified key, if the implementation supports * null values. */ public V remove(Object oKey) { throw new UnsupportedOperationException(); } /** * Returns the number of key-value mappings in this map. * * @return the number of key-value mappings in this map */ public int size() { // this begs for sub-class optimization int c = 0; for (Iterator iter = iterateKeys(); iter.hasNext(); ) { iter.next(); ++c; } return c; } /** * Returns a collection view of the values contained in this map. The * collection is backed by the map, so changes to the map are reflected in * the collection, and vice-versa. If the map is modified while an * iteration over the collection is in progress (except through the * iterator's own remove operation), the results of the * iteration are undefined. The collection supports element removal, * which removes the corresponding mapping from the map, via the * Iterator.remove, Collection.remove, * removeAll, retainAll and clear operations. * It is not expected to support the add or addAll operations. * * @return a Collection view of the values contained in this map */ public Collection values() { // no need to synchronize; it is acceptable that two threads would // instantiate a key set Collection coll = m_collValues; if (coll == null) { m_collValues = coll = instantiateValues(); } return coll; } // ----- CacheMap methods ----------------------------------------------- /** * Get all the specified keys, if they are in the Map. For each key * that is in the cache, that key and its corresponding value will be * placed in the map that is returned by this method. The absence of * a key in the returned map indicates that it was not in the cache, * which may imply (for caches that can load behind the scenes) that * the requested data could not be loaded. * * @param colKeys a collection of keys that may be in the named cache * * @return a Map of keys to values for the specified keys passed in * colKeys */ public Map getAll(Collection colKeys) { Map map = new ListMap<>(); for (K key : colKeys) { V val = get(key); if (val != null || containsKey(key)) { map.put(key, val); } } return map; } // ----- internal methods ----------------------------------------------- /** * Create an iterator over the keys in this Map. The Iterator must * support remove() if the Map supports removal. * * @return a new instance of an Iterator over the keys in this Map */ protected abstract Iterator iterateKeys(); /** * Removes the mapping for this key from this map if present. This method * exists to allow sub-classes to optimize remove functionality for * situations in which the original value is not required. * * @param oKey key whose mapping is to be removed from the map * * @return true iff the Map changed as the result of this operation */ protected boolean removeBlind(Object oKey) { if (containsKey(oKey)) { remove(oKey); return true; } else { return false; } } // ----- Object methods ------------------------------------------------- /** * Compares the specified object with this map for equality. Returns * true if the given object is also a map and the two maps * represent the same mappings. More formally, two maps t1 and * t2 represent the same mappings if * t1.keySet().equals(t2.keySet()) and for every key k * in t1.keySet(), (t1.get(k)==null ? t2.get(k)==null : * t1.get(k).equals(t2.get(k))) . This ensures that the * equals method works properly across different implementations * of the map interface. * * @param o object to be compared for equality with this Map * * @return true if the specified object is equal to this Map */ public boolean equals(Object o) { if (o instanceof Map) { Map that = (Map) o; if (this == that) { return true; } if (this.size() == that.size()) { for (Entry entry : entrySet()) { Object oKey = entry.getKey(); Object oThisValue = entry.getValue(); Object oThatValue; try { oThatValue = that.get(oKey); } catch (ClassCastException e) { // to be compatible with java.util.AbstractMap return false; } catch (NullPointerException e) { // to be compatible with java.util.AbstractMap return false; } if (oThisValue == this || oThisValue == that) { // this could be infinite recursion if (oThatValue != this && oThatValue != that) { // it is not safe to call equals(); it would // likely lead to infinite recursion return false; } } else if (!equals(oThisValue, oThatValue) || oThatValue == null && !that.containsKey(oKey)) { return false; } } // size is identical and all entries match return true; } } return false; } /** * Returns the hash code value for this Map. The hash code of a Map is * defined to be the sum of the hash codes of each entry in the Map's * entrySet() view. This ensures that t1.equals(t2) * implies that t1.hashCode()==t2.hashCode() for any two maps * t1 and t2, as required by the general contract of * Object.hashCode. * * @return the hash code value for this Map */ public int hashCode() { int nHash = 0; for (Entry entry : entrySet()) { nHash += entry.hashCode(); } return nHash; } /** * Returns a string representation of this Map. The string representation * consists of a list of key-value mappings in the order returned by the * Map's entrySet view's iterator, enclosed in braces * ("{}"). Adjacent mappings are separated by the characters * ", " (comma and space). Each key-value mapping is rendered as * the key followed by an equals sign ("=") followed by the * associated value. Keys and values are converted to strings as by * String.valueOf(Object). * * @return a String representation of this Map */ public String toString() { StringBuilder sb = new StringBuilder(100 + (size() << 3)); sb.append('{'); boolean fFirst = true; for (Entry entry : entrySet()) { if (fFirst) { fFirst = false; } else { sb.append(", "); } K key = entry.getKey(); V value = entry.getValue(); // as per AbstractMap, detect the condition in which case this // map is used as a key and/or a value inside itself, which // would result in infinite recursion sb.append(key == this ? "(this Map)" : String.valueOf(key)) .append('=') .append(value == this ? "(this Map)" : String.valueOf(value)); } sb.append('}'); return sb.toString(); } /** * Returns a shallow copy of this AbstractKeyBasedMap instance; * the keySet, entrySet and values collections are not cloned or copied * to (shared by) the clone. * * @return a shallow copy of this map */ protected Object clone() throws CloneNotSupportedException { AbstractKeyBasedMap that = (AbstractKeyBasedMap) super.clone(); that.m_setKeys = null; that.m_setEntries = null; that.m_collValues = null; return that; } // ----- inner class: DeferredCacheEvent -------------------------------- /** * A DeferredCacheEvent is a {@link CacheEvent} object that defers the loading * of the {@link #getOldValue() old value}. *

* This event has two predominant states; active and inactive. The event is * active from incarnation and can transition to inactive (via {@link #deactivate()}) * but not vice-versa. Being active allows the getOldValue implementation * to load the value from underlying map, thus all consumers of this * event must ensure they call getOldValue prior to returning control when * this event is dispatched. *

* Once inactive the correct value may no longer be available in the map * thus in this state a getOldValue will return either null or the cached old value. */ protected abstract static class DeferredCacheEvent extends CacheEvent { // ----- constructors ----------------------------------------------- /** * Constructs a new DeferredCacheEvent. * * @param map the ObservableMap * @param nId this event's id, one of {@link #ENTRY_INSERTED}, * {@link #ENTRY_UPDATED} or {@link #ENTRY_DELETED} * @param oKey the key into the map * @param oValueOld the old value (for update and delete events) * @param oValueNew the new value (for insert and update events) * @param fSynthetic true iff the event is caused by the cache * internal processing such as eviction or loading */ public DeferredCacheEvent(ObservableMap map, int nId, K oKey, V oValueOld, V oValueNew, boolean fSynthetic) { super(map, nId, oKey, oValueOld == null ? (V) NO_VALUE : oValueOld, oValueNew, fSynthetic); } // ----- helper methods --------------------------------------------- /** * {@inheritDoc} */ @Override public V getOldValue() { // since m_oValueOld is not volatile we double read the volatile // m_fActive to ensure the event was not deactivated whilst we were // loading the old value V oValueOld = m_valueOld; if (oValueOld == NO_VALUE && m_fActive) { oValueOld = readOldValue(); if (m_fActive) { return m_valueOld = oValueOld; } } return oValueOld == NO_VALUE ? null : oValueOld; } /** * Perform a deferred read for the old value in a usage-specific way. * * @return the old value */ protected abstract V readOldValue(); /** * Deactivate this DeferredCacheEvent instance. This is used to prevent * future {@link #getOldValue()} calls using the underlying map *after* * the event dispatching code returned and the content of the map had * been changed to a new value. *

* The contract between the DeferredCacheEvent and consumers of * the event states that consumers must call {@code getOldValue} prior to * returning from event dispatching logic. */ public void deactivate() { if (m_valueOld == NO_VALUE) { m_valueOld = null; } m_fActive = false; } /** * Whether the DeferredCacheEvent is in an active or inactive state. */ protected volatile boolean m_fActive = true; } // ----- inner class: KeySet -------------------------------------------- /** * Factory pattern: Create a Set that represents the keys in the Map * * @return a new instance of Set that represents the keys in the Map */ protected Set instantiateKeySet() { return new KeySet(); } /** * A set of keys backed by this map. */ protected class KeySet extends AbstractSet { // ----- Set interface ------------------------------------------ /** * Removes all of the elements from this set of Keys by clearing the * underlying Map. */ public void clear() { AbstractKeyBasedMap.this.clear(); } /** * Returns true if this collection contains the specified * element. More formally, returns true if and only if this * collection contains at least one element e such that * (o==null ? e==null : o.equals(e)).

* * @param o object to be checked for containment in this collection * * @return true if this collection contains the specified * element */ public boolean contains(Object o) { return AbstractKeyBasedMap.this.containsKey(o); } /** * Returns true if this Set is empty. * * @return true if this Set is empty */ public boolean isEmpty() { return AbstractKeyBasedMap.this.isEmpty(); } /** * Returns an iterator over the elements contained in this collection. * * @return an iterator over the elements contained in this collection */ public Iterator iterator() { if (isEmpty()) { return NullImplementation.getIterator(); } return AbstractKeyBasedMap.this.iterateKeys(); } /** * Removes the specified element from this Set of keys if it is * present by removing the associated key from the underlying * Map. * * @param o object to be removed from this set, if present * * @return true if the set contained the specified element */ public boolean remove(Object o) { return AbstractKeyBasedMap.this.removeBlind(o); } /** * Returns the number of elements in this collection. * * @return the number of elements in this collection */ public int size() { return AbstractKeyBasedMap.this.size(); } } // ----- inner class: EntrySet ------------------------------------------ /** * Factory pattern: Create a Set that represents the entries in the Map. * * @return a new instance of Set that represents the entries in the Map */ protected Set> instantiateEntrySet() { return new EntrySet(); } /** * A set of entries backed by this map. */ public class EntrySet extends AbstractSet> { // ----- Set interface ------------------------------------------ /** * Removes all of the elements from this set of Keys by clearing the * underlying Map. */ public void clear() { AbstractKeyBasedMap.this.clear(); } /** * Returns true if this collection contains the specified * element. More formally, returns true if and only if this * collection contains at least one element e such that * (o==null ? e==null : o.equals(e)).

* * @param o object to be checked for containment in this collection * * @return true if this collection contains the specified * element */ public boolean contains(Object o) { if (o instanceof Map.Entry) { Map.Entry entry = (Map.Entry) o; Object oKey = entry.getKey(); Object oValue = entry.getValue(); Map map = AbstractKeyBasedMap.this; return Base.equals(oValue, map.get(oKey)) && (oValue != null || map.containsKey(oKey)); } return false; } /** * Returns true if this Set is empty. * * @return true if this Set is empty */ public boolean isEmpty() { return AbstractKeyBasedMap.this.isEmpty(); } /** * Returns an iterator over the elements contained in this collection. * * @return an iterator over the elements contained in this collection */ public Iterator> iterator() { if (isEmpty()) { return NullImplementation.getIterator(); } return instantiateIterator(); } /** * Removes the specified element from this Set of entries if it is * present by removing the associated entry from the underlying Map. * * @param o object to be removed from this set, if present * * @return true if the set contained the specified element */ public boolean remove(Object o) { if (contains(o)) { AbstractKeyBasedMap.this.remove(((Map.Entry) o).getKey()); return true; } else { return false; } } /** * Returns the number of elements in this collection. * * @return the number of elements in this collection */ public int size() { return AbstractKeyBasedMap.this.size(); } // ----- inner class: Entry ------------------------------------- /** * Factory pattern. Create a Map Entry. * * @param oKey the Entry key (required) * @param oValue the Entry value (optional; lazy loaded if necessary) * * @return a new instance of an Entry with the specified key and * value (if one is provided) */ protected Map.Entry instantiateEntry(K oKey, V oValue) { return new Entry(oKey, oValue); } /** * A Map Entry implementation that defers its value acquisition from * the containing map (via {@link Map#get(Object)}) if the Entry is * constructed with a null value. */ protected class Entry extends SimpleMapEntry { /** * Construct an Entry. * * @param oKey the Entry key * @param oValue the Entry value (optional) */ public Entry(K oKey, V oValue) { super(oKey, oValue); } /** * Returns the value corresponding to this entry. * * @return the value corresponding to this entry */ public V getValue() { V oValue = super.getValue(); if (oValue == null) { oValue = AbstractKeyBasedMap.this.get(getKey()); super.setValue(oValue); } return oValue; } /** * Replaces the value corresponding to this entry with the * specified value (optional operation). (Writes through * to the map.) * * @param oValue new value to be stored in this entry * * @return old value corresponding to the entry */ public V setValue(V oValue) { V oValueOrig = AbstractKeyBasedMap.this.put(getKey(), oValue); super.setValue(oValue); return oValueOrig; } /** * Returns the hash code value for this map entry. The * hash code of a map entry e is defined to be: *

            *     (e.getKey()==null   ? 0 : e.getKey().hashCode()) ^
            *     (e.getValue()==null ? 0 : e.getValue().hashCode())
            * 
* This ensures that e1.equals(e2) implies that * e1.hashCode()==e2.hashCode() for any two * Entries e1 and e2, as required by the * general contract of Object.hashCode. * * @return the hash code value for this map entry. */ public int hashCode() { K oKey = getKey(); V oValue = getValue(); AbstractKeyBasedMap map = AbstractKeyBasedMap.this; return (oKey == null || oKey == map ? 0 : oKey .hashCode()) ^ (oValue == null || oValue == map ? 0 : oValue.hashCode()); } } // ----- inner class: Entry Set Iterator ------------------------ /** * Factory pattern. * * @return a new instance of an Iterator over the EntrySet */ protected Iterator> instantiateIterator() { return new EntrySetIterator(); } /** * An Iterator over the EntrySet that is backed by the Map. */ protected class EntrySetIterator implements Iterator> { // ----- Iterator interface ----------------------------- /** * Returns true if the iteration has more elements. (In * other words, returns true if next would * return an element rather than throwing an exception.) * * @return true if the iterator has more elements */ public boolean hasNext() { return m_iterKeys.hasNext(); } /** * Returns the next element in the iteration. * * @return the next element in the iteration * * @exception NoSuchElementException iteration has no more elements */ public Map.Entry next() { return instantiateEntry(m_iterKeys.next(), /*value*/ null); } /** * Removes from the underlying collection the last element * returned by the iterator. This method can be called only once * per call to next. The behavior of an iterator is * unspecified if the underlying collection is modified while the * iteration is in progress in any way other than by calling this * method. * * @exception IllegalStateException if the next method * has not yet been called, or the remove * method has already been called after the last call * to the next method */ public void remove() { m_iterKeys.remove(); } // ----- data members ----------------------------------- /** * Key iterator. */ protected Iterator m_iterKeys = AbstractKeyBasedMap.this.iterateKeys(); } } // ----- inner class: ValuesCollection ---------------------------------- /** * Factory pattern: Instantiate the values Collection. * * @return a new instance of Collection that represents this Map's values */ protected Collection instantiateValues() { return new ValuesCollection(); } /** * A Collection of values backed by this map. */ protected class ValuesCollection extends AbstractCollection { // ----- Set interface ------------------------------------------ /** * Removes all of the elements from this Collection by clearing the * underlying Map. */ public void clear() { AbstractKeyBasedMap.this.clear(); } /** * Returns true if this collection contains the specified * element. More formally, returns true if and only if this * collection contains at least one element e such that * (o==null ? e==null : o.equals(e)).

* * @param o object to be checked for containment in this collection * * @return true if this collection contains the specified * element */ public boolean contains(Object o) { // this is hardly optimal, and sub-classes that have better // means to implement this should do so if (o == null) { for (Iterator iter = iterator(); iter.hasNext(); ) { if (iter.next() == null) { return true; } } } else { for (Iterator iter = iterator(); iter.hasNext(); ) { if (Base.equals(o, iter.next())) { return true; } } } return false; } /** * Returns true if this Set is empty. * * @return true if this Set is empty */ public boolean isEmpty() { return AbstractKeyBasedMap.this.isEmpty(); } /** * Returns an iterator over the elements contained in this collection. * * @return an iterator over the elements contained in this collection */ public Iterator iterator() { if (isEmpty()) { return NullImplementation.getIterator(); } return instantiateIterator(); } /** * Removes the specified element from this Collection of values if it * is present by removing the associated key/value mapping from the * underlying Map. * * @param o object to be removed from this Collection, if present * * @return true if the Collection contained the specified element */ public boolean remove(Object o) { // this is hardly optimal, and sub-classes that have better // means to implement this should do so if (o == null) { for (Iterator iter = iterator(); iter.hasNext(); ) { if (iter.next() == null) { iter.remove(); return true; } } } else { for (Iterator iter = iterator(); iter.hasNext(); ) { if (Base.equals(o, iter.next())) { iter.remove(); return true; } } } return false; } /** * Returns the number of elements in this collection. * * @return the number of elements in this collection */ public int size() { return AbstractKeyBasedMap.this.size(); } // ----- inner class: Values Collection Iterator ---------------- /** * Factory pattern: Create a values Iterator. * * @return a new instance of an Iterator over the values Collection */ protected Iterator instantiateIterator() { return new ValuesIterator(); } /** * An Iterator over the values Collection that is backed by the * AbstractKeyBasedMap. */ protected class ValuesIterator implements Iterator { // ----- Iterator interface ----------------------------- /** * Returns true if the iteration has more elements. (In * other words, returns true if next would * return an element rather than throwing an exception.) * * @return true if the iterator has more elements */ public boolean hasNext() { return m_iterKeys.hasNext(); } /** * Returns the next element in the iteration. * * @return the next element in the iteration * * @exception NoSuchElementException iteration has no more elements */ public V next() { return AbstractKeyBasedMap.this.get(m_iterKeys.next()); } /** * Removes from the underlying collection the last element * returned by the iterator. This method can be called only once * per call to next. The behavior of an iterator is * unspecified if the underlying collection is modified while the * iteration is in progress in any way other than by calling this * method. * * @exception IllegalStateException if the next method * has not yet been called, or the remove * method has already been called after the last call * to the next method */ public void remove() { m_iterKeys.remove(); } // ----- data members ----------------------------------- /** * A key iterator. */ protected Iterator m_iterKeys = AbstractKeyBasedMap.this.iterateKeys(); } } // ----- data fields ---------------------------------------------------- /** * The key Set for this Map; lazily instantiated. */ private transient Set m_setKeys; /** * The entry Set for this Map; lazily instantiated. */ private transient Set> m_setEntries; /** * The values Collection for this Map; lazily instantiated. */ private transient Collection m_collValues; /** * Constant to indicate the old value in a DeferredCacheEvent has not been * populated. */ private static final Object NO_VALUE = new Object(); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy