/* --------------------------------------------------------- *
* __________ D E L T A S C R I P T *
* (_________() *
* / === / - A fast, dynamic scripting language *
* | == | - Version 4.13.11.0 *
* / === / - Developed by Adam R. Nelson *
* | = = | - 2011-2013 *
* / === / - Distributed under GNU LGPL v3 *
* (________() - http://github.com/ar-nelson/deltascript *
* *
* --------------------------------------------------------- */
package com.sector91.delta.script.objects;
import static com.sector91.util.StringTemplate.$;
import static com.sector91.delta.script.DScriptErr.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import com.sector91.delta.script.DScriptContext;
import com.sector91.delta.script.DScriptErr;
import com.sector91.delta.script.DeltaScript;
import com.sector91.delta.script.Operator;
import com.sector91.delta.script.annotations.DSDynamicField;
import com.sector91.delta.script.annotations.DSInaccessible;
import com.sector91.delta.script.annotations.DSName;
import com.sector91.delta.script.annotations.DSType;
import com.sector91.delta.script.objects.reflect.DS_JavaClass;
import com.sector91.util.ArrayIterator;
import com.sector91.util.ArrayUtil;
import com.sector91.util.StringUtil;
/**
* <p>
* A mutable, fixed-size sequence of objects. DeltaScript arrays are like C or
* Java arrays in that their size cannot be changed, but their elements can.
* Arrays are the most basic collection type in DeltaScript, but are not very
* powerful; in most cases, {@link DS_List}s and {@link DS_Set}s are more useful
* and flexible.
* </p><p>
* There are two ways to create arrays in DeltaScript:
* </p><pre>
* # Creates an empty array of size 10:
* ..array(10)
*
* # Creates an array of size 3 containing 1, 2, and 3:
* [1, 2, 3]
* </pre><p>
* Arrays and other collections can be expanded into array brackets:
* </p><pre>
* a1 = [1, 2, 3]
* a2 = [4, 5, 6]
* a3 = [a1..., a2...] #=> [1, 2, 3, 4, 5, 6]
* </pre><p>
* Arrays can also be built with comprehensions:
* </p><pre>
* a1 = [1, 2, 3]
* a2 = [x*2 for x from a1] #=> [2, 4, 6]
* </pre>
*
* @author Adam R. Nelson
* @version 4.13.11.0
*/
@DSType("Array")
public class DS_Array extends DS_AbstractObject
implements DS_Sequence,
Collection<DS_Object>,
Serializable
{
private static final long serialVersionUID =
DeltaScript.VERSION.majorVersion();
public static final String TYPE_NAME = "Array";
private static final DS_JavaClass DSCLASS = DS_JavaClass.fromClass(
DS_Array.class);
private final DS_Object[] array;
// Error Tags
// ----------------------------------------------------
private static final DS_Tag
T_ARRAY = DS_Tag.tag("Array"),
T_COPY_FROM = DS_Tag.tag("copyFrom"),
T_SET = DS_Tag.tag("set"),
T_SLICE = DS_Tag.tag("slice");
// Constructors
// ----------------------------------------------------
/**
* <p>Creates an empty DeltaScript array, populated with {@code blank}s.</p>
*/
public DS_Array(int size)
{
array = new DS_Object[size];
Arrays.fill(array, DS_Blank.BLANK);
}
/**
* <p>Creates a pre-populated DeltaScript array from a Java array. This
* array <strong>must not</strong> contain {@code null} items, because these
* items will not be automatically converted to {@code blank}s!</p>
*
* @param items The items to pre-populate the array with. Should not contain
* any {@code null} items.
*/
@DSInaccessible
public DS_Array(DS_Object... items)
{
array = new DS_Object[items.length];
System.arraycopy(items, 0, array, 0, items.length);
}
/**
* <p>Creates a pre-populated DeltaScript array from a Java {@link
* Collection}. This collection <strong>must not</strong> contain
* {@code null} items, because these items will not be automatically
* converted to {@code blank}s!</p></p>
*
* @param items The items to pre-populate the array with. Should not contain
* any {@code null} items.
*/
@DSInaccessible
public DS_Array(Collection<DS_Object> items)
{
DS_Object[] arr = new DS_Object[items.size()];
arr = items.toArray(arr);
array = arr;
}
// API Methods
// ----------------------------------------------------
/**
* Retrieves the item at index {@code i} in this array, or returns
* {@code blank} if no such item exists. Unlike the {@code []} indexing
* operator, this method does not accept negative indices.
*
* @param i The index of the item to retrieve from this array.
* @return The item at index {@code i}, or {@link DS_Blank#BLANK} if the
* index {@code i} is out of bounds.
*/
@DSName("get")
public DS_Object get(int i)
{
try
{return array[i];}
catch (ArrayIndexOutOfBoundsException ex)
{return DS_Blank.BLANK;}
}
/**
* Returns the item at index 0 of this array, or {@code blank} if this array
* is of length 0.
*
* @return The item at index 0 of this array, or {@link DS_Blank#BLANK} if
* this array is of length 0.
*/
@DSName("first") @DSDynamicField
public DS_Object getFirst()
{return isEmpty() ? DS_Blank.BLANK : array[0];}
/**
* Returns the item at index {@link #size()}{@code -1} of this array, or
* {@code blank} if this array is of length 0.
*
* @return The item at index {@code size-1} of this array, or {@link
* DS_Blank#BLANK} if this array is of length 0.
*/
@DSName("last") @DSDynamicField
public DS_Object getLast()
{return isEmpty() ? DS_Blank.BLANK : array[array.length-1];}
/**
* Sets index {@code i} in this array to the value {@code item}, and returns
* the previous value at index {@code i}. Throws an error with the tags
* {@code 'set} and {@code 'OutOfBounds} if {@code i} is out of bounds.
* Unlike the {@code []} indexing operator, this method does not accept
* negative indices.
*
* @param i The index to replace.
* @param item The item to replace the current value at index {@code i}
* with.
* @return The previous value at index {@code i}.
* @throws DScriptErr If the index {@code i} is out of bounds.
*/
@DSName("set")
public DS_Object set(int i, DS_Object item) throws DScriptErr
{
try
{
DS_Object oldValue = array[i];
array[i] = item;
return oldValue;
}
catch (ArrayIndexOutOfBoundsException ex)
{
throw new DScriptErr($("Index {} out of bounds for {} of size {}.",
i, getTypeName(), array.length),
T_ARRAY, T_SET, T_OUT_OF_BOUNDS);
}
}
/** Reverses the contents of this array in place. */
@DSName("reverse")
public void reverse()
{
final DS_Object[] buffer = new DS_Object[array.length];
for (int i=1; i<=array.length; i++)
buffer[array.length-i] = array[i-1];
System.arraycopy(buffer, 0, array, 0, array.length);
}
/**
* Performs a fast array copy operation, copying {@code len} elements of the
* array {@code other}, starting at {@code srcStart}, into this array,
* starting at {@code dstStart}. Does not accept negative indices. If any of
* the array indices are out of bounds, throws an error with the tags
* {@code 'OutOfBounds} and {@code 'copyFrom}.
*
* @param other The array to copy items from.
* @param srcStart The starting index of the segment copied from
* {@code other}.
* @param dstStart The starting index of the segment of this array to
* replace with the copied items.
* @param len The length of the segment to copy.
* @throws DScriptErr If either the segment to copy from or the segment to
* copy to is out of bounds.
*/
@DSName("copyFrom")
public void copyFrom(DS_Array other, int srcStart, int dstStart, int len)
throws DScriptErr
{
try
{System.arraycopy(other.array, srcStart, array, dstStart, len);}
catch (IndexOutOfBoundsException ex)
{
throw new DScriptErr("Array copy failed: index out of bounds.",
T_ARRAY, T_COPY_FROM, T_OUT_OF_BOUNDS);
}
}
// Operator Methods
// ----------------------------------------------------
/**
* Returns a randomly-selected element from this array, or {@code blank} if
* this array is of length 0.
*
* @return A randomly-selected element from this array, or {@link
* DS_Blank#BLANK} if this array is of length 0.
*/
public DS_Object random()
{
if (array.length == 0)
return DS_Blank.BLANK;
return array[DScriptContext.getContextRandom().nextInt(array.length)];
}
/**
* Returns a new array, consisting of the contents of this array followed by
* the contents of {@code other}.
*
* @param other The iterable to concatenate with this array.
* @return A new array, consisting of the contents of this array followed by
* the contents of {@code other}.
*/
public DS_Array concat(DS_Iterable other)
{return new DS_Array(ArrayUtil.concat(array, other.dumpToArray()));}
/**
* Creates a new array, consisting of this array with {@code item} appended
* to the end of it.
*
* @param item The item to append to the end of this array.
* @return A new array, consisting of this array with {@code item} appended
* to the end of it.
*/
public DS_Array concatItem(DS_Object item)
{
DS_Object[] newArray = new DS_Object[array.length+1];
System.arraycopy(array, 0, newArray, 0, array.length);
newArray[array.length] = item;
return new DS_Array(newArray);
}
/**
* Creates a new array consisting of the contents of this array repeated
* {@code times} times. If {@code times} is negative, the result will be
* reversed.
*
* @param times The number of times to repeat this array.
* @return A new array consisting of the contents of this array repeated
* {@code times} times.
*/
public DS_Array multiply(int times)
{
final int n = Math.abs(times);
// Handle some special cases, for improved performance.
if (n == 0)
return new DS_Array(0);
final DS_Array result;
if (n == 1)
result = clone();
else if (array.length == 1)
{
DS_Object[] arr = new DS_Object[times];
Arrays.fill(arr, array[0]);
return new DS_Array(arr);
}
// Duplicate the array.
else
{
DS_Object[] arr = new DS_Object[array.length * n];
for (int i=0; i<n; i++)
System.arraycopy(array, 0, arr, array.length*i, array.length);
result = new DS_Array(arr);
}
// Reverse the result, if necessary.
if (times < 0)
result.reverse();
return result;
}
// Collection Methods
// ----------------------------------------------------
public boolean add(DS_Object obj)
{
throw new UnsupportedOperationException("Cannot add objects to an " +
TYPE_NAME + "; its size is fixed.");
}
@DSName({"size", "length"}) @DSDynamicField
public int size()
{return array.length;}
@DSName("empty") @DSDynamicField
public boolean isEmpty()
{return array.length == 0;}
public boolean contains(Object o)
{
for (int i=0; i<array.length; i++)
if (array[i].equals(o)) return true;
return false;
}
public Iterator<DS_Object> iterator()
{return new ArrayIterator<DS_Object>(array);}
public Object[] toArray()
{return array;}
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a)
{return (T[])array;}
public DS_Object[] dumpToArray()
{return Arrays.copyOf(array, array.length);}
public DS_Object getIndex(DS_Object index) throws DScriptErr
{
if (index instanceof DS_Numeric && ((DS_Numeric)index).isIntegral())
{
// Scalar: Returns the item at the given index in this array.
// Negative indices count backward from the end of the array.
if (index instanceof DS_Scalar)
{
int i = ((DS_Scalar)index).intValue();
if (i < 0)
i += array.length;
try
{return array[i];}
catch (ArrayIndexOutOfBoundsException ex)
{
throw new DScriptErr($("Index {} out of bounds for {} of" +
" size {}.", i, getTypeName(), array.length),
T_ARRAY, T_INDEX, T_OUT_OF_BOUNDS);
}
}
// Range: Given an integer range, returns a *slice* of this array,
// an array containing all elements from the start of the range
// to the end of the range, counting by the range's step.
else if (index instanceof DS_Range)
{
List<DS_Object> accum = new ArrayList<DS_Object>();
for (DS_Object subindex : (DS_Range)index)
accum.add(getIndex(subindex));
return new DS_Array(accum);
}
}
throw new DScriptErr($("{} index must be an {} or integer {}; got {}" +
" instead.", getTypeName(), DS_Integer.TYPE_NAME,
DS_Range.TYPE_NAME, index.getTypeName()),
T_ARRAY, T_INDEX, T_INVALID_TYPE, T_NOT_INTEGER);
}
public DS_Object setIndex(DS_Object index, DS_Object value)
throws DScriptErr
{
if (index instanceof DS_Scalar && ((DS_Scalar)index).isIntegral())
{
int i = ((DS_Scalar)index).intValue();
if (i < 0)
i += array.length;
try
{array[i] = value;}
catch (ArrayIndexOutOfBoundsException ex)
{
throw new DScriptErr($("Index {} out of bounds for {} of size" +
" {}.", index, getTypeName(), array.length),
T_ARRAY, T_INDEXSET, T_OUT_OF_BOUNDS);
}
return value;
}
else
throw new DScriptErr(TYPE_NAME + " index must be an Integer.",
T_ARRAY, T_INDEXSET, T_INVALID_TYPE, T_NOT_INTEGER);
}
// DS_Object Methods
// ----------------------------------------------------
public DS_Object[] unbox()
{return array;}
public String getTypeName()
{return TYPE_NAME;}
@Override protected DS_JavaClass getDeltaScriptClass()
{return DSCLASS;}
@SuppressWarnings("incomplete-switch")
@Override public DS_Object operator(Operator op, DS_Object other)
throws DScriptErr
{
switch (op)
{
case ABSOLUTE:
return ScalarFactory.fromInt(size());
case RANDOM:
return random();
case IN:
return DS_Boolean.box(contains(other));
case ADD:
return concatItem(other);
}
if (other instanceof DS_Iterable && op == Operator.BIT_OR)
return concat((DS_Iterable)other);
else if (other instanceof DS_Integer && op == Operator.MULTIPLY)
return multiply(((DS_Integer)other).intValue());
return super.operator(op, other);
}
@Override public String toString()
{return StringUtil.wrapBracketedList(Arrays.asList(array), 80);}
public boolean equals(DS_Object other)
{
if (other instanceof DS_Array)
{
if (this == other) return true;
DS_Array t = (DS_Array)other;
if (t.size() != size()) return false;
try
{
for (int i=0; i<size(); i++)
if (!(array[i].equals(t.array[i])))
return false;
}
catch (Exception ex)
{return false;}
return true;
}
else return false;
}
@DSName("clone")
@Override public DS_Array clone()
{return new DS_Array(array);}
// Collection Methods
// ----------------------------------------------------
private UnsupportedOperationException immutable()
{
return new UnsupportedOperationException("An " + getTypeName() +
" cannot be modified.");
}
/**
* Returns the index of the first occurrence in this array of an item that
* is equal to {@code item}, or -1 if no such item is contained in this
* array.
*
* @param item The item to return the index of. Items in this array will be
* checked against this item using {@link DS_Object#equals(DS_Object)}.
* @return The index of the first occurrence in this array of an item that
* is equal to {@code item}, or -1 if no such item is contained in this
* array.
*/
@DSName("indexOf")
public int indexOf(DS_Object item)
{
for (int i=0; i<array.length; i++)
if (item.equals(array[i]))
return i;
return -1;
}
/**
* Returns the index of the last occurrence in this array of an item that
* is equal to {@code item}, or -1 if no such item is contained in this
* array.
*
* @param item The item to return the index of. Items in this array will be
* checked against this item using {@link DS_Object#equals(DS_Object)}.
* @return The index of the last occurrence in this array of an item that
* is equal to {@code item}, or -1 if no such item is contained in this
* array.
*/
@DSName("lastIndexOf")
public int lastIndexOf(DS_Object object)
{
for (int i=array.length-1; i>=0; i--)
if (object.equals(array[i]))
return i;
return -1;
}
/**
* Returns an array containing the items in this array at the indices from
* {@code start} to {@code end-1}. Does not accept negative indices. If
* {@code end} is less than {@code start}, the result will be reversed. If
* either `start` or `end` is out of bounds for this array, throws an error
* with the tags `'slice` and `'OutOfBounds`.
*/
@DSName("slice")
public DS_Array slice(int start, int end) throws DScriptErr
{
boolean reverse = false;
if (end < start)
{
int tmp = end+1;
end = start+1;
start = tmp;
reverse = true;
}
final DS_Object[] newArr = new DS_Object[end-start];
try
{System.arraycopy(array, start, newArr, 0, newArr.length);}
catch (IndexOutOfBoundsException ex)
{
throw new DScriptErr($("Invalid slice ({}, {}) for {} of size {}.",
start, end, getTypeName(), size()),
T_ARRAY, T_SLICE, T_OUT_OF_BOUNDS);
}
DS_Array result = new DS_Array(newArr);
if (reverse)
result.reverse();
return result;
}
public boolean addAll(Collection<? extends DS_Object> c)
{throw immutable();}
public void clear()
{throw immutable();}
public boolean containsAll(Collection<?> c)
{throw immutable();}
public boolean remove(Object o)
{throw immutable();}
public boolean removeAll(Collection<?> c)
{throw immutable();}
public boolean retainAll(Collection<?> c)
{throw immutable();}
}