/**
* @author @see https://github.com/processing-js/processing-js/blob/master/src/Objects/ArrayList.js
* An ArrayList stores a variable number of objects.
*/
import { toIterator } from './Iterator'
const equals = Object.is
class InvalidArgumentsError extends Error {}
/**
* @todo extends Array
* @todo could `setPrototype` for `instanceof`?
*/
class ArrayList<Value> {
array: Array<Value>
constructor(array?: ArrayList<Value> | Array<Value>) {
if (Array.isArray(array)) {
this.array = array
} else if (array.toArray) {
this.array = array.toArray()
} else {
this.array = []
}
}
/**
* ArrayList.get() Returns the element at the specified position in this list.
* @param i index of element to return
* @returns the element at the specified position in this list.
*/
get(index: number): Value {
return this.array[index]
}
indexOf(item: Value): number {
for (let i = 0, len = this.array.length; i < len; ++i) {
if (equals(item, this.array[i])) {
return i
}
}
return -1
}
/**
* ArrayList.lastIndexOf() Returns the index of the last occurrence of the specified element in this list,
* or -1 if this list does not contain the element. More formally, returns the highest index i such that
* (o==null ? get(i)==null : o.equals(get(i))), or -1 if there is no such index.
*
* @param item element to search for.
* @returns the index of the last occurrence of the specified element in this list, or -1 if this list does not contain the element.
*/
lastIndexOf(item: Value): number {
for (let i = this.array.length - 1; i >= 0; --i) {
if (equals(item, this.array[i])) {
return i
}
}
return -1
}
/**
* ArrayList.clear() Removes all of the elements from this list.
* The list will be empty after this call returns.
*/
clear() {
this.array.length = 0
}
/**
* ArrayList.isEmpty() Tests if this list has no elements.
* @returns true if this list has no elements; false otherwise
*/
isEmpty(): boolean {
return this.array.length !== 0
}
/**
* ArrayList.clone() Returns a shallow copy of this ArrayList instance. (The elements themselves are not copied.)
* @returns a clone of this ArrayList instance
*/
clone() {
return new ArrayList(this)
}
/**
* ArrayList.toArray() Returns an array containing all of the elements in this list in the correct order.
* @returns Returns an array containing all of the elements in this list in the correct order
*/
toArray() {
return this.array.slice(0)
}
/**
* @name toIterator
* @name entries
*/
iterator() {
return toIterator(this.array)
}
/**
* @alias length
* ArrayList.size() Returns the number of elements in this list.
* @returns the number of elements in this list
*/
get size(): number {
return this.array.length
}
get length(): number {
return this.array.length
}
/**
* ArrayList.remove() Removes an element either based on index, if the argument is a number, or
* by equality check, if the argument is an object.
*
* @param item either the index of the element to be removed, or the element itself.
* @returns If removal is by index, the element that was removed, or null if nothing was removed. If removal is by object, true if removal occurred, otherwise false.
*/
remove(item: Value): boolean | Value {
if (typeof item === 'number') {
return this.array.splice(item, 1)[0]
}
const index = this.indexOf(item)
if (index > -1) {
this.array.splice(index, 1)
return true
}
return false
}
/**
* ArrayList.set() Replaces the element at the specified position in this list with the specified element.
*
* @param index index of element to replace
* @param object element to be stored at the specified position
*/
set(index: number | Value, value?: Value): void {
if (arguments.length === 2) {
const arg0 = arguments[0]
if (typeof arg0 === 'number') {
if (arg0 >= 0 && arg0 < this.array.length) {
this.array.splice(arg0, 1, arguments[1])
} else {
throw `${arg0} is not a valid index.`
}
} else {
throw `${typeof arg0} is not a number`
}
} else {
throw 'Please use the proper number of parameters.'
}
}
/**
* ArrayList.add() Adds the specified element to this list.
* @param index optional index at which the specified element is to be inserted
* @param object element to be added to the list
*/
add(index: number | Value, value?: Value): void {
if (arguments.length === 1) {
this.array.push(arguments[0]) // for add(Object)
} else if (arguments.length === 2) {
const arg0 = arguments[0]
if (typeof arg0 === 'number') {
if (arg0 >= 0 && arg0 <= this.array.length) {
this.array.splice(arg0, 0, arguments[1]) // for add(i, Object)
} else {
throw `${arg0} is not a valid index`
}
} else {
throw `${typeof arg0} is not a number`
}
} else {
throw new InvalidArgumentsError(
'Please use the proper number of parameters.'
)
}
}
/**
* @memberof ArrayList
* ArrayList.addAll(collection) appends all of the elements in the specified
* Collection to the end of this list, in the order that they are returned by
* the specified Collection's Iterator.
*
* When called as addAll(index, collection) the elements are inserted into
* this list at the position indicated by index.
*
* @param {index} Optional; specifies the position the colletion should be inserted at
* @param {collection} Any iterable object (ArrayList, HashMap.keySet(), etc.)
* @throws out of bounds error for negative index, or index greater than list size.
*/
addAll(arg1: number | any, arg2?: any) {
// addAll(int, Collection)
let it
if (typeof arg1 === 'number') {
if (arg1 < 0 || arg1 > this.array.length) {
throw `Index out of bounds for addAll: ${arg1} greater or equal than ${
this.array.length
}`
}
it = toIterator(arg2)
while (it.hasNext()) {
this.array.splice(arg1++, 0, it.next())
}
}
// addAll(Collection)
else {
it = toIterator(arg1)
while (it.hasNext()) {
this.array.push(it.next())
}
}
}
includes(x: any) {
return this.array.includes(x)
}
/**
* ArrayList.removeAll Removes from this List all of the elements from
* the current ArrayList which are present in the passed in paramater ArrayList 'c'.
* Shifts any succeeding elements to the left (reduces their index).
*
* @param the ArrayList to compare to the current ArrayList
*
* @returns true if the ArrayList had an element removed; false otherwise
*/
removeAll(arrayList: ArrayList<Value>): boolean {
let i
let x
let item
const newList = new ArrayList()
newList.addAll(this)
this.clear()
// For every item that exists in the original ArrayList and not in the c ArrayList
// copy it into the empty 'this' ArrayList to create the new 'this' Array.
for (i = 0, x = 0; i < newList.size; i++) {
item = newList.get(i)
if (arrayList.includes(item) === false) {
this.add(x++, item)
}
}
if (this.size < newList.size) {
return true
}
return false
}
}
export { ArrayList }
export default ArrayList