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

com.hivemq.client.internal.util.collections.Index Maven / Gradle / Ivy

Go to download

HiveMQ MQTT Client is an MQTT 5.0 and MQTT 3.1.1 compatible and feature-rich high-performance Java client library with different API flavours and backpressure support

The newest version!
/*
 * Copyright 2018-present HiveMQ and the HiveMQ Community
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hivemq.client.internal.util.collections;

import com.hivemq.client.internal.annotations.NotThreadSafe;
import com.hivemq.client.internal.util.Pow2Util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @author Silvio Giebl
 */
@NotThreadSafe
public class Index {

    private static final int MAX_CAPACITY = 1 << 30;

    public static class Spec {

        private static final int DEFAULT_MIN_CAPACITY = 1 << 4;
        private static final float DEFAULT_NODE_THRESHOLD_FACTOR = 0.25f;

        final @NotNull Function keyFunction;
        final int minCapacity;
        final float nodeThresholdFactor;

        public Spec(final @NotNull Function keyFunction) {
            this(keyFunction, DEFAULT_MIN_CAPACITY, DEFAULT_NODE_THRESHOLD_FACTOR);
        }

        public Spec(final @NotNull Function keyFunction, final int minCapacity) {
            this(keyFunction, minCapacity, DEFAULT_NODE_THRESHOLD_FACTOR);
        }

        public Spec(final @NotNull Function keyFunction, final float nodeThresholdFactor) {
            this(keyFunction, DEFAULT_MIN_CAPACITY, nodeThresholdFactor);
        }

        public Spec(
                final @NotNull Function keyFunction,
                final int minCapacity,
                final float nodeThresholdFactor) {

            this.keyFunction = keyFunction;
            this.minCapacity = minCapacity;
            this.nodeThresholdFactor = nodeThresholdFactor;
        }
    }

    private final @NotNull Spec spec;
    private @Nullable Object @NotNull [] table;
    private int size;
    private int nodeCount;
    private int nodeThreshold;

    public Index(final @NotNull Spec spec) {
        this.spec = spec;
        final int minCapacityPow2 = 1 << Pow2Util.roundToPowerOf2Bits(spec.minCapacity);
        table = new Object[minCapacityPow2];
        calcThresholds(minCapacityPow2);
    }

    public int size() {
        return size;
    }

    public @Nullable E put(final @NotNull E entry) {
        return put(entry, true);
    }

    public @Nullable E putIfAbsent(final @NotNull E entry) {
        return put(entry, false);
    }

    private @Nullable E put(final @NotNull E entry, final boolean overwrite) {
        final Object[] table = this.table;
        final K key = spec.keyFunction.apply(entry);
        final int hash = key.hashCode();
        final int index = hash & (table.length - 1);
        final Object o = table[index];
        if (o == null) {
            table[index] = entry;
            added();
            return null;
        }
        if (o.getClass() == Node.class) {
            Node node = (Node) o;
            while (true) {
                if ((node.hash == hash) && spec.keyFunction.apply(cast(node.value)).equals(key)) {
                    final Object nodeValue = node.value;
                    if (overwrite) {
                        node.value = entry;
                    }
                    return cast(nodeValue);
                }
                final Object next = node.next;
                if (next.getClass() == Node.class) {
                    node = (Node) next;
                } else {
                    final E e = cast(next);
                    final K nextKey = spec.keyFunction.apply(e);
                    if (nextKey.equals(key)) {
                        if (overwrite) {
                            node.next = entry;
                        }
                        return e;
                    }
                    node.next = new Node(nextKey.hashCode(), next, entry);
                    added();
                    addedNode();
                    return null;
                }
            }
        }
        final E e = cast(o);
        final K oKey = spec.keyFunction.apply(e);
        if (oKey.equals(key)) {
            if (overwrite) {
                table[index] = entry;
            }
            return e;
        }
        table[index] = new Node(oKey.hashCode(), o, entry);
        added();
        addedNode();
        return null;
    }

