/* * This file is part of LanternServer, licensed under the MIT License (MIT). * * Copyright (c) LanternPowered <https://www.lanternpowered.org> * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * 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 org.lanternpowered.server.util.concurrent; import static org.lanternpowered.server.util.Conditions.checkArrayRange; import java.io.Serializable; import java.util.Arrays; import java.util.concurrent.atomic.AtomicIntegerArray; public class AtomicByteArray implements Serializable { private static final long serialVersionUID = 3434275139515033068L; // The amount of bytes packed in one integer private static final int PACKED_VALUES = Integer.SIZE / Byte.SIZE; private static final int VALUE_BITS = Integer.SIZE / PACKED_VALUES; private static final int VALUE_MASK = (1 << VALUE_BITS) - 1; private static final int INDEX_MASK = PACKED_VALUES - 1; private static final int INDEX_BITS = PACKED_VALUES >> 1; private static int key(int combined, int index, byte value) { index *= VALUE_BITS; // Reset the bits first combined &= ~(VALUE_MASK << index); // Apply the new content if needed if (value != 0) { combined |= (value & VALUE_MASK) << index; } return combined; } private static byte key(int combined, int index) { return (byte) ((combined >> (index * VALUE_BITS)) & VALUE_MASK); } private final int length; private final int backingArraySize; private final AtomicIntegerArray backingArray; /** * Creates a new {@link AtomicByteArray} of the given length, with all * elements initially zero. * * @param length the length of the array */ public AtomicByteArray(int length) { this.length = length; this.backingArraySize = (int) Math.ceil((double) length / (double) PACKED_VALUES); this.backingArray = new AtomicIntegerArray(this.backingArraySize); } /** * Creates a new {@link AtomicByteArray} of the given content. * * @param content the content */ public AtomicByteArray(byte[] content) { this(content.length, content); } /** * Creates a new {@link AtomicByteArray} of the given length, with all * elements copied from the initial content array. The lengths don't have * to match, if it's shorter then the remaining content be set to zero, * and if it's longer then will the rest of the content be ignored. * * @param length the length of the array * @param initialContent the initial content */ public AtomicByteArray(int length, byte[] initialContent) { this.length = length; this.backingArraySize = (int) Math.ceil((double) length / (double) PACKED_VALUES); int[] array = new int[this.backingArraySize]; for (int i = 0; i < this.backingArraySize; i++) { int j = i << INDEX_BITS; int value = 0; boolean flag = false; for (int k = 0; k < PACKED_VALUES; k++) { int l = j + k; if (l >= initialContent.length || l >= length) { flag = true; break; } value = key(value, k, initialContent[l]); } array[i] = value; if (flag) { break; } } this.backingArray = new AtomicIntegerArray(array); } private int getPacked(int index) { return this.backingArray.get(index >> INDEX_BITS); } /** * Gets the length of the array * * @return the length */ public final int length() { return this.length; } /** * Gets an element from the array at a given index * * @param index the index * @return the element */ public final byte get(int index) { checkArrayRange(index, this.length); return key(this.getPacked(index), index & INDEX_MASK); } /** * Sets an element in the array at a given index and returns the old value. * * @param index the index * @param value the new value * @return the old value */ public final byte getAndSet(int index, byte value) { checkArrayRange(index, this.length); boolean success = false; byte oldValue = 0; int backingIndex = index >> INDEX_BITS; int valueIndex = index & INDEX_MASK; while (!success) { int oldPacked = this.backingArray.get(backingIndex); oldValue = key(oldPacked, valueIndex); int newPacked = key(oldPacked, valueIndex, value); success = this.backingArray.compareAndSet(backingIndex, oldPacked, newPacked); } return oldValue; } /** * Sets the element at the given index, but only if the previous value was the expected value. * * @param index the index * @param expected the expected value * @param newValue the new value * @return true on success */ public final boolean compareAndSet(int index, byte expected, byte newValue) { checkArrayRange(index, this.length); boolean success = false; byte oldValue = 0; int backingIndex = index >> INDEX_BITS; int valueIndex = index & INDEX_MASK; while (!success) { int oldPacked = this.backingArray.get(backingIndex); oldValue = key(oldPacked, valueIndex); if (oldValue != expected) { return false; } int newPacked = key(oldPacked, valueIndex, newValue); success = this.backingArray.compareAndSet(backingIndex, oldPacked, newPacked); } return true; } private byte addAndGet(int index, byte delta, boolean old) { checkArrayRange(index, this.length); boolean success = false; byte newValue = 0; byte oldValue = 0; while (!success) { oldValue = this.get(index); newValue = (byte) (oldValue + delta); success = this.compareAndSet(index, oldValue, newValue); } return old ? oldValue : newValue; } /** * Gets an array containing all the values in the array. The returned values are * not guaranteed to be from the same time instant. * * If an array is provided and it is the correct length, then * that array will be used as the destination array. * * @param array the provided array * @return an array containing the values in the array */ public final byte[] getArray(byte[] array) { if (array == null || array.length != this.length) { array = new byte[this.length]; } for (int i = 0; i < this.length; i += PACKED_VALUES) { int packed = this.getPacked(i); for (int j = 0; j < PACKED_VALUES; j++) { if (i + j >= this.length) { break; } array[i + j] = key(packed, j); } } return array; } /** * Sets an element to the given value. * * @param index the index * @param value the new value */ public final void set(int index, byte value) { this.getAndSet(index, value); } /** * Sets an element to the given value, but the update may not happen immediately. * * @param index the index * @param value the new value */ public final void lazySet(int index, byte value) { this.set(index, value); } /** * Sets the element at the given index, but only if the previous value was the expected value. This may fail spuriously. * * @param index the index * @param expected the expected value * @param newValue the new value * @return true on success */ public final boolean weakCompareAndSet(int index, byte expected, byte newValue) { return this.compareAndSet(index, expected, newValue); } /** * Atomically adds a delta to an element, and gets the new value. * * @param index the index * @param delta the delta to add to the element * @return the new value */ public final byte addAndGet(int index, byte delta) { return this.addAndGet(index, delta, false); } /** * Atomically adds a delta to an element, and gets the old value. * * @param index the index * @param delta the delta to add to the element * @return the old value */ public final byte getAndAdd(int index, byte delta) { return this.addAndGet(index, delta, true); } /** * Atomically increments an element and returns the old value. * * @param index the index * @return the old value */ public final byte getAndIncrement(int index) { return this.getAndAdd(index, (byte) 1); } /** * Atomically decrements an element and returns the old value. * * @param index the index * @return the old value */ public final byte getAndDecrement(int index) { return this.getAndAdd(index, (byte) -1); } /** * Atomically increments an element and returns the new value. * * @param index the index * @return the new value */ public final byte incrementAndGet(int index) { return this.addAndGet(index, (byte) 1); } /** * Atomically decrements an element and returns the new value. * * @param index the index * @return the new value */ public final byte decrementAndGet(int index) { return this.addAndGet(index, (byte) -1); } /** * Gets an array containing all the values in the array. * * The returned values are not guaranteed to be from the same time instant. * * @return the array */ public final byte[] getArray() { return this.getArray(null); } /** * Returns a string representation of the array. * * The returned values are not guaranteed to be from the same time instant. * * @return the string representation */ @Override public String toString() { return Arrays.toString(this.getArray()); } }