OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 part of js.wrapping; | |
6 | |
7 /// Adapter to handle a js array as a dart [List]. | |
8 /// You can provide a translator to automatically wrap contained Proxy to some | |
9 /// TypedProxy or something else. | |
10 class JsArrayToListAdapter<E> extends TypedProxy /*with ListMixin<E>*/ implement
s List<E> { | |
11 | |
12 /// Like [JsArrayToListAdapter.fromProxy] but with `null` handling for | |
13 /// [proxy]. | |
14 static JsArrayToListAdapter cast(Proxy proxy, [Translator translator]) => | |
15 proxy == null ? null : | |
16 new JsArrayToListAdapter.fromProxy(proxy, translator); | |
17 | |
18 /// Same as [cast] but for array containing [Serializable] elements. | |
19 static JsArrayToListAdapter castListOfSerializables(Proxy proxy, | |
20 Mapper<dynamic, Serializable> fromJs, {mapOnlyNotNull: false}) => | |
21 proxy == null ? null : new JsArrayToListAdapter.fromProxy(proxy, | |
22 new TranslatorForSerializable(fromJs, | |
23 mapOnlyNotNull: mapOnlyNotNull)); | |
24 | |
25 final Translator<E> _translator; | |
26 | |
27 /// Create a new adapter from a proxy of a Js list. | |
28 JsArrayToListAdapter.fromProxy(Proxy proxy, [Translator<E> translator]) | |
29 : this._translator = translator, | |
30 super.fromProxy(proxy); | |
31 | |
32 // Iterable | |
33 @override Iterator<E> get iterator => new _JsIterator<E>(this); | |
34 @override int get length => $unsafe.length; | |
35 | |
36 // Collection | |
37 @override void add(E value) { $unsafe.push(_toJs(value)); } | |
38 @override void clear() { this.length = 0; } | |
39 @override bool remove(Object element) => removeAt(indexOf(element)) != null; | |
40 | |
41 // List | |
42 @override E operator [](int index) { | |
43 if (index < 0 || index >= this.length) throw new RangeError.value(index); | |
44 return _fromJs($unsafe[index]); | |
45 } | |
46 @override void operator []=(int index, E value) { | |
47 if (index < 0 || index >= this.length) throw new RangeError.value(index); | |
48 $unsafe[index] = _toJs(value); | |
49 } | |
50 @override void set length(int length) { $unsafe.length = length; } | |
51 @override void sort([int compare(E a, E b)]) { | |
52 final sortedList = _asList()..sort(compare); | |
53 setRange(0, sortedList.length, sortedList); | |
54 } | |
55 @override void shuffle([Random random]) { | |
56 final shuffledList = _asList()..shuffle(random); | |
57 setRange(0, shuffledList.length, shuffledLength); | |
58 } | |
59 @override void insert(int index, E element) { | |
60 $unsafe.splice(index, 0, _toJs(element)); | |
61 } | |
62 @override E removeAt(int index) { | |
63 if (index < 0 || index >= this.length) throw new RangeError.value(index); | |
64 return _fromJs($unsafe.splice(index, 1)[0]); | |
65 } | |
66 @override E removeLast() => _fromJs($unsafe.pop()); | |
67 @override List<E> sublist(int start, [int end]) => | |
68 _asList().sublist(start, end); | |
69 @deprecated @override List<E> getRange(int start, int length) => | |
70 _asList().getRange(start, length); | |
71 @override void setRange(int start, int length, List<E> from, | |
72 [int startFrom = 0]) { | |
73 final args = [start, length]; | |
74 for(int i = startFrom; i < startFrom + length; i++) { | |
75 args.add(_toJs(from[i])); | |
76 } | |
77 $unsafe["splice"].apply($unsafe, array(args)); | |
78 } | |
79 @override void removeRange(int start, int end) { | |
80 $unsafe.splice(start, end - start); | |
81 } | |
82 | |
83 // private methods | |
84 dynamic _toJs(E e) => _translator == null ? e : _translator.toJs(e); | |
85 E _fromJs(dynamic value) => _translator == null ? value : | |
86 _translator.fromJs(value); | |
87 | |
88 List<E> _asList() { | |
89 final list = new List<E>(); | |
90 for (int i = 0; i < length; i++) { | |
91 list.add(this[i]); | |
92 } | |
93 return list; | |
94 } | |
95 | |
96 // ListMixin duplication until http://dartbug.com/9339 is fixed | |
97 | |
98 | |
99 // Iterable interface. | |
100 // Iterator<E> get iterator => new ListIterator<E>(this); | |
101 | |
102 E elementAt(int index) => this[index]; | |
103 | |
104 void forEach(void action(E element)) { | |
105 int length = this.length; | |
106 for (int i = 0; i < length; i++) { | |
107 action(this[i]); | |
108 if (length != this.length) { | |
109 throw new ConcurrentModificationError(this); | |
110 } | |
111 } | |
112 } | |
113 | |
114 bool get isEmpty => length == 0; | |
115 | |
116 bool get isNotEmpty => !isEmpty; | |
117 | |
118 E get first { | |
119 if (length == 0) throw new StateError("No elements"); | |
120 return this[0]; | |
121 } | |
122 | |
123 E get last { | |
124 if (length == 0) throw new StateError("No elements"); | |
125 return this[length - 1]; | |
126 } | |
127 | |
128 E get single { | |
129 if (length == 0) throw new StateError("No elements"); | |
130 if (length > 1) throw new StateError("Too many elements"); | |
131 return this[0]; | |
132 } | |
133 | |
134 bool contains(E element) { | |
135 int length = this.length; | |
136 for (int i = 0; i < length; i++) { | |
137 if (this[i] == element) return true; | |
138 if (length != this.length) { | |
139 throw new ConcurrentModificationError(this); | |
140 } | |
141 } | |
142 return false; | |
143 } | |
144 | |
145 bool every(bool test(E element)) { | |
146 int length = this.length; | |
147 for (int i = 0; i < length; i++) { | |
148 if (!test(this[i])) return false; | |
149 if (length != this.length) { | |
150 throw new ConcurrentModificationError(this); | |
151 } | |
152 } | |
153 return true; | |
154 } | |
155 | |
156 bool any(bool test(E element)) { | |
157 int length = this.length; | |
158 for (int i = 0; i < length; i++) { | |
159 if (test(this[i])) return true; | |
160 if (length != this.length) { | |
161 throw new ConcurrentModificationError(this); | |
162 } | |
163 } | |
164 return false; | |
165 } | |
166 | |
167 E firstWhere(bool test(E element), { E orElse() }) { | |
168 int length = this.length; | |
169 for (int i = 0; i < length; i++) { | |
170 E element = this[i]; | |
171 if (test(element)) return element; | |
172 if (length != this.length) { | |
173 throw new ConcurrentModificationError(this); | |
174 } | |
175 } | |
176 if (orElse != null) return orElse(); | |
177 throw new StateError("No matching element"); | |
178 } | |
179 | |
180 E lastWhere(bool test(E element), { E orElse() }) { | |
181 int length = this.length; | |
182 for (int i = length - 1; i >= 0; i--) { | |
183 E element = this[i]; | |
184 if (test(element)) return element; | |
185 if (length != this.length) { | |
186 throw new ConcurrentModificationError(this); | |
187 } | |
188 } | |
189 if (orElse != null) return orElse(); | |
190 throw new StateError("No matching element"); | |
191 } | |
192 | |
193 E singleWhere(bool test(E element)) { | |
194 int length = this.length; | |
195 E match = null; | |
196 bool matchFound = false; | |
197 for (int i = 0; i < length; i++) { | |
198 E element = this[i]; | |
199 if (test(element)) { | |
200 if (matchFound) { | |
201 throw new StateError("More than one matching element"); | |
202 } | |
203 matchFound = true; | |
204 match = element; | |
205 } | |
206 if (length != this.length) { | |
207 throw new ConcurrentModificationError(this); | |
208 } | |
209 } | |
210 if (matchFound) return match; | |
211 throw new StateError("No matching element"); | |
212 } | |
213 | |
214 String join([String separator = ""]) { | |
215 int length = this.length; | |
216 if (!separator.isEmpty) { | |
217 if (length == 0) return ""; | |
218 String first = "${this[0]}"; | |
219 if (length != this.length) { | |
220 throw new ConcurrentModificationError(this); | |
221 } | |
222 StringBuffer buffer = new StringBuffer(first); | |
223 for (int i = 1; i < length; i++) { | |
224 buffer.write(separator); | |
225 buffer.write(this[i]); | |
226 if (length != this.length) { | |
227 throw new ConcurrentModificationError(this); | |
228 } | |
229 } | |
230 return buffer.toString(); | |
231 } else { | |
232 StringBuffer buffer = new StringBuffer(); | |
233 for (int i = 0; i < length; i++) { | |
234 buffer.write(this[i]); | |
235 if (length != this.length) { | |
236 throw new ConcurrentModificationError(this); | |
237 } | |
238 } | |
239 return buffer.toString(); | |
240 } | |
241 } | |
242 | |
243 Iterable<E> where(bool test(E element)) => _asList().where(test); | |
244 | |
245 Iterable map(f(E element)) => _asList().map(f); | |
246 | |
247 Iterable expand(Iterable f(E element)) => _asList().expand(f); | |
248 | |
249 E reduce(E combine(E previousValue, E element)) { | |
250 if (length == 0) throw new StateError("No elements"); | |
251 E value = this[0]; | |
252 for (int i = 1; i < length; i++) { | |
253 value = combine(value, this[i]); | |
254 } | |
255 return value; | |
256 } | |
257 | |
258 fold(var initialValue, combine(var previousValue, E element)) { | |
259 var value = initialValue; | |
260 int length = this.length; | |
261 for (int i = 0; i < length; i++) { | |
262 value = combine(value, this[i]); | |
263 if (length != this.length) { | |
264 throw new ConcurrentModificationError(this); | |
265 } | |
266 } | |
267 return value; | |
268 } | |
269 | |
270 Iterable<E> skip(int count) => _asList().skip(count); | |
271 | |
272 Iterable<E> skipWhile(bool test(E element)) => _asList().skipWhile(test); | |
273 | |
274 Iterable<E> take(int count) => _asList().take(count); | |
275 | |
276 Iterable<E> takeWhile(bool test(E element)) => _asList().takeWhile(test); | |
277 | |
278 List<E> toList({ bool growable: true }) { | |
279 List<E> result; | |
280 if (growable) { | |
281 result = new List<E>()..length = length; | |
282 } else { | |
283 result = new List<E>(length); | |
284 } | |
285 for (int i = 0; i < length; i++) { | |
286 result[i] = this[i]; | |
287 } | |
288 return result; | |
289 } | |
290 | |
291 Set<E> toSet() { | |
292 Set<E> result = new Set<E>(); | |
293 for (int i = 0; i < length; i++) { | |
294 result.add(this[i]); | |
295 } | |
296 return result; | |
297 } | |
298 | |
299 // Collection interface. | |
300 // void add(E element) { | |
301 // this[this.length++] = element; | |
302 // } | |
303 | |
304 void addAll(Iterable<E> iterable) { | |
305 for (E element in iterable) { | |
306 this[this.length++] = element; | |
307 } | |
308 } | |
309 | |
310 // void remove(Object element) { | |
311 // for (int i = 0; i < this.length; i++) { | |
312 // if (this[i] == element) { | |
313 // this.setRange(i, i + this.length - 1, this, i + 1); | |
314 // this.length -= 1; | |
315 // return; | |
316 // } | |
317 // } | |
318 // } | |
319 | |
320 void removeWhere(bool test(E element)) { | |
321 _filter(this, test, false); | |
322 } | |
323 | |
324 void retainWhere(bool test(E element)) { | |
325 _filter(this, test, true); | |
326 } | |
327 | |
328 static void _filter(List source, | |
329 bool test(var element), | |
330 bool retainMatching) { | |
331 List retained = []; | |
332 int length = source.length; | |
333 for (int i = 0; i < length; i++) { | |
334 var element = source[i]; | |
335 if (test(element) == retainMatching) { | |
336 retained.add(element); | |
337 } | |
338 if (length != source.length) { | |
339 throw new ConcurrentModificationError(source); | |
340 } | |
341 } | |
342 if (retained.length != source.length) { | |
343 source.setRange(0, retained.length, retained); | |
344 source.length = retained.length; | |
345 } | |
346 } | |
347 | |
348 // void clear() { this.length = 0; } | |
349 | |
350 // List interface. | |
351 | |
352 // E removeLast() { | |
353 // if (length == 0) { | |
354 // throw new StateError("No elements"); | |
355 // } | |
356 // E result = this[length - 1]; | |
357 // length--; | |
358 // return result; | |
359 // } | |
360 | |
361 // void sort([Comparator<E> compare]) { | |
362 // Sort.sort(this, compare); | |
363 // } | |
364 | |
365 Map<int, E> asMap() => _asList().asMap(); | |
366 | |
367 void _rangeCheck(int start, int end) { | |
368 if (start < 0 || start > this.length) { | |
369 throw new RangeError.range(start, 0, this.length); | |
370 } | |
371 if (end < start || end > this.length) { | |
372 throw new RangeError.range(end, start, this.length); | |
373 } | |
374 } | |
375 | |
376 // List<E> sublist(int start, [int end]) { | |
377 // if (end == null) end = length; | |
378 // _rangeCheck(start, end); | |
379 // int length = end - start; | |
380 // List<E> result = new List<E>()..length = length; | |
381 // for (int i = 0; i < length; i++) { | |
382 // result[i] = this[start + i]; | |
383 // } | |
384 // return result; | |
385 // } | |
386 | |
387 // Iterable<E> getRange(int start, int end) { | |
388 // _rangeCheck(start, end); | |
389 // return new SubListIterable(this, start, end); | |
390 // } | |
391 | |
392 // void removeRange(int start, int end) { | |
393 // _rangeCheck(start, end); | |
394 // int length = end - start; | |
395 // setRange(start, this.length - length, this, end); | |
396 // this.length -= length; | |
397 // } | |
398 | |
399 void fillRange(int start, int end, [E fill]) { | |
400 _rangeCheck(start, end); | |
401 for (int i = start; i < end; i++) { | |
402 this[i] = fill; | |
403 } | |
404 } | |
405 | |
406 // void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0])
{ | |
407 // _rangeCheck(start, end); | |
408 // int length = end - start; | |
409 // if (length == 0) return; | |
410 // | |
411 // if (skipCount < 0) throw new ArgumentError(skipCount); | |
412 // | |
413 // List otherList; | |
414 // int otherStart; | |
415 // // TODO(floitsch): Make this accept more. | |
416 // if (iterable is List) { | |
417 // otherList = iterable; | |
418 // otherStart = skipCount; | |
419 // } else { | |
420 // otherList = iterable.skip(skipCount).toList(growable: false); | |
421 // otherStart = 0; | |
422 // } | |
423 // if (otherStart + length > otherList.length) { | |
424 // throw new StateError("Not enough elements"); | |
425 // } | |
426 // if (otherStart < start) { | |
427 // // Copy backwards to ensure correct copy if [from] is this. | |
428 // for (int i = length - 1; i >= 0; i--) { | |
429 // this[start + i] = otherList[otherStart + i]; | |
430 // } | |
431 // } else { | |
432 // for (int i = 0; i < length; i++) { | |
433 // this[start + i] = otherList[otherStart + i]; | |
434 // } | |
435 // } | |
436 // } | |
437 | |
438 void replaceRange(int start, int end, Iterable<E> newContents) { | |
439 // TODO(floitsch): Optimize this. | |
440 removeRange(start, end); | |
441 insertAll(start, newContents); | |
442 } | |
443 | |
444 int indexOf(E element, [int startIndex = 0]) { | |
445 if (startIndex >= this.length) { | |
446 return -1; | |
447 } | |
448 if (startIndex < 0) { | |
449 startIndex = 0; | |
450 } | |
451 for (int i = startIndex; i < this.length; i++) { | |
452 if (this[i] == element) { | |
453 return i; | |
454 } | |
455 } | |
456 return -1; | |
457 } | |
458 | |
459 /** | |
460 * Returns the last index in the list [a] of the given [element], starting | |
461 * the search at index [startIndex] to 0. | |
462 * Returns -1 if [element] is not found. | |
463 */ | |
464 int lastIndexOf(E element, [int startIndex]) { | |
465 if (startIndex == null) { | |
466 startIndex = this.length - 1; | |
467 } else { | |
468 if (startIndex < 0) { | |
469 return -1; | |
470 } | |
471 if (startIndex >= this.length) { | |
472 startIndex = this.length - 1; | |
473 } | |
474 } | |
475 for (int i = startIndex; i >= 0; i--) { | |
476 if (this[i] == element) { | |
477 return i; | |
478 } | |
479 } | |
480 return -1; | |
481 } | |
482 | |
483 // void insert(int index, E element) { | |
484 // if (index < 0 || index > length) { | |
485 // throw new RangeError.range(index, 0, length); | |
486 // } | |
487 // if (index == this.length) { | |
488 // add(element); | |
489 // return; | |
490 // } | |
491 // // We are modifying the length just below the is-check. Without the check | |
492 // // Array.copy could throw an exception, leaving the list in a bad state | |
493 // // (with a length that has been increased, but without a new element). | |
494 // if (index is! int) throw new ArgumentError(index); | |
495 // this.length++; | |
496 // setRange(index + 1, this.length, this, index); | |
497 // this[index] = element; | |
498 // } | |
499 | |
500 // E removeAt(int index) { | |
501 // E result = this[index]; | |
502 // setRange(index, this.length - 1, this, index + 1); | |
503 // length--; | |
504 // return result; | |
505 // } | |
506 | |
507 void insertAll(int index, Iterable<E> iterable) { | |
508 if (index < 0 || index > length) { | |
509 throw new RangeError.range(index, 0, length); | |
510 } | |
511 // TODO(floitsch): we can probably detect more cases. | |
512 if (iterable is! List && iterable is! Set /*&& iterable is! SubListIterable*
/) { | |
513 iterable = iterable.toList(); | |
514 } | |
515 int insertionLength = iterable.length; | |
516 // There might be errors after the length change, in which case the list | |
517 // will end up being modified but the operation not complete. Unless we | |
518 // always go through a "toList" we can't really avoid that. | |
519 this.length += insertionLength; | |
520 setRange(index + insertionLength, this.length, this, index); | |
521 setAll(index, iterable); | |
522 } | |
523 | |
524 void setAll(int index, Iterable<E> iterable) { | |
525 if (iterable is List) { | |
526 setRange(index, index + iterable.length, iterable); | |
527 } else { | |
528 for (E element in iterable) { | |
529 this[index++] = element; | |
530 } | |
531 } | |
532 } | |
533 | |
534 Iterable<E> get reversed => _asList().reversed; | |
535 | |
536 String toString() => _asList().toString(); | |
537 } | |
538 | |
539 class _JsIterator<E> implements Iterator<E> { | |
540 final JsArrayToListAdapter<E> _jsArray; | |
541 final int length; | |
542 int _currentIndex = -1; | |
543 | |
544 _JsIterator(JsArrayToListAdapter<E> jsArray) : this._jsArray = jsArray, | |
545 length = jsArray.length; | |
546 | |
547 // Iterator | |
548 @override bool moveNext() { | |
549 if (_currentIndex + 1 < length) { | |
550 _currentIndex++; | |
551 return true; | |
552 } | |
553 return false; | |
554 } | |
555 @override E get current => _jsArray[_currentIndex]; | |
556 } | |
OLD | NEW |