    public @Nullable E get(final @NotNull K key) {
        final Object[] table = this.table;
        final int hash = key.hashCode();
        final int index = hash & (table.length - 1);
        final Object o = table[index];
        if (o == null) {
            return null;
        }
        if (o.getClass() == Node.class) {
            Node node = (Node) o;
            while (true) {
                if ((node.hash == hash) && spec.keyFunction.apply(cast(node.value)).equals(key)) {
                    return cast(node.value);
                }
                final Object next = node.next;
                if (next.getClass() == Node.class) {
                    node = (Node) next;
                } else {
                    final E e = cast(next);
                    if (spec.keyFunction.apply(e).equals(key)) {
                        return e;
                    }
                    return null;
                }
            }
        }
        final E e = cast(o);
        if (spec.keyFunction.apply(e).equals(key)) {
            return e;
        }
        return null;
    }

    public @Nullable E remove(final @NotNull K key) {
        final Object[] table = this.table;
        final int hash = key.hashCode();
        final int index = hash & (table.length - 1);
        final Object o = table[index];
        if (o == null) {
            return null;
        }
        if (o.getClass() == Node.class) {
            Node node = (Node) o;
            if ((node.hash == hash) && spec.keyFunction.apply(cast(node.value)).equals(key)) {
                table[index] = node.next;
                removedNode();
                removed();
                return cast(node.value);
            }
            Object next = node.next;
            if (next.getClass() != Node.class) {
                final E e = cast(next);
                if (spec.keyFunction.apply(e).equals(key)) {
                    table[index] = node.value;
                    removedNode();
                    removed();
                    return e;
                }
                return null;
            }
            Node prevNode;
            while (true) {
                prevNode = node;
                node = (Node) next;
                if ((node.hash == hash) && spec.keyFunction.apply(cast(node.value)).equals(key)) {
                    prevNode.next = node.next;
                    removedNode();
                    removed();
                    return cast(node.value);
                }
                next = node.next;
                if (next.getClass() != Node.class) {
                    final E e = cast(next);
                    if (spec.keyFunction.apply(e).equals(key)) {
                        prevNode.next = node.value;
                        removedNode();
                        removed();
                        return e;
                    }
                    return null;
                }
            }
        }
        final E e = cast(o);
        if (spec.keyFunction.apply(e).equals(key)) {
            table[index] = null;
            removed();
            return e;
        }
        return null;
    }

    public void clear() {
        if (size > 0) {
            if (table.length == spec.minCapacity) {
                Arrays.fill(table, null);
            } else {
                table = new Object[spec.minCapacity];
            }
            size = 0;
            nodeCount = 0;
            calcThresholds(spec.minCapacity);
        }
    }

    public @NotNull E any() {
        for (final Object o : table) {
            if (o != null) {
                if (o.getClass() == Node.class) {
                    final Node node = (Node) o;
                    return cast(node.value);
                }
                return cast(o);
            }
        }
        throw new NoSuchElementException();
    }

    public void forEach(final @NotNull Consumer consumer) {
        for (final Object o : table) {
            if (o != null) {
                if (o.getClass() == Node.class) {
                    Node node = (Node) o;
                    while (true) {
                        consumer.accept(cast(node.value));
                        final Object next = node.next;
                        if (next.getClass() == Node.class) {
                            node = (Node) next;
                        } else {
                            consumer.accept(cast(next));
                            break;
                        }
                    }
                } else {
                    consumer.accept(cast(o));
                }
            }
        }
    }

    private void added() {
        size++;
    }

