/*
* ============================================================================
* GNU Lesser General Public License
* ============================================================================
*
* Square Brackets - Java Array Framework.
* Copyright (C) 2013 Beatgrid Media B.V.
*
* 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Beatgrid Media B.V.
* Sisalbaan 5-A
* 2352 AZ Leiderdorp
* The Netherlands
*
* info@squarebrackets.org
* http://squarebrackets.org
*/
package org.squarebrackets;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.util.Comparator;
import java.util.NoSuchElementException;
/**
* <p>Fixed-size mutable wrapper for {@code DoubleBuffer}s.</p>
*
* <p>This mutable double array is constructed by the static interface methods of {@code MutableDoubleArray}.
* The {@code valueOf} methods wrap a new {@code MutableDoubleArray} around a buffer.</p>
*
* <p>Arrays express characteristics to indicate that, for instance, elements are listed in sorted order, or no
* duplicate elements exist. These characteristics allow array implementations to optimize their algorithms based
* on the contents of the array. For example, operations that lookup elements can use a binary search algorithm if
* the array is marked as being sorted. These optimizations may reduce the time complexity of such operations by
* several orders of magnitude.
* Mutable primitive arrays, such as this class, always report {@code MUTABLE} and {@code NONNULL}.</p>
*
* <p>This class attempts to establish these characteristics to accommodate these optimizations at best effort. This
* means that this implementation determines which set of characteristics apply to its elements without traversing these
* elements, in other words, without incurring a performance penalty. Characteristics are always invalidated if
* conditions of these characteristics are broken as result of array modification. Characteristics are only added in
* scenarios in which their conditions can be 'tested' without traversal of elements. For example, the {@code SORTED}
* characteristic is added if the length of an array is reduced to zero or one element, or if all elements are replaced
* by an array that is marked as being sorted. The semantics of characteristics processing is documented for each
* "destructive" method. Users can take full control of characteristics assignment by means of the
* {@link org.squarebrackets.MutableArray#doAction(org.squarebrackets.ArrayAction)} method.</p>
*
* @author Leon van Zantvoort
*/
@NotThreadSafe
class MutableDoubleBufferArrayImpl extends DoubleBufferArrayImpl implements MutableDoubleArray, MutableArrayCharacteristics<Double> {
/** Is {@code true} if control is transferred to caller of {@code doAction}. */
protected transient boolean inAction;
/** Allows caller of {@code doAction} to control state of array. */
private transient ArrayContext<Double> context;
/**
* Package visible constructor.
*
* @param buffer backing buffer.
* @param characteristics array characteristics.
* @param duplicate {@code true} to duplicate the {@code buffer}, {@code false} to use {@code buffer} reference
* as backing buffer.
* @throws IllegalArgumentException if buffer is read only.
* @throws NullPointerException if the specified array is null.
*/
MutableDoubleBufferArrayImpl(@Nonnull DoubleBuffer buffer, int characteristics, boolean duplicate) {
super(buffer, characteristics, duplicate);
if (buffer.isReadOnly()) {
throw new IllegalArgumentException("Buffer is read only.");
}
}
/**
* Returns the array context of this array, or {@code null} if control is already transferred to caller of
* {@code doAction}.
*
* @return array context.
*/
protected ArrayContext<Double> getArrayContext() {
if (inAction) {
return null;
}
if (context == null) {
context = new ArrayContextImpl(); // Lazy initialization.
}
return context;
}
/**
* Updates the SORTED and DISTINCT characteristics of this array by looking at the elements surrounding the
* specified {@code value}. The SORTED and/or DISTINCT characteristics are removed if the value invalidates the
* characteristics. This method must only be invoked when this array reports the {@code SORTED} characteristic.
*
* @param elementIndex index relative to the backing buffer.
* @param value value that is stored at index.
*/
protected void updateCharacteristics(int elementIndex, double value) {
assert hasCharacteristics(SORTED);
if (elements.remaining() > 1) {
if (elementIndex > elements.position()) {
if (value < elements.get(elementIndex - 1)) {
removeCharacteristics(SORTED | DISTINCT);
} else if (value == elements.get(elementIndex - 1)) {
removeCharacteristics(DISTINCT);
}
}
if (elementIndex < elements.limit() - 1) {
if (value > elements.get(elementIndex + 1)) {
removeCharacteristics(SORTED | DISTINCT);
} else if (value == elements.get(elementIndex + 1)) {
removeCharacteristics(DISTINCT);
}
}
}
}
/**
* Updates the SORTED and DISTINCT characteristics of this array by looking at the elements surrounding the
* first and last element of the specified {@code array}. The SORTED and/or DISTINCT characteristics are removed if
* these elements invalidate the characteristics.
*
* @param elementIndex index relative to the backing buffer.
* @param array array that is stored, starting at index.
*/
protected void updateCharacteristics(int elementIndex, @Nonnull Array<? extends Double> array) {
assert hasCharacteristics(SORTED);
int len = array.length();
if (elements.remaining() > len) {
if (len > 0) {
updateCharacteristics(elementIndex, elements.get(elementIndex));
}
if (len > 1) {
if (hasCharacteristics(SORTED)) {
updateCharacteristics(elementIndex + len - 1, elements.get(elementIndex + len - 1));
}
}
}
}
/**
* Sets the specified characteristics for this array.
*
* @param characteristics characteristics to be set for this array.
* @return the characteristics that were actually stored for this array.
*/
@Override
public int characteristics(int characteristics) {
if (!inAction) {
this.characteristics = filterCharacteristics(characteristics);
}
return this.characteristics;
}
/**
* Allow array implementation to filter characteristics based on array specific logic.
*
* <p>Default implementation passes specified value straight through.</p>
*
* @param characteristics characteristics to be filtered.
* @return the filtered characteristics.
*/
@Override
protected int filterCharacteristics(int characteristics) {
return (characteristics | NONNULL | MUTABLE) & ~(RESIZABLE | REPLACEABLE);
}
/**
* Performs the specified {@code action} without affecting the array's set of characteristics. Ownership of
* array characteristics control is moved to the caller by means of the {@code ArrayContext} object. Errors or
* runtime exceptions thrown by the action are relayed to the caller.
*
* @param <T> return type.
* @param action the action to be performed.
* @return the value returned by the action's {@code run} method.
* @throws IllegalStateException if action is called when array does not own control of characteristics.
* @exception NullPointerException if the action is {@code null}.
*/
@SuppressWarnings("InconsistentJavaDoc")
@Override
public final <T> T doAction(@Nonnull ArrayAction<Double, T> action) {
ArrayContext<Double> context = getArrayContext();
if (context == null) {
throw new IllegalStateException("Already in action.");
}
try {
inAction = true;
return action.run(context);
} finally {
inAction = false;
}
}
/**
* Returns the backing array (optional operation). All elements of {@code this} mutable array and parents of this
* mutable array are stored in the returned object. Modifications to the backing array are reflected by {@code this}
* mutable array and it's parents.
*
* <p>The resulting object must be cast to the primitive or object type of the implementing class.</p>
*
* <p><strong>Characteristics:</strong></p>
*
* <p>This method allows the caller to modify this array outside the scope of the implementing class. Hence, it can
* not guarantee correctness of the characteristics of this array. For that reason, content related characteristics
* (such as {@code SORTED} and {@code DISTINCT}) are removed by calling this method.
* Characteristics removal can be suppressed by obtaining the backing array through {@code doAction}:</p>
*
* <pre>
*{@literal double[] backingArray = (double[]) array.doAction(ArrayContext::array);}</pre>
*
* @return backing array.
* @throws UnsupportedOperationException if array does not expose backing array.
*/
@Override
public double[] array() {
if (elements.hasArray()) {
characteristics(0);
return elements.array();
}
throw new UnsupportedOperationException("Backing array not exposed by buffer.");
}
/**
* Returns a duplicate of the backing buffer (optional operation). All elements of {@code this} array and parents of
* this array are shared by this buffer. Changes to the buffer's content will be visible in this array, and vice
* versa; the buffers' position, limit, and mark values will be independent.
*
* <p><strong>Characteristics:</strong></p>
*
* <p>This method allows the caller to modify the buffer outside the scope of the implementing class. Hence, it can
* not guarantee correctness of the characteristics of this array. For that reason, content related
* characteristics (such as {@code NONNULL}, {@code SORTED} and {@code DISTINCT}) are removed by calling this method.
* Characteristics removal can be suppressed by obtaining the backing buffer through {@code doAction}. The buffer
* obtained through {@code doAction} is the actual backing buffer instance, giving the caller full control over
* the buffer's position, limit and mark values. Changes to these values, directly, or in directly (i.e., through
* {@code get()} or {@code put()}) are reflected by {@code this} array. It is therefore advised to obtain a
* duplicate of the buffer as demonstrated below, unless such changes are intentional.</p>
*
* <pre>
*{@literal DoubleBuffer backingBuffer = (DoubleBuffer) array.doAction(ArrayContext::buffer).duplicate();}</pre>
*
* @return duplicate of the backing buffer.
* @throws UnsupportedOperationException if array is not backed by a buffer.
*/
@Override
public DoubleBuffer buffer() {
characteristics(0);
return elements.duplicate();
}
/**
* Sets the comparator that is used for sorting this array, {@code null} if array is sorted according to natural
* ordering of elements, or {@code null} if array is not sorted.
*
* @param comparator the {@code Comparator} used to compare array elements. A {@code null} value indicates that the
* elements' <i>Comparable natural ordering</i> is be used.
* @throws UnsupportedOperationException if array is a primitive array.
*/
@Override
public void setComparator(@Nullable Comparator<? super Double> comparator) {
throw new UnsupportedOperationException("Comparator not support for primitive arrays.");
}
/**
* Replaces element at specified {@code index} by specified object.
*
* <p><strong>Characteristics:</strong></p>
*
* <p>Modification of this array may affect this array's characteristics. The array implementations of the
* Java Array Framework, as returned by the static factory methods, adhere to the characteristics contract:</p>
*
* <ul>
* <li>if this array reports {@code SORTED} prior to this call, {@code SORTED} is reported after this
* invocation if the specified {@code object} does not break the sorted order of this array. Arrays
* sorted according to natural order are not regarded sorted if they contain {@code null} element(s);</li>
* <li>if this array reports {@code SORTED} and {@code DISTINCT} prior to this call, {@code DISTINCT} is
* reported after this invocation if the specified {@code object} was not already present in this array.</li>
* </ul>
*
* @param index index of item to be replaced, relative to the offset of the array.
* @param object object to replace existing value.
* @return object that is replaced.
* @throws IndexOutOfBoundsException if {@code index} position is invalid.
* @throws NullPointerException if the specified {@code object} is null.
*/
@SuppressWarnings("InconsistentJavaDoc")
@Override
public Double set(int index, Double object) {
if (Tripwire.ENABLED) {
Tripwire.trip(getClass());
}
return setDouble(index, object);
}
/**
* Replaces element at specified {@code index} by specified double value.
*
* <p><strong>Characteristics:</strong></p>
*
* <p>Modification of this array may affect this array's characteristics. The array implementations of the
* Java Array Framework, as returned by the static factory methods, adhere to the characteristics contract:</p>
*
* <ul>
* <li>if this array reports {@code SORTED} prior to this call, {@code SORTED} is reported after this
* invocation if the specified {@code object} does not break the sorted order of this array;</li>
* <li>if this array reports {@code SORTED} and {@code DISTINCT} prior to this call, {@code DISTINCT} is
* reported after this invocation if the specified {@code object} was not already present in this array.</li>
* </ul>
*
* @param index index relative to the offset of the array.
* @param value double value to replace existing value.
* @return double value that is replaced.
* @throws IndexOutOfBoundsException if {@code index} position is invalid.
*/
@Override
public double setDouble(int index, double value) {
int elementIndex = elements.position() + index;
double oldValue = elements.get(elementIndex);
elements.put(elementIndex, value);
if (hasCharacteristics(SORTED)) {
updateCharacteristics(elementIndex, value);
}
return oldValue;
}
/**
* Replaces all elements of this array by the elements of the specified array. Note that the length of {@code array}
* needs to be equal to the length of this array.
*
* <p><strong>Characteristics:</strong></p>
*
* <p>Modification of this array may affect the array's characteristics. The array implementations of the
* Java Array Framework, as returned by the static factory methods, adhere to the characteristics contract:</p>
*
* <ul>
* <li>if the specified {@code array} reports {@code SORTED}, {@code SORTED} is reported after this invocation if
* the comparator of the specified {@code array} is equal to this array's comparator;</li>
* <li>if the specified {@code array} reports {@code SORTED} and {@code DISTINCT}, {@code DISTINCT} is reported
* after this invocation if the comparator of the specified {@code array} is equal to this array's comparator.
* </ul>
*
* @param array array to replace elements of this array.
* @throws NullPointerException if the specified array is null or if the array contains null elements and this
* array does not permit null elements.
* @throws IllegalStateException if length of the specified array is not equal to length of this array.
*/
@SuppressWarnings("InconsistentJavaDoc")
@Override
public void setAll(@Nonnull Array<? extends Double> array) {
int offset = elements.position();
int length = elements.remaining();
int lengthDiff = array.length() - length;
if (lengthDiff == 0) {
DoubleArray cast = ArrayCast.toDoubleArray(array);
if (cast != null && elements.hasArray()) {
cast.toArray(elements.array(), elements.arrayOffset() + offset, length);
} else {
elements.mark();
elements.put(NativeArrays.toDoubleArray(array), 0, length).reset();
}
characteristics(array.characteristics()); // All elements are replaced by new array; Copy characteristics.
if (hasCharacteristics(SORTED)) {
if (array.getComparator() != null) {
removeCharacteristics(SORTED | DISTINCT);
} else {
updateCharacteristics(offset, array);
}
}
} else {
throw new IllegalStateException("Array length.");
}
}
/**
* Sorts this array according to natural ordering.
*
* <p><strong>Characteristics:</strong></p>
*
* <p>Modification of this array may affect the array's characteristics. The array implementations of the
* Java Array Framework, as returned by the static factory methods, adhere to the characteristics contract:</p>
*
* <ul>
* <li>{@code SORTED} is reported after this invocation.</li>
* </ul>
*/
@SuppressWarnings("InconsistentJavaDoc")
@Override
public void sort() {
if (!hasCharacteristics(SORTED)) {
if (elements.hasArray()) {
int arrayOffset = elements.arrayOffset();
java.util.Arrays.sort(elements.array(), arrayOffset + elements.position(), arrayOffset + elements.limit());
} else {
double[] a = toArray();
java.util.Arrays.sort(a);
elements.mark();
elements.put(a).reset();
}
addCharacteristics(SORTED); // Array is guaranteed to be sorted.
}
}
/**
* Sorts this array according to natural ordering. The underlying implementation utilizes multiple threads
* to reduce sorting time. The algorithm may require a working space equal to the size of this array.
*
* <p><strong>Characteristics:</strong></p>
*
* <p>Modification of this array may affect the array's characteristics. The array implementations of the
* Java Array Framework, as returned by the static factory methods, adhere to the characteristics contract:</p>
*
* <ul>
* <li>{@code SORTED} is reported after this invocation.</li>
* </ul>
*/
@SuppressWarnings("InconsistentJavaDoc")
@Override
public void parallelSort() {
if (!hasCharacteristics(SORTED)) {
if (elements.hasArray()) {
int arrayOffset = elements.arrayOffset();
java.util.Arrays.parallelSort(elements.array(), arrayOffset + elements.position(), arrayOffset + elements.limit());
} else {
double[] a = toArray();
java.util.Arrays.parallelSort(a);
elements.mark();
elements.put(a).reset();
}
addCharacteristics(SORTED); // Array is guaranteed to be sorted.
}
}
/**
* Array context implementation.
*/
@SuppressWarnings("MissingMethodJavaDoc")
private class ArrayContextImpl implements ArrayContext<Double> {
void checkState() {
if (!inAction) {
throw new IllegalStateException("Not in action.");
}
}
@Override
public boolean hasArray() {
checkState();
return elements.hasArray();
}
@Override
public Object array() {
checkState();
return elements.array();
}
@Override
public boolean hasBuffer() {
checkState();
return true;
}
@Override
public Buffer buffer() {
checkState();
return elements;
}
@Override
public Class<? super Double> getComponentType() {
checkState();
return Double.TYPE;
}
@Override
public int characteristics(int newCharacteristics) {
checkState();
return characteristics = filterCharacteristics(newCharacteristics);
}
@Override
public int characteristics() {
checkState();
return characteristics;
}
@Override
public @CheckForNull Comparator<? super Double> getComparator() {
checkState();
return null;
}
@Override
public void setComparator(@Nullable Comparator<? super Double> comparator) {
checkState();
throw new UnsupportedOperationException();
}
}
@SuppressWarnings({"MissingMethodJavaDoc", "MissingFieldJavaDoc"})
private static class SubMutableDoubleBufferArrayImpl extends MutableDoubleBufferArrayImpl {
final MutableDoubleBufferArrayImpl parent;
SubMutableDoubleBufferArrayImpl(@Nonnull MutableDoubleBufferArrayImpl parent, @Nonnull DoubleBuffer buffer) {
super(buffer, parent.characteristics, false);
this.parent = parent;
}
@Override
protected ArrayContext<Double> getArrayContext() {
return parent.inAction ? null : super.getArrayContext();
}
@Override
protected void updateCharacteristics(int elementIndex, double value) {
super.updateCharacteristics(elementIndex, value);
if (hasCharacteristics(SORTED)) {
parent.updateCharacteristics(elementIndex, value);
}
}
@Override
protected void updateCharacteristics(int elementIndex, @Nonnull Array<? extends Double> array) {
super.updateCharacteristics(elementIndex, array);
if (hasCharacteristics(SORTED)) {
parent.updateCharacteristics(elementIndex, array);
}
}
@Override
public int characteristics(int characteristics) {
// Parent array retains characteristics that are shared with sub array. Additional characteristics
// assigned to sub array are not set for parent array, as it is not guaranteed that these
// characteristics hold for parent array.
parent.retainCharacteristics(characteristics);
return super.characteristics(characteristics);
}
}
/**
* <p>Returns a new sub array object for the specified {@code offset}. The specified {@code offset} is relative to
* the existing offset, as returned by {@link #offset()}. The length of the resulting array is reduced by
* the number of elements that are added to the current offset:</p>
*
* <pre>int newLength = length() - offset;</pre>
*
* <p>Effectively, the virtual {@code toIndex} of the array remains the same.</p>
*
* @param offset new relative offset.
* @return a new sub array object with the new {@code offset}.
*/
@Override
public MutableDoubleArray offset(int offset) {
if (offset == 0) {
return this;
}
int newOffset = elements.position() + offset;
int newLength = elements.remaining() - offset;
subArrayCheck(newOffset, newLength);
DoubleBuffer copy = elements.duplicate();
copy.position(newOffset);
return new SubMutableDoubleBufferArrayImpl(this, copy);
}
/**
* <p>Returns a new sub array object for the specified {@code length}. Note that {@code length} must be smaller than
* or equal to the current length, as returned by {@code #length}.</p>
*
* @param length new length, must be smaller than or equal to current length.
* @return a new sub array object with the new {@code length}.
*/
@Override
public MutableDoubleArray length(int length) {
if (elements.remaining() == length) {
return this;
}
int offset = elements.position();
subArrayCheck(offset, length);
DoubleBuffer copy = elements.duplicate();
copy.limit(offset + length);
return new SubMutableDoubleBufferArrayImpl(this, copy);
}
/**
* Convenience method, combining {@code offset(int)} and {@code length(int)}.
*
* @param fromIndex low endpoint (inclusive) of the sub array.
* @param toIndex high endpoint (exclusive) of the sub array.
* @return a view of the specified range within this array.
* @throws IndexOutOfBoundsException for an illegal endpoint index value ({@code fromIndex < 0 || toIndex > length ||
* fromIndex > toIndex}).
*/
@Override
public MutableDoubleArray subArray(int fromIndex, int toIndex) {
int offset = elements.position();
int length = elements.remaining();
int newOffset = offset + fromIndex;
int newLength = toIndex - fromIndex;
if (newOffset == offset && newLength == length) {
return this;
}
subArrayCheck(newOffset, newLength);
DoubleBuffer copy = elements.duplicate();
copy.position(newOffset);
copy.limit(newOffset + newLength);
return new SubMutableDoubleBufferArrayImpl(this, copy);
}
@SuppressWarnings({"MissingMethodJavaDoc", "MissingFieldJavaDoc"})
private class ArrayIteratorImpl implements ArrayIterator.OfDouble {
int index;
int lastIndex = -1;
ArrayIteratorImpl() {
this(0);
}
ArrayIteratorImpl(int index) {
this.index = elements.position() + index;
}
@Override
public boolean hasNext() {
return index < elements.limit();
}
@Override
public double nextDouble() {
int i = index;
if (i >= elements.limit()) {
throw new NoSuchElementException();
}
index = i + 1;
return elements.get(lastIndex = i);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public boolean hasPrevious() {
return index > elements.position();
}
@Override
public double previousDouble() {
int i = index - 1;
if (i < elements.position()) {
throw new NoSuchElementException();
}
assert i < elements.limit();
index = i;
return elements.get(lastIndex = i);
}
@Override
public int nextIndex() {
return index;
}
@Override
public int previousIndex() {
return index - 1;
}
@Override
public void setDouble(double value) {
if (lastIndex < 0) {
throw new IllegalStateException();
}
assert lastIndex < elements.limit();
MutableDoubleBufferArrayImpl.this.set(lastIndex, value);
}
@Override
public void addDouble(double value) {
throw new UnsupportedOperationException();
}
}
/**
* Returns an array iterator over the elements in this array.
*
* @return an array iterator over the elements in this array.
*/
@Override
public ArrayIterator.OfDouble iterator() {
return new ArrayIteratorImpl();
}
/**
* Returns an array iterator over the elements in this array.
*
* @param index index of the first element to be returned from the array iterator.
* @return a array iterator over the elements in this array.
* @throws IndexOutOfBoundsException if the index is out of range.
*/
@Override
public ArrayIterator.OfDouble iterator(int index) {
rangeCheckForIterator(index);
return new ArrayIteratorImpl(index);
}
/**
* Write replace method for the serialization proxy pattern. This method must be overwritten by sub classes if their
* serialized form differs from this class.
*
* @return serialization proxy.
*/
@Override
Object writeReplace() {
return new SerializationProxy(this);
}
@SuppressWarnings("MissingMethodJavaDoc")
private void readObject(ObjectInputStream s) throws InvalidObjectException {
throw new InvalidObjectException("Proxy required");
}
@SuppressWarnings({"MissingMethodJavaDoc", "MissingFieldJavaDoc"})
private static class SerializationProxy implements Serializable {
static final long serialVersionUID = 8219170256053125256L;
final int length;
final boolean direct;
final int characteristics;
transient DoubleBuffer elements;
SerializationProxy(MutableDoubleBufferArrayImpl array) {
this.length = array.elements.remaining();
this.direct = array.elements.isDirect();
this.characteristics = array.characteristics;
this.elements = array.elements;
}
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
elements.mark();
for (int i = 0; i < length; i++) {
s.writeDouble(elements.get());
}
elements.reset();
}
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
if (direct) {
elements = ByteBuffer.allocateDirect(Double.SIZE / Byte.SIZE * length).asDoubleBuffer();
} else {
elements = DoubleBuffer.allocate(length);
}
for (int i = 0; i < length; i++) {
elements.put(s.readDouble());
}
elements.rewind();
}
private Object readResolve() {
return new MutableDoubleBufferArrayImpl(elements, characteristics, false);
}
}
}