/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.util; import java.util.Arrays; import java.util.AbstractList; import java.util.RandomAccess; import java.io.Serializable; import org.opengis.util.Cloneable; import org.geotools.resources.XArray; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; /** * A list of unsigned integer values. This class packs the values in the minimal amount of bits * required for storing unsigned integers of the given {@linkplain #maximalValue maximal value}. * <p> * This class is <strong>not</strong> thread-safe. Synchronizations (if wanted) are user's * reponsability. * * @since 2.5 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (Geomatys) */ public class IntegerList extends AbstractList<Integer> implements RandomAccess, Serializable, Cloneable { /** * For cross-version compatibility. */ private static final long serialVersionUID = 1241962316404811189L; /** * The size of the primitive type used for the {@link #values} array. */ private static final int VALUE_SIZE = 64; /** * The shift to apply on {@code index} in order to produce a result equivalent to * {@code index} / {@value #VALUE_SIZE}. */ private static final int BASE_SHIFT = 6; /** * The mask to apply on {@code index} in order to produce a result equivalent to * {@code index} % {@value #VALUE_SIZE}. */ private static final int OFFSET_MASK = 63; /** * The packed values. We use the {@code long} type instead of {@code int} * on the basis that 64 bits machines are becoming more and more common. */ private long[] values; /** * The bit count for values. */ private final int bitCount; /** * The mask computed as {@code (1 << bitCount) - 1}. */ private final int mask; /** * The list size. Initially 0. */ private int size; /** * Creates an initially empty list with the given initial capacity. * * @param initialCapacity The initial capacity. * @param maximalValue The maximal value to be allowed, inclusive. */ public IntegerList(int initialCapacity, int maximalValue) { this(initialCapacity, maximalValue, false); } /** * Creates a new list with the given initial size. * The value of all elements are initialized to 0. * * @param initialCapacity The initial capacity. * @param maximalValue The maximal value to be allowed, inclusive. * @param fill If {@code true}, the initial {@linkplain #size} is set to the initial * capacity with all values set to 0. */ public IntegerList(final int initialCapacity, int maximalValue, final boolean fill) { if (initialCapacity <= 0) { throw new IllegalArgumentException(Errors.format( ErrorKeys.NOT_GREATER_THAN_ZERO_$1, initialCapacity)); } if (maximalValue <= 0) { throw new IllegalArgumentException(Errors.format( ErrorKeys.NOT_GREATER_THAN_ZERO_$1, maximalValue)); } int bitCount = 0; do { bitCount++; maximalValue >>>= 1; } while (maximalValue != 0); this.bitCount = bitCount; mask = (1 << bitCount) - 1; values = new long[length(initialCapacity)]; if (fill) { size = initialCapacity; } } /** * Returns the array length required for holding a list of the given size. * * @param size The list size. * @return The array length for holding a list of the given size. */ private int length(int size) { size *= bitCount; int length = size >>> BASE_SHIFT; if ((size & OFFSET_MASK) != 0) { length++; } return length; } /** * Returns the maximal value that can be stored in this list. * May be slighly higher than the value given to the constructor. * * @return The maximal value, inclusive. */ public int maximalValue() { return mask; } /** * Returns the current number of values in this list. * * @return The number of values. */ public int size() { return size; } /** * Sets the list size to the given value. If the new size is lower than previous size, * then the elements after the new size are discarted. If the new size is greater than * the previous one, then the extra elements are initialized to 0. * * @param size The new size. */ public void resize(final int size) { if (size < 0) { throw new IllegalArgumentException(); } if (size > this.size) { int base = this.size * bitCount; final int offset = base & OFFSET_MASK; base >>>= BASE_SHIFT; if (offset != 0 && base < values.length) { values[base] &= (1L << offset) - 1; base++; } final int length = length(size); Arrays.fill(values, base, Math.min(length, values.length), 0L); if (length > values.length) { values = XArray.resize(values, length); } } this.size = size; } /** * Fills the list with the given value. Every existing values are overwritten from index * 0 inclusive up to {@link #size} exclusive. * * @param value The value to set. */ @SuppressWarnings("fallthrough") public void fill(int value) { if (value < 0 || value > mask) { throw new IllegalArgumentException(Errors.format(ErrorKeys.VALUE_OUT_OF_BOUNDS_$3, value, 0, mask)); } final long p; if (value == 0) { p = 0; // All bits set to 0. } else if (value == mask) { p = -1L; // All bits set to 1. } else switch (bitCount) { case 1: value |= (value << 1); // Fall through case 2: value |= (value << 2); // Fall through case 4: value |= (value << 4); // Fall through case 8: value |= (value << 8); // Fall through case 16: value |= (value << 16); // Fall through case 32: p = (value & 0xFFFFFFFFL) | ((long) value << 32); break; default: { // General case (unoptimized) for (int i=0; i<size; i++) { setUnchecked(i, value); } return; } } Arrays.fill(values, 0, length(size), p); } /** * Discarts all elements in this list. */ @Override public void clear() { size = 0; } /** * Adds the given element to this list. * * @param value The value to add. * @throws NullPointerException if the given value is null. * @throws IllegalArgumentException if the given value is out of bounds. */ @Override public boolean add(final Integer value) throws IllegalArgumentException { addInteger(value); return true; } /** * Adds the given element as the {@code int} primitive type. * * @param value The value to add. * @throws IllegalArgumentException if the given value is out of bounds. */ public void addInteger(final int value) throws IllegalArgumentException { if (value < 0 || value > mask) { throw new IllegalArgumentException(Errors.format(ErrorKeys.VALUE_OUT_OF_BOUNDS_$3, value, 0, mask)); } final int length = length(++size); if (length > values.length) { values = XArray.resize(values, 2*values.length); } setUnchecked(size - 1, value); } /** * Returns the element at the given index. * * @param index The element index. * @return The value at the given index. * @throws IndexOutOfBoundsException if the given index is out of bounds. */ public Integer get(final int index) throws IndexOutOfBoundsException { return getInteger(index); } /** * Returns the element at the given index as the {@code int} primitive type. * * @param index The element index. * @return The value at the given index. * @throws IndexOutOfBoundsException if the given index is out of bounds. */ public int getInteger(int index) throws IndexOutOfBoundsException { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException(Errors.format(ErrorKeys.INDEX_OUT_OF_BOUNDS_$1, index)); } index *= bitCount; int base = index >>> BASE_SHIFT; int offset = index & OFFSET_MASK; int value = (int) (values[base] >>> offset); offset = VALUE_SIZE - offset; if (offset < bitCount) { final int high = (int) values[++base]; value |= (high << offset); } value &= mask; return value; } /** * Sets the element at the given index. * * @param index The element index. * @param value The value at the given index. * @return The previous value at the given index. * @throws IndexOutOfBoundsException if the given index is out of bounds. * @throws IllegalArgumentException if the given value is out of bounds. * @throws NullPointerException if the given value is null. */ @Override public Integer set(final int index, final Integer value) throws IndexOutOfBoundsException { final Integer old = get(index); setInteger(index, value); return old; } /** * Sets the element at the given index as the {@code int} primitive type. * * @param index The element index. * @param value The value at the given index. * @throws IndexOutOfBoundsException if the given index is out of bounds. * @throws IllegalArgumentException if the given value is out of bounds. */ public void setInteger(int index, int value) throws IndexOutOfBoundsException { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException(Errors.format(ErrorKeys.INDEX_OUT_OF_BOUNDS_$1, index)); } if (value < 0 || value > mask) { throw new IllegalArgumentException(Errors.format(ErrorKeys.VALUE_OUT_OF_BOUNDS_$3, value, 0, mask)); } setUnchecked(index, value); } /** * Sets the element at the given index as the {@code int} primitive type. * This argument does not check argument validity, since it is assumed already done. * * @param index The element index. * @param value The value at the given index. * @throws IndexOutOfBoundsException if the given index is out of bounds. * @throws IllegalArgumentException if the given value is out of bounds. */ private void setUnchecked(int index, int value) { index *= bitCount; int base = index >>> BASE_SHIFT; int offset = index & OFFSET_MASK; values[base] &= ~(((long) mask) << offset); values[base] |= ((long) value) << offset; offset = VALUE_SIZE - offset; if (offset < bitCount) { value >>>= offset; values[++base] &= ~(((long) mask) >>> offset); values[base] |= value; } } /** * Returns the occurence of the given value in this list. * * @param value The value to search for. * @return The number of time the given value occurs in this list. */ public int occurence(final int value) { int count = 0; final int size = size(); for (int i=0; i<size; i++) { if (getInteger(i) == value) { count++; } } return count; } /** * Trims the capacity of this list to be its current size. */ public void trimToSize() { values = XArray.resize(values, length(size)); } /** * Returns a clone of this list. * * @return A clone of this list. */ @Override public IntegerList clone() { final IntegerList clone; try { clone = (IntegerList) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(e); } clone.values = clone.values.clone(); return clone; } }