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

org.jboss.remoting3._private.IntIndexHashMap Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2017 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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 org.jboss.remoting3._private;

import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.IntFunction;
import java.util.function.ToIntFunction;

/**
 * Lock-free concurrent integer-indexed hash map.
 *
 * @param  the value type
 *
 * @author David M. Lloyd
 */
public final class IntIndexHashMap extends AbstractCollection implements IntIndexMap {
    private static final int DEFAULT_INITIAL_CAPACITY = 512;
    private static final int MAXIMUM_CAPACITY = 1 << 30;
    private static final float DEFAULT_LOAD_FACTOR = 0.60f;

    /** A row which has been resized into the new view. */
    private static final Object[] RESIZED = new Object[0];
    /** A non-existent table entry (as opposed to a {@code null} value). */
    private static final Object NONEXISTENT = new Object();

    private final ToIntFunction indexer;
    private final Equaller ve;

    private volatile Table table;

    private final float loadFactor;
    private final int initialCapacity;

    @SuppressWarnings({"unchecked", "rawtypes"})
    private static final AtomicIntegerFieldUpdater sizeUpdater = AtomicIntegerFieldUpdater.newUpdater(Table.class, "size");

    @SuppressWarnings({"unchecked", "rawtypes"})
    private static final AtomicReferenceFieldUpdater tableUpdater = AtomicReferenceFieldUpdater.newUpdater(IntIndexHashMap.class, Table.class, "table");

    /**
     * Construct a new instance.
     *
     * @param indexer the key indexer
     * @param valueEqualler the value equaller
     * @param initialCapacity the initial capacity
     * @param loadFactor the load factor
     */
    public IntIndexHashMap(ToIntFunction indexer, Equaller valueEqualler, int initialCapacity, float loadFactor) {
        if (valueEqualler == null) {
            throw new IllegalArgumentException("valueEqualler is null");
        }
        this.indexer = indexer;
        ve = valueEqualler;
        if (initialCapacity < 0) {
            throw new IllegalArgumentException("Initial capacity must be > 0");
        }
        if (initialCapacity > MAXIMUM_CAPACITY) {
            initialCapacity = MAXIMUM_CAPACITY;
        }
        if (loadFactor <= 0.0 || Float.isNaN(loadFactor) || loadFactor >= 1.0) {
            throw new IllegalArgumentException("Load factor must be between 0.0f and 1.0f");
        }

        int capacity = 1;

        while (capacity < initialCapacity) {
            capacity <<= 1;
        }

        this.loadFactor = loadFactor;
        this.initialCapacity = capacity;

        final Table table = new Table(capacity, loadFactor);
        tableUpdater.set(this, table);
    }

