![JAR search and dependency download from the Maven repository](/logo.png)
org.jhotdraw8.icollection.impl.champ.HashCollisionNode Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.jhotdraw8.icollection Show documentation
Show all versions of org.jhotdraw8.icollection Show documentation
JHotDraw8 Immutable Collections
The newest version!
/*
* @(#)HashCollisionNode.java
* Copyright © 2023 The authors and contributors of JHotDraw. MIT License.
*/
package org.jhotdraw8.icollection.impl.champ;
import org.jhotdraw8.icollection.impl.IdentityObject;
import org.jhotdraw8.icollection.util.ListHelper;
import org.jspecify.annotations.Nullable;
import java.util.Arrays;
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 org.jhotdraw8.icollection.impl.champ.NodeFactory.newHashCollisionNode;
/**
* Represents a hash-collision node in a CHAMP trie.
*
* XXX hash-collision nodes may become huge performance bottlenecks.
* If the trie contains keys that implement {@link Comparable} then a hash-collision
* nodes should be a sorted tree structure (for example a red-black tree).
* Otherwise, hash-collision node should be a vector (for example a bit mapped trie).
*
* 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 data type
*/
class HashCollisionNode extends Node {
private static final HashCollisionNode> EMPTY = new HashCollisionNode<>(0, new Object[0]);
private final int hash;
Object[] data;
HashCollisionNode(int hash, Object[] data) {
this.data = data;
this.hash = hash;
}
@Override
int dataArity() {
return data.length;
}
@Override
boolean hasDataArityOne() {
return false;
}
@SuppressWarnings("unchecked")
@Override
boolean equivalent(Object other) {
if (this == other) {
return true;
}
HashCollisionNode> that = (HashCollisionNode>) other;
Object[] thatEntries = that.data;
if (hash != that.hash || thatEntries.length != data.length) {
return false;
}
// Linear scan for each key, because of arbitrary element order.
Object[] thatEntriesCloned = thatEntries.clone();
int remainingLength = thatEntriesCloned.length;
outerLoop:
for (Object key : data) {
for (int j = 0; j < remainingLength; j += 1) {
Object todoKey = thatEntriesCloned[j];
if (Objects.equals(todoKey, key)) {
// We have found an equal entry. We do not need to compare
// this entry again. So we replace it with the last entry
// from the array and reduce the remaining length.
System.arraycopy(thatEntriesCloned, remainingLength - 1, thatEntriesCloned, j, 1);
remainingLength -= 1;
continue outerLoop;
}
}
return false;
}
return true;
}
@SuppressWarnings("unchecked")
@Override
@Nullable
Object find(D key, int dataHash, int shift, BiPredicate equalsFunction) {
for (Object entry : data) {
if (equalsFunction.test(key, (D) entry)) {
return entry;
}
}
return NO_DATA;
}
@Override
@SuppressWarnings("unchecked")
D getData(int index) {
return (D) data[index];
}
@Override
Node getNode(int index) {
throw new IllegalStateException("Is leaf node.");
}
@Override
Object getNodeRaw(int index) {
throw new IllegalStateException("Is leaf node.");
}
@Override
boolean hasData() {
return data.length > 0;
}
@Override
boolean hasNodes() {
return false;
}
@Override
int nodeArity() {
return 0;
}
@SuppressWarnings("unchecked")
@Override
Node remove(@Nullable IdentityObject owner, D data,
int dataHash, int shift, ChangeEvent details, BiPredicate equalsFunction) {
for (int idx = 0, i = 0; i < this.data.length; i += 1, idx++) {
if (equalsFunction.test((D) this.data[i], data)) {
@SuppressWarnings("unchecked") D currentVal = (D) this.data[i];
details.setRemoved(currentVal);
if (this.data.length == 1) {
return BitmapIndexedNode.emptyNode();
} else if (this.data.length == 2) {
// Create root node with singleton element.
// This node will either be the new root
// returned, or be unwrapped and inlined.
return NodeFactory.newBitmapIndexedNode(owner, 0, bitpos(mask(dataHash, 0)),
new Object[]{getData(idx ^ 1)});
}
// copy keys and remove 1 element at position idx
Object[] entriesNew = ListHelper.copyComponentRemove(this.data, idx, 1);
if (isAllowedToUpdate(owner)) {
this.data = entriesNew;
return this;
}
return newHashCollisionNode(owner, dataHash, entriesNew);
}
}
return this;
}
@SuppressWarnings("unchecked")
@Override
Node put(@Nullable IdentityObject owner, D newData,
int dataHash, int shift, ChangeEvent details,
BiFunction updateFunction, BiPredicate equalsFunction,
ToIntFunction hashFunction) {
assert this.hash == dataHash;
for (int i = 0; i < this.data.length; i++) {
D oldData = (D) this.data[i];
if (equalsFunction.test(oldData, newData)) {
D updatedData = updateFunction.apply(oldData, newData);
if (updatedData == oldData) {
details.found(oldData);
return this;
}
details.setReplaced(oldData, updatedData);
if (isAllowedToUpdate(owner)) {
this.data[i] = updatedData;
return this;
}
final Object[] newKeys = ListHelper.copySet(this.data, i, updatedData);
return newHashCollisionNode(owner, dataHash, newKeys);
}
}
// copy entries and add 1 more at the end
Object[] entriesNew = ListHelper.copyComponentAdd(this.data, this.data.length, 1);
entriesNew[this.data.length] = newData;
details.setAdded(newData);
if (isAllowedToUpdate(owner)) {
this.data = entriesNew;
return this;
}
return newHashCollisionNode(owner, dataHash, entriesNew);
}
@Override
protected int calculateSize() {
return dataArity();
}
@SuppressWarnings("unchecked")
@Override
protected Node putAll(@Nullable IdentityObject owner, Node otherNode, int shift, BulkChangeEvent bulkChange, BiFunction updateFunction, BiPredicate equalsFunction, ToIntFunction hashFunction, ChangeEvent details) {
if (otherNode == this) {
bulkChange.inBoth += dataArity();
return this;
}
HashCollisionNode that = (HashCollisionNode) otherNode;
// The buffer initially contains all data elements from this node.
// Every time we find a matching data element in both nodes, we do not need to ever look at that data element again.
// So, we swap it out with a data element from the end of unprocessed data elements, and subtract 1 from unprocessedSize.
// If that node contains a data element that is not in this node, we add it to the end, and add 1 to bufferSize.
// Buffer content:
// 0..unprocessedSize-1 = unprocessed data elements from this node
// unprocessedSize..resultSize-1 = data elements that we have updated from that node, or that we have added from that node.
final int thisSize = this.dataArity();
final int thatSize = that.dataArity();
Object[] buffer = Arrays.copyOf(this.data, thisSize + thatSize);
System.arraycopy(this.data, 0, buffer, 0, this.data.length);
Object[] thatArray = that.data;
int resultSize = thisSize;
int unprocessedSize = thisSize;
boolean updated = false;
outer:
for (int i = 0; i < thatSize; i++) {
D thatData = (D) thatArray[i];
for (int j = 0; j < unprocessedSize; j++) {
D thisData = (D) buffer[j];
if (equalsFunction.test(thatData, thisData)) {
D swap = (D) buffer[--unprocessedSize];
D updatedData = updateFunction.apply(thisData, thatData);
updated |= updatedData != thisData;
buffer[unprocessedSize] = updatedData;
buffer[j] = swap;
bulkChange.inBoth++;
continue outer;
}
}
buffer[resultSize++] = thatData;
}
return newCroppedHashCollisionNode(updated | resultSize != thisSize, buffer, resultSize);
}
@SuppressWarnings("unchecked")
@Override
protected Node removeAll(@Nullable IdentityObject owner, Node otherNode, int shift, BulkChangeEvent bulkChange, BiFunction updateFunction, BiPredicate equalsFunction, ToIntFunction hashFunction, ChangeEvent details) {
if (otherNode == this) {
bulkChange.removed += dataArity();
return (Node) EMPTY;
}
HashCollisionNode that = (HashCollisionNode) otherNode;
// The buffer initially contains all data elements from this node.
// Every time we find a data element that must be removed, we replace it with the last element from the
// result part of the buffer, and reduce resultSize by 1.
// Buffer content:
// 0..resultSize-1 = data elements from this node that have not been removed
final int thisSize = this.dataArity();
final int thatSize = that.dataArity();
int resultSize = thisSize;
Object[] buffer = this.data.clone();
Object[] thatArray = that.data;
outer:
for (int i = 0; i < thatSize && resultSize > 0; i++) {
D thatData = (D) thatArray[i];
for (int j = 0; j < resultSize; j++) {
D thisData = (D) buffer[j];
if (equalsFunction.test(thatData, thisData)) {
buffer[j] = buffer[--resultSize];
bulkChange.removed++;
continue outer;
}
}
}
return newCroppedHashCollisionNode(thisSize != resultSize, buffer, resultSize);
}
private HashCollisionNode newCroppedHashCollisionNode(boolean changed, Object[] buffer, int size) {
if (changed) {
if (buffer.length != size) {
buffer = Arrays.copyOf(buffer, size);
}
return new HashCollisionNode<>(hash, buffer);
}
return this;
}
@SuppressWarnings("unchecked")
@Override
protected Node retainAll(IdentityObject owner, Node otherNode, int shift, BulkChangeEvent bulkChange, BiFunction updateFunction, BiPredicate equalsFunction, ToIntFunction hashFunction, ChangeEvent details) {
if (otherNode == this) {
bulkChange.removed += dataArity();
return (Node) EMPTY;
}
HashCollisionNode that = (HashCollisionNode) otherNode;
// The buffer initially contains all data elements from this node.
// Every time we find a data element that must be retained, we swap it into the result-part of the buffer.
// 0..resultSize-1 = data elements from this node that must be retained
// resultSize..thisSize-1 = data elements that might need to be retained
final int thisSize = this.dataArity();
final int thatSize = that.dataArity();
int resultSize = 0;
Object[] buffer = this.data.clone();
Object[] thatArray = that.data;
outer:
for (int i = 0; i < thatSize && thisSize != resultSize; i++) {
D thatData = (D) thatArray[i];
for (int j = resultSize; j < thisSize; j++) {
D thisData = (D) buffer[j];
if (equalsFunction.test(thatData, thisData)) {
D swap = (D) buffer[resultSize];
buffer[resultSize++] = thisData;
buffer[j] = swap;
continue outer;
}
}
bulkChange.removed++;
}
return newCroppedHashCollisionNode(thisSize != resultSize, buffer, resultSize);
}
@SuppressWarnings("unchecked")
@Override
protected Node filterAll(@Nullable IdentityObject owner, Predicate super D> predicate, int shift, BulkChangeEvent bulkChange) {
final int thisSize = this.dataArity();
int resultSize = 0;
Object[] buffer = new Object[thisSize];
Object[] thisArray = this.data;
for (int i = 0; i < thisSize; i++) {
D thisData = (D) thisArray[i];
if (predicate.test(thisData)) {
buffer[resultSize++] = thisData;
} else {
bulkChange.removed++;
}
}
return newCroppedHashCollisionNode(thisSize != resultSize, buffer, resultSize);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy