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

org.jhotdraw8.icollection.ChampMap Maven / Gradle / Ivy

The newest version!
/*
 * @(#)SimpleImmutableMap.java
 * Copyright © 2023 The authors and contributors of JHotDraw. MIT License.
 */
package org.jhotdraw8.icollection;

import org.jhotdraw8.icollection.facade.ReadOnlySetFacade;
import org.jhotdraw8.icollection.immutable.ImmutableMap;
import org.jhotdraw8.icollection.impl.champmap.BitmapIndexedNode;
import org.jhotdraw8.icollection.impl.champmap.ChangeEvent;
import org.jhotdraw8.icollection.impl.champmap.EntryIterator;
import org.jhotdraw8.icollection.impl.champmap.Node;
import org.jhotdraw8.icollection.impl.iteration.IteratorSpliterator;
import org.jhotdraw8.icollection.impl.iteration.MappedIterator;
import org.jhotdraw8.icollection.readonly.ReadOnlyMap;
import org.jhotdraw8.icollection.readonly.ReadOnlySet;
import org.jhotdraw8.icollection.serialization.MapSerializationProxy;
import org.jspecify.annotations.Nullable;

import java.io.ObjectStreamException;
import java.io.Serial;
import java.io.Serializable;
import java.util.AbstractMap;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.Spliterator;

