/////////////////////////////////////////////////////////////////////////////// // Copyright (c) 2001, Eric D. Friedman All Rights Reserved. // // 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; either // version 2.1 of the License, or (at your option) any later version. // // 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 General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// package com.intellij.util.containers; import gnu.trove.HashFunctions; import gnu.trove.TIntProcedure; import org.jetbrains.annotations.NotNull; import java.util.Arrays; import java.util.Random; /** * A resizable, array-backed list of int primitives. * * Created: Sat Dec 29 14:21:12 2001 * * @author Eric D. Friedman * @version $Id: TIntArrayList.java,v 1.5 2004/09/24 09:11:15 cdr Exp $ */ public class ByteArrayList implements Cloneable { /** the data of the list */ protected transient byte[] _data; /** the index after the last entry in the list */ protected transient int _pos; /** the default capacity for new lists */ protected static final int DEFAULT_CAPACITY = 4; /** * Creates a new {@code TIntArrayList} instance with the * default capacity. */ public ByteArrayList() { this(DEFAULT_CAPACITY); } /** * Creates a new {@code TIntArrayList} instance with the * specified capacity. * * @param capacity an {@code int} value */ public ByteArrayList(int capacity) { _data = new byte[capacity]; _pos = 0; } /** * Creates a new {@code TIntArrayList} instance whose * capacity is the greater of the length of <tt>values</tt> and * DEFAULT_CAPACITY and whose initial contents are the specified * values. * * @param values an {@code int[]} value */ public ByteArrayList(byte[] values) { this(Math.max(values.length, DEFAULT_CAPACITY)); add(values); } // sizing /** * Grow the internal array as needed to accomodate the specified * number of elements. The size of the array doubles on each * resize unless <tt>capacity</tt> requires more than twice the * current capacity. * * @param capacity an {@code int} value */ public void ensureCapacity(int capacity) { byte[] data = _data; if (capacity > data.length) { int newCap = Math.max(data.length < 100000 ? data.length << 1 : data.length * 4 / 3, capacity); byte[] tmp = new byte[newCap]; System.arraycopy(data, 0, tmp, 0, data.length); _data = tmp; } } /** * Returns the number of values in the list. * * @return the number of values in the list. */ public int size() { return _pos; } /** * Tests whether this list contains any values. * * @return true if the list is empty. */ public boolean isEmpty() { return _pos == 0; } /** * Sheds any excess capacity above and beyond the current size of * the list. */ public void trimToSize() { if (_data.length > size()) { byte[] tmp = new byte[size()]; toNativeArray(tmp, 0, tmp.length); _data = tmp; } } // modifying /** * Adds <tt>val</tt> to the end of the list, growing as needed. * * @param val an {@code int} value */ public void add(byte val) { ensureCapacity(_pos + 1); _data[_pos++] = val; } /** * Adds the values in the array <tt>vals</tt> to the end of the * list, in order. * * @param vals an {@code int[]} value */ public void add(byte[] vals) { add(vals, 0, vals.length); } /** * Adds a subset of the values in the array <tt>vals</tt> to the * end of the list, in order. * * @param vals an {@code int[]} value * @param offset the offset at which to start copying * @param length the number of values to copy. */ public void add(byte[] vals, int offset, int length) { ensureCapacity(_pos + length); System.arraycopy(vals, offset, _data, _pos, length); _pos += length; } /** * Inserts <tt>value</tt> into the list at <tt>offset</tt>. All * values including and to the right of <tt>offset</tt> are shifted * to the right. * * @param offset an {@code int} value * @param value an {@code int} value */ public void insert(int offset, byte value) { if (offset == _pos) { add(value); return; } ensureCapacity(_pos + 1); // shift right System.arraycopy(_data, offset, _data, offset + 1, _pos - offset); // insert _data[offset] = value; _pos++; } /** * Inserts the array of <tt>values</tt> into the list at * <tt>offset</tt>. All values including and to the right of * <tt>offset</tt> are shifted to the right. * * @param offset an {@code int} value * @param values an {@code int[]} value */ public void insert(int offset, byte[] values) { insert(offset, values, 0, values.length); } /** * Inserts a slice of the array of <tt>values</tt> into the list * at <tt>offset</tt>. All values including and to the right of * <tt>offset</tt> are shifted to the right. * * @param offset an {@code int} value * @param values an {@code int[]} value * @param valOffset the offset in the values array at which to * start copying. * @param len the number of values to copy from the values array */ public void insert(int offset, byte[] values, int valOffset, int len) { if (offset == _pos) { add(values, valOffset, len); return; } ensureCapacity(_pos + len); // shift right System.arraycopy(_data, offset, _data, offset + len, _pos - offset); // insert System.arraycopy(values, valOffset, _data, offset, len); _pos += len; } /** * Returns the value at the specified offset. * * @param offset an {@code int} value * @return an {@code int} value */ public byte get(int offset) { if (offset >= _pos) { throw new ArrayIndexOutOfBoundsException("Index out of range: "+offset+"; size: "+_pos); } return _data[offset]; } /** * Returns the value at the specified offset without doing any * bounds checking. * * @param offset an {@code int} value * @return an {@code int} value */ public byte getQuick(int offset) { return _data[offset]; } /** * Sets the value at the specified offset. * * @param offset an {@code int} value * @param val an {@code int} value */ public void set(int offset, byte val) { if (offset < 0 || offset >= _pos) { throw new ArrayIndexOutOfBoundsException(offset); } _data[offset] = val; } /** * Sets the value at the specified offset and returns the * previously stored value. * * @param offset an {@code int} value * @param val an {@code int} value * @return the value previously stored at offset. */ public byte getSet(int offset, byte val) { if (offset < 0 || offset >= _pos) { throw new ArrayIndexOutOfBoundsException(offset); } byte old = _data[offset]; _data[offset] = val; return old; } /** * Replace the values in the list starting at <tt>offset</tt> with * the contents of the <tt>values</tt> array. * * @param offset the first offset to replace * @param values the source of the new values */ public void set(int offset, byte[] values) { set(offset, values, 0, values.length); } /** * Replace the values in the list starting at <tt>offset</tt> with * <tt>length</tt> values from the <tt>values</tt> array, starting * at valOffset. * * @param offset the first offset to replace * @param values the source of the new values * @param valOffset the first value to copy from the values array * @param length the number of values to copy */ public void set(int offset, byte[] values, int valOffset, int length) { if (offset < 0 || offset + length > _pos) { throw new ArrayIndexOutOfBoundsException(offset); } System.arraycopy(_data, offset, values, valOffset, length); } public void copy(int source, int destination, int length) { if (source < 0 || source + length > _pos) { throw new ArrayIndexOutOfBoundsException(source); } if (destination < 0 || destination + length > _pos) { throw new ArrayIndexOutOfBoundsException(destination); } System.arraycopy(_data, source, _data, destination, length); } /** * Sets the value at the specified offset without doing any bounds * checking. * * @param offset an {@code int} value * @param val an {@code int} value */ public void setQuick(int offset, byte val) { _data[offset] = val; } /** * Flushes the internal state of the list, resetting the capacity * to the default. */ public void clear() { clear(DEFAULT_CAPACITY); } /** * Flushes the internal state of the list, setting the capacity of * the empty list to <tt>capacity</tt>. * * @param capacity an {@code int} value */ public void clear(int capacity) { _data = new byte[capacity]; _pos = 0; } /** * Sets the size of the list to 0, but does not change its * capacity. This method can be used as an alternative to the * {@link #clear clear} method if you want to recyle a list without * allocating new backing arrays. * * @see #clear */ public void reset() { _pos = 0; fill((byte)0); } /** * Sets the size of the list to 0, but does not change its * capacity. This method can be used as an alternative to the * {@link #clear clear} method if you want to recyle a list * without allocating new backing arrays. This method differs * from {@link #reset reset} in that it does not clear the old * values in the backing array. Thus, it is possible for {@link * #getQuick getQuick} to return stale data if this method is used * and the caller is careless about bounds checking. * * @see #reset * @see #clear * @see #getQuick */ public void resetQuick() { _pos = 0; } /** * Removes the value at <tt>offset</tt> from the list. * * @param offset an {@code int} value * @return the value previously stored at offset. */ public byte remove(int offset) { byte old = get(offset); remove(offset, 1); return old; } /** * Removes <tt>length</tt> values from the list, starting at * <tt>offset</tt> * * @param offset an {@code int} value * @param length an {@code int} value */ public void remove(int offset, int length) { if (offset < 0 || offset >= _pos) { throw new ArrayIndexOutOfBoundsException(offset); } if (offset == 0) { // data at the front System.arraycopy(_data, length, _data, 0, _pos - length); } else if (_pos - length == offset) { // no copy to make, decrementing pos "deletes" values at // the end } else { // data in the middle System.arraycopy(_data, offset + length, _data, offset, _pos - (offset + length)); } _pos -= length; // no need to clear old values beyond _pos, because this is a // primitive collection and 0 takes as much room as any other // value } /** * Reverse the order of the elements in the list. */ public void reverse() { reverse(0, _pos); } /** * Reverse the order of the elements in the range of the list. * * @param from the inclusive index at which to start reversing * @param to the exclusive index at which to stop reversing */ public void reverse(int from, int to) { if (from == to) { return; // nothing to do } if (from > to) { throw new IllegalArgumentException("from cannot be greater than to"); } for (int i = from, j = to - 1; i < j; i++, j--) { swap(i, j); } } /** * Shuffle the elements of the list using the specified random * number generator. * * @param rand a {@code Random} value */ public void shuffle(Random rand) { for (int i = _pos; i-- > 1;) { swap(i, rand.nextInt(i)); } } /** * Swap the values at offsets <tt>i</tt> and <tt>j</tt>. * * @param i an offset into the data array * @param j an offset into the data array */ private final void swap(int i, int j) { byte tmp = _data[i]; _data[i] = _data[j]; _data[j] = tmp; } // copying /** * Returns a clone of this list. Since this is a primitive * collection, this will be a deep clone. * * @return a deep clone of the list. */ @Override public Object clone() { ByteArrayList clone = null; try { clone = (ByteArrayList)super.clone(); clone._data = (byte[])_data.clone(); } catch (CloneNotSupportedException e) { // it's supported } // end of try-catch return clone; } /** * Copies the contents of the list into a native array. * * @return an {@code int[]} value */ @NotNull public byte[] toNativeArray() { return toNativeArray(0, _pos); } /** * Copies a slice of the list into a native array. * * @param offset the offset at which to start copying * @param len the number of values to copy. * @return an {@code int[]} value */ @NotNull public byte[] toNativeArray(int offset, int len) { byte[] rv = new byte[len]; toNativeArray(rv, offset, len); return rv; } /** * Copies a slice of the list into a native array. * * @param dest the array to copy into. * @param offset the offset of the first value to copy * @param len the number of values to copy. */ public void toNativeArray(byte[] dest, int offset, int len) { if (len == 0) { return; // nothing to copy } if (offset < 0 || offset >= _pos) { throw new ArrayIndexOutOfBoundsException(offset); } System.arraycopy(_data, offset, dest, 0, len); } // comparing /** * Compares this list to another list, value by value. * * @param other the object to compare against * @return true if other is a TIntArrayList and has exactly the * same values. */ public boolean equals(Object other) { if (other == this) { return true; } else if (other instanceof ByteArrayList) { ByteArrayList that = (ByteArrayList)other; if (that.size() != this.size()) { return false; } else { for (int i = _pos; i-- > 0;) { if (this._data[i] != that._data[i]) { return false; } } return true; } } else { return false; } } public int hashCode() { int h = 0; for (int i = _pos; i-- > 0;) { h += HashFunctions.hash(_data[i]); } return h; } // procedures /** * Applies the procedure to each value in the list in ascending * (front to back) order. * * @param procedure a {@code TIntProcedure} value * @return true if the procedure did not terminate prematurely. */ public boolean forEach(TIntProcedure procedure) { for (int i = 0; i < _pos; i++) { if (! procedure.execute(_data[i])) { return false; } } return true; } /** * Applies the procedure to each value in the list in descending * (back to front) order. * * @param procedure a {@code TIntProcedure} value * @return true if the procedure did not terminate prematurely. */ public boolean forEachDescending(TIntProcedure procedure) { for (int i = _pos; i-- > 0;) { if (! procedure.execute(_data[i])) { return false; } } return true; } // sorting /** * Sort the values in the list (ascending) using the Sun quicksort * implementation. * * @see java.util.Arrays#sort */ public void sort() { Arrays.sort(_data, 0, _pos); } /** * Sort a slice of the list (ascending) using the Sun quicksort * implementation. * * @param fromIndex the index at which to start sorting (inclusive) * @param toIndex the index at which to stop sorting (exclusive) * @see java.util.Arrays#sort */ public void sort(int fromIndex, int toIndex) { Arrays.sort(_data, fromIndex, toIndex); } // filling /** * Fills every slot in the list with the specified value. * * @param val the value to use when filling */ public void fill(byte val) { Arrays.fill(_data, 0, _pos, val); } /** * Fills a range in the list with the specified value. * * @param fromIndex the offset at which to start filling (inclusive) * @param toIndex the offset at which to stop filling (exclusive) * @param val the value to use when filling */ public void fill(int fromIndex, int toIndex, byte val) { if (toIndex > _pos) { ensureCapacity(toIndex); _pos = toIndex; } Arrays.fill(_data, fromIndex, toIndex, val); } // searching /** * Performs a binary search for <tt>value</tt> in the entire list. * Note that you <b>must</b> {@link #sort sort} the list before * doing a search. * * @param value the value to search for * @return the absolute offset in the list of the value, or its * negative insertion point into the sorted list. */ public int binarySearch(byte value) { return binarySearch(value, 0, _pos); } /** * Performs a binary search for <tt>value</tt> in the specified * range. Note that you <b>must</b> {@link #sort sort} the list * or the range before doing a search. * * @param value the value to search for * @param fromIndex the lower boundary of the range (inclusive) * @param toIndex the upper boundary of the range (exclusive) * @return the absolute offset in the list of the value, or its * negative insertion point into the sorted list. */ public int binarySearch(byte value, int fromIndex, int toIndex) { if (fromIndex < 0) { throw new ArrayIndexOutOfBoundsException(fromIndex); } if (toIndex > _pos) { throw new ArrayIndexOutOfBoundsException(toIndex); } int low = fromIndex; int high = toIndex - 1; while (low <= high) { int mid = (low + high) >> 1; byte midVal = _data[mid]; if (midVal < value) { low = mid + 1; } else if (midVal > value) { high = mid - 1; } else { return mid; // value found } } return -(low + 1); // value not found. } /** * Searches the list front to back for the index of * <tt>value</tt>. * * @param value an {@code int} value * @return the first offset of the value, or -1 if it is not in * the list. * @see #binarySearch for faster searches on sorted lists */ public int indexOf(byte value) { return indexOf(0, value); } /** * Searches the list front to back for the index of * <tt>value</tt>, starting at <tt>offset</tt>. * * @param offset the offset at which to start the linear search * (inclusive) * @param value an {@code int} value * @return the first offset of the value, or -1 if it is not in * the list. * @see #binarySearch for faster searches on sorted lists */ public int indexOf(int offset, byte value) { for (int i = offset; i < _pos; i++) { if (_data[i] == value) { return i; } } return -1; } /** * Searches the list back to front for the last index of * <tt>value</tt>. * * @param value an {@code int} value * @return the last offset of the value, or -1 if it is not in * the list. * @see #binarySearch for faster searches on sorted lists */ public int lastIndexOf(byte value) { return lastIndexOf(_pos, value); } /** * Searches the list back to front for the last index of * <tt>value</tt>, starting at <tt>offset</tt>. * * @param offset the offset at which to start the linear search * (exclusive) * @param value an {@code int} value * @return the last offset of the value, or -1 if it is not in * the list. * @see #binarySearch for faster searches on sorted lists */ public int lastIndexOf(int offset, byte value) { for (int i = offset; i-- > 0;) { if (_data[i] == value) { return i; } } return -1; } /** * Searches the list for <tt>value</tt> * * @param value an {@code int} value * @return true if value is in the list. */ public boolean contains(byte value) { return lastIndexOf(value) >= 0; } /** * Searches the list for values satisfying <tt>condition</tt> in * the manner of the *nix <tt>grep</tt> utility. * * @param condition a condition to apply to each element in the list * @return a list of values which match the condition. */ public ByteArrayList grep(TIntProcedure condition) { ByteArrayList list = new ByteArrayList(); for (int i = 0; i < _pos; i++) { if (condition.execute(_data[i])) { list.add(_data[i]); } } return list; } /** * Searches the list for values which do <b>not</b> satisfy * <tt>condition</tt>. This is akin to *nix {@code grep -v}. * * @param condition a condition to apply to each element in the list * @return a list of values which do not match the condition. */ public ByteArrayList inverseGrep(TIntProcedure condition) { ByteArrayList list = new ByteArrayList(); for (int i = 0; i < _pos; i++) { if (! condition.execute(_data[i])) { list.add(_data[i]); } } return list; } /** * Finds the maximum value in the list. * * @return the largest value in the list. * @exception IllegalStateException if the list is empty */ public int max() { if (size() == 0) { throw new IllegalStateException("cannot find maximum of an empty list"); } int max = _data[_pos - 1]; for (int i = _pos - 1; i-- > 0;) { max = Math.max(max, _data[_pos]); } return max; } /** * Finds the minimum value in the list. * * @return the smallest value in the list. * @exception IllegalStateException if the list is empty */ public int min() { if (size() == 0) { throw new IllegalStateException("cannot find minimum of an empty list"); } int min = _data[_pos - 1]; for (int i = _pos - 1; i-- > 0;) { min = Math.min(min, _data[_pos]); } return min; } // stringification /** * Returns a String representation of the list, front to back. * * @return a {@code String} value */ public String toString() { final StringBuffer buf = new StringBuffer("{"); forEach(new TIntProcedure() { @Override public boolean execute(int val) { buf.append(val); buf.append(", "); return true; } }); buf.append("}"); return buf.toString(); } } // TIntArrayList