// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. part of _interceptors; /** * The interceptor class for [List]. The compiler recognizes this * class as an interceptor, and changes references to [:this:] to * actually use the receiver of the method, which is generated as an extra * argument added to each member. */ @JsName(name: 'Array') class JSArray implements List, JSIndexable { const JSArray(); /** * Returns a fresh JavaScript Array, marked as fixed-length. * * [length] must be a non-negative integer. */ factory JSArray.fixed(int length) { // Explicit type test is necessary to guard against JavaScript conversions // in unchecked mode. if ((length is !int) || (length < 0)) { throw new ArgumentError("Length must be a non-negative integer: $length"); } return new JSArray.markFixed(JS('', 'new Array(#)', length)); } /** * Returns a fresh growable JavaScript Array of zero length length. */ factory JSArray.emptyGrowable() => new JSArray.markGrowable(JS('', '[]')); /** * Returns a fresh growable JavaScript Array with initial length. * * [validatedLength] must be a non-negative integer. */ factory JSArray.growable(int length) { // Explicit type test is necessary to guard against JavaScript conversions // in unchecked mode. if ((length is !int) || (length < 0)) { throw new ArgumentError("Length must be a non-negative integer: $length"); } return new JSArray.markGrowable(JS('', 'new Array(#)', length)); } /** * Constructor for adding type parameters to an existing JavaScript Array. * The compiler specially recognizes this constructor. * * var a = new JSArray.typed(JS('JSExtendableArray', '[]')); * a is List --> true * a is List --> false * * Usually either the [JSArray.markFixed] or [JSArray.markGrowable] * constructors is used instead. * * The input must be a JavaScript Array. The JS form is just a re-assertion * to help type analysis when the input type is sloppy. */ factory JSArray.typed(allocation) => JS('JSArray', '#', allocation); factory JSArray.markFixed(allocation) => JS('JSFixedArray', '#', markFixedList(new JSArray.typed(allocation))); factory JSArray.markGrowable(allocation) => JS('JSExtendableArray', '#', new JSArray.typed(allocation)); static List markFixedList(List list) { // Functions are stored in the hidden class and not as properties in // the object. We never actually look at the value, but only want // to know if the property exists. JS('void', r'#.fixed$length = Array', list); return JS('JSFixedArray', '#', list); } checkMutable(reason) { if (this is !JSMutableArray) { throw new UnsupportedError(reason); } } checkGrowable(reason) { if (this is !JSExtendableArray) { throw new UnsupportedError(reason); } } void add(E value) { checkGrowable('add'); JS('void', r'#.push(#)', this, value); } E removeAt(int index) { if (index is !int) throw new ArgumentError(index); if (index < 0 || index >= length) { throw new RangeError.value(index); } checkGrowable('removeAt'); return JS('var', r'#.splice(#, 1)[0]', this, index); } void insert(int index, E value) { if (index is !int) throw new ArgumentError(index); if (index < 0 || index > length) { throw new RangeError.value(index); } checkGrowable('insert'); JS('void', r'#.splice(#, 0, #)', this, index, value); } void insertAll(int index, Iterable iterable) { checkGrowable('insertAll'); IterableMixinWorkaround.insertAllList(this, index, iterable); } void setAll(int index, Iterable iterable) { checkMutable('setAll'); IterableMixinWorkaround.setAllList(this, index, iterable); } E removeLast() { checkGrowable('removeLast'); if (length == 0) throw new RangeError.value(-1); return JS('var', r'#.pop()', this); } bool remove(Object element) { checkGrowable('remove'); for (int i = 0; i < this.length; i++) { if (this[i] == element) { JS('var', r'#.splice(#, 1)', this, i); return true; } } return false; } void removeWhere(bool test(E element)) { // This could, and should, be optimized. IterableMixinWorkaround.removeWhereList(this, test); } void retainWhere(bool test(E element)) { IterableMixinWorkaround.removeWhereList(this, (E element) => !test(element)); } Iterable where(bool f(E element)) { return new IterableMixinWorkaround().where(this, f); } Iterable expand(Iterable f(E element)) { return IterableMixinWorkaround.expand(this, f); } void addAll(Iterable collection) { for (E e in collection) { this.add(e); } } void clear() { length = 0; } void forEach(void f(E element)) { int length = this.length; for (int i = 0; i < length; i++) { f(JS('', '#[#]', this, i)); if (length != this.length) { throw new ConcurrentModificationError(this); } } } Iterable map(f(E element)) { return IterableMixinWorkaround.mapList(this, f); } String join([String separator = ""]) { var list = new List(this.length); for (int i = 0; i < this.length; i++) { list[i] = "${this[i]}"; } return JS('String', "#.join(#)", list, separator); } Iterable take(int n) { return new IterableMixinWorkaround().takeList(this, n); } Iterable takeWhile(bool test(E value)) { return new IterableMixinWorkaround().takeWhile(this, test); } Iterable skip(int n) { return new IterableMixinWorkaround().skipList(this, n); } Iterable skipWhile(bool test(E value)) { return new IterableMixinWorkaround().skipWhile(this, test); } E reduce(E combine(E value, E element)) { return IterableMixinWorkaround.reduce(this, combine); } fold(initialValue, combine(previousValue, E element)) { return IterableMixinWorkaround.fold(this, initialValue, combine); } E firstWhere(bool test(E value), {E orElse()}) { return IterableMixinWorkaround.firstWhere(this, test, orElse); } E lastWhere(bool test(E value), {E orElse()}) { return IterableMixinWorkaround.lastWhereList(this, test, orElse); } E singleWhere(bool test(E value)) { return IterableMixinWorkaround.singleWhere(this, test); } E elementAt(int index) { return this[index]; } List sublist(int start, [int end]) { checkNull(start); // TODO(ahe): This is not specified but co19 tests it. if (start is !int) throw new ArgumentError(start); if (start < 0 || start > length) { throw new RangeError.range(start, 0, length); } if (end == null) { end = length; } else { if (end is !int) throw new ArgumentError(end); if (end < start || end > length) { throw new RangeError.range(end, start, length); } } if (start == end) return []; return new JSArray.markGrowable( JS('', r'#.slice(#, #)', this, start, end)); } Iterable getRange(int start, int end) { return new IterableMixinWorkaround().getRangeList(this, start, end); } E get first { if (length > 0) return this[0]; throw new StateError("No elements"); } E get last { if (length > 0) return this[length - 1]; throw new StateError("No elements"); } E get single { if (length == 1) return this[0]; if (length == 0) throw new StateError("No elements"); throw new StateError("More than one element"); } void removeRange(int start, int end) { checkGrowable('removeRange'); int receiverLength = this.length; if (start < 0 || start > receiverLength) { throw new RangeError.range(start, 0, receiverLength); } if (end < start || end > receiverLength) { throw new RangeError.range(end, start, receiverLength); } Lists.copy(this, end, this, start, receiverLength - end); this.length = receiverLength - (end - start); } void setRange(int start, int end, Iterable iterable, [int skipCount = 0]) { checkMutable('set range'); IterableMixinWorkaround.setRangeList(this, start, end, iterable, skipCount); } void fillRange(int start, int end, [E fillValue]) { checkMutable('fill range'); IterableMixinWorkaround.fillRangeList(this, start, end, fillValue); } void replaceRange(int start, int end, Iterable iterable) { checkGrowable('removeRange'); IterableMixinWorkaround.replaceRangeList(this, start, end, iterable); } bool any(bool f(E element)) => IterableMixinWorkaround.any(this, f); bool every(bool f(E element)) => IterableMixinWorkaround.every(this, f); Iterable get reversed => new IterableMixinWorkaround().reversedList(this); void sort([int compare(E a, E b)]) { checkMutable('sort'); IterableMixinWorkaround.sortList(this, compare); } void shuffle([Random random]) { IterableMixinWorkaround.shuffleList(this, random); } int indexOf(Object element, [int start = 0]) { return IterableMixinWorkaround.indexOfList(this, element, start); } int lastIndexOf(Object element, [int start]) { return IterableMixinWorkaround.lastIndexOfList(this, element, start); } bool contains(Object other) { for (int i = 0; i < length; i++) { if (this[i] == other) return true; } return false; } bool get isEmpty => length == 0; bool get isNotEmpty => !isEmpty; String toString() => ListBase.listToString(this); List toList({ bool growable: true }) { if (growable) { return new JSArray.markGrowable(JS('', '#.slice()', this)); } else { return new JSArray.markFixed(JS('', '#.slice()', this)); } } Set toSet() => new Set.from(this); Iterator get iterator => new ListIterator(this); int get hashCode => Primitives.objectHashCode(this); int get length => JS('JSUInt32', r'#.length', this); void set length(int newLength) { if (newLength is !int) throw new ArgumentError(newLength); if (newLength < 0) throw new RangeError.value(newLength); checkGrowable('set length'); JS('void', r'#.length = #', this, newLength); } E operator [](int index) { if (index is !int) throw new ArgumentError(index); if (index >= length || index < 0) throw new RangeError.value(index); return JS('var', '#[#]', this, index); } void operator []=(int index, E value) { checkMutable('indexed set'); if (index is !int) throw new ArgumentError(index); if (index >= length || index < 0) throw new RangeError.value(index); JS('void', r'#[#] = #', this, index, value); } Map asMap() { return new IterableMixinWorkaround().asMapList(this); } } /** * Dummy subclasses that allow the backend to track more precise * information about arrays through their type. The CPA type inference * relies on the fact that these classes do not override [] nor []=. */ class JSMutableArray extends JSArray implements JSMutableIndexable {} class JSFixedArray extends JSMutableArray {} class JSExtendableArray extends JSMutableArray {}