/**
 * Implements the {@link ImmutableMap} interface using a Compressed Hash-Array
 * Mapped Prefix-tree (CHAMP).
 * 

* Features: *

    *
  • supports up to 231 - 1 entries
  • *
  • allows null keys and null values
  • *
  • is immutable
  • *
  • is thread-safe
  • *
  • does not guarantee a specific iteration order
  • *
*

* Performance characteristics: *

    *
  • put: O(log₃₂ N)
  • *
  • remove: O(log₃₂ N)
  • *
  • containsKey: O(log₃₂ N)
  • *
  • toMutable: O(1) + O(log₃₂ N) distributed across subsequent updates in the mutable copy
  • *
  • clone: O(1)
  • *
  • iterator.next(): O(1)
  • *
*

* Implementation details: *

* This map performs read and write operations of single elements in O(log₃₂ N) time, * and in O(log₃₂ N) space. *

* The CHAMP trie contains nodes that may be shared with other maps. *

* If a write operation is performed on a node, then this map creates a * copy of the node and of all parent nodes up to the root (copy-path-on-write). *

* This map can create a mutable copy of itself in O(1) time and O(1) space * using method {@link #toMutable()}. The mutable copy shares its nodes * with this map, until it has gradually replaced the nodes with exclusively * owned nodes. *

* All operations on this map can be performed concurrently, without a need for * synchronisation. *

* The immutable version of this map extends from the non-public class * {@code ChampBitmapIndexNode}. This design safes 16 bytes for every instance, * and reduces the number of redirections for finding an element in the * collection by 1. *

* References: *

* Portions of the code in this class has been derived from 'The Capsule Hash Trie Collections Library'. *

*
Michael J. Steindorfer (2017). * Efficient Immutable Collections.
*
michael.steindorfer.name *
The Capsule Hash Trie Collections Library. *
Copyright (c) Michael Steindorfer. BSD-2-Clause License
*
github.com *
* * @param the key type * @param the value type */ @SuppressWarnings("exports") public class ChampMap implements ImmutableMap, Serializable { private static final ChampMap EMPTY = new ChampMap<>(BitmapIndexedNode.emptyNode(), 0); @Serial private static final long serialVersionUID = 0L; /** * We do not guarantee an iteration order. Make sure that nobody accidentally relies on it. */ static final int SALT = new Random().nextInt(); final transient BitmapIndexedNode root; final int size; /** * Creates a new instance with the provided privateData data object. *

* This constructor is intended to be called from a constructor * of the subclass, that is called from method {@link #newInstance(PrivateData)}. * * @param privateData an privateData data object */ @SuppressWarnings("unchecked") protected ChampMap(PrivateData privateData) { this(((Map.Entry, ?>) privateData.get()).getKey(), ((Map.Entry) privateData.get()).getValue()); } /** * Creates a new instance with the provided privateData object as its internal data structure. *

* Subclasses must override this method, and return a new instance of their subclass! * * @param privateData the internal data structure needed by this class for creating the instance. * @return a new instance of the subclass */ protected ChampMap newInstance(PrivateData privateData) { return new ChampMap<>(privateData); } private ChampMap newInstance(BitmapIndexedNode root, int size) { return newInstance(new PrivateData(new AbstractMap.SimpleImmutableEntry<>(root, size))); } ChampMap(BitmapIndexedNode root, int size) { this.root = root; this.size = size; } @Override public int characteristics() { return Spliterator.IMMUTABLE | Spliterator.SIZED | Spliterator.DISTINCT; } /** * Returns an immutable copy of the provided map. * * @param c a map * @param the key type * @param the value type * @return an immutable copy */ @SuppressWarnings("unchecked") public static ChampMap copyOf(Iterable> c) { return ChampMap.of().putAll(c); } /** * Returns an immutable copy of the provided map. * * @param map a map * @param the key type * @param the value type * @return an immutable copy */ public static ChampMap copyOf(Map map) { return ChampMap.of().putAll(map); } static boolean entryKeyEquals(SimpleImmutableEntry a, SimpleImmutableEntry b) { return Objects.equals(a.getKey(), b.getKey()); } static int keyHash(Object e) { return SALT ^ Objects.hashCode(e); } static int entryKeyHash(SimpleImmutableEntry e) { return SALT ^ keyHash(e.getKey()); } /** * Returns an empty immutable map. * * @param the key type * @param the value type * @return an empty immutable map */ @SuppressWarnings("unchecked") public static ChampMap of() { return (ChampMap) ChampMap.EMPTY; } /** * {@inheritDoc} */ @Override public ChampMap clear() { return isEmpty() ? this : of(); } /** * {@inheritDoc} */ @Override public boolean containsKey(@Nullable Object o) { @SuppressWarnings("unchecked") final K key = (K) o; return root.findByKey(key, keyHash(key), 0) != Node.NO_DATA; } @Override public boolean equals(@Nullable Object other) { if (other == this) { return true; } if (other instanceof ChampMap that) { return size == that.size && root.equivalent(that.root); } return ReadOnlyMap.mapEquals(this, other); } @Override @SuppressWarnings("unchecked") public @Nullable V get(Object o) { Object result = root.findByKey((K) o, keyHash(o), 0); return result == Node.NO_DATA ? null : (V) result; } /** * Update function for a map: we keep the old entry if it has the same * value as the new entry. * * @param oldv the old entry * @param newv the new entry * @param the key type * @param the value type * @return the old or the new entry */ @Nullable static SimpleImmutableEntry updateEntry(@Nullable SimpleImmutableEntry oldv, @Nullable SimpleImmutableEntry newv) { return Objects.equals(oldv.getValue(), newv.getValue()) ? oldv : newv; } @Override public int hashCode() { return ReadOnlyMap.iteratorToHashCode(iterator()); } @Override public boolean isEmpty() { return size == 0; } @Override public Iterator> iterator() { return new EntryIterator<>(root, null, null); } @Override public int maxSize() { return Integer.MAX_VALUE; } @Override public ChampMap put(K key, @Nullable V value) { var details = new ChangeEvent(); var newRootNode = root.put(null, key, value, keyHash(key), 0, details, ChampMap::keyHash); if (details.isModified()) { return newInstance(newRootNode, details.isReplaced() ? size : size + 1); } return this; } @Override public ChampMap putAll(Map m) { return (ChampMap) ImmutableMap.super.putAll(m); } @SuppressWarnings("unchecked") @Override public ChampMap putAll(Iterable> c) { var m = toMutable(); return m.putAll(c) ? m.toImmutable() : this; } @Override public ChampMap remove(K key) { int keyHash = keyHash(key); var details = new ChangeEvent(); var newRootNode = root.remove(null, key, keyHash, 0, details); if (details.isModified()) { return size == 1 ? ChampMap.of() : newInstance(newRootNode, size - 1); } return this; } @SuppressWarnings("unchecked") @Override public ChampMap removeAll(Iterable c) { var m = toMutable(); return m.removeAll(c) ? m.toImmutable() : this; } @SuppressWarnings("unchecked") @Override public ChampMap retainAll(Iterable c) { var m = toMutable(); return m.retainAll(c) ? m.toImmutable() : this; } @Override public ReadOnlySet readOnlyKeySet() { return new ReadOnlySetFacade<>( () -> new MappedIterator<>(new EntryIterator<>(root, null, null), Map.Entry::getKey), this::size, this::containsKey, Spliterator.IMMUTABLE); } @Override public int size() { return size; } public Spliterator> spliterator() { return new IteratorSpliterator<>(iterator(), size(), characteristics(), null); } /** * Creates a mutable copy of this map. * * @return a mutable CHAMP map */ @Override public MutableChampMap toMutable() { return new MutableChampMap<>(this); } @Override public MutableChampMap asMap() { return new MutableChampMap<>(this); } /** * Returns a string representation of this map. *

* The string representation is consistent with the one produced * by {@link AbstractMap#toString()}. * * @return a string representation */ @Override public String toString() { return ReadOnlyMap.mapToString(this); } @Serial private Object writeReplace() throws ObjectStreamException { return new SerializationProxy<>(this.toMutable()); } static class SerializationProxy extends MapSerializationProxy { @Serial private static final long serialVersionUID = 0L; SerializationProxy(Map target) { super(target); } @Serial @Override protected Object readResolve() { return ChampMap.of().putAll(deserializedEntries); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy