/* --------------------------------------------------------- * * __________ 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();} }