| // 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. |
| */ |
| class JSArray<E> extends Interceptor implements List<E>, JSIndexable { |
| |
| const JSArray(); |
| |
| 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<E> iterable) { |
| checkGrowable('insertAll'); |
| IterableMixinWorkaround.insertAllList(this, index, iterable); |
| } |
| |
| void setAll(int index, Iterable<E> 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<E> where(bool f(E element)) { |
| return IterableMixinWorkaround.where(this, f); |
| } |
| |
| Iterable expand(Iterable f(E element)) { |
| return IterableMixinWorkaround.expand(this, f); |
| } |
| |
| void addAll(Iterable<E> collection) { |
| for (E e in collection) { |
| this.add(e); |
| } |
| } |
| |
| void clear() { |
| length = 0; |
| } |
| |
| void forEach(void f(E element)) { |
| return IterableMixinWorkaround.forEach(this, f); |
| } |
| |
| 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<E> take(int n) { |
| return IterableMixinWorkaround.takeList(this, n); |
| } |
| |
| Iterable<E> takeWhile(bool test(E value)) { |
| return IterableMixinWorkaround.takeWhile(this, test); |
| } |
| |
| Iterable<E> skip(int n) { |
| return IterableMixinWorkaround.skipList(this, n); |
| } |
| |
| Iterable<E> skipWhile(bool test(E value)) { |
| return 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); |
| } |
| |
| dynamic firstWhere(bool test(E value), {Object orElse()}) { |
| return IterableMixinWorkaround.firstWhere(this, test, orElse); |
| } |
| |
| dynamic lastWhere(bool test(E value), {Object 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<E> 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); |
| } |
| } |
| // TODO(ngeoffray): Parameterize the return value. |
| if (start == end) return []; |
| return JS('JSExtendableArray', r'#.slice(#, #)', this, start, end); |
| } |
| |
| |
| Iterable<E> getRange(int start, int end) { |
| return 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); |
| } |
| Arrays.copy(this, |
| end, |
| this, |
| start, |
| receiverLength - end); |
| this.length = receiverLength - (end - start); |
| } |
| |
| void setRange(int start, int end, Iterable<E> 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<E> 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<E> get reversed => IterableMixinWorkaround.reversedList(this); |
| |
| void sort([int compare(E a, E b)]) { |
| checkMutable('sort'); |
| IterableMixinWorkaround.sortList(this, compare); |
| } |
| |
| 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() => IterableMixinWorkaround.toStringIterable(this, '[', ']'); |
| |
| List<E> toList({ bool growable: true }) => |
| new List<E>.from(this, growable: growable); |
| |
| Set<E> toSet() => new Set<E>.from(this); |
| |
| Iterator<E> get iterator => new ListIterator<E>(this); |
| |
| int get hashCode => Primitives.objectHashCode(this); |
| |
| int get length => JS('int', 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<int, E> asMap() { |
| return 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 {} |