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

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

The newest version!
/*
 * @(#)MutableChampMap.java
 * Copyright © 2023 The authors and contributors of JHotDraw. MIT License.
 */

package org.jhotdraw8.icollection;

import org.jhotdraw8.icollection.facade.SetFacade;
import org.jhotdraw8.icollection.impl.champmap.AbstractMutableChampMap;
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.FailFastIterator;
import org.jhotdraw8.icollection.impl.iteration.IteratorSpliterator;
import org.jhotdraw8.icollection.serialization.MapSerializationProxy;
import org.jspecify.annotations.Nullable;

import java.io.Serial;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Spliterator;

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

* Features: *

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

* Performance characteristics: *

    *
  • put: O(log₃₂ N)
  • *
  • remove: O(log₃₂ N)
  • *
  • containsKey: O(log₃₂ N)
  • *
  • toImmutable: O(1) + O(log₃₂ N) distributed across subsequent updates in * this map
  • *
  • clone: O(1) + O(log₃₂ N) distributed across subsequent updates in this * map and in the clone
  • *
  • iterator.next: O(1)
  • *
*

* Implementation details: *

* See description at {@link ChampMap}. *

* 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 */ public class MutableChampMap extends AbstractMutableChampMap { @Serial private static final long serialVersionUID = 0L; /** * Constructs a new empty map. */ public MutableChampMap() { root = BitmapIndexedNode.emptyNode(); } /** * Constructs a map containing the same entries as in the specified * {@link Map}. * * @param m a map */ @SuppressWarnings("this-escape") public MutableChampMap(Map m) { if (m instanceof MutableChampMap) { @SuppressWarnings("unchecked") MutableChampMap that = (MutableChampMap) m; this.root = that.root; this.size = that.size; this.modCount = 0; } else { this.root = BitmapIndexedNode.emptyNode(); this.putAll(m); } } /** * Constructs a map containing the same entries as in the specified * {@link Iterable}. * * @param m an iterable */ @SuppressWarnings("this-escape") public MutableChampMap(Iterable> m) { if (m instanceof ChampMap) { @SuppressWarnings("unchecked") ChampMap that = (ChampMap) m; this.root = that.root; this.size = that.size; } else { this.root = BitmapIndexedNode.emptyNode(); for (Entry e : m) { this.put(e.getKey(), e.getValue()); } } } /** * Removes all entries from this map. */ @Override public void clear() { root = BitmapIndexedNode.emptyNode(); size = 0; modCount++; } /** * Returns a shallow copy of this map. */ @Override public MutableChampMap clone() { return (MutableChampMap) super.clone(); } @Override @SuppressWarnings("unchecked") public boolean containsKey(@Nullable Object o) { return root.findByKey((K) o, ChampMap.keyHash(o), 0) != Node.NO_DATA; } @Override public Iterator> iterator() { return new FailFastIterator<>( new EntryIterator<>(root, this::iteratorRemoveKey, this::iteratorPutIfPresent), this::getModCount ); } @Override public Spliterator> spliterator() { return new IteratorSpliterator<>(iterator(), size(), Spliterator.NONNULL | characteristics(), null); } /** * Returns a {@link Set} view of the entries contained in this map. * * @return a view of the entries contained in this map */ @Override public Set> entrySet() { return new SetFacade<>( this::iterator, this::spliterator, this::size, this::containsEntry, this::clear, null, this::removeEntry ); } /** * Returns the value to which the specified key is mapped, * or {@code null} if this map contains no entry for the key. * * @param o the key whose associated value is to be returned * @return the associated value or null */ @Override @SuppressWarnings("unchecked") public @Nullable V get(Object o) { Object result = root.findByKey((K) o, ChampMap.keyHash(o), 0); return result == Node.NO_DATA ? null : (V) result; } private void iteratorPutIfPresent(@Nullable K k, @Nullable V v) { if (containsKey(k)) { owner = null; put(k, v); } } @Override public @Nullable V put(K key, V value) { return putEntry(key, value).getOldValue(); } /* @Override @SuppressWarnings("unchecked") public boolean putAll(Iterable> c) { if (c instanceof MutableChampMap m) { c = (Iterable>) m.toImmutable(); } if (isEmpty() && c instanceof SimpleImmutableMap that) { if (that.isEmpty()) { return false; } root = (BitmapIndexedNode) (BitmapIndexedNode) that.root; size = that.size; modCount++; return true; } if (c instanceof SimpleImmutableMap that) { var bulkChange = new BulkChangeEvent(); var newRootNode = root.putAll(getOrCreateOwner(), (Node>) (Node) that.root, 0, bulkChange, SimpleImmutableMap::updateEntry, SimpleImmutableMap::entryKeyEquals, SimpleImmutableMap::entryKeyHash, new ChangeEvent<>()); if (bulkChange.inBoth == that.size() && !bulkChange.replaced) { return false; } root = newRootNode; size += that.size - bulkChange.inBoth; modCount++; return true; } return super.putAll(c); }*/ ChangeEvent putEntry(@Nullable K key, @Nullable V val) { int keyHash = ChampMap.keyHash(key); ChangeEvent details = new ChangeEvent<>(); root = root.put(getOrCreateOwner(), key, val, keyHash, 0, details, ChampMap::keyHash); if (details.isModified() && !details.isReplaced()) { size += 1; modCount++; } return details; } @Override public V remove(Object o) { @SuppressWarnings("unchecked") final K key = (K) o; return removeKey(key).getOldValue(); } @Override public boolean removeAll(Iterable c) { return super.removeAll(c); } /* @SuppressWarnings("unchecked") @Override public boolean retainAll(Iterable c) { if (isEmpty()) { return false; } if ((c instanceof Collection cc && cc.isEmpty()) || (c instanceof ReadOnlyCollection rc) && rc.isEmpty()) { clear(); return true; } BulkChangeEvent bulkChange = new BulkChangeEvent(); BitmapIndexedNode newRootNode; if (c instanceof Collection that) { newRootNode = root.filterAll(getOrCreateOwner(), e -> that.contains(e.getKey()), 0, bulkChange); } else if (c instanceof ReadOnlyCollection that) { newRootNode = root.filterAll(getOrCreateOwner(), e -> that.contains(e.getKey()), 0, bulkChange); } else { HashSet that = new HashSet<>(); c.forEach(that::add); newRootNode = root.filterAll(getOrCreateOwner(), that::contains, 0, bulkChange); } if (bulkChange.removed == 0) { return false; } root = newRootNode; size -= bulkChange.removed; modCount++; return true; }*/ ChangeEvent removeKey(K key) { int keyHash = ChampMap.keyHash(key); ChangeEvent details = new ChangeEvent<>(); root = root.remove(getOrCreateOwner(), key, keyHash, 0, details); if (details.isModified()) { size = size - 1; modCount++; } return details; } void iteratorRemoveKey(K key) { // Note: mutator must be null, because we must not change the structure of the trie, while iterating over it. int keyHash = ChampMap.keyHash(key); ChangeEvent details = new ChangeEvent<>(); root = root.remove(null, key, keyHash, 0, details); if (details.isModified()) { size = size - 1; modCount++; } } @SuppressWarnings("unchecked") protected boolean removeEntry(@Nullable Object o) { if (containsEntry(o)) { assert o != null; @SuppressWarnings("unchecked") Entry entry = (Entry) o; remove(entry.getKey()); return true; } return false; } /** * Returns an immutable copy of this map. * * @return an immutable copy */ public ChampMap toImmutable() { owner = null; return isEmpty() ? ChampMap.of() : new ChampMap<>(root, size); } @Serial private Object writeReplace() { return new SerializationProxy<>(this); } private static class SerializationProxy extends MapSerializationProxy { @Serial private static final long serialVersionUID = 0L; protected SerializationProxy(Map target) { super(target); } @Serial @Override protected Object readResolve() { return new MutableChampMap<>(deserializedEntries); } } }