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

com.oracle.coherence.common.collections.InflatableMap Maven / Gradle / Ivy

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

import java.io.Externalizable;
import java.io.IOException;
import java.io.NotActiveException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;

import java.lang.reflect.Array;

import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;


/**
* An implementation of java.util.Map that is optimal (in terms of both size
* and speed) for very small sets of data but still works excellently with
* large sets of data.  This implementation is not thread-safe.
* 

* The InflatableMap implementation switches at runtime between several different * sub-implementations for storing the Map of objects, described here: * *

    *
  1. "empty map" - a map that contains no data; *
  2. "single entry" - a reference directly to a single map entry *
  3. "Object[]" - a reference to an array of entries; the item limit for * this implementation is determined by the THRESHOLD constant; *
  4. "delegation" - for more than THRESHOLD items, a map is created to * delegate the map management to; sub-classes can override the default * delegation class (java.util.HashMap) by overriding the factory method * instantiateMap. *
*

* The InflatableMap implementation supports the null key value. * * @author cp 06/29/99 */ public class InflatableMap extends AbstractMap implements Cloneable, Externalizable { // ----- constructors --------------------------------------------------- /** * Construct a InflatableMap. */ public InflatableMap() { } /** * Construct a InflatableMap with the same mappings as the given map. * * @param map the map whose mappings are to be placed in this map. */ public InflatableMap(Map map) { putAll(map); } // ----- Map interface -------------------------------------------------- /** * Returns true if this map contains no key-value mappings. * * @return true if this map contains no key-value mappings */ public boolean isEmpty() { return m_nImpl == I_EMPTY; } /** * Returns the number of key-value mappings in this map. * * @return the number of key-value mappings in this map */ public int size() { switch (m_nImpl) { case I_EMPTY: return 0; case I_SINGLE: return 1; case I_ARRAY_1: case I_ARRAY_2: case I_ARRAY_3: case I_ARRAY_4: case I_ARRAY_5: case I_ARRAY_6: case I_ARRAY_7: case I_ARRAY_8: return m_nImpl - I_ARRAY_1 + 1; case I_OTHER: return ((Map) m_oContents).size(); default: throw new IllegalStateException(); } } /** * 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) { switch (m_nImpl) { case I_EMPTY: return false; case I_SINGLE: { Object oKeyEntry = ((Map.Entry) m_oContents).getKey(); return Objects.equals(oKey, oKeyEntry); } case I_ARRAY_1: case I_ARRAY_2: case I_ARRAY_3: case I_ARRAY_4: case I_ARRAY_5: case I_ARRAY_6: case I_ARRAY_7: case I_ARRAY_8: { // "Entry[]" implementation Map.Entry[] aEntry = (Map.Entry[]) m_oContents; int c = m_nImpl - I_ARRAY_1 + 1; return indexOf(aEntry, c, oKey) >= 0; } case I_OTHER: return ((Map) m_oContents).containsKey(oKey); default: throw new IllegalStateException(); } } /** * 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 V get(Object oKey) { switch (m_nImpl) { case I_EMPTY: return null; case I_SINGLE: { Map.Entry entry = (Map.Entry) m_oContents; K entryKey = entry.getKey(); return Objects.equals(oKey, entryKey) ? entry.getValue() : null; } case I_ARRAY_1: case I_ARRAY_2: case I_ARRAY_3: case I_ARRAY_4: case I_ARRAY_5: case I_ARRAY_6: case I_ARRAY_7: case I_ARRAY_8: { // "Entry[]" implementation Map.Entry[] aEntry = (Map.Entry[]) m_oContents; int c = m_nImpl - I_ARRAY_1 + 1; int i = indexOf(aEntry, c, oKey); return i < 0 ? null : aEntry[i].getValue(); } case I_OTHER: return ((Map) m_oContents).get(oKey); default: throw new IllegalStateException(); } } /** * Associates the specified value with the specified key in this map. * * @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 null * if there was no mapping for key */ public V put(K key, V value) { switch (m_nImpl) { case I_EMPTY: m_oContents = instantiateEntry(key, value); m_nImpl = I_SINGLE; return null; case I_SINGLE: { Map.Entry entry = (Map.Entry) m_oContents; K entryKey = entry.getKey(); V prevValue = null; if (Objects.equals(key, entryKey)) { prevValue = entry.getValue(); entry.setValue(value); } else { // grow to array implementation Map.Entry[] aEntry = new Map.Entry[THRESHOLD]; aEntry[0] = entry; aEntry[1] = instantiateEntry(key, value); m_nImpl = I_ARRAY_2; m_oContents = aEntry; } return prevValue; } case I_ARRAY_1: case I_ARRAY_2: case I_ARRAY_3: case I_ARRAY_4: case I_ARRAY_5: case I_ARRAY_6: case I_ARRAY_7: case I_ARRAY_8: { // "Entry[]" implementation int nImpl = m_nImpl; Map.Entry[] aEntry = (Map.Entry[]) m_oContents; int c = nImpl - I_ARRAY_1 + 1; int i = indexOf(aEntry, c, key); if (i >= 0) { Map.Entry entry = aEntry[i]; V prevValue = entry.getValue(); entry.setValue(value); return prevValue; } // check if adding the object exceeds the "lite" threshold if (c >= THRESHOLD) { // time to switch to a different map implementation Map map = instantiateMap(); for (i = 0; i < c; ++i) { Map.Entry entry = aEntry[i]; map.put(entry.getKey(), entry.getValue()); } map.put(key, value); m_nImpl = I_OTHER; m_oContents = map; } else { // use the next available element in the array aEntry[c] = instantiateEntry(key, value); m_nImpl = (byte) (nImpl + 1); } return null; } case I_OTHER: return ((Map) m_oContents).put(key, value); default: throw new IllegalStateException(); } } /** * 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) { switch (m_nImpl) { case I_EMPTY: return null; case I_SINGLE: { Map.Entry entry = (Map.Entry) m_oContents; K entryKey = entry.getKey(); V prevValue = null; if (Objects.equals(oKey, entryKey)) { prevValue = entry.getValue(); m_nImpl = I_EMPTY; m_oContents = null; } return prevValue; } case I_ARRAY_1: case I_ARRAY_2: case I_ARRAY_3: case I_ARRAY_4: case I_ARRAY_5: case I_ARRAY_6: case I_ARRAY_7: case I_ARRAY_8: { // "Entry[]" implementation int nImpl = m_nImpl; Map.Entry[] aEntry = (Map.Entry[]) m_oContents; int c = nImpl - I_ARRAY_1 + 1; int i = indexOf(aEntry, c, oKey); if (i < 0) { return null; } V prevValue = aEntry[i].getValue(); if (c == 1) { m_nImpl = I_EMPTY; m_oContents = null; } else { System.arraycopy(aEntry, i + 1, aEntry, i, c - i - 1); aEntry[c-1] = null; m_nImpl = (byte) --nImpl; } return prevValue; } case I_OTHER: { Map map = (Map) m_oContents; V prevValue = map.remove(oKey); checkShrinkFromOther(); return prevValue; } default: throw new IllegalStateException(); } } /** * Clear all key/value mappings. */ public void clear() { m_nImpl = I_EMPTY; m_oContents = null; } // ----- Cloneable interface -------------------------------------------- /** * Create a clone of the ImmutableArrayList. * * @return a clone of this list */ public Object clone() { InflatableMap that; try { that = (InflatableMap) super.clone(); } catch (CloneNotSupportedException e) { throw ensureRuntimeException(e); } switch (this.m_nImpl) { case I_EMPTY: // nothing to do break; case I_SINGLE: { Map.Entry entry = (Map.Entry) m_oContents; that.m_oContents = that.instantiateEntry(entry.getKey(), entry.getValue()); } break; case I_ARRAY_1: case I_ARRAY_2: case I_ARRAY_3: case I_ARRAY_4: case I_ARRAY_5: case I_ARRAY_6: case I_ARRAY_7: case I_ARRAY_8: { Map.Entry[] aEntryThis = (Map.Entry[]) this.m_oContents; Map.Entry[] aEntryThat = new Map.Entry[THRESHOLD]; for (int i = 0, c = m_nImpl - I_ARRAY_1 + 1; i < c; ++i) { Map.Entry entryThis = aEntryThis[i]; aEntryThat[i] = that.instantiateEntry( entryThis.getKey(), entryThis.getValue()); } that.m_oContents = aEntryThat; } break; case I_OTHER: Map mapThis = (Map) this.m_oContents; Map mapThat = that.instantiateMap(); mapThat.putAll(mapThis); that.m_oContents = mapThat; break; default: throw new IllegalStateException(); } return that; } // ----- inner class: EntrySet ------------------------------------------ /** * Returns a set view of the mappings contained in this map. Each element * in the returned set is an {@link java.util.Map.Entry Map 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() { return instantiateEntrySet(); } /** * A Set of entries backed by this Map. */ protected class EntrySet extends AbstractSet> implements Serializable { /** * Returns an iterator over the elements contained in this collection. * * @return an iterator over the elements contained in this collection */ public Iterator> iterator() { InflatableMap map = InflatableMap.this; int c = map.size(); return c == 0 ? NULL_ITERATOR : new EntryIterator<>(map, (Map.Entry[]) toArray(new Map.Entry[c])); } /** * Returns true if this Set is empty. * * @return true if this Set is empty */ public boolean isEmpty() { return InflatableMap.this.isEmpty(); } /** * Returns the number of elements in this collection. * * @return the number of elements in this collection */ public int size() { return InflatableMap.this.size(); } /** * 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(); InflatableMap map = InflatableMap.this; Object oActual = map.get(oKey); return oActual == null ? oValue == null && map.containsKey(oKey) : Objects.equals(oValue, oActual); } return false; } /** * Returns an array containing all of the elements in this collection. * * @return an array containing all of the elements in this collection */ public Object[] toArray() { return toArray((Object[]) null); } /** * Returns an array with a runtime type is that of the specified array * and that contains all of the elements in this collection. If the * collection fits in the specified array, it is returned therein. * Otherwise, a new array is allocated with the runtime type of the * specified array and the size of this collection. *

* If the collection fits in the specified array with room to spare * (i.e. the array has more elements than the collection), the element * in the array immediately following the end of the collection is set * to null. This is useful in determining the length of the * collection only if the caller knows that the collection does * not contain any null elements.) * * @param ao the array into which the elements of the collection are * to be stored, if it is big enough; otherwise, a new * array of the same runtime type is allocated for this * purpose * * @return an array containing the elements of the collection * * @throws ArrayStoreException if the runtime type of the specified * array is not a supertype of the runtime type of every * element in this collection */ public Object[] toArray(Object ao[]) { InflatableMap map = InflatableMap.this; // create the array to store the map contents int c = map.size(); if (ao == null) { ao = c == 0 ? NO_OBJECTS : new Object[c]; } else if (ao.length < c) { // if it is not big enough, a new array of the same runtime // type is allocated ao = (Object[]) Array.newInstance(ao.getClass().getComponentType(), c); } else if (ao.length > c) { // if the collection fits in the specified array with room to // spare, the element in the array immediately following the // end of the collection is set to null ao[c] = null; } switch (map.m_nImpl) { case I_EMPTY: break; case I_SINGLE: ao[0] = map.m_oContents; break; case I_ARRAY_1: case I_ARRAY_2: case I_ARRAY_3: case I_ARRAY_4: case I_ARRAY_5: case I_ARRAY_6: case I_ARRAY_7: case I_ARRAY_8: System.arraycopy((Map.Entry[]) m_oContents, 0, ao, 0, c); break; case I_OTHER: ao = ((Map) m_oContents).entrySet().toArray(ao); break; default: throw new IllegalStateException(); } return ao; } } // ----- Externalizable interface --------------------------------------- /** * Initialize this object from the data in the passed ObjectInput stream. * * @param in the stream to read data from in order to restore the object * * @exception IOException if an I/O exception occurs */ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { if (!isEmpty()) { throw new NotActiveException(); } int c = in.readInt(); switch (c) { case 0: break; case 1: m_nImpl = I_SINGLE; m_oContents = instantiateEntry((K) in.readObject(), (V) in.readObject()); break; case 2: case 3: case 4: case 5: case 6: case 7: case 8: { Map.Entry[] aEntry = new Map.Entry[THRESHOLD]; for (int i = 0; i < c; ++i) { aEntry[i] = instantiateEntry((K) in.readObject(), (V) in.readObject()); } m_nImpl = (byte) (I_ARRAY_1 + c - 1); m_oContents = aEntry; } break; default: { Map map = instantiateMap(); for (int i = 0; i < c; ++i) { map.put((K) in.readObject(), (V) in.readObject()); } m_nImpl = I_OTHER; m_oContents = map; } break; } } /** * Write this object's data to the passed ObjectOutput stream. * * @param out the stream to write the object to * * @exception IOException if an I/O exception occurs */ public synchronized void writeExternal(ObjectOutput out) throws IOException { // format is int size followed by that many key/value pairs int nImpl = m_nImpl; switch (nImpl) { case I_EMPTY: out.writeInt(0); break; case I_SINGLE: { Map.Entry entry = (Map.Entry) m_oContents; out.writeInt(1); out.writeObject(entry.getKey()); out.writeObject(entry.getValue()); } break; case I_ARRAY_1: case I_ARRAY_2: case I_ARRAY_3: case I_ARRAY_4: case I_ARRAY_5: case I_ARRAY_6: case I_ARRAY_7: case I_ARRAY_8: { Map.Entry[] aEntry = (Map.Entry[]) m_oContents; int c = nImpl - I_ARRAY_1 + 1; out.writeInt(c); for (int i = 0; i < c; ++i) { Map.Entry entry = aEntry[i]; out.writeObject(entry.getKey()); out.writeObject(entry.getValue()); } } break; case I_OTHER: { Map map = ((Map) m_oContents); int c = map.size(); Map.Entry[] aEntry = (Map.Entry[]) map.entrySet().toArray(new Map.Entry[c]); out.writeInt(c); for (int i = 0; i < c; ++i) { Map.Entry entry = aEntry[i]; out.writeObject(entry.getKey()); out.writeObject(entry.getValue()); } } break; default: throw new IllegalStateException(); } } // ----- internal methods ----------------------------------------------- /** * (Factory pattern) Instantiate a Map Entry. * This method permits inheriting classes to easily override the * implementation of the Entry object. * * @param key the key * @param value the value * * @return an instance of a Map Entry */ protected Map.Entry instantiateEntry(K key, V value) { return new SimpleEntry(key, value); } /** * (Factory pattern) Instantiate an Entry Set. * This method permits inheriting classes to easily override the * implementation of the EntrySet object. * * @return an instance of Entry Set */ protected Set> instantiateEntrySet() { return new EntrySet(); } /** * (Factory pattern) Instantiate a Map object to store entries in once * the "lite" threshold has been exceeded. This method permits inheriting * classes to easily override the choice of the Map object. * * @return an instance of Map */ protected Map instantiateMap() { return new HashMap<>(); } /** * Scan up to the first c elements of the passed Entry array * aEntry looking for the specified key key. If it is * found, return its position i in the array such that * (0 <= i < c). If it is not found, return -1. * * @param aEntry the array of objects to search * @param c the number of Entry objects in the array to search * @param oKey the key to look for * * @return the index of the object, if found; otherwise -1 */ private int indexOf(Map.Entry[] aEntry, int c, Object oKey) { // first quick-scan by reference for (int i = 0; i < c; ++i) { if (oKey == aEntry[i].getKey()) { return i; } } // slow scan by equals() if (oKey != null) { for (int i = 0; i < c; ++i) { if (oKey.equals(aEntry[i].getKey())) { return i; } } } return -1; } /** * Return the specified exception as a RuntimeException, wrapping it if necessary. * * @param t the exception * * @return the RuntimeException */ protected RuntimeException ensureRuntimeException(Throwable t) { return t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t); } /** * After a mutation operation has reduced the size of an underlying Map, * check if the delegation model should be replaced with a more size- * efficient storage approach, and switch accordingly. */ protected void checkShrinkFromOther() { assert m_nImpl == I_OTHER; // check if the Map is now significantly below the "lite" // threshold Map map = (Map) m_oContents; int c = map.size(); switch (c) { case 0: m_nImpl = I_EMPTY; m_oContents = null; break; case 1: { Map.Entry entry = (Map.Entry) map.entrySet().toArray()[0]; m_oContents = instantiateEntry(entry.getKey(), entry.getValue()); m_nImpl = I_SINGLE; } break; case 2: case 3: case 4: { // shrink to "Entry[]" implementation Map.Entry[] aEntry = new Map.Entry[THRESHOLD]; int i = 0; for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); ) { Map.Entry entry = (Map.Entry) iter.next(); aEntry[i++] = instantiateEntry(entry.getKey(), entry.getValue()); } assert i == c; m_nImpl = (byte) (I_ARRAY_1 + i - 1); m_oContents = aEntry; } break; } } // ----- inner class: EntryIterator ------------------------------------- /** * A simple Iterator for InflatableMap Entry objects. This class is static in * order to allow the EntrySet to be quickly garbage-collected. */ public static class EntryIterator implements Iterator> { // ----- constructors ------------------------------------------- /** * Construct an EntryIterator. * * @param map the InflatableMap to delegate remove() calls to * @param aEntry the array of Map Entry objects to iterate */ public EntryIterator(InflatableMap map, Map.Entry[] aEntry) { m_map = map; m_aEntry = aEntry; } // ----- 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_iPrev + 1 < m_aEntry.length; } /** * Returns the next element in the iteration. * * @return the next element in the iteration * * @exception NoSuchElementException iteration has no more * elements */ public Entry next() { int iNext = m_iPrev + 1; if (iNext < m_aEntry.length) { m_iPrev = iNext; m_fCanRemove = true; return m_aEntry[iNext]; } else { throw new NoSuchElementException(); } } /** * Removes from the underlying set 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 set 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() { if (m_fCanRemove) { m_fCanRemove = false; m_map.remove(m_aEntry[m_iPrev].getKey()); } else { throw new IllegalStateException(); } } // ----- data members ------------------------------------------- /** * The InflatableMap. Having this field allows the EntrySet to be * quickly collected by a GC, because the EntryIterator does not * have to maintain a reference to it in order to get to the InflatableMap. */ Map m_map; /** * The entries to iterate. */ Map.Entry[] m_aEntry; /** * The previous index iterated. */ int m_iPrev = -1; /** * True if the previously iterated element can be removed. */ boolean m_fCanRemove = false; } // ----- constants ------------------------------------------------------ /** * A constant array of zero size. (This saves having to allocate what * should be a constant.) */ private static final Object[] NO_OBJECTS = new Object[0]; /** * The default point above which the InflatableMap delegates to another Map * implementation. */ protected static final int THRESHOLD = 8; /** * Implementation: Empty Map. */ private static final int I_EMPTY = 0; /** * Implementation: Single-item Map. */ private static final int I_SINGLE = 1; /** * Implementation: Array Map of 1 item. */ private static final int I_ARRAY_1 = 2; /** * Implementation: Array Map of 2 items. */ private static final int I_ARRAY_2 = 3; /** * Implementation: Array Map of 3 items. */ private static final int I_ARRAY_3 = 4; /** * Implementation: Array Map of 4 items. */ private static final int I_ARRAY_4 = 5; /** * Implementation: Array Map of 5 items. */ private static final int I_ARRAY_5 = 6; /** * Implementation: Array Map of 6 items. */ private static final int I_ARRAY_6 = 7; /** * Implementation: Array Map of 7 items. */ private static final int I_ARRAY_7 = 8; /** * Implementation: Array Map of 8 items. */ private static final int I_ARRAY_8 = 9; /** * Implementation: Delegation. */ protected static final int I_OTHER = 10; /** * An empty iterator. */ private static final Iterator NULL_ITERATOR = new Iterator() { @Override public boolean hasNext() { return false; } @Override public Object next() { throw new NoSuchElementException(); } }; // ----- data members --------------------------------------------------- /** * Implementation, one of I_EMPTY, I_SINGLE, I_ARRAY_* or I_OTHER. */ protected byte m_nImpl; /** * The Map contents, based on the implementation being used. */ protected Object m_oContents; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy