Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ import java.io.Serializable; /** * * A variable length {@link DoubleArray} implementation that automatically * handles expanding and contracting its internal storage array as elements are * added and removed. * * * The internal storage array starts with capacity determined by the * <code>initialCapacity</code> property, which can be set by the constructor. * The default initial capacity is 16. Adding elements using * {@link #addElement(double)} appends elements to the end of the array. When * there are no open entries at the end of the internal storage array, the array * is expanded. The size of the expanded array depends on the * <code>expansionMode</code> and <code>expansionFactor</code> properties. * The <code>expansionMode</code> determines whether the size of the array is * multiplied by the <code>expansionFactor</code> (MULTIPLICATIVE_MODE) or if * the expansion is additive (ADDITIVE_MODE -- <code>expansionFactor</code> * storage locations added). The default <code>expansionMode</code> is * MULTIPLICATIVE_MODE and the default <code>expansionFactor</code> is 2.0. * * * The {@link #addElementRolling(double)} method adds a new element to the end * of the internal storage array and adjusts the "usable window" of the internal * array forward by one position (effectively making what was the second element * the first, and so on). Repeated activations of this method (or activation of * {@link #discardFrontElements(int)}) will effectively orphan the storage * locations at the beginning of the internal storage array. To reclaim this * storage, each time one of these methods is activated, the size of the * internal storage array is compared to the number of addressable elements (the * <code>numElements</code> property) and if the difference is too large, the * internal array is contracted to size <code>numElements + 1.</code> The * determination of when the internal storage array is "too large" depends on * the <code>expansionMode</code> and <code>contractionFactor</code> * properties. If the <code>expansionMode</code> is * <code>MULTIPLICATIVE_MODE</code>, contraction is triggered when the ratio * between storage array length and <code>numElements</code> exceeds * <code>contractionFactor.</code> If the <code>expansionMode</code> is * <code>ADDITIVE_MODE,</code> the number of excess storage locations is * compared to <code>contractionFactor.</code> * * * To avoid cycles of expansions and contractions, the * <code>expansionFactor</code> must not exceed the * <code>contractionFactor.</code> Constructors and mutators for both of these * properties enforce this requirement, throwing IllegalArgumentException if it * is violated. * * * @version $Revision: 618057 $ $Date: 2008-02-03 11:58:54 -0700 (Sun, 03 Feb * 2008) $ */ public class ResizableDoubleArray implements Serializable { /** Serializable version identifier */ private static final long serialVersionUID = -3485529955529426875L; /** additive expansion mode */ public static final int ADDITIVE_MODE = 1; /** multiplicative expansion mode */ public static final int MULTIPLICATIVE_MODE = 0; /** * The contraction criteria determines when the internal array will be * contracted to fit the number of elements contained in the element array + * 1. */ protected float contractionCriteria = 2.5f; /** * The expansion factor of the array. When the array needs to be expanded, the * new array size will be <code>internalArray.length * expansionFactor</code> * if <code>expansionMode</code> is set to MULTIPLICATIVE_MODE, or * <code>internalArray.length + expansionFactor</code> if * <code>expansionMode</code> is set to ADDITIVE_MODE. */ protected float expansionFactor = 2.0f; /** * Determines whether array expansion by <code>expansionFactor</code> is * additive or multiplicative. */ protected int expansionMode = MULTIPLICATIVE_MODE; /** * The initial capacity of the array. Initial capacity is not exposed as a * property as it is only meaningful when passed to a constructor. */ protected int initialCapacity = 16; /** * The internal storage array. */ protected double[] internalArray; /** * The number of addressable elements in the array. Note that this has nothing * to do with the length of the internal storage array. */ protected int numElements = 0; /** * The position of the first addressable element in the internal storage * array. The addressable elements in the array are <code> * internalArray[startIndex],...,internalArray[startIndex + numElements -1] * </code> */ protected int startIndex = 0; /** * Create a ResizableArray with default properties. * <ul> * <li><code>initialCapacity = 16</code></li> * <li><code>expansionMode = MULTIPLICATIVE_MODE</code></li> * <li><code>expansionFactor = 2.5</code></li> * <li><code>contractionFactor = 2.0</code></li> * </ul> */ public ResizableDoubleArray() { internalArray = new double[initialCapacity]; } /** * Create a ResizableArray with the specified initial capacity. Other * properties take default values: * <ul> * <li><code>expansionMode = MULTIPLICATIVE_MODE</code></li> * <li><code>expansionFactor = 2.5</code></li> * <li><code>contractionFactor = 2.0</code></li> * </ul> * * @param initialCapacity * The initial size of the internal storage array * @throws IllegalArgumentException * if initialCapacity is not > 0 */ public ResizableDoubleArray(int initialCapacity) { setInitialCapacity(initialCapacity); internalArray = new double[this.initialCapacity]; } /** * * Create a ResizableArray with the specified initial capacity and expansion * factor. The remaining properties take default values: * <ul> * <li><code>expansionMode = MULTIPLICATIVE_MODE</code></li> * <li><code>contractionFactor = 0.5 + expansionFactor</code></li> * </ul> * * * Throws IllegalArgumentException if the following conditions are not met: * <ul> * <li><code>initialCapacity > 0</code></li> * <li><code>expansionFactor > 1</code></li> * </ul> * * * @param initialCapacity * The initial size of the internal storage array * @param expansionFactor * the array will be expanded based on this parameter * @throws IllegalArgumentException * if parameters are not valid */ public ResizableDoubleArray(int initialCapacity, float expansionFactor) { this.expansionFactor = expansionFactor; setInitialCapacity(initialCapacity); internalArray = new double[initialCapacity]; setContractionCriteria(expansionFactor + 0.5f); } /** * * Create a ResizableArray with the specified initialCapacity, * expansionFactor, and contractionCriteria. The <code>expansionMode</code> * will default to <code>MULTIPLICATIVE_MODE.</code> * * * Throws IllegalArgumentException if the following conditions are not met: * <ul> * <li><code>initialCapacity > 0</code></li> * <li><code>expansionFactor > 1</code></li> * <li><code>contractionFactor >= expansionFactor</code></li> * </ul> * * * @param initialCapacity * The initial size of the internal storage array * @param expansionFactor * the array will be expanded based on this parameter * @param contractionCriteria * The contraction Criteria. * @throws IllegalArgumentException * if parameters are not valid */ public ResizableDoubleArray(int initialCapacity, float expansionFactor, float contractionCriteria) { this.expansionFactor = expansionFactor; setContractionCriteria(contractionCriteria); setInitialCapacity(initialCapacity); internalArray = new double[initialCapacity]; } /** * * Create a ResizableArray with the specified properties. * * * Throws IllegalArgumentException if the following conditions are not met: * <ul> * <li><code>initialCapacity > 0</code></li> * <li><code>expansionFactor > 1</code></li> * <li><code>contractionFactor >= expansionFactor</code></li> * <li><code>expansionMode in {MULTIPLICATIVE_MODE, ADDITIVE_MODE}</code> * </li> * </ul> * * * @param initialCapacity * the initial size of the internal storage array * @param expansionFactor * the array will be expanded based on this parameter * @param contractionCriteria * the contraction Criteria * @param expansionMode * the expansion mode * @throws IllegalArgumentException * if parameters are not valid */ public ResizableDoubleArray(int initialCapacity, float expansionFactor, float contractionCriteria, int expansionMode) { this.expansionFactor = expansionFactor; setContractionCriteria(contractionCriteria); setInitialCapacity(initialCapacity); setExpansionMode(expansionMode); internalArray = new double[initialCapacity]; } /** * Adds an element to the end of this expandable array. * * @param value * to be added to end of array */ public synchronized void addElement(double value) { numElements++; if ((startIndex + numElements) > internalArray.length) { expand(); } internalArray[startIndex + (numElements - 1)] = value; if (shouldContract()) { contract(); } } /** * * Adds an element to the end of the array and removes the first element in * the array. Returns the discarded first element. The effect is similar to a * push operation in a FIFO queue. * * * Example: If the array contains the elements 1, 2, 3, 4 (in that order) and * addElementRolling(5) is invoked, the result is an array containing the * entries 2, 3, 4, 5 and the value returned is 1. * * * @param value * the value to be added to the array * @return the value which has been discarded or "pushed" out of the array by * this rolling insert */ public synchronized double addElementRolling(double value) { double discarded = internalArray[startIndex]; if ((startIndex + (numElements + 1)) > internalArray.length) { expand(); } // Increment the start index startIndex += 1; // Add the new value internalArray[startIndex + (numElements - 1)] = value; // Check the contraction criteria if (shouldContract()) { contract(); } return discarded; } /** * Checks the expansion factor and the contraction criteria and throws an * IllegalArgumentException if the contractionCriteria is less than the * expansionCriteria * * @param expansionFactor * factor to be checked * @param contractionCritera * critera to be checked * @throws IllegalArgumentException * if the contractionCriteria is less than the expansionCriteria. */ protected void checkContractExpand(float contractionCritera, float expansionFactor) { if (contractionCritera < expansionFactor) { String msg = "Contraction criteria can never be smaller than " + "the expansion factor. This would lead to a never " + "ending loop of expansion and contraction as a newly " + "expanded internal storage array would immediately " + "satisfy the criteria for contraction"; throw new IllegalArgumentException(msg); } if (contractionCriteria <= 1.0) { String msg = "The contraction criteria must be a number larger " + "than one. If the contractionCriteria is less than or " + "equal to one an endless loop of contraction and " + "expansion would ensue as an internalArray.length " + "== numElements would satisfy the contraction criteria"; throw new IllegalArgumentException(msg); } if (expansionFactor <= 1.0) { String msg = "The expansion factor must be a number greater than 1.0"; throw new IllegalArgumentException(msg); } } /** * Clear the array, reset the size to the initialCapacity and the number of * elements to zero. */ public synchronized void clear() { numElements = 0; internalArray = new double[initialCapacity]; } /** * Contracts the storage array to the (size of the element set) + 1 - to avoid * a zero length array. This function also resets the startIndex to zero. */ public synchronized void contract() { double[] tempArray = new double[numElements + 1]; // Copy and swap - copy only the element array from the src array. System.arraycopy(internalArray, startIndex, tempArray, 0, numElements); internalArray = tempArray; // Reset the start index to zero startIndex = 0; } /** * Discards the <code>i<code> initial elements of the array. For example, * if the array contains the elements 1,2,3,4, invoking * <code>discardFrontElements(2)</code> will cause the first two elements * to be discarded, leaving 3,4 in the array. Throws illegalArgumentException * if i exceeds numElements. * * @param i the number of elements to discard from the front of the array * @throws IllegalArgumentException if i is greater than numElements. */ public synchronized void discardFrontElements(int i) { if (i > numElements) { String msg = "Cannot discard more elements than are" + "contained in this array."; throw new IllegalArgumentException(msg); } else if (i < 0) { String msg = "Cannot discard a negative number of elements."; throw new IllegalArgumentException(msg); } else { // "Subtract" this number of discarded from numElements numElements -= i; startIndex += i; } if (shouldContract()) { contract(); } } /** * Expands the internal storage array using the expansion factor. * * if <code>expansionMode</code> is set to MULTIPLICATIVE_MODE, the new * array size will be <code>internalArray.length * expansionFactor.</code> * If <code>expansionMode</code> is set to ADDITIVE_MODE, the length after * expansion will be <code>internalArray.length + expansionFactor</code> * */ protected synchronized void expand() { // notice the use of Math.ceil(), this gaurantees that we will always // have an array of at least currentSize + 1. Assume that the // current initial capacity is 1 and the expansion factor // is 1.000000000000000001. The newly calculated size will be // rounded up to 2 after the multiplication is performed. int newSize = 0; if (expansionMode == MULTIPLICATIVE_MODE) { newSize = (int) Math.ceil(internalArray.length * expansionFactor); } else { newSize = internalArray.length + Math.round(expansionFactor); } double[] tempArray = new double[newSize]; // Copy and swap System.arraycopy(internalArray, 0, tempArray, 0, internalArray.length); internalArray = tempArray; } /** * Expands the internal storage array to the specified size. * * @param size * Size of the new internal storage array */ private synchronized void expandTo(int size) { double[] tempArray = new double[size]; // Copy and swap System.arraycopy(internalArray, 0, tempArray, 0, internalArray.length); internalArray = tempArray; } /** * The contraction criteria defines when the internal array will contract to * store only the number of elements in the element array. If the * <code>expansionMode</code> is <code>MULTIPLICATIVE_MODE</code>, * contraction is triggered when the ratio between storage array length and * <code>numElements</code> exceeds <code>contractionFactor</code>. If * the <code>expansionMode</code> is <code>ADDITIVE_MODE</code>, the * number of excess storage locations is compared to * <code>contractionFactor.</code> * * @return the contraction criteria used to reclaim memory. */ public float getContractionCriteria() { return contractionCriteria; } /** * Returns the element at the specified index * * @param index * index to fetch a value from * @return value stored at the specified index * @throws ArrayIndexOutOfBoundsException * if <code>index</code> is less than zero or is greater than * <code>getNumElements() - 1</code>. */ public synchronized double getElement(int index) { if (index >= numElements) { String msg = "The index specified: " + index + " is larger than the current number of elements"; throw new ArrayIndexOutOfBoundsException(msg); } else if (index >= 0) { return internalArray[startIndex + index]; } else { String msg = "Elements cannot be retrieved from a negative array index"; throw new ArrayIndexOutOfBoundsException(msg); } } /** * Returns a double array containing the elements of this * <code>ResizableArray</code>. This method returns a copy, not a reference * to the underlying array, so that changes made to the returned array have no * effect on this <code>ResizableArray.</code> * * @return the double array. */ public synchronized double[] getElements() { double[] elementArray = new double[numElements]; System.arraycopy(internalArray, startIndex, elementArray, 0, numElements); return elementArray; } /** * The expansion factor controls the size of a new aray when an array needs to * be expanded. The <code>expansionMode</code> determines whether the size * of the array is multiplied by the <code>expansionFactor</code> * (MULTIPLICATIVE_MODE) or if the expansion is additive (ADDITIVE_MODE -- * <code>expansionFactor</code> storage locations added). The default * <code>expansionMode</code> is MULTIPLICATIVE_MODE and the default * <code>expansionFactor</code> is 2.0. * * @return the expansion factor of this expandable double array */ public float getExpansionFactor() { return expansionFactor; } /** * The <code>expansionMode</code> determines whether the internal storage * array grows additively (ADDITIVE_MODE) or multiplicatively * (MULTIPLICATIVE_MODE) when it is expanded. * * @return Returns the expansionMode. */ public int getExpansionMode() { return expansionMode; } /** * Notice the package scope on this method. This method is simply here for the * JUnit test, it allows us check if the expansion is working properly after a * number of expansions. This is not meant to be a part of the public * interface of this class. * * @return the length of the internal storage array. */ synchronized int getInternalLength() { return (internalArray.length); } /** * Returns the number of elements currently in the array. Please note that * this is different from the length of the internal storage array. * * @return number of elements */ public synchronized int getNumElements() { return (numElements); } /** * Returns the internal storage array. Note that this method returns a * reference to the internal storage array, not a copy, and to correctly * address elements of the array, the <code>startIndex</code> is required * (available via the {@link #start} method). This method should only be used * in cases where copying the internal array is not practical. The * {@link #getElements} method should be used in all other cases. * * * @return the internal storage array used by this object */ public synchronized double[] getValues() { return (internalArray); } /** * Sets the contraction criteria for this ExpandContractDoubleArray. * * @param contractionCriteria * contraction criteria */ public void setContractionCriteria(float contractionCriteria) { checkContractExpand(contractionCriteria, getExpansionFactor()); this.contractionCriteria = contractionCriteria; } /** * Sets the element at the specified index. If the specified index is greater * than <code>getNumElements() - 1</code>, the <code>numElements</code> * property is increased to <code>index +1</code> and additional storage is * allocated (if necessary) for the new element and all (uninitialized) * elements between the new element and the previous end of the array). * * @param index * index to store a value in * @param value * value to store at the specified index * @throws ArrayIndexOutOfBoundsException * if <code>index</code> is less than zero. */ public synchronized void setElement(int index, double value) { if (index < 0) { String msg = "Cannot set an element at a negative index"; throw new ArrayIndexOutOfBoundsException(msg); } if (index + 1 > numElements) { numElements = index + 1; } if ((startIndex + index) >= internalArray.length) { expandTo(startIndex + (index + 1)); } internalArray[startIndex + index] = value; } /** * Sets the expansionFactor. Throws IllegalArgumentException if the the * following conditions are not met: * <ul> * <li><code>expansionFactor > 1</code></li> * <li><code>contractionFactor >= expansionFactor</code></li> * </ul> * * @param expansionFactor * the new expansion factor value. * @throws IllegalArgumentException * if expansionFactor is <= 1 or greater than contractionFactor */ public void setExpansionFactor(float expansionFactor) { checkContractExpand(getContractionCriteria(), expansionFactor); // The check above verifies that the expansion factor is > 1.0; this.expansionFactor = expansionFactor; } /** * Sets the <code>expansionMode</code>. The specified value must be one of * ADDITIVE_MODE, MULTIPLICATIVE_MODE. * * @param expansionMode * The expansionMode to set. * @throws IllegalArgumentException * if the specified mode value is not valid */ public void setExpansionMode(int expansionMode) { if (expansionMode != MULTIPLICATIVE_MODE && expansionMode != ADDITIVE_MODE) { throw new IllegalArgumentException("Illegal expansionMode setting."); } this.expansionMode = expansionMode; } /** * Sets the initial capacity. Should only be invoked by constructors. * * @param initialCapacity * of the array * @throws IllegalArgumentException * if <code>initialCapacity</code> is not positive. */ protected void setInitialCapacity(int initialCapacity) { if (initialCapacity > 0) { synchronized (this) { this.initialCapacity = initialCapacity; } } else { String msg = "The initial capacity supplied: " + initialCapacity + "must be a positive integer"; throw new IllegalArgumentException(msg); } } /** * This function allows you to control the number of elements contained in * this array, and can be used to "throw out" the last n values in an array. * This function will also expand the internal array as needed. * * @param i * a new number of elements * @throws IllegalArgumentException * if <code>i</code> is negative. */ public synchronized void setNumElements(int i) { // If index is negative thrown an error if (i < 0) { String msg = "Number of elements must be zero or a positive " + "integer"; throw new IllegalArgumentException(msg); } // Test the new num elements, check to see if the array needs to be // expanded to accomodate this new number of elements if ((startIndex + i) > internalArray.length) { expandTo(startIndex + i); } // Set the new number of elements to new value numElements = i; } /** * Returns true if the internal storage array has too many unused storage * positions. * * @return true if array satisfies the contraction criteria */ private synchronized boolean shouldContract() { if (expansionMode == MULTIPLICATIVE_MODE) { return (internalArray.length / ((float) numElements)) > contractionCriteria; } else { return (internalArray.length - numElements) > contractionCriteria; } } /** * Returns the starting index of the internal array. The starting index is the * position of the first addressable element in the internal storage array. * The addressable elements in the array are <code> * internalArray[startIndex],...,internalArray[startIndex + numElements -1] * </code> * * @return starting index */ public synchronized int start() { return startIndex; } }