    /**
     * Construct a new instance.
     *
     * @param indexer the key indexer
     * @param valueEqualler the value equaller
     */
    public IntIndexHashMap(ToIntFunction indexer, Equaller valueEqualler) {
        this(indexer, valueEqualler, DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Construct a new instance.
     *
     * @param indexer the key indexer
     * @param initialCapacity the initial capacity
     * @param loadFactor the load factor
     */
    public IntIndexHashMap(ToIntFunction indexer, int initialCapacity, final float loadFactor) {
        this(indexer, Equaller.DEFAULT, initialCapacity, loadFactor);
    }

    /**
     * Construct a new instance.
     *
     * @param indexer the key indexer
     * @param loadFactor the load factor
     */
    public IntIndexHashMap(ToIntFunction indexer, final float loadFactor) {
        this(indexer, DEFAULT_INITIAL_CAPACITY, loadFactor);
    }

    /**
     * Construct a new instance.
     *
     * @param indexer the key indexer
     * @param initialCapacity the initial capacity
     */
    public IntIndexHashMap(ToIntFunction indexer, final int initialCapacity) {
        this(indexer, initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Construct a new instance.
     *
     * @param indexer the key indexer
     */
    public IntIndexHashMap(ToIntFunction indexer) {
        this(indexer, DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

    public V putIfAbsent(final V value) {
        final V result = doPut(value, true, table);
        return result == NONEXISTENT ? null : result;
    }

    public V computeIfAbsent(final int index, final IntFunction producer) {
        final Table table = this.table;
        V result = doGet(table, index);
        if (result == NONEXISTENT) {
            final V newVal = producer.apply(index);
            result = doPut(newVal, true, table);
            return result == NONEXISTENT ? newVal : result;
        } else {
            return result;
        }
    }

    public V removeKey(final int index) {
        final V result = doRemove(index, table);
        return result == NONEXISTENT ? null : result;
    }

    @SuppressWarnings({ "unchecked" })
    public boolean remove(final Object value) {
        return doRemove((V) value, table);
    }

    public boolean containsKey(final int index) {
        return doGet(table, index) != NONEXISTENT;
    }

    public V get(final int index) {
        final V result = doGet(table, index);
        return result == NONEXISTENT ? null : result;
    }

    public V put(final V value) {
        final V result = doPut(value, false, table);
        return result == NONEXISTENT ? null : result;
    }

    public V replace(final V value) {
        final V result = doReplace(value, table);
        return result == NONEXISTENT ? null : result;
    }

    public boolean replace(final V oldValue, final V newValue) {
        if (indexer.applyAsInt(oldValue) != indexer.applyAsInt(newValue)) {
            throw new IllegalArgumentException("Can only replace with value which has the same key");
        }
        return doReplace(oldValue, newValue, table);
    }

    public int applyAsInt(final V argument) {
        return indexer.applyAsInt(argument);
    }

    public boolean add(final V v) {
        return doPut(v, true, table) == NONEXISTENT;
    }

    @SuppressWarnings("unchecked")
    public  T[] toArray(final T[] a) {
        final ArrayList list = new ArrayList(size());
        for (final V item : this) {
            list.add((T) item);
        }
        return list.toArray(a);
    }

    public Object[] toArray() {
        final ArrayList list = new ArrayList(size());
        for (V item : this) {
            list.add(item);
        }
        return list.toArray();
    }

    @SuppressWarnings({ "unchecked" })
    public boolean contains(final Object o) {
        return ve.equals((V) o, get(indexer.applyAsInt((V) o)));
    }

    public Iterator iterator() {
        return new EntryIterator();
    }

    public int size() {
        return table.size & 0x7fffffff;
    }

    private boolean doReplace(final V oldValue, final V newValue, final Table table) {
        final int key = indexer.applyAsInt(oldValue);
        final AtomicReferenceArray array = table.array;
        final int idx = key & array.length() - 1;

        OUTER: for (;;) {
            // Fetch the table row.
            V[] oldRow = array.get(idx);
            if (oldRow == null) {
                // no match for the key
                return false;
            }
            if (oldRow == RESIZED) {
                return doReplace(oldValue, newValue, table.resizeView);
            }

            for (int i = 0, length = oldRow.length; i < length; i++) {
                final V tryItem = oldRow[i];
                if (ve.equals(tryItem, oldValue)) {
                    final V[] newRow = oldRow.clone();
                    newRow[i] = newValue;
                    if (array.compareAndSet(i, oldRow, newRow)) {
                        return true;
                    } else {
                        continue OUTER;
                    }
                }
            }
            return false;
        }
    }

    private V doReplace(final V value, final Table table) {
        final int key = indexer.applyAsInt(value);
        final AtomicReferenceArray array = table.array;
        final int idx = key & array.length() - 1;

        OUTER: for (;;) {
            // Fetch the table row.
            V[] oldRow = array.get(idx);
            if (oldRow == null) {
                // no match for the key
                return nonexistent();
            }
            if (oldRow == RESIZED) {
                return doReplace(value, table.resizeView);
            }

            // Find the matching Item in the row.
            for (int i = 0, length = oldRow.length; i < length; i++) {
                final V tryItem = oldRow[i];
                if (key == indexer.applyAsInt(tryItem)) {
                    final V[] newRow = oldRow.clone();
                    newRow[i] = value;
                    if (array.compareAndSet(i, oldRow, newRow)) {
                        return tryItem;
                    } else {
                        continue OUTER;
                    }
                }
            }
            return nonexistent();
        }
    }

    private boolean doRemove(final V item, final Table table) {
        int key = indexer.applyAsInt(item);

        final AtomicReferenceArray array = table.array;
        final int idx = key & array.length() - 1;

        V[] oldRow;

        OUTER: for (;;) {
            oldRow = array.get(idx);
            if (oldRow == null) {
                return false;
            }
            if (oldRow == RESIZED) {
                boolean result;
                if (result = doRemove(item, table.resizeView)) {
                    sizeUpdater.getAndDecrement(table);
                }
                return result;
            }

            for (int i = 0; i < oldRow.length; i ++) {
                if (ve.equals(item, oldRow[i])) {
                    if (array.compareAndSet(idx, oldRow, remove(oldRow, i))) {
                        sizeUpdater.getAndDecrement(table);
                        return true;
                    } else {
                        continue OUTER;
                    }
                }
            }
            // not found
            return false;
        }
    }

    private V doRemove(final int key, final Table table) {
        final AtomicReferenceArray array = table.array;
        final int idx = key & array.length() - 1;

        V[] oldRow;

        OUTER: for (;;) {
            oldRow = array.get(idx);
            if (oldRow == null) {
                return nonexistent();
            }
            if (oldRow == RESIZED) {
                V result;
                if ((result = doRemove(key, table.resizeView)) != NONEXISTENT) {
                    sizeUpdater.getAndDecrement(table);
                }
                return result;
            }

            for (int i = 0; i < oldRow.length; i ++) {
                if (key == indexer.applyAsInt(oldRow[i])) {
                    if (array.compareAndSet(idx, oldRow, remove(oldRow, i))) {
                        sizeUpdater.getAndDecrement(table);
                        return oldRow[i];
                    } else {
                        continue OUTER;
                    }
                }
            }
            // not found
            return nonexistent();
        }
    }

    private V doPut(V value, boolean ifAbsent, Table table) {
        final int hashCode = indexer.applyAsInt(value);
        final AtomicReferenceArray array = table.array;
        final int idx = hashCode & array.length() - 1;

        OUTER: for (;;) {

            // Fetch the table row.
            V[] oldRow = array.get(idx);
            if (oldRow == RESIZED) {
                // row was transported to the new table so recalculate everything
                final V result = doPut(value, ifAbsent, table.resizeView);
                // keep a consistent size view though!
                if (result == NONEXISTENT) sizeUpdater.getAndIncrement(table);
                return result;
            }
            if (oldRow != null) {
                // Find the matching Item in the row.
                V oldItem;
                for (int i = 0, length = oldRow.length; i < length; i++) {
                    if (hashCode == indexer.applyAsInt(oldRow[i])) {
                        if (ifAbsent) {
                            return oldRow[i];
                        } else {
                            V[] newRow = oldRow.clone();
                            newRow[i] = value;
                            oldItem = oldRow[i];
                            if (array.compareAndSet(idx, oldRow, newRow)) {
                                return oldItem;
                            } else {
                                // retry
                                continue OUTER;
                            }
                        }
                    }
                }
            }

            if (array.compareAndSet(idx, oldRow, addItem(oldRow, value))) {
                // Up the table size.
                final int threshold = table.threshold;
                int newSize = sizeUpdater.incrementAndGet(table);
                // if the sign bit is set the value will be < 0 meaning if a resize is in progress this condition is false
                while (newSize > threshold) {
                    if (sizeUpdater.compareAndSet(table, newSize, newSize | 0x80000000)) {
                        resize(table);
                        break;
                    } else {
                        newSize = table.size;
                    }
                }
                // Success.
                return nonexistent();
            }
        }
    }

    private void resize(Table origTable) {
        final AtomicReferenceArray origArray = origTable.array;
        final int origCapacity = origArray.length();
        final Table newTable = new Table(origCapacity << 1, loadFactor);
        // Prevent resize until we're done...
        newTable.size = 0x80000000;
        origTable.resizeView = newTable;
        final AtomicReferenceArray newArray = newTable.array;

        for (int i = 0; i < origCapacity; i ++) {
            // for each row, try to resize into two new rows
            V[] origRow, newRow0, newRow1;
            int count0, count1;
            do {
                count0 = count1 = 0;
                origRow = origArray.get(i);
                if (origRow != null) {
                    for (V item : origRow) {
                        if ((indexer.applyAsInt(item) & origCapacity) == 0) {
                            count0++;
                        } else {
                            count1++;
                        }
                    }
                    if (count0 != 0) {
                        newRow0 = createRow(count0);
                        int j = 0;
                        for (V item : origRow) {
                            if ((indexer.applyAsInt(item) & origCapacity) == 0) {
                                newRow0[j++] = item;
                            }
                        }
                        newArray.lazySet(i, newRow0);
                    }
                    if (count1 != 0) {
                        newRow1 = createRow(count1);
                        int j = 0;
                        for (V item : origRow) {
                            if ((indexer.applyAsInt(item) & origCapacity) != 0) {
                                newRow1[j++] = item;
                            }
                        }
                        newArray.lazySet(i + origCapacity, newRow1);
                    }
                }
            } while (! origArray.compareAndSet(i, origRow, IntIndexHashMap.resized()));
            sizeUpdater.getAndAdd(newTable, count0 + count1);
        }

        int size;
        do {
            size = newTable.size;
            if ((size & 0x7fffffff) >= newTable.threshold) {
                // shorter path for reads and writes
                table = newTable;
                // then time for another resize, right away
                resize(newTable);
                return;
            }
        } while (!sizeUpdater.compareAndSet(newTable, size, size & 0x7fffffff));

        // All done, plug in the new table
        table = newTable;
    }

    private static  V[] remove(V[] row, int idx) {
        final int len = row.length;
        assert idx < len;
        if (len == 1) {
            return null;
        }
        V[] newRow = createRow(len - 1);
        if (idx > 0) {
            System.arraycopy(row, 0, newRow, 0, idx);
        }
        if (idx < len - 1) {
            System.arraycopy(row, idx + 1, newRow, idx, len - 1 - idx);
        }
        return newRow;
    }

    private V doGet(final Table table, final int key) {
        final AtomicReferenceArray array = table.array;
        final V[] row = array.get(key & (array.length() - 1));
        if(row == RESIZED) {
            return doGet(table.resizeView, key);
        }
        if (row != null) for (V item : row) {
            if (key == indexer.applyAsInt(item)) {
                return item;
            }
        }
        return nonexistent();
    }

    public void clear() {
        table = new Table(initialCapacity, loadFactor);
    }

    private static  V[] addItem(final V[] row, final V newItem) {
        if (row == null) {
            return createRow(newItem);
        } else {
            final int length = row.length;
            V[] newRow = Arrays.copyOf(row, length + 1);
            newRow[length] = newItem;
            return newRow;
        }
    }

    @SuppressWarnings("unchecked")
    private static  V[] createRow(final V newItem) {
        return (V[]) new Object[] { newItem };
    }

    @SuppressWarnings("unchecked")
    private static  V[] createRow(final int length) {
        return (V[]) new Object[length];
    }

    @SuppressWarnings("unchecked")
    private static  V nonexistent() {
        return (V) NONEXISTENT;
    }

    @SuppressWarnings("unchecked")
    private static  V[] resized() {
        return (V[]) RESIZED;
    }

    final class RowIterator implements Iterator {
        private final Table table;
        V[] row;

        private int idx;
        private int removeIdx = -1;
        private V next = nonexistent();

        RowIterator(final Table table, final V[] row) {
            this.table = table;
            this.row = row;
        }

        public boolean hasNext() {
            while (next == NONEXISTENT) {
                final V[] row = this.row;
                if (row == null || idx == row.length) {
                    return false;
                }
                next = row[idx++];
            }
            return true;
        }

        public V next() {
            if (hasNext()) try {
                removeIdx = idx - 1;
                return next;
            } finally {
                next = nonexistent();
            }
            throw new NoSuchElementException();
        }

        public void remove() {
            int removeIdx = this.removeIdx;
            this.removeIdx = -1;
            if (removeIdx == -1) {
                throw new IllegalStateException("next() not yet called");
            }
            doRemove(row[removeIdx], table);
        }
    }

    final class BranchIterator implements Iterator {
        private final Iterator branch0;
        private final Iterator branch1;

        private boolean branch;

        BranchIterator(final Iterator branch0, final Iterator branch1) {
            this.branch0 = branch0;
            this.branch1 = branch1;
        }

        public boolean hasNext() {
            return branch0.hasNext() || branch1.hasNext();
        }

        public V next() {
            if (branch) {
                return branch1.next();
            }
            if (branch0.hasNext()) {
                return branch0.next();
            } else {
                branch = true;
                return branch1.next();
            }
        }

        public void remove() {
            if (branch) {
                branch0.remove();
            } else {
                branch1.remove();
            }
        }
    }

    private Iterator createRowIterator(Table table, int rowIdx) {
        final AtomicReferenceArray array = table.array;
        final V[] row = array.get(rowIdx);
        if (row == RESIZED) {
            final Table resizeView = table.resizeView;
            return new BranchIterator(createRowIterator(resizeView, rowIdx), createRowIterator(resizeView, rowIdx + array.length()));
        } else {
            return new RowIterator(table, row);
        }
    }

    final class EntryIterator implements Iterator {
        private final Table table = IntIndexHashMap.this.table;
        private Iterator tableIterator;
        private Iterator removeIterator;
        private int tableIdx;
        private V next;

        public boolean hasNext() {
            while (next == null) {
                if (tableIdx == table.array.length()) {
                    return false;
                }
                if (tableIterator == null) {
                    tableIterator = createRowIterator(table, tableIdx++);
                }
                if (tableIterator.hasNext()) {
                    next = tableIterator.next();
                    return true;
                } else {
                    tableIterator = null;
                }
            }
            return true;
        }

        public V next() {
            if (hasNext()) try {
                return next;
            } finally {
                removeIterator = tableIterator;
                next = null;
            }
            throw new NoSuchElementException();
        }

        public void remove() {
            final Iterator removeIterator = this.removeIterator;
            if (removeIterator == null) {
                throw new IllegalStateException();
            } else try {
                removeIterator.remove();
            } finally {
                this.removeIterator = null;
            }
        }
    }

    static final class Table {
        final AtomicReferenceArray array;
        final int threshold;
        /** Bits 0-30 are size; bit 31 is 1 if the table is being resized. */
        volatile int size;
        volatile Table resizeView;

        private Table(int capacity, float loadFactor) {
            array = new AtomicReferenceArray(capacity);
            threshold = capacity == MAXIMUM_CAPACITY ? Integer.MAX_VALUE : (int)(capacity * loadFactor);
        }
    }
}