
org.stjs.javascript.Array Maven / Gradle / Ivy
/**
* Copyright 2011 Alexandru Craciun, Eyal Kaspi
*
* Licensed 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.
*/
package org.stjs.javascript;
import java.util.*;
import org.stjs.javascript.annotation.BrowserCompatibility;
import org.stjs.javascript.annotation.ServerSide;
import org.stjs.javascript.annotation.Template;
import org.stjs.javascript.functions.Callback1;
import org.stjs.javascript.functions.Callback3;
import org.stjs.javascript.functions.Function3;
import org.stjs.javascript.functions.Function4;
import static org.stjs.javascript.JSGlobal.String;
/**
* This interface represents an array from Javascript.The value may be typed. The iteration is done on the indexes to have the javascript
* equivalent of
* for(var key in array)
* The methods are prefixed with $ to let the generator know that is should generate braket access instead, i.e
* array.$get(key) => array[key]
* array.$set(key, value) => array[key]=value It is generally a bad idea for code written in ST-JS to create subclasses of Array, as that will
* not be translated properly. However, it may be useful for some bridges.
*
* The documentation of this class is mostly adapted from the ECMAScript 5.1 Specification:
* http://www.ecma-international.org/ecma-262/5.1/
*
* Browser compatibility information comes from: http://kangax.github.io/es5-compat-table
* @author acraciun, npiguet
*/
public class Array implements Iterable {
private static final Object UNSET = new Object();
private ArrayStore array = new PackedArrayStore();
private long length = 0;
private long setElements = 0;
private java.util.Map nonArrayElements = new LinkedHashMap();
/**
* Constructs a new empty Array.
*/
public Array() {
// nothing
}
/**
* Constructs a new empty Array and sets it's length property to len.
*
* @param len
* the length of this new array
*/
public Array(Number len) {
double signedLen = len.doubleValue();
double unsignedLen = JSAbstractOperations.ToUInt32(signedLen);
if (signedLen != unsignedLen) {
throw new Error("RangeError", len + " is out of range for Array length");
}
this.$length((int) unsignedLen);
}
/**
* Constructs an Array based on a java.lang.List. This constructor can only be used on server side code. The
* returned array has the same length as the List, and has no unset indices. Indices that are null
* in the List are considered to be set to null in the returned Array.
*
* @param values the list of values to add to this array
* @return a new Array that is equivalent to the specified List
*/
@ServerSide
public Array(List values) {
this.length = values.size();
this.setElements = values.size();
this.array = new PackedArrayStore<>(values);
}
/**
* Constructs a new Array containing all the specified elements in the order in which they appear in the
* argument list. If the specified values contain exactly one element, and that element is a Number, then this
* constructor behaves like Array(Number).
*
* @param values
* the values to add to this array, in the order in which they appear in the argument list
*/
@SafeVarargs
public Array(V... values) {
// special case when there is a single Number argument: Should behave like Array(Number)
if (values.length == 1 && values[0] instanceof Number) {
double signedLen = ((Number) values[0]).doubleValue();
double unsignedLen = JSAbstractOperations.ToUInt32(signedLen);
if (signedLen != unsignedLen) {
throw new Error("RangeError", signedLen + " is out of range for Array length");
}
this.$length((int) unsignedLen);
} else {
this.push(values);
}
}
/**
* Returns a java.lang.List that corresponds to this Array. This method can only be called from server side code
* and cannot be used in code that is translated to JavaScript.
*
* @return a List that corresponds this Array
* @throws IllegalStateException if the Array contains more than Integer.MAX_VALUE elements,
* if it contains non-Array elements or
* if it has some unset indices (holes)
*/
@ServerSide
public List toList() {
if (this.length > Integer.MAX_VALUE) {
throw new IllegalStateException("Array is too long: " + this.length + " > " + Integer.MAX_VALUE);
} else if (this.nonArrayElements.size() > 0) {
throw new IllegalStateException("Array contains non-array elements: " + this.nonArrayElements.keySet());
} else if(this.length != this.setElements){
throw new IllegalStateException("Array is sparse");
}
ArrayList result = new ArrayList<>((int) length);
for (int i = 0; i < this.$length(); i++) {
result.add(this.$get(i));
}
return result;
}
/**
* Returns an Iterator that allow this Array to be used in foreach statements. The returned
* iterator is designed to make Java for-each statements on Arrays match the corresponding JavaScript
* for-in statement. As a result, the returned iterator will iterate over the indices of the Array
* converted to String (the JavaScript behavior) instead of iterating directly over the values (the Java
* behavior).
*
*
* This method is marked as Deprecated because you should never call this method directly in code that will be
* translated to JavaScript; the generated JavaScript code will not work because the prototype of Array in
* JavaScript does not contain any iterator method.
*
* You may safely call this method from a Java runtime if necessary.
*/
@Override
@ServerSide
@BrowserCompatibility("none")
public Iterator iterator() {
// Extract from the ECMA-262 specification: chapter 12.6.4 The for-in Statement
//
// The mechanics and order of enumerating the properties is not specified. Properties of the object being
// enumerated may be deleted during enumeration. If a property that has not yet been visited during
// enumeration is deleted, then it will not be visited. If new properties are added to the object being
// enumerated during enumeration, the newly added properties are not guaranteed to be visited in the active
// enumeration. A property name must not be visited more than once in any enumeration.
return new Iterator() {
private Iterator> arrayIter = entryIterator(0, $length(), true);
private Iterator nonArrayIter = nonArrayElements.keySet().iterator();
@Override
public boolean hasNext() {
return arrayIter.hasNext() || nonArrayIter.hasNext();
}
@Override
public String next() {
if (arrayIter.hasNext()) {
Entry nextEntry = arrayIter.next();
return Long.toString(nextEntry.key);
}
return nonArrayIter.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
private Iterator> entryIterator(final long actualStart, final long actualEndExcluded, final boolean isForward){
return new Iterator>() {
private ArrayStore prevStore = array;
private Long prevKey = 0L;
private Iterator> iter = array.entryIterator(actualStart, actualEndExcluded, isForward);
@Override
public boolean hasNext() {
updateEntryIterator();
return iter.hasNext();
}
@Override
public Entry next() {
updateEntryIterator();
Entry entry = iter.next();
prevKey = entry.key;
return entry;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
private void updateEntryIterator(){
if(prevStore != array){
prevStore = array;
iter = array.entryIterator(prevKey + (isForward ? 1 : -1), actualEndExcluded, isForward);
}
}
};
}
/**
* Translated to array[index] in JavaScript, returns the element at the specified index in this
* Array.
*
* @param index
* the index
* @return the element a the specified index
*/
@Template("get")
public V $get(int index) {
return this.$get((long) index);
}
/**
* Translated to array[index] in JavaScript, returns the element at the specified index in this
* Array.
*
* @param index
* the index
* @return the element a the specified index
*/
@Template("get")
public V $get(long index){
if (index < 0) {
// index is not an array index, so we'll look into the non-array element values
return this.nonArrayElements.get(Long.toString(index));
}
// index is an array element, so let's ask the array store
return this.array.get(index);
}
/**
* Translated to array[index] in JavaScript, returns the element at the specified index in this
* Array.
*
* @param index
* the index
* @return the element a the specified index
*/
@Template("get")
public V $get(String index) {
Long i = toArrayIndex(index);
if (i == null) {
// index is not an array Index , look in the non-array elements
return this.nonArrayElements.get(index);
}
return array.get(i);
}
private Long toArrayIndex(String index) {
if (index == null) {
return null;
}
Double asNum = JSAbstractOperations.ToNumber(index);
Double asInt = JSAbstractOperations.ToInteger(asNum);
if (Double.isNaN(asNum) || asInt < 0 || asInt >= JSAbstractOperations.UINT_MAX_VALUE_D - 1
|| !asInt.equals(asNum)) {
// index is not an array Index , look in the non-array elements
return null;
}
return asInt.longValue();
}
/**
* Translated to array[index] = value in JavaScript, sets the element at the specified index in this
* Array to the specified value.
*
* @param index
* the index
* @param value
* the value
*/
@Template("set")
public void $set(int index, V value) {
this.$set((long)index, value);
}
/**
* Translated to array[index] = value in JavaScript, sets the element at the specified index in this
* Array to the specified value.
*
* @param index
* the index
* @param value
* the value
*/
@Template("set")
public void $set(long index, V value) {
if (index < 0) {
// not an array index. Set the value in the non-array properties
this.nonArrayElements.put(JSAbstractOperations.ToString(index), value);
} else {
this.doSet(index, value);
}
}
/**
* Translated to array[index] = value in JavaScript, sets the element at the specified index in this
* Array to the specified value.
*
* @param index
* the index
* @param value
* the value
*/
@Template("set")
public void $set(String index, V value) {
Long i = this.toArrayIndex(index);
if (i == null) {
this.nonArrayElements.put(JSAbstractOperations.ToString(index), value);
} else {
this.$set(i, value);
}
}
/**
* Deletes the element at the specified index from this Array, leaving an empty hole. Unlike
* splice() This method does not affect the length of this Array, it only unsets the value at the
* specified index. After $delete() is called on an index, that index will not be iterated over any more in
* for-each loops on this Array. $delete() can also be used to remove non-array elements from this
* Array (e.g. myArray.$delete("foobar")).
*
* myArray.$delete(index) is translated to delete myArray[index] in JavaScript.
*
* @param index
* the index
* @return true
*/
@Template("delete")
public boolean $delete(String index) {
Long i = this.toArrayIndex(index);
if (i == null) {
this.nonArrayElements.remove(index);
} else {
this.doDelete(i);
}
return true;
}
/**
* Deletes the element at the specified index from this Array, leaving an empty hole. Unlike
* splice() This method does not affect the length of this Array, it only unsets the value at the
* specified index. After $delete() is called on an index, that index will not be iterated over any more in
* for-each loops on this Array. $delete() can also be used to remove non-array elements from this
* Array (e.g. myArray.$delete("foobar"))
*
* myArray.$delete(index) is translated to delete myArray[index] in JavaScript.
*
* @param index
* the index
* @return true
*/
@Template("delete")
public boolean $delete(int index) {
return this.$delete((long)index);
}
/**
* Deletes the element at the specified index from this Array, leaving an empty hole. Unlike
* splice() This method does not affect the length of this Array, it only unsets the value at the
* specified index. After $delete() is called on an index, that index will not be iterated over any more in
* for-each loops on this Array. $delete() can also be used to remove non-array elements from this
* Array (e.g. myArray.$delete("foobar"))
*
* myArray.$delete(index) is translated to delete myArray[index] in JavaScript.
*
* @param index
* the index
* @return true
*/
@Template("delete")
public boolean $delete(long index) {
if (index < 0) {
// not an array index. Remove the property from the non-array properties
this.nonArrayElements.remove(JSAbstractOperations.ToString(index));
} else {
this.doDelete(index);
}
return true;
}
private void doDelete(long index) {
if (index >= this.$length()) {
// nothing to do
return;
}
boolean isSet = array.isSet(index);
long newSetElements = this.setElements;
if (isSet) {
// there is only something to do if the element is set. If it is already unset, nothing to do.
newSetElements--;
this.switchStoreIfNeeded(this.length, newSetElements);
this.array.delete(index);
this.setElements = newSetElements;
}
}
private void doSet(long index, V value) {
// check if store type is correct for setting this value
boolean isSet = array.isSet(index);
long newLength = (long) Math.max(this.length, index + 1);
long newSetElements = this.setElements;
if (!isSet) {
// unsetElement, we may need to convert between stores
newSetElements++;
this.switchStoreIfNeeded(newLength, newSetElements);
}
// store type is now correct, let's do the actual setting
if (newLength > this.length) {
array.padTo(newLength);
}
array.set(index, value);
this.length = newLength;
this.setElements = newSetElements;
}
/**
* Translated to array.length in JavaScript, returns the length of this Array. The returned value
* is always greater than the highest index in this Array.
*
* @return the length of this Array.
*/
@Template("toProperty")
public int $length() {
return (int) this.length;
}
/**
* Translated to array.length = newLength in JavaScript, sets the length of this Array.
*
*
* Attempting to set the length property of this Array to a value that is numerically less than or equal to
* the largest index contained within this Array will result in the Array being truncated to the
* new length.
*
* @param newLength
* the new length of this Array
*/
@Template("toProperty")
public void $length(int newLength) {
if (newLength == this.length) {
// nothing to do, we are not changing the length of the array
return;
} else if (newLength > this.length) {
// growing the array
this.switchStoreIfNeeded(newLength, this.setElements);
this.array.padTo(newLength);
this.length = newLength;
} else {
// truncating the array
long newSetElements = 0;
if (newLength > 0) {
newSetElements = this.setElements - this.array.getSetElements(newLength - 1, this.length);
}
this.switchStoreIfNeeded(newLength, newSetElements);
this.array.truncateFrom(newLength);
this.length = newLength;
this.setElements = newSetElements;
}
}
private void switchStoreIfNeeded(long newLength, long newSetElements) {
if (!this.array.isEfficientStoreFor(newLength, newSetElements)) {
this.array = this.array.switchStoreType(newLength);
}
}
/**
* Appends all the elements of each of the specified Arrays in order to the elements of this Array
* . This method does not change the existing arrays, but returns a new array, containing the values of the joined
* arrays.
*
* @param arrays
* the Arrays to be concatenated to this Array
* @return a new Array, containing all the elements of the joined Arrays.
*/
@SafeVarargs
public final Array concat(Array extends V>... arrays) {
Array result = new Array<>();
// Add the elements of this array
long i = 0;
Iterator> thisIter = this.entryIterator(0, this.$length(), true);
while (thisIter.hasNext()) {
Entry entry = thisIter.next();
result.$set(i + entry.key, entry.value);
}
i = this.$length();
if (arrays != null) {
// add the elements of all the other specified arrays
for (Array extends V> arr : arrays) {
Iterator extends Entry extends V>> arrIter = arr.array.entryIterator(0, arr.$length(), true);
while (arrIter.hasNext()) {
Entry extends V> entry = arrIter.next();
result.$set(i + entry.key, entry.value);
}
i += arr.$length();
}
}
result.$length((int) i);
return result;
}
/**
* Appends all the specified elements in the order they appear in the arguments list to the elements of this
* Array. This method does not change the existing arrays, but returns a new array, containing the values
* of the joined arrays.
*
* @param values
* the elements to be concatenated to this Array
* @return a new Array, containing all the elements of the joined Arrays.
*/
@SafeVarargs
public final Array concat(V... values) {
Array result = new Array();
// add the elements of this array
long i = 0;
Iterator> iter = this.entryIterator(0, this.$length(), true);
while (iter.hasNext()) {
Entry entry = iter.next();
result.$set(i + entry.key, entry.value);
}
i = this.$length();
result.$length(this.$length());
if (values != null) {
// add the specified values
for (int j = 0; j < values.length; j++) {
result.$set(i + j, values[j]);
}
}
return result;
}
/**
* Searches this Array for the specified item, and returns its position. Items are compared for equality
* using the === operator.
*
*
* The search will start at the beginning and end at the end of the Array.
*
*
* Returns -1 if the item is not found.
*
*
* If the item is present more than once, indexOf returns the position of the first occurrence.
*
* @param element
* the item to search for
* @return the index at which the element was found, -1 if not found
*/
@BrowserCompatibility("IE:9+")
public int indexOf(V element) {
return indexOf(element, 0);
}
/**
* Searches this Array for the specified item, and returns its position. Items are compared for equality
* using the === operator.
*
*
* The search will start at the specified position and end the search at the end of the Array.
*
*
* Returns -1 if the item is not found.
*
*
* If the specified position is greater than or equal to the length of this array, this Array is
* not searched and -1 is returned. If start is negative, it will be treated as length+start. If
* the computed starting element is less than 0, this whole Array will be searched.
*
*
* If the item is present more than once, indexOf method returns the position of the first occurrence.
*
* @param element
* the item to search for
* @param start
* where to start the search. Negative values will start at the given position counting from the end
* @return the index at which the element was found, -1 if not found
*/
@BrowserCompatibility("IE:9+")
public int indexOf(V element, int start) {
long actualStart;
if (start < 0) {
actualStart = (long) Math.max(0, this.$length() + start);
} else {
actualStart = (long) Math.min(this.$length(), start);
}
Iterator> iter = this.entryIterator(actualStart, this.$length(), true);
return indexOf(element, iter);
}
private int indexOf(V element, Iterator> iter) {
// this method doesn't depend on the order of iteration, and is used both by indexOf and lastIndexOf
while (iter.hasNext()) {
Entry entry = iter.next();
// Double.equals has an annoying behavior that we must correct for
// this implementation to be as close to JS as possible
// ie: myDouble.equals(Double.NaN) will return true if myDouble.isNan()
// but myDouble == Double.NaN will return false...
// In JS, there is no such distinction, and any NaN is not equal to anything
boolean isNan = entry.value instanceof Double && Double.isNaN((Double) entry.value);
if (!isNan && //
(entry.value != null && entry.value.equals(element) || //
entry.value == null && element == null) //
) {
return (int) entry.key;
}
}
return -1;
}
/**
* Converts all of the elements of this Array to Strings, and concatenates these Strings
* using a single comma (",") as a separator.
*
*
* If this Array is empty, the empty string is returned.
*
* @return the string representation of the values in this Array, separated by a comma.
*/
public String join() {
return join(",");
}
/**
* Converts all of the elements of this Array to Strings, and concatenates these Strings
* using the specified separator.
*
*
* If this Array is empty, the empty string is returned.
*
* @return the string representation of the values in this Array, separated by the specified separator.
*/
public String join(String separator) {
StringBuilder builder = new StringBuilder();
String realSeparator = "";
for (int i = 0; i < this.length; i++) {
builder.append(realSeparator);
realSeparator = separator;
V item = this.array.get(i);
if (item != null) {
builder.append(JSAbstractOperations.ToString(item));
}
}
return builder.toString();
}
/**
* Removes the last element from this Array and returns it.
*
*
* If this Array is empty, undefined is returned.
*
* @return the last element from this Array
*/
public V pop() {
if (this.length == 0) {
return null;
}
Array removed = this.splice((int) (this.length - 1), 1);
return removed.$get(0);
}
/**
* The specified elements are appended at the end of this Array in the order in which they appear in the
* arguments list and the new length of this Array is returned.
*
* @param values
* the values to be appended
* @return the new length of this Array
*/
@SafeVarargs
public final int push(V... values) {
this.splice(this.$length(), 0, values);
return (int) this.length;
}
/**
* The elements of this Array are rearranged so as to reverse their order, and this Array is
* returned.
*
* @return this Array
*/
public Array reverse() {
this.array.reverse();
return this;
}
/**
* Removes the first element from this Array and returns it.
*
*
* If this Array is empty, null is returned.
*
* @return the first element of this Array.
*/
public V shift() {
Array removed = this.splice(0, 1);
if (removed.$length() == 1) {
return removed.$get(0);
}
return null;
}
/**
* Returns a new Array containing all the elements of this Array starting from the given start
* index until the end of this Array.
*
*
* If start is negative, it is treated as length+start.
*
*
* If the selection range falls outside of this array, an empty Array is returned.
*
* @param start
* the index from which to start the selection. Use negative values to specified an index starting from
* the end of this Array.
* @return a new Array containing all the elements of this Array starting from the given start
* index until the end of the Array.
*/
public Array slice(int start) {
return slice(start, this.$length());
}
/**
* Returns a new Array containing all the elements of this Array starting from the given start
* index and ending at (but not including) the given end index.
*
*
* If start is negative, it is treated as length+start. If end is negative, it is treated
* as length+end.
*
*
* If the selection range falls outside of this Array, an empty Array is returned.
*
* @param start
* the index from which to start the selection. Use negative values to specified an index starting from
* the end
* @param end
* the index at which to end the selection (the element at this index is excluded). Use negative values
* to specified an index starting from the end
* @return a new Array containing all the elements of this Array starting from the given start
* index and ending at the given end index
*/
public Array slice(int start, int end) {
long actualStart;
if (start < 0) {
actualStart = (long) Math.max(this.$length() + start, 0);
} else {
actualStart = (long) Math.min(start, this.$length());
}
long actualEnd;
if (end < 0) {
actualEnd = (long) Math.max(this.$length() + end, 0);
} else {
actualEnd = (long) Math.min(end, this.$length());
}
return this.array.slice(actualStart, actualEnd);
}
/**
* Deletes the specified number of elements from this Array starting at specified start index, and returns
* a new Array containing the deleted elements (if any).
*
*
* If start is negative, it is treated as length+start.
*
* @param start
* the index at which to start deleting elements. Use negative values to specified an index starting from
* the end
* @param deleteCount
* the number of elements to be deleted
* @return a new Array containing the deleted elements (if any)
*/
public Array splice(int start, int deleteCount) {
return this.splice(start, deleteCount, (V[]) null);
}
/**
* Deletes the specified number of elements from this Array starting at specified start index, replaces the
* deleted elements with the specified values, and returns a new Array containing the deleted elements (if
* any).
*
*
* If start is negative, it is treated as length+start.
*
* @param start
* the index at which to start deleting elements. Use negative values to specify an index starting from
* the end.
* @param deleteCount
* the number of elements to be deleted
* @param values
* the elements with which the deleted elements must be replaced
* @return a new Array containing the deleted elements (if any)
*/
@SafeVarargs
public final Array splice(int start, int deleteCount, V... values) {
long actualStart;
if (start < 0) {
actualStart = (long) Math.max(this.length + start, 0);
} else {
actualStart = (long) Math.min(this.length, start);
}
long actualDeleteCount = (long) Math.min(Math.max(deleteCount, 0), this.length - actualStart);
if (values == null) {
@SuppressWarnings("unchecked")
V[] tmp = (V[]) new Object[0];
values = tmp;
}
if (actualDeleteCount == 0 && values.length == 0) {
// we are not deleting or adding anything. This is basically a NOP
return new Array();
}
long newLength = this.length - actualDeleteCount + values.length;
long newSetElements = this.setElements + values.length
- this.array.getSetElements(actualStart, actualStart + deleteCount);
this.switchStoreIfNeeded(newLength, newSetElements);
Array deleted = this.slice((int) actualStart, (int) (actualStart + actualDeleteCount));
this.array.splice(actualStart, actualDeleteCount, values);
this.length = newLength;
this.setElements = newSetElements;
return deleted;
}
/**
* Sorts the elements of this Array using the natural order of their string representations.
*
*
* The sort is not necessarily stable (that is, elements that compare equal do not necessarily remain in their
* original order).
*
*
* sort returns this Array.
*
*
* Note1: Because non-existent property values always compare greater than undefined property
* values, and undefined always compares greater than any other value, undefined property values always sort to the
* end of the result, followed by non-existent property values.
*
* @return this array
*/
public Array sort() {
return this.sort(null);
}
/**
* Sorts the elements of this Array using the specified SortFunction to determine ordering.
*
*
* The sort is not necessarily stable (that is, elements that compare equal do not necessarily remain in their
* original order).
*
*
* This methods return this Array.
*
*
* If the specified SortFunction is null, then this method is equivalent to sort().
*
*
* If comparefn is not a consistent comparison function for the elements of this Array, the
* behaviour of sort is implementation-defined.
*
*
* Note1: Because non-existent property values always compare greater than undefined property
* values, and undefined always compares greater than any other value, undefined property values always sort to the
* end of the result, followed by non-existent property values.
*
* @param comparefn
* a sort function that can compare elements of this Array.
* @return this Array
*/
public Array sort(SortFunction super V> comparefn) {
if(comparefn == null) {
this.array.sort(defaultSortComparator());
} else {
this.array.sort(comparefn);
}
return this;
}
private SortFunction defaultSortComparator(){
return new SortFunction() {
@Override
public int $invoke(V a, V b) {
String aString = String(a);
String bString = String(b);
return aString.compareTo(bString);
}
};
}
/**
* Prepends the specified values to the start of this Array, such that their order within this
* Array is the same as the order in which they appear in the argument list. Returns the new length of this
* Array.
*
* @param values
* the values to the prepended to the start of this Array
* @return the new length of this Array
*/
@SafeVarargs
public final int unshift(V... values) {
this.splice(0, 0, values);
return this.$length();
}
/**
* Calls the specified callback function once for each element present in this Array, in ascending order.
* callbackfn is called only for elements of this Array which actually exist; it is not called for
* missing elements of this Array.
*
*
* callbackfn is called with the value of the element as its argument.
*
*
* forEach does not directly mutate this Array but this Array may be mutated by the calls
* to callbackfn.
*
*
* The range of elements processed by forEach is set before the first call to callbackfn. Elements
* which are appended to this Array after the call to forEach begins will not be visited by
* callbackfn. If existing elements of this Array are changed, their value as passed to callback
* will be the value at the time forEach visits them; elements that are deleted after the call to
* forEach begins and before being visited are not visited.
*
* @param callbackfn
* the callback function to be called for each element
*/
@BrowserCompatibility("IE:9+")
public void forEach(final Callback1 super V> callbackfn) {
forEach(new Callback3>() {
@Override
public void $invoke(V v, Long aLong, Array strings) {
callbackfn.$invoke(v);
}
});
}
/**
* Calls the specified callback function once for each element present in this Array, in ascending order.
* callbackfn is called only for elements of this Array which actually exist; it is not called for
* missing elements of this Array.
*
*
* callbackfn is called with three arguments: the value of the element, the index of the element, and the
* Array being traversed (this Array).
*
*
* forEach does not directly mutate this Array but this Array may be mutated by the calls
* to callbackfn.
*
*
* The range of elements processed by forEach is set before the first call to callbackfn. Elements
* which are appended to this Array after the call to forEach begins will not be visited by
* callbackfn. If existing elements of this Array are changed, their value as passed to callback
* will be the value at the time forEach visits them; elements that are deleted after the call to
* forEach begins and before being visited are not visited.
*
* @param callbackfn
* the callback function to be called for each element
*/
@BrowserCompatibility("IE:9+")
public void forEach(Callback3 super V, Long, ? super Array> callbackfn) {
if(callbackfn == null){
throw new Error("TypeError", "callbackfn is null");
}
Iterator> iter = this.entryIterator(0, $length(), true);
while(iter.hasNext()){
Entry val = iter.next();
callbackfn.$invoke(val.value, val.key, this);
}
}
/**
* this method does exactly as {@link #forEach(Callback1)}, but allows in java 8 the usage of lambda easily as the forEach method overloads
* the method from the new Iterable interface. The generated code is, as expected, forEach
* @param callbackfn
*/
@Template("prefix")
@BrowserCompatibility("IE:9+")
public void $forEach(Callback1 super V> callbackfn) {
forEach(callbackfn);
}
/**
* this method does exactly as {@link #forEach(Callback1)}, but allows in java 8 the usage of lambda easily as the forEach method overloads
* the method from the new Iterable interface. The generated code is, as expected, forEach
* @param callbackfn
*/
@Template("prefix")
@BrowserCompatibility("IE:9+")
public void $forEach(Callback3 super V, Long, ? super Array> callbackfn) {
forEach(callbackfn);
}
/**
* Converts all the elements of this Array to their String representation, concatenates them using a comma
* as a separator. Calling this method is equivalent to calling join().
*
* @return the string representation of the values in this Array, separated by a comma.
*/
@Override
public String toString() {
return join();
}
/**
* Converts all the elements of this Array to Strings using their toLocaleString method, and concatenate
* the resulting Strings by a separator String that has been derived in an implementation-defined locale-specific
* way. The result of calling this function is intended to be analogous to the result of toString(), except
* that the result of this function is intended to be locale-specific.
*
* @return a string representation of all the elements in this Array, separated by a locale-specific
* separator
*/
public String toLocaleString() {
StringBuilder builder = new StringBuilder();
String sep = "";
for (int i = 0; i < this.length; i++) {
builder.append(sep);
sep = ",";
V item = this.array.get(i);
if (item != null) {
builder.append(JSObjectAdapter.toLocaleString(item));
}
}
return builder.toString();
}
/**
* Searches the elements of this Array in descending order for the specified element, and returns the index
* of the last position at which the element was found, or -1 if not found.
*
*
* Elements are compared for equality using the === operator.
*
*
* If there are multiple occurrences of the specified element in this Array, then the largest index among
* occurrences is returned.
*
* @param searchElement
* the element to search for
* @return the last index where this element is in this Array, or -1 if not found
*/
@BrowserCompatibility("IE:9+")
public int lastIndexOf(V searchElement) {
return lastIndexOf(searchElement, this.$length() - 1);
}
/**
* Searches the elements of this Array in descending order starting at the specified index for the
* specified element, and returns the index of the last index at which the element was found, or -1 if not found.
*
*
* Elements are compared for equality using the === operator.
*
*
* If there are multiple occurrences of the specified element in this Array, then the largest index among
* occurrences is returned.
*
*
* If fromIndex is greater than or equal to the length of this Array, the whole array will be
* searched. If fromIndex is negative, then it is treated as length+fromIndex. If the computed
* index is less than 0, -1 is returned.
*
* @param searchElement
* The element to search for
* @param fromIndex
* the index from which to start searching
* @return the last index where this element is in this Array, or -1 if not found
*/
@BrowserCompatibility("IE:9+")
public int lastIndexOf(V searchElement, int fromIndex) {
long actualStart;
if (fromIndex < 0) {
actualStart = (long) Math.max(-1, this.$length() + fromIndex);
} else {
actualStart = (long) Math.min(this.$length() - 1, fromIndex);
}
Iterator> iter = this.entryIterator(actualStart, -1, false);
return indexOf(searchElement, iter);
}
/**
* Returns true if the specified callback function returns true for every element in this
* Array.
*
*
* callbackfn is called on each element in ascending order until one of the calls returns false.
* Once callbackfn has returned false for the first time, the search in this Array is interrupted,
* callbackfn is never called on any more elements and every returns false. If
* callbackfn returned true for all the elements, then every returns true.
* callbackfn is called only for elements of this Array which actually exist; it is not called for
* missing elements of this Array.
*
*
* callbackfn has 3 arguments: the value of the element, the index of the element, and the Array
* being traversed (this Array).
*
*
* every does not directly mutate this Array, but this Array may be mutated by the calls
* to callbackfn.
*
*
* The range of elements processed by every is set before the first call to callbackfn. Elements
* which are appended to this Array after the call to every begins will not be visited by
* callbackfn. If existing elements of this Array are changed, their value as passed to
* callbackfn will be the value at the time every visits them; elements that are deleted after the
* call to every begins and before being visited are not visited. every acts like the "for all"
* quantifier in mathematics. In particular, for an empty Array, it returns true.
*
* @param callbackfn
* the callback function to call
* @return true if callbackfn returns true for ALL the elements in this Array,
* false if not
*/
@BrowserCompatibility("IE:9+")
public boolean every(Function3 super V, Long, ? super Array, Boolean> callbackfn) {
if(callbackfn == null){
throw new Error("TypeError", "callbackfn is null");
}
Iterator> iter = this.entryIterator(0, this.$length(), true);
while (iter.hasNext()) {
Entry entry = iter.next();
Boolean result = callbackfn.$invoke(entry.value, entry.key, this);
if (!Boolean.TRUE.equals(result)) {
// false or null was returned
return false;
}
}
return true;
}
/**
* Returns true if the specified callback function returns true for at least one element in this
* Array.
*
*
* some calls callbackfn once for each element present in this Array, in ascending order,
* until it finds one where callbackfn returns true. If such an element is found, some
* immediately returns true. Otherwise, some returns false. callbackfn is called
* only for elements of this Array which actually exist; it is not called for missing elements of this
* Array.
*
*
* callbackfn has three arguments: the value of the element, the index of the element, and the
* Array being traversed (this Array).
*
* some does not directly mutate this Array but this Array may be mutated by the calls to
* callbackfn.
*
*
* The range of elements processed by some is set before the first call to callbackfn. Elements
* that are appended to this Array after the call to some begins will not be visited by
* callbackfn. If existing elements of this Array are changed, their value as passed to
* callbackfn will be the value at the time that some visits them; elements that are deleted after
* the call to some begins and before being visited are not visited. some acts like the "exists"
* quantifier in mathematics. In particular, for an empty Array, it returns false.
*
* @param callbackfn
* the callback function to call
* @return true if callbackfn returns true for at least one element in this
* Array, false otherwise
*/
@BrowserCompatibility("IE:9+")
public boolean some(Function3 super V, Long, ? super Array, Boolean> callbackfn) {
if(callbackfn == null){
throw new Error("TypeError", "callbackfn is null");
}
Iterator> iter = this.entryIterator(0, this.$length(), true);
while (iter.hasNext()) {
Entry entry = iter.next();
Boolean result = callbackfn.$invoke(entry.value, entry.key, this);
if (Boolean.TRUE.equals(result)) {
// false or null was returned
return true;
}
}
return false;
}
/**
* Constructs a new Array, such that the value at each index in the new Array is the result of
* calling the specified callback function on the value at the corresponding index in this Array.
*
*
* map calls callbackfn once for each element in this Array, in ascending order, and
* constructs a new Array from the results. callbackfn is called only for elements of the array
* which actually exist; it is not called for missing elements of this Array.
*
*
* callbackfn has three arguments: the value of the element, the index of the element, and the
* Array being traversed (this Array).
*
*
* map does not directly mutate this Array but this Array may be mutated by the calls to
* callbackfn.
*
*
* The range of elements processed by map is set before the first call to callbackfn. Elements
* which are appended to this Array after the call to map begins will not be visited by
* callbackfn. If existing elements of this Array are changed, their value as passed to
* callbackfn will be the value at the time map visits them; elements that are deleted after the
* call to map begins and before being visited are not visited.
*
* @param callbackfn
* the callback used to create the elements of the new array
* @return a new Array containing new elements as returned by the specified callback function
*/
@BrowserCompatibility("IE:9+")
public Array map(Function3 super V, Long, ? super Array, T> callbackfn) {
if(callbackfn == null){
throw new Error("TypeError", "callbackfn is null");
}
int lengthBefore = this.$length();
Iterator> iter = this.entryIterator(0, this.$length(), true);
Array result = new Array<>();
while (iter.hasNext()) {
Entry entry = iter.next();
T mapped = callbackfn.$invoke(entry.value, entry.key, this);
result.$set(entry.key, mapped);
}
result.$length(lengthBefore);
return result;
}
/**
* Constructs a new Array containing only the elements of this Array for which the specified
* callback function returns true.
*
*
* filter calls callbackfn once for each element in this Array, in ascending order, and
* constructs a new Array of all the values for which callbackfn returns true.
* callbackfn is called only for elements of this Array which actually exist; it is not called for
* missing elements of this Array.
*
*
* callbackfn has three arguments: the value of the element, the index of the element, and the
* Array being traversed (this Array).
*
*
* filter does not directly mutate this Array, but this Array may be mutated by the calls
* to callbackfn.
*
*
* The range of elements processed by filter is set before the first call to callbackfn. Elements
* which are appended to this Array after the call to filter begins will not be visited by
* callbackfn. If existing elements of this Array are changed their value as passed to
* callbackfn will be the value at the time filter visits them; elements that are deleted after
* the call to filter begins and before being visited are not visited.
*
* @param callbackfn
* the callback function used to decide if an element should be appended to the new Array
* @return a new Array containing only the elements of this Array for which the specified callback
* function returns true.
*/
@BrowserCompatibility("IE:9+")
public Array filter(Function3 super V, Long, ? super Array, Boolean> callbackfn) {
if(callbackfn == null){
throw new Error("TypeError", "callbackfn is null");
}
Iterator> iter = this.entryIterator(0, this.$length(), true);
Array result = new Array<>();
while (iter.hasNext()) {
Entry entry = iter.next();
boolean selected = callbackfn.$invoke(entry.value, entry.key, this);
if(selected){
result.push(entry.value);
}
}
return result;
}
/**
* Applies the specified function against an accumulator and each value of this Array (from left-to-right)
* as to reduce it to a single value, omitting the first element and using it as initial accumulator value.
*
*
* reduce calls the callback, as a function, once for each element present in this Array except
* for the first one, in ascending order.
*
*
* callbackfn has four arguments: the previousValue (or value from the previous call to
* callbackfn), the currentValue (value of the current element), the currentIndex, and
* the Array being traversed (this Array). The first time that callback is called (that is, on the
* second element element of this array) previousValue will be equal to the first value in this
* Array and currentValue will be equal to the second. It is a TypeError if this Array
* contains no elements.
*
*
* reduce does not directly mutate this Array but this Array may be mutated by the calls
* to callbackfn.
*
*
* The range of elements processed by reduce is set before the first call to callbackfn. Elements
* that are appended to this Array after the call to reduce begins will not be visited by
* callbackfn. If existing elements of this Array are changed, their value as passed to
* callbackfn will be the value at the time reduce visits them; elements that are deleted after
* the call to reduce begins and before being visited are not visited.
*
* @param callbackfn
* the function used to reduce the array to a single value
* @return a single value derived from calling the callback function on all the elements of this Array
*/
@BrowserCompatibility("IE:9+, Safari:4+, Opera:10.50+")
public V reduce(Function4, V> callbackfn) {
return doReduce(callbackfn, UNSET, true);
}
/**
* Applies the specified function against an accumulator and each value of this Array (from left-to-right)
* as to reduce it to a single value.
*
*
* reduce calls the callback, as a function, once for each element present in this Array, in
* ascending order.
*
*
* callbackfn has four arguments: the previousValue (or value from the previous call to
* callbackfn), the currentValue (value of the current element), the currentIndex, and
* the Array being traversed (this Array). The first time that callback is called
* previousValue will be equal to initialValue and currentValue will be equal to the
* first value in this Array.
*
*
* reduce does not directly mutate this Array but this Array may be mutated by the calls
* to callbackfn.
*
*
* The range of elements processed by reduce is set before the first call to callbackfn. Elements
* that are appended to this Array after the call to reduce begins will not be visited by
* callbackfn. If existing elements of this Array are changed, their value as passed to
* callbackfn will be the value at the time reduce visits them; elements that are deleted after
* the call to reduce begins and before being visited are not visited.
*
* @param callbackfn
* the function used to reduce this Array to a single value
* @return a single value derived from calling the callback function on all the elements of this Array
*/
@BrowserCompatibility("IE:9+, Safari:4+, Opera:10.50+")
public T reduce(Function4, T> callbackfn, T initialValue) {
return doReduce(callbackfn, initialValue, true);
}
/**
* Applies the specified function against an accumulator and each value of this Array (from right-to-left)
* as to reduce it to a single value, omitting the first element and using it as initial accumulator value.
*
*
* reduceRight calls the callback, as a function, once for each element present in this Array
* except for the first one, in descending order.
*
*
* callbackfn has four arguments: the previousValue (or value from the previous call to
* callbackfn), the currentValue (value of the current element), the currentIndex, and
* the Array being traversed (this Array). The first time that callback is called (that is, on the
* second-to-last element element of this array) previousValue will be equal to the last value in
* this Array and currentValue will be equal to the second-to-last. It is a TypeError if this
* Array contains no elements.
*
*
* reduceRight does not directly mutate this Array but this Array may be mutated by the
* calls to callbackfn.
*
*
* The range of elements processed by reduceRight is set before the first call to callbackfn.
* Elements that are appended to this Array after the call to reduceRight begins will not be
* visited by callbackfn. If existing elements of this Array are changed, their value as passed to
* callbackfn will be the value at the time reduceRight visits them; elements that are deleted
* after the call to reduceRight begins and before being visited are not visited.
*
* @param callbackfn
* the function used to reduce the array to a single value
* @return a single value derived from calling the callback function on all the elements of this Array
*/
@BrowserCompatibility("IE:9+, Safari:4+, Opera:10.50+")
public V reduceRight(Function4, V> callbackfn) {
return doReduce(callbackfn, UNSET, false);
}
/**
* Applies the specified function against an accumulator and each value of this Array (from right-to-left)
* as to reduce it to a single value.
*
*
* reduceRight calls the callback, as a function, once for each element present in this Array, in
* descending order.
*
*
* callbackfn has four arguments: the previousValue (or value from the previous call to
* callbackfn), the currentValue (value of the current element), the currentIndex, and
* the Array being traversed (this Array). The first time that callback is called
* previousValue will be equal to initialValue and currentValue will be equal to the last
* value in this Array.
*
*
* reduceRight does not directly mutate this Array but this Array may be mutated by the
* calls to callbackfn.
*
*
* The range of elements processed by reduceRight is set before the first call to callbackfn.
* Elements that are appended to this Array after the call to reduceRight begins will not be
* visited by callbackfn. If existing elements of this Array are changed, their value as passed to
* callbackfn will be the value at the time reduceRight visits them; elements that are deleted
* after the call to reduceRight begins and before being visited are not visited.
*
* @param callbackfn
* the function used to reduce this Array to a single value
* @return a single value derived from calling the callback function on all the elements of this Array
*/
@BrowserCompatibility("IE:9+, Safari:4+, Opera:10.50+")
public T reduceRight(Function4, T> callbackfn, T initialValue) {
return doReduce(callbackfn, initialValue, false);
}
private T doReduce(Function4, T> callbackfn, Object initialValue, boolean isForward){
if(callbackfn == null){
throw new Error("TypeError", "callbackfn is null");
}
Iterator> iter;
if(isForward) {
iter = this.entryIterator(0, this.$length(), true);
} else {
iter = this.entryIterator(this.$length(), -1, false);
}
T accumulator;
if(initialValue == UNSET){
// when initialValue is UNSET (the parameter was not specified)
// then the types T and V are the same.
if(!iter.hasNext()){
throw new Error("TypeError", "Array is empty and initialValue was not provided");
}
@SuppressWarnings("unchecked")
T temp = (T)iter.next().value;
accumulator = temp;
} else {
// when initialValue is set (ie: not UNSET, the parameter was specified, but may be null)
// then the type of initialValue is T
@SuppressWarnings("unchecked")
T temp = (T)initialValue;
accumulator = temp;
}
while (iter.hasNext()) {
Entry entry = iter.next();
accumulator = callbackfn.$invoke(accumulator, entry.value, entry.key, this);
}
return accumulator;
}
/**
* Returns true if the specified object is an Array.
* @param o the object to check
* @return true if the object is an Array, false if not
*/
public static boolean isArray(Object o){
return o instanceof Array;
}
private abstract class ArrayStore {
ArrayStore switchStoreType(long newLength) {
ArrayStore that;
if (this instanceof PackedArrayStore) {
that = new SparseArrayStore<>();
} else {
that = new PackedArrayStore<>();
}
that.padTo(newLength);
Iterator> entries = this.entryIterator(0, newLength, true);
while (entries.hasNext()) {
Entry entry = entries.next();
that.set(entry.key, entry.value);
}
return that;
}
abstract void truncateFrom(long newLength);
abstract void padTo(long newLength);
abstract void splice(long actualStart, long actualDeleteCount, E[] values);
abstract long getSetElements(long firstIncluded, long lastExcluded);
abstract Iterator> entryIterator(long actualStart, long actualEndExcluded, boolean isForward);
abstract boolean isEfficientStoreFor(long newLength, long newElementCount);
abstract void set(long index, E value);
abstract boolean isSet(long index);
abstract void delete(long index);
abstract E get(long index);
abstract Array slice(long fromIncluded, long toExcluded);
abstract void reverse();
public abstract void sort(SortFunction super E> comparefn);
}
private final class PackedArrayStore extends ArrayStore {
/**
* We can't use instead of