com.tangosol.util.ObservableHashMap Maven / Gradle / Ivy
Show all versions of coherence Show documentation
/*
* 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;
/**
* An ObservableMap implementation that extends the SafeHashMap.
*
* This Map implements the ObservableMap interface, meaning it provides
* event notifications to any interested listener for each insert, update and
* delete.
*
* @author cp 2002.02.12
*/
public class ObservableHashMap
extends SafeHashMap
implements ObservableMap
{
// ----- constructors ---------------------------------------------------
/**
* Construct the ObservableHashMap.
*/
public ObservableHashMap()
{
super();
}
/**
* Construct an ObservableHashMap using the specified settings.
*
* @param cInitialBuckets the initial number of hash buckets, 0 < n
* @param flLoadFactor the acceptable load factor before resizing
* occurs, 0 < n, such that a load factor of 1.0
* causes resizing when the number of entries
* exceeds the number of buckets
* @param flGrowthRate the rate of bucket growth when a resize occurs,
* 0 < n, such that a growth rate of 1.0 will
* double the number of buckets:
* bucketCount = bucketCount * (1 + growthRate)
*/
public ObservableHashMap(int cInitialBuckets, float flLoadFactor, float flGrowthRate)
{
super(cInitialBuckets, flLoadFactor, flGrowthRate);
}
// ----- Map interface --------------------------------------------------
/**
* Store a value in the cache.
*
* @param key the key with which to associate the cache value
* @param value the value to cache
*
* @return the value that was cached associated with that key, or null if
* no value was cached associated with that key
*/
public synchronized V put(K key, V value)
{
// COH-6009: map mutations must be synchronized with event dispatch
// to ensure in-order delivery
return super.put(key, value);
}
/**
* Remove an entry from the cache.
*
* @param oKey the key of a cached value
*
* @return the value that was cached associated with that key, or null if
* no value was cached associated with that key
*/
public synchronized V remove(Object oKey)
{
// COH-6009: map mutations must be synchronized with event dispatch
// to ensure in-order delivery
Entry entry = (Entry) getEntryInternal(oKey);
if (entry == null)
{
return null;
}
else
{
removeEntryInternal(entry);
entry.onRemove();
return entry.getValue();
}
}
/**
* Remove everything from the cache, notifying any registered listeners.
*/
public synchronized void clear()
{
clear(false);
}
// ----- ObservableMap methods ------------------------------------------
@SuppressWarnings("unchecked")
@Override
public synchronized void addMapListener(MapListener listener)
{
addMapListener(listener, (Filter) null, false);
}
@SuppressWarnings("unchecked")
@Override
public synchronized void removeMapListener(MapListener listener)
{
removeMapListener(listener, (Filter) null);
}
@Override
public synchronized void addMapListener(MapListener super K, ? super V> listener, K key, boolean fLite)
{
Base.azzert(listener != null);
MapListenerSupport support = m_listenerSupport;
if (support == null)
{
support = m_listenerSupport = new MapListenerSupport();
}
support.addListener(listener, key, fLite);
}
@Override
public synchronized void removeMapListener(MapListener super K, ? super V> listener, K key)
{
Base.azzert(listener != null);
MapListenerSupport support = m_listenerSupport;
if (support != null)
{
support.removeListener(listener, key);
if (support.isEmpty())
{
m_listenerSupport = null;
}
}
}
@Override
public synchronized void addMapListener(MapListener super K, ? super V> listener, Filter filter, boolean fLite)
{
Base.azzert(listener != null);
MapListenerSupport support = m_listenerSupport;
if (support == null)
{
support = m_listenerSupport = new MapListenerSupport();
}
support.addListener(listener, filter, fLite);
}
@Override
public synchronized void removeMapListener(MapListener super K, ? super V> listener, Filter filter)
{
Base.azzert(listener != null);
MapListenerSupport support = m_listenerSupport;
if (support != null)
{
support.removeListener(listener, filter);
if (support.isEmpty())
{
m_listenerSupport = null;
}
}
}
// ----- ObservableHashMap methods --------------------------------------
/**
* Removes all mappings from this map.
*
* Note: the removal of entries caused by this truncate operation will
* not be observable.
*
* @since 12.2.1.4
*/
public synchronized void truncate()
{
clear(true);
}
// ----- event dispatching ----------------------------------------------
/**
* Accessor for the MapListenerSupport for sub-classes.
*
* @return the MapListenerSupport, or null if there are no listeners
*/
protected MapListenerSupport getMapListenerSupport()
{
return m_listenerSupport;
}
/**
* Determine if the OverflowMap has any listeners at all.
*
* @return true iff this OverflowMap has at least one MapListener
*/
protected boolean hasListeners()
{
// m_listenerSupport defaults to null, and it is reset to null when
// the last listener unregisters
return m_listenerSupport != null;
}
/**
* Dispatch the passed event.
*
* @param evt a CacheEvent object
*/
protected void dispatchEvent(MapEvent evt)
{
MapListenerSupport listenerSupport = getMapListenerSupport();
if (listenerSupport != null)
{
// the events can only be generated while the current thread
// holds the monitor on this map
synchronized (this)
{
listenerSupport.fireEvent(evt, false);
}
}
}
// ----- inner class: Entry ---------------------------------------------
/**
* Factory method. This method exists to allow the Cache class to be
* easily inherited from by allowing the Entry class to be easily
* sub-classed.
*
* @return an instance of Entry that holds the passed cache value
*/
protected SafeHashMap.Entry instantiateEntry()
{
return new Entry<>();
}
/**
* A holder for a cached value.
*
* @author cp 2000.12.12 (StmtCacheValue)
* @author cp 2001.04.19
*/
@SuppressWarnings("TypeParameterHidesVisibleType")
protected class Entry
extends SafeHashMap.Entry
{
// ----- Map.Entry interface ------------------------------------
/**
* This method is invoked when the containing Map has actually
* added this Entry to itself.
*/
@SuppressWarnings("unchecked")
protected void onAdd()
{
// issue add notification
ObservableHashMap map = ObservableHashMap.this;
if (map.hasListeners())
{
map.dispatchEvent(new MapEvent(map, MapEvent.ENTRY_INSERTED,
getKey(), null, getValue()));
}
}
/**
* Update the cached value.
*
* @param oValue the new value to cache
*
* @return the old cache value
*/
@SuppressWarnings("unchecked")
public V setValue(V oValue)
{
ObservableHashMap map = ObservableHashMap.this;
synchronized (map)
{
// perform the entry update
V oPrev = super.setValue(oValue);
// note: previous to Coherence 3.1, there was an optimization
// that would only raise an event if the reference was changing,
// i.e. if (oPrev != oValue) {..}
// issue update notification
if (map.hasListeners())
{
map.dispatchEvent(new MapEvent(map, MapEvent.ENTRY_UPDATED,
getKey(), oPrev, oValue));
}
return oPrev;
}
}
/**
* Called to inform the Entry that it has been removed.
*/
@SuppressWarnings("unchecked")
protected void onRemove()
{
// issue remove notification
ObservableHashMap map = ObservableHashMap.this;
if (map.hasListeners())
{
map.dispatchEvent(new MapEvent(map, MapEvent.ENTRY_DELETED,
getKey(), getValue(), null));
}
}
}
// ----- helper methods -------------------------------------------------
@SuppressWarnings("unchecked")
protected void clear(boolean fTruncate)
{
// COH-6009: map mutations must be synchronized with event dispatch
// to ensure in-order delivery
if (fTruncate)
{
// clear the map
super.clear();
}
else
{
// grab a copy of all entries
SafeHashMap.Entry[] aeBucket = m_aeBucket;
// clear the map
super.clear();
// walk all buckets
for (int i = 0, c = aeBucket.length; i < c; ++i)
{
// walk all entries in the bucket
Entry entry = (Entry) aeBucket[i];
while (entry != null)
{
entry.onRemove();
entry = (Entry) entry.m_eNext;
}
}
}
}
// ----- data members ---------------------------------------------------
/**
* The MapListenerSupport object.
*/
protected transient MapListenerSupport m_listenerSupport;
}