package net.glowstone.util; public final class VariableValueArray implements Cloneable { private final long[] backing; private final int capacity; private final int bitsPerValue; private final long valueMask; public VariableValueArray(int bitsPerValue, int capacity) { if (capacity < 0) { throw new IllegalArgumentException(String.format("capacity (%s) must not be negative", capacity)); } if (bitsPerValue < 1) { throw new IllegalArgumentException(String.format("bitsPerValue (%s) must not be less than 1", bitsPerValue)); } if (bitsPerValue > 64) { throw new IllegalArgumentException(String.format("bitsPerValue (%s) must not be greater than 64", bitsPerValue)); } backing = new long[(int) Math.ceil((bitsPerValue * capacity) / 64.0)]; this.bitsPerValue = bitsPerValue; valueMask = (1L << bitsPerValue) - 1L; this.capacity = capacity; } public long[] getBacking() { return backing; } public int getCapacity() { return capacity; } public int getBitsPerValue() { return bitsPerValue; } public long getLargestPossibleValue() { return valueMask; } public int get(int index) { checkIndex(index); index *= bitsPerValue; int i0 = index >> 6; int i1 = index & 0x3f; long value = backing[i0] >>> i1; int i2 = i1 + bitsPerValue; // The value is divided over two long values if (i2 > 64) { value |= backing[++i0] << 64 - i1; } return (int) (value & valueMask); } public void set(int index, int value) { checkIndex(index); if (value < 0) { throw new IllegalArgumentException(String.format("value (%s) must not be negative", value)); } if (value > valueMask) { throw new IllegalArgumentException(String.format("value (%s) must not be greater than %s", value, valueMask)); } index *= bitsPerValue; int i0 = index >> 6; int i1 = index & 0x3f; backing[i0] = this.backing[i0] & ~(this.valueMask << i1) | (value & valueMask) << i1; int i2 = i1 + bitsPerValue; // The value is divided over two long values if (i2 > 64) { i0++; backing[i0] = backing[i0] & ~((1L << i2 - 64) - 1L) | value >> 64 - i1; } } private void checkIndex(int index) { if (index < 0) { throw new IndexOutOfBoundsException(String.format("index (%s) must not be negative", index)); } if (index >= capacity) { throw new IndexOutOfBoundsException(String.format("index (%s) must not be greater than the capacity (%s)", index, capacity)); } } /** * Creates a new VariableValueArray with the contents of this one, and the * given bits per value. * * @param newBitsPerValue * The new value. Must be larger than the current value ( * {@link #getBitsPerValue()}). * @throws IllegalArgumentException * If newBitsPerValue is less than or equal to the current bits * per value. Setting it to the same size would be a waste of * resources, and decreasing could lead to data loss. * @return A new VariableValueArray */ public VariableValueArray increaseBitsPerValueTo(int newBitsPerValue) { if (newBitsPerValue < this.bitsPerValue) { throw new IllegalArgumentException("Cannot decrease bits per value! (was " + this.bitsPerValue + ", new size " + newBitsPerValue + ")"); } else if (newBitsPerValue == this.bitsPerValue) { throw new IllegalArgumentException("Cannot resize to the same size! (size was " + newBitsPerValue + ")"); } VariableValueArray returned = new VariableValueArray(newBitsPerValue, this.capacity); for (int i = 0; i < this.capacity; i++) { returned.set(i, this.get(i)); } return returned; } @Override public VariableValueArray clone() { VariableValueArray clone = new VariableValueArray(this.bitsPerValue, this.capacity); System.arraycopy(this.backing, 0, clone.backing, 0, this.backing.length); return clone; } /** * Calculates the number of bits that would be needed to store the given * value. */ public static int calculateNeededBits(int number) { int count = 0; do { count++; number >>>= 1; } while (number != 0); return count; } }