mapper, int fromIndex, int additionalCharacteristics, long est) {
+ super(est, additionalCharacteristics);
+ this.vector = vector;
+ this.mapper = mapper;
+ index = vector.size() - 1-fromIndex;
+ }
+
+ @Override
+ public boolean tryAdvance(Consumer super K> action) {
+ if (moveNext()) {
+ action.accept(current);
+ return true;
+ }
+ return false;
+ }
+
+ boolean moveNext() {
+ if (index < 0) {
+ return false;
+ }
+ Object o = vector.get(index--);
+ if (o instanceof ChampTombstone) {
+ ChampTombstone t = (ChampTombstone) o;
+ index -= t.before();
+ o = vector.get(index--);
+ }
+ current = mapper.apply(o);
+ return true;
+ }
+
+ K current() {
+ return current;
+ }
+ }
+
+ /**
+ * A {@code SequencedData} stores a sequence number plus some data.
+ *
+ * {@code SequencedData} objects are used to store sequenced data in a CHAMP
+ * trie (see {@link ChampTrie.Node}).
+ *
+ * The kind of data is specified in concrete implementations of this
+ * interface.
+ *
+ * All sequence numbers of {@code SequencedData} objects in the same CHAMP trie
+ * are unique. Sequence numbers range from {@link Integer#MIN_VALUE} (exclusive)
+ * to {@link Integer#MAX_VALUE} (inclusive).
+ *
+ * References:
+ *
+ * The code in this class has been derived from JHotDraw 8.
+ *
+ * JHotDraw 8. Copyright © 2023 The authors and contributors of JHotDraw.
+ * MIT License .
+ * github.com
+ *
+ */
+ static interface ChampSequencedData {
+ /**
+ * We use {@link Integer#MIN_VALUE} to detect overflows in the sequence number.
+ *
+ * {@link Integer#MIN_VALUE} is the only integer number which can not
+ * be negated.
+ *
+ * Therefore, we can not use {@link Integer#MIN_VALUE} as a sequence number
+ * anyway.
+ */
+ int NO_SEQUENCE_NUMBER = Integer.MIN_VALUE;
+
+ static ChampTrie.BitmapIndexedNode buildSequencedTrie(ChampTrie.BitmapIndexedNode root, ChampTrie.IdentityObject owner) {
+ ChampTrie.BitmapIndexedNode seqRoot = emptyNode();
+ ChampTrie.ChangeEvent details = new ChampTrie.ChangeEvent<>();
+ for (ChampIteration.ChampSpliterator i = new ChampIteration.ChampSpliterator(root, null, 0, 0); i.moveNext(); ) {
+ K elem = i.current();
+ seqRoot = seqRoot.put(owner, elem, seqHash(elem.getSequenceNumber()),
+ 0, details, (oldK, newK) -> oldK, ChampSequencedData::seqEquals, ChampSequencedData::seqHash);
+ }
+ return seqRoot;
+ }
+
+ /**
+ * Returns true if the sequenced elements must be renumbered because
+ * {@code first} or {@code last} are at risk of overflowing.
+ *
+ * {@code first} and {@code last} are estimates of the first and last
+ * sequence numbers in the trie. The estimated extent may be larger
+ * than the actual extent, but not smaller.
+ *
+ * @param size the size of the trie
+ * @param first the estimated first sequence number
+ * @param last the estimated last sequence number
+ * @return
+ */
+ static boolean mustRenumber(int size, int first, int last) {
+ return size == 0 && (first != -1 || last != 0)
+ || last > Integer.MAX_VALUE - 2
+ || first < Integer.MIN_VALUE + 2;
+ }
+
+ static Vector vecBuildSequencedTrie(ChampTrie.BitmapIndexedNode root, ChampTrie.IdentityObject owner, int size) {
+ ArrayList list = new ArrayList<>(size);
+ for (ChampIteration.ChampSpliterator i = new ChampIteration.ChampSpliterator(root, Function.identity(), 0, Long.MAX_VALUE); i.moveNext(); ) {
+ list.add(i.current());
+ }
+ list.sort(Comparator.comparing(ChampSequencedData::getSequenceNumber));
+ return Vector.ofAll(list);
+ }
+
+ static boolean vecMustRenumber(int size, int offset, int vectorSize) {
+ return size == 0
+ || vectorSize >>> 1 > size
+ || (long) vectorSize - offset > Integer.MAX_VALUE - 2
+ || offset < Integer.MIN_VALUE + 2;
+ }
+
+ /**
+ * Renumbers the sequence numbers in all nodes from {@code 0} to {@code size}.
+ *
+ * Afterwards the sequence number for the next inserted entry must be
+ * set to the value {@code size};
+ *
+ * @param size the size of the trie
+ * @param root the root of the trie
+ * @param sequenceRoot the sequence root of the trie
+ * @param owner the owner that will own the renumbered trie
+ * @param hashFunction the hash function for data elements
+ * @param equalsFunction the equals function for data elements
+ * @param factoryFunction the factory function for data elements
+ * @param
+ * @return a new renumbered root
+ */
+ static ChampTrie.BitmapIndexedNode renumber(int size,
+ ChampTrie.BitmapIndexedNode root,
+ ChampTrie.BitmapIndexedNode sequenceRoot,
+ ChampTrie.IdentityObject owner,
+ ToIntFunction hashFunction,
+ BiPredicate equalsFunction,
+ BiFunction factoryFunction
+
+ ) {
+ if (size == 0) {
+ return root;
+ }
+ ChampTrie.BitmapIndexedNode newRoot = root;
+ ChampTrie.ChangeEvent details = new ChampTrie.ChangeEvent<>();
+ int seq = 0;
+
+ for (ChampIteration.ChampSpliterator i = new ChampIteration.ChampSpliterator<>(sequenceRoot, Function.identity(), 0, 0); i.moveNext(); ) {
+ K e = i.current();
+ K newElement = factoryFunction.apply(e, seq);
+ newRoot = newRoot.put(owner,
+ newElement,
+ Objects.hashCode(e), 0, details,
+ (oldk, newk) -> oldk.getSequenceNumber() == newk.getSequenceNumber() ? oldk : newk,
+ equalsFunction, hashFunction);
+ seq++;
+ }
+ return newRoot;
+ }
+
+ /**
+ * Renumbers the sequence numbers in all nodes from {@code 0} to {@code size}.
+ *
+ * Afterward, the sequence number for the next inserted entry must be
+ * set to the value {@code size};
+ *
+ * @param
+ * @param size the size of the trie
+ * @param root the root of the trie
+ * @param vector the sequence root of the trie
+ * @param owner the owner that will own the renumbered trie
+ * @param hashFunction the hash function for data elements
+ * @param equalsFunction the equals function for data elements
+ * @param factoryFunction the factory function for data elements
+ * @return a new renumbered root and a new vector with matching entries
+ */
+ @SuppressWarnings("unchecked")
+ static Tuple2, Vector> vecRenumber(
+ int size,
+ ChampTrie.BitmapIndexedNode root,
+ Vector vector,
+ ChampTrie.IdentityObject owner,
+ ToIntFunction hashFunction,
+ BiPredicate equalsFunction,
+ BiFunction factoryFunction) {
+ if (size == 0) {
+ new Tuple2<>(root, vector);
+ }
+ ChampTrie.BitmapIndexedNode renumberedRoot = root;
+ Vector renumberedVector = Vector.of();
+ ChampTrie.ChangeEvent details = new ChampTrie.ChangeEvent<>();
+ BiFunction forceUpdate = (oldk, newk) -> newk;
+ int seq = 0;
+ for (ChampVectorSpliterator i = new ChampVectorSpliterator(vector, o -> (K) o, 0, Long.MAX_VALUE, 0); i.moveNext(); ) {
+ K current = i.current();
+ K data = factoryFunction.apply(current, seq++);
+ renumberedVector = renumberedVector.append(data);
+ renumberedRoot = renumberedRoot.put(owner, data, hashFunction.applyAsInt(current), 0, details, forceUpdate, equalsFunction, hashFunction);
+ }
+
+ return new Tuple2<>(renumberedRoot, renumberedVector);
+ }
+
+
+ static boolean seqEquals(K a, K b) {
+ return a.getSequenceNumber() == b.getSequenceNumber();
+ }
+
+ static int seqHash(K e) {
+ return seqHash(e.getSequenceNumber());
+ }
+
+ /**
+ * Computes a hash code from the sequence number, so that we can
+ * use it for iteration in a CHAMP trie.
+ *
+ * Convert the sequence number to unsigned 32 by adding Integer.MIN_VALUE.
+ * Then reorders its bits from 66666555554444433333222221111100 to
+ * 00111112222233333444445555566666.
+ *
+ * @param sequenceNumber a sequence number
+ * @return a hash code
+ */
+ static int seqHash(int sequenceNumber) {
+ int u = sequenceNumber + Integer.MIN_VALUE;
+ return (u >>> 27)
+ | ((u & 0b00000_11111_00000_00000_00000_00000_00) >>> 17)
+ | ((u & 0b00000_00000_11111_00000_00000_00000_00) >>> 7)
+ | ((u & 0b00000_00000_00000_11111_00000_00000_00) << 3)
+ | ((u & 0b00000_00000_00000_00000_11111_00000_00) << 13)
+ | ((u & 0b00000_00000_00000_00000_00000_11111_00) << 23)
+ | ((u & 0b00000_00000_00000_00000_00000_00000_11) << 30);
+ }
+
+ static ChampTrie.BitmapIndexedNode seqRemove(ChampTrie.BitmapIndexedNode seqRoot, ChampTrie.IdentityObject owner,
+ K key, ChampTrie.ChangeEvent details) {
+ return seqRoot.remove(owner,
+ key, seqHash(key.getSequenceNumber()), 0, details,
+ ChampSequencedData::seqEquals);
+ }
+
+ static ChampTrie.BitmapIndexedNode seqUpdate(ChampTrie.BitmapIndexedNode seqRoot, ChampTrie.IdentityObject owner,
+ K key, ChampTrie.ChangeEvent details,
+ BiFunction replaceFunction) {
+ return seqRoot.put(owner,
+ key, seqHash(key.getSequenceNumber()), 0, details,
+ replaceFunction,
+ ChampSequencedData::seqEquals, ChampSequencedData::seqHash);
+ }
+
+ final static ChampTombstone TOMB_ZERO_ZERO = new ChampTombstone(0, 0);
+
+ static Tuple2, Integer> vecRemove(Vector vector, K oldElem, int offset) {
+ // If the element is the first, we can remove it and its neighboring tombstones from the vector.
+ int size = vector.size();
+ int index = oldElem.getSequenceNumber() + offset;
+ if (index == 0) {
+ if (size > 1) {
+ Object o = vector.get(1);
+ if (o instanceof ChampTombstone) {
+ ChampTombstone t = (ChampTombstone) o;
+ return new Tuple2<>(vector.removeRange(0, 2 + t.after()), offset - 2 - t.after());
+ }
+ }
+ return new Tuple2<>(vector.tail(), offset - 1);
+ }
+
+ // If the element is the last , we can remove it and its neighboring tombstones from the vector.
+ if (index == size - 1) {
+ Object o = vector.get(size - 2);
+ if (o instanceof ChampTombstone) {
+ ChampTombstone t = (ChampTombstone) o;
+ return new Tuple2<>(vector.removeRange(size - 2 - t.before(), size), offset);
+ }
+ return new Tuple2<>(vector.init(), offset);
+ }
+
+ // Otherwise, we replace the element with a tombstone, and we update before/after skip counts
+ assert index > 0 && index < size - 1;
+ Object before = vector.get(index - 1);
+ Object after = vector.get(index + 1);
+ if (before instanceof ChampTombstone && after instanceof ChampTombstone) {
+ ChampTombstone tb = (ChampTombstone) before;
+ ChampTombstone ta = (ChampTombstone) after;
+ vector = vector.update(index - 1 - tb.before(), new ChampTombstone(0, 2 + tb.before() + ta.after()));
+ vector = vector.update(index, TOMB_ZERO_ZERO);
+ vector = vector.update(index + 1 + ta.after(), new ChampTombstone(2 + tb.before() + ta.after(), 0));
+ } else if (before instanceof ChampTombstone) {
+ ChampTombstone tb = (ChampTombstone) before;
+ vector = vector.update(index - 1 - tb.before(), new ChampTombstone(0, 1 + tb.before()));
+ vector = vector.update(index, new ChampTombstone(1 + tb.before(), 0));
+ } else if (after instanceof ChampTombstone) {
+ ChampTombstone ta = (ChampTombstone) after;
+ vector = vector.update(index, new ChampTombstone(0, 1 + ta.after()));
+ vector = vector.update(index + 1 + ta.after(), new ChampTombstone(1 + ta.after(), 0));
+ } else {
+ vector = vector.update(index, TOMB_ZERO_ZERO);
+ }
+ return new Tuple2<>(vector, offset);
+ }
+
+
+ static Vector removeRange(Vector v, int fromIndex, int toIndex) {
+ ChampTrie.ChampListHelper.checkIndex(fromIndex, toIndex + 1);
+ ChampTrie.ChampListHelper.checkIndex(toIndex, v.size() + 1);
+ if (fromIndex == 0) {
+ return v.slice(toIndex, v.size());
+ }
+ if (toIndex == v.size()) {
+ return v.slice(0, fromIndex);
+ }
+ final Vector begin = v.slice(0, fromIndex);
+ return begin.appendAll(() -> v.iterator(toIndex));
+ }
+
+
+ static Vector vecUpdate(Vector newSeqRoot, ChampTrie.IdentityObject owner, K newElem, ChampTrie.ChangeEvent details,
+ BiFunction replaceFunction) {
+ return newSeqRoot;
+ }
+
+ /**
+ * Gets the sequence number of the data.
+ *
+ * @return sequence number in the range from {@link Integer#MIN_VALUE}
+ * (exclusive) to {@link Integer#MAX_VALUE} (inclusive).
+ */
+ int getSequenceNumber();
+
+
+ }
+
+ /**
+ * A {@code SequencedElement} stores an element of a set and a sequence number.
+ *
+ * {@code hashCode} and {@code equals} are based on the element - the sequence
+ * number is not included.
+ *
+ * References:
+ *
+ * The code in this class has been derived from JHotDraw 8.
+ *
+ * JHotDraw 8. Copyright © 2023 The authors and contributors of JHotDraw.
+ * MIT License .
+ * github.com
+ *
+ */
+ static class ChampSequencedElement implements ChampSequencedData {
+
+ private final E element;
+ private final int sequenceNumber;
+
+ ChampSequencedElement(E element) {
+ this.element = element;
+ this.sequenceNumber = NO_SEQUENCE_NUMBER;
+ }
+
+ ChampSequencedElement(E element, int sequenceNumber) {
+ this.element = element;
+ this.sequenceNumber = sequenceNumber;
+ }
+ public static int keyHash( Object a) {
+ return Objects.hashCode(a);
+ }
+
+
+ static ChampSequencedElement forceUpdate(ChampSequencedElement oldK, ChampSequencedElement newK) {
+ return newK;
+ }
+
+ static ChampSequencedElement update(ChampSequencedElement oldK, ChampSequencedElement newK) {
+ return oldK;
+ }
+
+
+ static ChampSequencedElement updateAndMoveToFirst(ChampSequencedElement oldK, ChampSequencedElement newK) {
+ return oldK.getSequenceNumber() == newK.getSequenceNumber() + 1 ? oldK : newK;
+ }
+
+
+ static ChampSequencedElement updateAndMoveToLast(ChampSequencedElement oldK, ChampSequencedElement newK) {
+ return oldK.getSequenceNumber() == newK.getSequenceNumber() - 1 ? oldK : newK;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ChampSequencedElement> that = (ChampSequencedElement>) o;
+ return Objects.equals(element, that.element);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(element);
+ }
+
+ E getElement() {
+ return element;
+ }
+
+ public int getSequenceNumber() {
+ return sequenceNumber;
+ }
+
+ @Override
+ public String toString() {
+ return "{" +
+ "" + element +
+ ", seq=" + sequenceNumber +
+ '}';
+ }
+ }
+
+ /**
+ * A {@code ChampSequencedEntry} stores an entry of a map and a sequence number.
+ *
+ * {@code hashCode} and {@code equals} are based on the key and the value
+ * of the entry - the sequence number is not included.
+ *
+ * References:
+ *
+ * The code in this class has been derived from JHotDraw 8.
+ *
+ * JHotDraw 8. Copyright © 2023 The authors and contributors of JHotDraw.
+ * MIT License .
+ * github.com
+ *
+ */
+ static class ChampSequencedEntry extends AbstractMap.SimpleImmutableEntry
+ implements ChampSequencedData {
+
+ private static final long serialVersionUID = 0L;
+ private final int sequenceNumber;
+
+ ChampSequencedEntry(K key) {
+ super(key, null);
+ sequenceNumber = NO_SEQUENCE_NUMBER;
+ }
+
+ ChampSequencedEntry(K key, V value) {
+ super(key, value);
+ sequenceNumber = NO_SEQUENCE_NUMBER;
+ }
+ ChampSequencedEntry(K key, V value, int sequenceNumber) {
+ super(key, value);
+ this.sequenceNumber = sequenceNumber;
+ }
+
+ static ChampSequencedEntry forceUpdate(ChampSequencedEntry oldK, ChampSequencedEntry newK) {
+ return newK;
+ }
+ static boolean keyEquals(ChampSequencedEntry a, ChampSequencedEntry b) {
+ return Objects.equals(a.getKey(), b.getKey());
+ }
+
+ static boolean keyAndValueEquals(ChampSequencedEntry a, ChampSequencedEntry b) {
+ return Objects.equals(a.getKey(), b.getKey()) && Objects.equals(a.getValue(), b.getValue());
+ }
+
+ static int entryKeyHash(ChampSequencedEntry a) {
+ return Objects.hashCode(a.getKey());
+ }
+
+ static int keyHash( Object key) {
+ return Objects.hashCode(key);
+ }
+ static ChampSequencedEntry update(ChampSequencedEntry oldK, ChampSequencedEntry newK) {
+ return Objects.equals(oldK.getValue(), newK.getValue()) ? oldK :
+ new ChampSequencedEntry<>(oldK.getKey(), newK.getValue(), oldK.getSequenceNumber());
+ }
+
+
+ static ChampSequencedEntry updateAndMoveToFirst(ChampSequencedEntry oldK, ChampSequencedEntry newK) {
+ return Objects.equals(oldK.getValue(), newK.getValue())
+ && oldK.getSequenceNumber() == newK.getSequenceNumber() + 1 ? oldK : newK;
+ }
+
+
+ static ChampSequencedEntry updateAndMoveToLast(ChampSequencedEntry oldK, ChampSequencedEntry newK) {
+ return Objects.equals(oldK.getValue(), newK.getValue())
+ && oldK.getSequenceNumber() == newK.getSequenceNumber() - 1 ? oldK : newK;
+ }
+
+ // FIXME This behavior is enforced by AbstractMapTest.shouldPutExistingKeyAndNonEqualValue().
+ // This behavior replaces the existing key with the new one if it has not the same identity.
+ // This behavior does not match the behavior of java.util.HashMap.put().
+ // This behavior violates the contract of the map: we do create a new instance of the map,
+ // although it is equal to the previous instance.
+ static ChampSequencedEntry updateWithNewKey(ChampSequencedEntry oldK, ChampSequencedEntry newK) {
+ return Objects.equals(oldK.getValue(), newK.getValue())
+ && oldK.getKey() == newK.getKey()
+ ? oldK
+ : new ChampSequencedEntry<>(newK.getKey(), newK.getValue(), oldK.getSequenceNumber());
+ }
+ public int getSequenceNumber() {
+ return sequenceNumber;
+ }
+ }
+
+ /**
+ * A tombstone is used by {@code VectorSet} to mark a deleted slot in its Vector.
+ *
+ * A tombstone stores the minimal number of neighbors 'before' and 'after' it in the
+ * Vector.
+ *
+ * When we insert a new tombstone, we update 'before' and 'after' values only on
+ * the first and last tombstone of a sequence of tombstones. Therefore, a delete
+ * operation requires reading of up to 3 neighboring elements in the vector, and
+ * updates of up to 3 elements.
+ *
+ * There are no tombstones at the first and last element of the vector. When we
+ * remove the first or last element of the vector, we remove the tombstones.
+ *
+ * Example: Tombstones are shown as before .after .
+ *
+ *
+ *
+ * Indices: 0 1 2 3 4 5 6 7 8 9
+ * Initial situation: Values: 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j'
+ *
+ * Deletion of element 5:
+ * - read elements at indices 4, 5, 6 'e' 'f' 'g'
+ * - notice that none of them are tombstones
+ * - put tombstone 0.0 at index 5 0.0
+ *
+ * After deletion of element 5: 'a' 'b' 'c' 'd' 'e' 0.0 'g' 'h' 'i' 'j'
+ *
+ * After deletion of element 7: 'a' 'b' 'c' 'd' 'e' 0.0 'g' 0.0 'i' 'j'
+ *
+ * Deletion of element 8:
+ * - read elements at indices 7, 8, 9 0.0 'i' 'j'
+ * - notice that 7 is a tombstone 0.0
+ * - put tombstones 0.1, 1.0 at indices 7 and 8
+ *
+ * After deletion of element 8: 'a' 'b' 'c' 'd' 'e' 0.0 'g' 0.1 1.0 'j'
+ *
+ * Deletion of element 6:
+ * - read elements at indices 5, 6, 7 0.0 'g' 0.1
+ * - notice that two of them are tombstones
+ * - put tombstones 0.3, 0.0, 3.0 at indices 5, 6 and 8
+ *
+ * After deletion of element 6: 'a' 'b' 'c' 'd' 'e' 0.3 0.0 0.1 3.0 'j'
+ *
+ * Deletion of the last element 9:
+ * - read elements at index 8 3.0
+ * - notice that it is a tombstone
+ * - remove the last element and the neighboring tombstone sequence
+ *
+ * After deletion of element 9: 'a' 'b' 'c' 'd' 'e'
+ *
+ * References:
+ *
+ * The code in this class has been derived from JHotDraw 8.
+ *
+ * The design of this class is inspired by 'VectorMap.scala'.
+ *
+ * JHotDraw 8. Copyright © 2023 The authors and contributors of JHotDraw.
+ * MIT License .
+ * github.com
+ *
+ * VectorMap.scala
+ * The Scala library. Copyright EPFL and Lightbend, Inc. Apache License 2.0.
+ * github.com
+ *
+ *
+ */
+ static final class ChampTombstone {
+ private final int before;
+ private final int after;
+
+ /**
+ * @param before minimal number of neighboring tombstones before this one
+ * @param after minimal number of neighboring tombstones after this one
+ */
+ ChampTombstone(int before, int after) {
+ this.before = before;
+ this.after = after;
+ }
+
+ public int before() {
+ return before;
+ }
+
+ public int after() {
+ return after;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) return true;
+ if (obj == null || obj.getClass() != this.getClass()) return false;
+ ChampTombstone that = (ChampTombstone) obj;
+ return this.before == that.before &&
+ this.after == that.after;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(before, after);
+ }
+
+ @Override
+ public String toString() {
+ return "ChampTombstone[" +
+ "before=" + before + ", " +
+ "after=" + after + ']';
+ }
+
+
+ }
+}
diff --git a/src/main/java/io/vavr/collection/ChampTransience.java b/src/main/java/io/vavr/collection/ChampTransience.java
new file mode 100644
index 000000000..6860e821d
--- /dev/null
+++ b/src/main/java/io/vavr/collection/ChampTransience.java
@@ -0,0 +1,232 @@
+/*
+ * ____ ______________ ________________________ __________
+ * \ \/ / \ \/ / __/ / \ \/ / \
+ * \______/___/\___\______/___/_____/___/\___\______/___/\___\
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright 2023 Vavr, https://vavr.io
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package io.vavr.collection;
+
+import io.vavr.Tuple2;
+
+import java.util.AbstractMap;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Predicate;
+
+/**
+ * Provides abstract base classes for transient collections.
+ */
+class ChampTransience {
+ /**
+ * Abstract base class for a transient CHAMP collection.
+ *
+ * References:
+ *
+ * The code in this class has been derived from JHotDraw 8.
+ *
+ * JHotDraw 8. Copyright © 2023 The authors and contributors of JHotDraw.
+ * MIT License .
+ * github.com
+ *
+ *
+ * @param the data type of the CHAMP trie
+ */
+ abstract static class ChampAbstractTransientCollection {
+ /**
+ * The current owner id of this map.
+ *
+ * All nodes that have the same non-null owner id, are exclusively owned
+ * by this map, and therefore can be mutated without affecting other map.
+ *
+ * If this owner id is null, then this map does not own any nodes.
+ */
+
+ ChampTrie.IdentityObject owner;
+
+ /**
+ * The root of this CHAMP trie.
+ */
+ ChampTrie.BitmapIndexedNode root;
+
+ /**
+ * The number of entries in this map.
+ */
+ int size;
+
+ /**
+ * The number of times this map has been structurally modified.
+ */
+ int modCount;
+
+ int size() {
+ return size;
+ }
+
+ boolean isEmpty() {
+ return size == 0;
+ }
+
+ ChampTrie.IdentityObject makeOwner() {
+ if (owner == null) {
+ owner = new ChampTrie.IdentityObject();
+ }
+ return owner;
+ }
+ }
+
+ /**
+ * Abstract base class for a transient CHAMP map.
+ *
+ * References:
+ *
+ * The code in this class has been derived from JHotDraw 8.
+ *
+ * JHotDraw 8. Copyright © 2023 The authors and contributors of JHotDraw.
+ * MIT License .
+ * github.com
+ *
+ *
+ * @param the element type
+ */
+ abstract static class ChampAbstractTransientMap extends ChampAbstractTransientCollection {
+ @SuppressWarnings("unchecked")
+ boolean removeAll(Iterable> c) {
+ if (isEmpty()) {
+ return false;
+ }
+ boolean modified = false;
+ for (Object key : c) {
+ ChampTrie.ChangeEvent details = removeKey((K)key);
+ modified |= details.isModified();
+ }
+ return modified;
+ }
+
+ abstract ChampTrie.ChangeEvent removeKey(K key);
+ abstract void clear();
+ abstract V put(K key, V value);
+
+ boolean putAllTuples(Iterable extends Tuple2 extends K,? extends V>> c) {
+ boolean modified = false;
+ for (Tuple2 extends K,? extends V> e : c) {
+ V oldValue = put(e._1,e._2);
+ modified = modified || !Objects.equals(oldValue, e);
+ }
+ return modified;
+ }
+
+ @SuppressWarnings("unchecked")
+ boolean retainAllTuples(Iterable extends Tuple2> c) {
+ if (isEmpty()) {
+ return false;
+ }
+ if (c instanceof Collection> && ((Collection>) c).isEmpty()
+ || c instanceof Traversable> && ((Traversable>) c).isEmpty()) {
+ clear();
+ return true;
+ }
+ if (c instanceof Collection>) {
+ Collection> that = (Collection>) c;
+ return filterAll(e -> that.contains(e.getKey()));
+ }else if (c instanceof java.util.Map, ?>) {
+ java.util.Map, ?> that = (java.util.Map, ?>) c;
+ return filterAll(e -> that.containsKey(e.getKey())&&Objects.equals(e.getValue(),that.get(e.getKey())));
+ } else {
+ java.util.HashSet that = new HashSet<>();
+ c.forEach(t->that.add(new AbstractMap.SimpleImmutableEntry<>(t._1,t._2)));
+ return filterAll(that::contains);
+ }
+ }
+
+ abstract boolean filterAll(Predicate> predicate);
+ }
+
+ /**
+ * Abstract base class for a transient CHAMP set.
+ *
+ * References:
+ *
+ * The code in this class has been derived from JHotDraw 8.
+ *
+ * JHotDraw 8. Copyright © 2023 The authors and contributors of JHotDraw.
+ * MIT License .
+ * github.com
+ *
+ *
+ * @param the element type
+ * @param the data type of the CHAMP trie
+ */
+ abstract static class ChampAbstractTransientSet extends ChampAbstractTransientCollection {
+ abstract void clear();
+ abstract boolean remove(Object o);
+ boolean removeAll( Iterable> c) {
+ if (isEmpty()) {
+ return false;
+ }
+ if (c == this) {
+ clear();
+ return true;
+ }
+ boolean modified = false;
+ for (Object o : c) {
+ modified |= remove(o);
+ }
+ return modified;
+ }
+
+ abstract java.util.Iterator iterator();
+ boolean retainAll( Iterable> c) {
+ if (isEmpty()) {
+ return false;
+ }
+ if (c instanceof Collection> && ((Collection>) c).isEmpty()) {
+ Collection> cc = (Collection>) c;
+ clear();
+ return true;
+ }
+ Predicate predicate;
+ if (c instanceof Collection>) {
+ Collection> that = (Collection>) c;
+ predicate = that::contains;
+ } else {
+ HashSet that = new HashSet<>();
+ c.forEach(that::add);
+ predicate = that::contains;
+ }
+ boolean removed = false;
+ for (Iterator i = iterator(); i.hasNext(); ) {
+ E e = i.next();
+ if (!predicate.test(e)) {
+ remove(e);
+ removed = true;
+ }
+ }
+ return removed;
+ }
+ }
+}
diff --git a/src/main/java/io/vavr/collection/ChampTrie.java b/src/main/java/io/vavr/collection/ChampTrie.java
new file mode 100644
index 000000000..e84711f95
--- /dev/null
+++ b/src/main/java/io/vavr/collection/ChampTrie.java
@@ -0,0 +1,1724 @@
+/*
+ * ____ ______________ ________________________ __________
+ * \ \/ / \ \/ / __/ / \ \/ / \
+ * \______/___/\___\______/___/_____/___/\___\______/___/\___\
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright 2023 Vavr, https://vavr.io
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package io.vavr.collection;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+import java.util.function.ToIntFunction;
+
+import static io.vavr.collection.ChampTrie.ChampListHelper.arrayEquals;
+import static io.vavr.collection.ChampTrie.NodeFactory.newBitmapIndexedNode;
+import static io.vavr.collection.ChampTrie.NodeFactory.newHashCollisionNode;
+
+/**
+ * 'Compressed Hash-Array Mapped Prefix-tree' (CHAMP) trie.
+ *
+ * References:
+ *
+ * The Capsule Hash Trie Collections Library.
+ * Copyright (c) Michael Steindorfer. BSD-2-Clause License
+ * github.com
+ *
+ *
+ */
+class ChampTrie {
+ /**
+ * Represents a node in a 'Compressed Hash-Array Mapped Prefix-tree'
+ * (CHAMP) trie.
+ *
+ * A trie is a tree structure that stores a set of data objects; the
+ * path to a data object is determined by a bit sequence derived from the data
+ * object.
+ *
+ * In a CHAMP trie, the bit sequence is derived from the hash code of a data
+ * object. A hash code is a bit sequence with a fixed length. This bit sequence
+ * is split up into parts. Each part is used as the index to the next child node
+ * in the tree, starting from the root node of the tree.
+ *
+ * The nodes of a CHAMP trie are compressed. Instead of allocating a node for
+ * each data object, the data objects are stored directly in the ancestor node
+ * at which the path to the data object starts to become unique. This means,
+ * that in most cases, only a prefix of the bit sequence is needed for the
+ * path to a data object in the tree.
+ *
+ * If the hash code of a data object in the set is not unique, then it is
+ * stored in a {@link HashCollisionNode}, otherwise it is stored in a
+ * {@link BitmapIndexedNode}. Since the hash codes have a fixed length,
+ * all {@link HashCollisionNode}s are located at the same, maximal depth
+ * of the tree.
+ *
+ * In this implementation, a hash code has a length of
+ * {@value #HASH_CODE_LENGTH} bits, and is split up in little-endian order into parts of
+ * {@value #BIT_PARTITION_SIZE} bits (the last part contains the remaining bits).
+ *
+ * References:
+ *
+ * This class has been derived from 'The Capsule Hash Trie Collections Library'.
+ *
+ * The Capsule Hash Trie Collections Library.
+ * Copyright (c) Michael Steindorfer. BSD-2-Clause License
+ * github.com
+ *
+ *
+ * @param the type of the data objects that are stored in this trie
+ */
+ abstract static class Node {
+ /**
+ * Represents no data.
+ * We can not use {@code null}, because we allow storing null-data in the
+ * trie.
+ */
+ static final Object NO_DATA = new Object();
+ static final int HASH_CODE_LENGTH = 32;
+ /**
+ * Bit partition size in the range [1,5].
+ *
+ * The bit-mask must fit into the 32 bits of an int field ({@code 32 = 1<<5}).
+ * (You can use a size of 6, if you replace the bit-mask fields with longs).
+ */
+ static final int BIT_PARTITION_SIZE = 5;
+ static final int BIT_PARTITION_MASK = (1 << BIT_PARTITION_SIZE) - 1;
+ static final int MAX_DEPTH = (HASH_CODE_LENGTH + BIT_PARTITION_SIZE - 1) / BIT_PARTITION_SIZE + 1;
+
+
+ Node() {
+ }
+
+ /**
+ * Given a masked dataHash, returns its bit-position
+ * in the bit-map.
+ *
+ * For example, if the bit partition is 5 bits, then
+ * we 2^5 == 32 distinct bit-positions.
+ * If the masked dataHash is 3 then the bit-position is
+ * the bit with index 3. That is, 1<<3 = 0b0100.
+ *
+ * @param mask masked data hash
+ * @return bit position
+ */
+ static int bitpos(int mask) {
+ return 1 << mask;
+ }
+
+ static E getFirst( Node node) {
+ while (node instanceof BitmapIndexedNode) {
+ BitmapIndexedNode bxn = (BitmapIndexedNode) node;
+ int nodeMap = bxn.nodeMap();
+ int dataMap = bxn.dataMap();
+ if ((nodeMap | dataMap) == 0) {
+ break;
+ }
+ int firstNodeBit = Integer.numberOfTrailingZeros(nodeMap);
+ int firstDataBit = Integer.numberOfTrailingZeros(dataMap);
+ if (nodeMap != 0 && firstNodeBit < firstDataBit) {
+ node = node.getNode(0);
+ } else {
+ return node.getData(0);
+ }
+ }
+ if (node instanceof HashCollisionNode) {
+ HashCollisionNode hcn = (HashCollisionNode) node;
+ return hcn.getData(0);
+ }
+ throw new NoSuchElementException();
+ }
+
+ static E getLast( Node node) {
+ while (node instanceof BitmapIndexedNode) {
+ BitmapIndexedNode bxn = (BitmapIndexedNode) node;
+ int nodeMap = bxn.nodeMap();
+ int dataMap = bxn.dataMap();
+ if ((nodeMap | dataMap) == 0) {
+ break;
+ }
+ if (Integer.compareUnsigned(nodeMap, dataMap) > 0) {
+ node = node.getNode(node.nodeArity() - 1);
+ } else {
+ return node.getData(node.dataArity() - 1);
+ }
+ }
+ if (node instanceof HashCollisionNode) {
+ HashCollisionNode hcn = (HashCollisionNode) node;
+ return hcn.getData(hcn.dataArity() - 1);
+ }
+ throw new NoSuchElementException();
+ }
+
+ static int mask(int dataHash, int shift) {
+ return (dataHash >>> shift) & BIT_PARTITION_MASK;
+ }
+
+ static Node mergeTwoDataEntriesIntoNode(IdentityObject owner,
+ K k0, int keyHash0,
+ K k1, int keyHash1,
+ int shift) {
+ if (shift >= HASH_CODE_LENGTH) {
+ Object[] entries = new Object[2];
+ entries[0] = k0;
+ entries[1] = k1;
+ return NodeFactory.newHashCollisionNode(owner, keyHash0, entries);
+ }
+
+ int mask0 = mask(keyHash0, shift);
+ int mask1 = mask(keyHash1, shift);
+
+ if (mask0 != mask1) {
+ // both nodes fit on same level
+ int dataMap = bitpos(mask0) | bitpos(mask1);
+
+ Object[] entries = new Object[2];
+ if (mask0 < mask1) {
+ entries[0] = k0;
+ entries[1] = k1;
+ } else {
+ entries[0] = k1;
+ entries[1] = k0;
+ }
+ return NodeFactory.newBitmapIndexedNode(owner, (0), dataMap, entries);
+ } else {
+ Node node = mergeTwoDataEntriesIntoNode(owner,
+ k0, keyHash0,
+ k1, keyHash1,
+ shift + BIT_PARTITION_SIZE);
+ // values fit on next level
+
+ int nodeMap = bitpos(mask0);
+ return NodeFactory.newBitmapIndexedNode(owner, nodeMap, (0), new Object[]{node});
+ }
+ }
+
+ abstract int dataArity();
+
+ /**
+ * Checks if this trie is equivalent to the specified other trie.
+ *
+ * @param other the other trie
+ * @return true if equivalent
+ */
+ abstract boolean equivalent( Object other);
+
+ /**
+ * Finds a data object in the CHAMP trie, that matches the provided data
+ * object and data hash.
+ *
+ * @param data the provided data object
+ * @param dataHash the hash code of the provided data
+ * @param shift the shift for this node
+ * @param equalsFunction a function that tests data objects for equality
+ * @return the found data, returns {@link #NO_DATA} if no data in the trie
+ * matches the provided data.
+ */
+ abstract Object find(D data, int dataHash, int shift, BiPredicate equalsFunction);
+
+ abstract D getData(int index);
+
+ IdentityObject getOwner() {
+ return null;
+ }
+
+ abstract Node getNode(int index);
+
+ abstract boolean hasData();
+
+ boolean isNodeEmpty() {
+ return !hasData() && !hasNodes();
+ }
+
+ boolean hasMany() {
+ return hasNodes() || dataArity() > 1;
+ }
+
+ abstract boolean hasDataArityOne();
+
+ abstract boolean hasNodes();
+
+ boolean isAllowedToUpdate( IdentityObject y) {
+ IdentityObject x = getOwner();
+ return x != null && x == y;
+ }
+
+ abstract int nodeArity();
+
+ /**
+ * Removes a data object from the trie.
+ *
+ * @param owner A non-null value means, that this method may update
+ * nodes that are marked with the same unique id,
+ * and that this method may create new mutable nodes
+ * with this unique id.
+ * A null value means, that this method must not update
+ * any node and may only create new immutable nodes.
+ * @param data the data to be removed
+ * @param dataHash the hash-code of the data object
+ * @param shift the shift of the current node
+ * @param details this method reports the changes that it performed
+ * in this object
+ * @param equalsFunction a function that tests data objects for equality
+ * @return the updated trie
+ */
+ abstract Node remove(IdentityObject owner, D data,
+ int dataHash, int shift,
+ ChangeEvent details,
+ BiPredicate equalsFunction);
+
+ /**
+ * Inserts or replaces a data object in the trie.
+ *
+ * @param owner A non-null value means, that this method may update
+ * nodes that are marked with the same unique id,
+ * and that this method may create new mutable nodes
+ * with this unique id.
+ * A null value means, that this method must not update
+ * any node and may only create new immutable nodes.
+ * @param newData the data to be inserted,
+ * or to be used for merging if there is already
+ * a matching data object in the trie
+ * @param dataHash the hash-code of the data object
+ * @param shift the shift of the current node
+ * @param details this method reports the changes that it performed
+ * in this object
+ * @param updateFunction only used if there is a matching data object
+ * in the trie.
+ * Given the existing data object (first argument) and
+ * the new data object (second argument), yields a
+ * new data object or returns either of the two.
+ * In all cases, the update function must return
+ * a data object that has the same data hash
+ * as the existing data object.
+ * @param equalsFunction a function that tests data objects for equality
+ * @param hashFunction a function that computes the hash-code for a data
+ * object
+ * @return the updated trie
+ */
+ abstract Node put(IdentityObject owner, D newData,
+ int dataHash, int shift, ChangeEvent details,
+ BiFunction updateFunction,
+ BiPredicate equalsFunction,
+ ToIntFunction hashFunction);
+ /**
+ * Inserts or replaces data elements from the specified other trie in this trie.
+ *
+ * @param owner
+ * @param otherNode a node with the same shift as this node from the other trie
+ * @param shift the shift of this node and the other node
+ * @param bulkChange updates the field {@link BulkChangeEvent#inBoth}
+ * @param updateFunction the update function for data elements
+ * @param equalsFunction the equals function for data elements
+ * @param hashFunction the hash function for data elements
+ * @param details the change event for single elements
+ * @return the updated trie
+ */
+ abstract Node putAll(IdentityObject owner, Node otherNode, int shift,
+ BulkChangeEvent bulkChange,
+ BiFunction updateFunction,
+ BiPredicate equalsFunction,
+ ToIntFunction hashFunction,
+ ChangeEvent details);
+
+ /**
+ * Removes data elements in the specified other trie from this trie.
+ *
+ * @param owner
+ * @param otherNode a node with the same shift as this node from the other trie
+ * @param shift the shift of this node and the other node
+ * @param bulkChange updates the field {@link BulkChangeEvent#removed}
+ * @param updateFunction the update function for data elements
+ * @param equalsFunction the equals function for data elements
+ * @param hashFunction the hash function for data elements
+ * @param details the change event for single elements
+ * @return the updated trie
+ */
+ abstract Node removeAll(IdentityObject owner, Node otherNode, int shift,
+ BulkChangeEvent bulkChange,
+ BiFunction updateFunction,
+ BiPredicate equalsFunction,
+ ToIntFunction hashFunction,
+ ChangeEvent details);
+
+ /**
+ * Retains data elements in this trie that are also in the other trie - removes the rest.
+ *
+ * @param owner
+ * @param otherNode a node with the same shift as this node from the other trie
+ * @param shift the shift of this node and the other node
+ * @param bulkChange updates the field {@link BulkChangeEvent#removed}
+ * @param updateFunction the update function for data elements
+ * @param equalsFunction the equals function for data elements
+ * @param hashFunction the hash function for data elements
+ * @param details the change event for single elements
+ * @return the updated trie
+ */
+ abstract Node retainAll(IdentityObject owner, Node otherNode, int shift,
+ BulkChangeEvent bulkChange,
+ BiFunction updateFunction,
+ BiPredicate equalsFunction,
+ ToIntFunction hashFunction,
+ ChangeEvent details);
+
+ /**
+ * Retains data elements in this trie for which the provided predicate returns true.
+ *
+ * @param owner
+ * @param predicate a predicate that returns true for data elements that should be retained
+ * @param shift the shift of this node and the other node
+ * @param bulkChange updates the field {@link BulkChangeEvent#removed}
+ * @return the updated trie
+ */
+ abstract Node filterAll(IdentityObject owner, Predicate super D> predicate, int shift,
+ BulkChangeEvent bulkChange);
+
+ abstract int calculateSize();}
+
+ /**
+ * Represents a bitmap-indexed node in a CHAMP trie.
+ *
+ * References:
+ *
+ * Portions of the code in this class have been derived from 'The Capsule Hash Trie Collections Library', and from
+ * 'JHotDraw 8'.
+ *
+ * The Capsule Hash Trie Collections Library.
+ * Copyright (c) Michael Steindorfer. BSD-2-Clause License
+ * github.com
+ *
+ * JHotDraw 8. Copyright © 2023 The authors and contributors of JHotDraw.
+ * MIT License .
+ * github.com
+ *
+ *
+ *
+ * @param the data type
+ */
+ static class BitmapIndexedNode extends Node {
+ static final BitmapIndexedNode> EMPTY_NODE = newBitmapIndexedNode(null, (0), (0), new Object[]{});
+
+ final Object [] mixed;
+ private final int nodeMap;
+ private final int dataMap;
+
+ BitmapIndexedNode(int nodeMap,
+ int dataMap, Object [] mixed) {
+ this.nodeMap = nodeMap;
+ this.dataMap = dataMap;
+ this.mixed = mixed;
+ assert mixed.length == nodeArity() + dataArity();
+ }
+
+ @SuppressWarnings("unchecked")
+ static BitmapIndexedNode emptyNode() {
+ return (BitmapIndexedNode) EMPTY_NODE;
+ }
+
+ BitmapIndexedNode copyAndInsertData(IdentityObject owner, int bitpos,
+ D data) {
+ int idx = dataIndex(bitpos);
+ Object[] dst = ChampListHelper.copyComponentAdd(this.mixed, idx, 1);
+ dst[idx] = data;
+ return newBitmapIndexedNode(owner, nodeMap, dataMap | bitpos, dst);
+ }
+
+ BitmapIndexedNode copyAndMigrateFromDataToNode(IdentityObject owner,
+ int bitpos, Node node) {
+
+ int idxOld = dataIndex(bitpos);
+ int idxNew = this.mixed.length - 1 - nodeIndex(bitpos);
+ assert idxOld <= idxNew;
+
+ // copy 'src' and remove entryLength element(s) at position 'idxOld' and
+ // insert 1 element(s) at position 'idxNew'
+ Object[] src = this.mixed;
+ Object[] dst = new Object[src.length];
+ System.arraycopy(src, 0, dst, 0, idxOld);
+ System.arraycopy(src, idxOld + 1, dst, idxOld, idxNew - idxOld);
+ System.arraycopy(src, idxNew + 1, dst, idxNew + 1, src.length - idxNew - 1);
+ dst[idxNew] = node;
+ return newBitmapIndexedNode(owner, nodeMap | bitpos, dataMap ^ bitpos, dst);
+ }
+
+ BitmapIndexedNode copyAndMigrateFromNodeToData(IdentityObject owner,
+ int bitpos, Node node) {
+ int idxOld = this.mixed.length - 1 - nodeIndex(bitpos);
+ int idxNew = dataIndex(bitpos);
+
+ // copy 'src' and remove 1 element(s) at position 'idxOld' and
+ // insert entryLength element(s) at position 'idxNew'
+ Object[] src = this.mixed;
+ Object[] dst = new Object[src.length];
+ assert idxOld >= idxNew;
+ System.arraycopy(src, 0, dst, 0, idxNew);
+ System.arraycopy(src, idxNew, dst, idxNew + 1, idxOld - idxNew);
+ System.arraycopy(src, idxOld + 1, dst, idxOld + 1, src.length - idxOld - 1);
+ dst[idxNew] = node.getData(0);
+ return newBitmapIndexedNode(owner, nodeMap ^ bitpos, dataMap | bitpos, dst);
+ }
+
+ BitmapIndexedNode copyAndSetNode(IdentityObject owner, int bitpos,
+ Node node) {
+
+ int idx = this.mixed.length - 1 - nodeIndex(bitpos);
+ if (isAllowedToUpdate(owner)) {
+ // no copying if already editable
+ this.mixed[idx] = node;
+ return this;
+ } else {
+ // copy 'src' and set 1 element(s) at position 'idx'
+ final Object[] dst = ChampListHelper.copySet(this.mixed, idx, node);
+ return newBitmapIndexedNode(owner, nodeMap, dataMap, dst);
+ }
+ }
+
+ @Override
+ int dataArity() {
+ return Integer.bitCount(dataMap);
+ }
+
+ int dataIndex(int bitpos) {
+ return Integer.bitCount(dataMap & (bitpos - 1));
+ }
+
+ int index(int map, int bitpos) {
+ return Integer.bitCount(map & (bitpos - 1));
+ }
+
+ int dataMap() {
+ return dataMap;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ boolean equivalent( Object other) {
+ if (this == other) {
+ return true;
+ }
+ BitmapIndexedNode> that = (BitmapIndexedNode>) other;
+ Object[] thatNodes = that.mixed;
+ // nodes array: we compare local data from 0 to splitAt (excluded)
+ // and then we compare the nested nodes from splitAt to length (excluded)
+ int splitAt = dataArity();
+ return nodeMap() == that.nodeMap()
+ && dataMap() == that.dataMap()
+ && arrayEquals(mixed, 0, splitAt, thatNodes, 0, splitAt)
+ && arrayEquals(mixed, splitAt, mixed.length, thatNodes, splitAt, thatNodes.length,
+ (a, b) -> ((Node) a).equivalent(b) );
+ }
+
+
+ @Override
+
+ Object find(D key, int dataHash, int shift, BiPredicate equalsFunction) {
+ int bitpos = bitpos(mask(dataHash, shift));
+ if ((nodeMap & bitpos) != 0) {
+ return getNode(nodeIndex(bitpos)).find(key, dataHash, shift + BIT_PARTITION_SIZE, equalsFunction);
+ }
+ if ((dataMap & bitpos) != 0) {
+ D k = getData(dataIndex(bitpos));
+ if (equalsFunction.test(k, key)) {
+ return k;
+ }
+ }
+ return NO_DATA;
+ }
+
+
+ @Override
+ @SuppressWarnings("unchecked")
+
+ D getData(int index) {
+ return (D) mixed[index];
+ }
+
+
+ @Override
+ @SuppressWarnings("unchecked")
+ Node