    private void addedNode() {
        if ((++nodeCount > nodeThreshold) && (table.length < MAX_CAPACITY)) {
            final Object[] oldTable = table;
            final int oldCapacity = oldTable.length;
            final int newCapacity = oldCapacity << 1;
            final int newMask = newCapacity - 1;
            final Object[] newTable = new Object[newCapacity];
            int newNodeCount = 0;

            for (int oldIndex = 0; oldIndex < oldCapacity; oldIndex++) {
                final Object o = oldTable[oldIndex];
                if (o != null) {
                    if (o.getClass() == Node.class) {
                        Node node = (Node) o;
                        Node low = null, prevLow = null;
                        Node high = null, prevHigh = null;
                        final int highIndex = oldIndex + oldCapacity;
                        while (true) {
                            if ((node.hash & newMask) == oldIndex) {
                                if (low == null) {
                                    low = node;
                                    newTable[oldIndex] = node;
                                } else {
                                    prevLow = low;
                                    low = node;
                                    prevLow.next = low;
                                }
                            } else {
                                if (high == null) {
                                    high = node;
                                    newTable[highIndex] = node;
                                } else {
                                    prevHigh = high;
                                    high = node;
                                    prevHigh.next = high;
                                }
                            }
                            newNodeCount++;
                            final Object next = node.next;
                            if (next.getClass() == Node.class) {
                                node = (Node) next;
                            } else {
                                final E e = cast(next);
                                if ((spec.keyFunction.apply(e).hashCode() & newMask) == oldIndex) {
                                    if (low == null) {
                                        newTable[oldIndex] = e;
                                    } else {
                                        low.next = e;
                                    }
                                    if (high != null) {
                                        if (prevHigh == null) {
                                            newTable[highIndex] = high.value;
                                        } else {
                                            prevHigh.next = high.value;
                                        }
                                        newNodeCount--;
                                    }
                                } else {
                                    if (high == null) {
                                        newTable[highIndex] = e;
                                    } else {
                                        high.next = e;
                                    }
                                    if (low != null) {
                                        if (prevLow == null) {
                                            newTable[oldIndex] = low.value;
                                        } else {
                                            prevLow.next = low.value;
                                        }
                                        newNodeCount--;
                                    }
                                }
                                break;
                            }
                        }
                    } else {
                        final int hash = spec.keyFunction.apply(cast(o)).hashCode();
                        final int newIndex = hash & newMask;
                        newTable[newIndex] = o;
                    }
                }
            }

            table = newTable;
            nodeCount = newNodeCount;
            calcThresholds(newCapacity);
        }
    }

    private void removedNode() {
        nodeCount--;
    }

    private void removed() {
        if ((--size < nodeThreshold) && (table.length > spec.minCapacity)) {
            final Object[] oldTable = table;
            final int oldCapacity = oldTable.length;
            final int newCapacity = oldCapacity >> 1;
            final Object[] newTable = new Object[newCapacity];
            int newNodeCount = nodeCount;

            System.arraycopy(oldTable, 0, newTable, 0, newCapacity);
            for (int oldIndex = newCapacity; oldIndex < oldCapacity; oldIndex++) {
                final Object old = oldTable[oldIndex];
                if (old != null) {
                    final int newIndex = oldIndex - newCapacity;
                    final Object o = newTable[newIndex];
                    if (o == null) {
                        newTable[newIndex] = old;
                    } else if (o.getClass() == Node.class) {
                        Node node = (Node) o;
                        Object next;
                        while (true) {
                            next = node.next;
                            if (next.getClass() == Node.class) {
                                node = (Node) next;
                            } else {
                                break;
                            }
                        }
                        node.next = new Node(spec.keyFunction.apply(cast(next)).hashCode(), next, old);
                        newNodeCount++;
                    } else {
                        newTable[newIndex] = new Node(spec.keyFunction.apply(cast(o)).hashCode(), o, old);
                        newNodeCount++;
                    }
                }
            }

            table = newTable;
            nodeCount = newNodeCount;
            calcThresholds(newCapacity);
        }
    }

    private void calcThresholds(final int capacity) {
        nodeThreshold = (int) (capacity * spec.nodeThresholdFactor);
    }

    private @NotNull E cast(final @NotNull Object o) {
        //noinspection unchecked
        return (E) o;
    }

    private static class Node {

        final int hash;
        @NotNull Object value;
        @NotNull Object next;

        Node(final int hash, final @NotNull Object value, final @NotNull Object next) {
            this.hash = hash;
            this.value = value;
            this.next = next;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy