| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, 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 dart.collection; | |
| 6 | |
| 7 /** | |
| 8 * This [Iterable] mixin implements all [Iterable] members except `iterator`. | |
| 9 * | |
| 10 * All other methods are implemented in terms of `iterator`. | |
| 11 */ | |
| 12 abstract class IterableMixin<E> implements Iterable<E> { | |
| 13 // This class has methods copied verbatim into: | |
| 14 // - IterableBase | |
| 15 // - SetMixin | |
| 16 // If changing a method here, also change the other copies. | |
| 17 | |
| 18 Iterable/*<T>*/ map/*<T>*/(/*=T*/ f(E element)) => | |
| 19 new MappedIterable<E, dynamic/*=T*/>(this, f); | |
| 20 | |
| 21 Iterable<E> where(bool f(E element)) => new WhereIterable<E>(this, f); | |
| 22 | |
| 23 Iterable/*<T>*/ expand/*<T>*/(Iterable/*<T>*/ f(E element)) => | |
| 24 new ExpandIterable<E, dynamic/*=T*/>(this, f); | |
| 25 | |
| 26 bool contains(Object element) { | |
| 27 for (E e in this) { | |
| 28 if (e == element) return true; | |
| 29 } | |
| 30 return false; | |
| 31 } | |
| 32 | |
| 33 void forEach(void f(E element)) { | |
| 34 for (E element in this) f(element); | |
| 35 } | |
| 36 | |
| 37 E reduce(E combine(E value, E element)) { | |
| 38 Iterator<E> iterator = this.iterator; | |
| 39 if (!iterator.moveNext()) { | |
| 40 throw IterableElementError.noElement(); | |
| 41 } | |
| 42 E value = iterator.current; | |
| 43 while (iterator.moveNext()) { | |
| 44 value = combine(value, iterator.current); | |
| 45 } | |
| 46 return value; | |
| 47 } | |
| 48 | |
| 49 dynamic/*=T*/ fold/*<T>*/(var/*=T*/ initialValue, | |
| 50 dynamic/*=T*/ combine(var/*=T*/ previousValue, E element)) { | |
| 51 var value = initialValue; | |
| 52 for (E element in this) value = combine(value, element); | |
| 53 return value; | |
| 54 } | |
| 55 | |
| 56 bool every(bool f(E element)) { | |
| 57 for (E element in this) { | |
| 58 if (!f(element)) return false; | |
| 59 } | |
| 60 return true; | |
| 61 } | |
| 62 | |
| 63 String join([String separator = ""]) { | |
| 64 Iterator<E> iterator = this.iterator; | |
| 65 if (!iterator.moveNext()) return ""; | |
| 66 StringBuffer buffer = new StringBuffer(); | |
| 67 if (separator == null || separator == "") { | |
| 68 do { | |
| 69 buffer.write("${iterator.current}"); | |
| 70 } while (iterator.moveNext()); | |
| 71 } else { | |
| 72 buffer.write("${iterator.current}"); | |
| 73 while (iterator.moveNext()) { | |
| 74 buffer.write(separator); | |
| 75 buffer.write("${iterator.current}"); | |
| 76 } | |
| 77 } | |
| 78 return buffer.toString(); | |
| 79 } | |
| 80 | |
| 81 bool any(bool f(E element)) { | |
| 82 for (E element in this) { | |
| 83 if (f(element)) return true; | |
| 84 } | |
| 85 return false; | |
| 86 } | |
| 87 | |
| 88 List<E> toList({ bool growable: true }) => | |
| 89 new List<E>.from(this, growable: growable); | |
| 90 | |
| 91 Set<E> toSet() => new Set<E>.from(this); | |
| 92 | |
| 93 int get length { | |
| 94 assert(this is! EfficientLength); | |
| 95 int count = 0; | |
| 96 Iterator it = iterator; | |
| 97 while (it.moveNext()) { | |
| 98 count++; | |
| 99 } | |
| 100 return count; | |
| 101 } | |
| 102 | |
| 103 bool get isEmpty => !iterator.moveNext(); | |
| 104 | |
| 105 bool get isNotEmpty => !isEmpty; | |
| 106 | |
| 107 Iterable<E> take(int count) { | |
| 108 return new TakeIterable<E>(this, count); | |
| 109 } | |
| 110 | |
| 111 Iterable<E> takeWhile(bool test(E value)) { | |
| 112 return new TakeWhileIterable<E>(this, test); | |
| 113 } | |
| 114 | |
| 115 Iterable<E> skip(int count) { | |
| 116 return new SkipIterable<E>(this, count); | |
| 117 } | |
| 118 | |
| 119 Iterable<E> skipWhile(bool test(E value)) { | |
| 120 return new SkipWhileIterable<E>(this, test); | |
| 121 } | |
| 122 | |
| 123 E get first { | |
| 124 Iterator<E> it = iterator; | |
| 125 if (!it.moveNext()) { | |
| 126 throw IterableElementError.noElement(); | |
| 127 } | |
| 128 return it.current; | |
| 129 } | |
| 130 | |
| 131 E get last { | |
| 132 Iterator<E> it = iterator; | |
| 133 if (!it.moveNext()) { | |
| 134 throw IterableElementError.noElement(); | |
| 135 } | |
| 136 E result; | |
| 137 do { | |
| 138 result = it.current; | |
| 139 } while(it.moveNext()); | |
| 140 return result; | |
| 141 } | |
| 142 | |
| 143 E get single { | |
| 144 Iterator<E> it = iterator; | |
| 145 if (!it.moveNext()) throw IterableElementError.noElement(); | |
| 146 E result = it.current; | |
| 147 if (it.moveNext()) throw IterableElementError.tooMany(); | |
| 148 return result; | |
| 149 } | |
| 150 | |
| 151 E firstWhere(bool test(E value), { E orElse() }) { | |
| 152 for (E element in this) { | |
| 153 if (test(element)) return element; | |
| 154 } | |
| 155 if (orElse != null) return orElse(); | |
| 156 throw IterableElementError.noElement(); | |
| 157 } | |
| 158 | |
| 159 E lastWhere(bool test(E value), { E orElse() }) { | |
| 160 E result = null; | |
| 161 bool foundMatching = false; | |
| 162 for (E element in this) { | |
| 163 if (test(element)) { | |
| 164 result = element; | |
| 165 foundMatching = true; | |
| 166 } | |
| 167 } | |
| 168 if (foundMatching) return result; | |
| 169 if (orElse != null) return orElse(); | |
| 170 throw IterableElementError.noElement(); | |
| 171 } | |
| 172 | |
| 173 E singleWhere(bool test(E value)) { | |
| 174 E result = null; | |
| 175 bool foundMatching = false; | |
| 176 for (E element in this) { | |
| 177 if (test(element)) { | |
| 178 if (foundMatching) { | |
| 179 throw IterableElementError.tooMany(); | |
| 180 } | |
| 181 result = element; | |
| 182 foundMatching = true; | |
| 183 } | |
| 184 } | |
| 185 if (foundMatching) return result; | |
| 186 throw IterableElementError.noElement(); | |
| 187 } | |
| 188 | |
| 189 E elementAt(int index) { | |
| 190 if (index is! int) throw new ArgumentError.notNull("index"); | |
| 191 RangeError.checkNotNegative(index, "index"); | |
| 192 int elementIndex = 0; | |
| 193 for (E element in this) { | |
| 194 if (index == elementIndex) return element; | |
| 195 elementIndex++; | |
| 196 } | |
| 197 throw new RangeError.index(index, this, "index", null, elementIndex); | |
| 198 } | |
| 199 | |
| 200 | |
| 201 String toString() => IterableBase.iterableToShortString(this, '(', ')'); | |
| 202 } | |
| 203 | |
| 204 /** | |
| 205 * Base class for implementing [Iterable]. | |
| 206 * | |
| 207 * This class implements all methods of [Iterable] except [Iterable.iterator] | |
| 208 * in terms of `iterator`. | |
| 209 */ | |
| 210 abstract class IterableBase<E> extends Iterable<E> { | |
| 211 const IterableBase(); | |
| 212 | |
| 213 /** | |
| 214 * Convert an `Iterable` to a string like [IterableBase.toString]. | |
| 215 * | |
| 216 * Allows using other delimiters than '(' and ')'. | |
| 217 * | |
| 218 * Handles circular references where converting one of the elements | |
| 219 * to a string ends up converting [iterable] to a string again. | |
| 220 */ | |
| 221 static String iterableToShortString(Iterable iterable, | |
| 222 [String leftDelimiter = '(', | |
| 223 String rightDelimiter = ')']) { | |
| 224 if (_isToStringVisiting(iterable)) { | |
| 225 if (leftDelimiter == "(" && rightDelimiter == ")") { | |
| 226 // Avoid creating a new string in the "common" case. | |
| 227 return "(...)"; | |
| 228 } | |
| 229 return "$leftDelimiter...$rightDelimiter"; | |
| 230 } | |
| 231 List parts = []; | |
| 232 _toStringVisiting.add(iterable); | |
| 233 try { | |
| 234 _iterablePartsToStrings(iterable, parts); | |
| 235 } finally { | |
| 236 assert(identical(_toStringVisiting.last, iterable)); | |
| 237 _toStringVisiting.removeLast(); | |
| 238 } | |
| 239 return (new StringBuffer(leftDelimiter) | |
| 240 ..writeAll(parts, ", ") | |
| 241 ..write(rightDelimiter)).toString(); | |
| 242 } | |
| 243 | |
| 244 /** | |
| 245 * Converts an `Iterable` to a string. | |
| 246 * | |
| 247 * Converts each elements to a string, and separates the results by ", ". | |
| 248 * Then wraps the result in [leftDelimiter] and [rightDelimiter]. | |
| 249 * | |
| 250 * Unlike [iterableToShortString], this conversion doesn't omit any | |
| 251 * elements or puts any limit on the size of the result. | |
| 252 * | |
| 253 * Handles circular references where converting one of the elements | |
| 254 * to a string ends up converting [iterable] to a string again. | |
| 255 */ | |
| 256 static String iterableToFullString(Iterable iterable, | |
| 257 [String leftDelimiter = '(', | |
| 258 String rightDelimiter = ')']) { | |
| 259 if (_isToStringVisiting(iterable)) { | |
| 260 return "$leftDelimiter...$rightDelimiter"; | |
| 261 } | |
| 262 StringBuffer buffer = new StringBuffer(leftDelimiter); | |
| 263 _toStringVisiting.add(iterable); | |
| 264 try { | |
| 265 buffer.writeAll(iterable, ", "); | |
| 266 } finally { | |
| 267 assert(identical(_toStringVisiting.last, iterable)); | |
| 268 _toStringVisiting.removeLast(); | |
| 269 } | |
| 270 buffer.write(rightDelimiter); | |
| 271 return buffer.toString(); | |
| 272 } | |
| 273 } | |
| 274 | |
| 275 /** A set used to identify cyclic lists during toString() calls. */ | |
| 276 final List _toStringVisiting = []; | |
| 277 | |
| 278 /** Check if we are currently visiting `o` in a toString call. */ | |
| 279 bool _isToStringVisiting(Object o) { | |
| 280 for (int i = 0; i < _toStringVisiting.length; i++) { | |
| 281 if (identical(o, _toStringVisiting[i])) return true; | |
| 282 } | |
| 283 return false; | |
| 284 } | |
| 285 | |
| 286 /** | |
| 287 * Convert elments of [iterable] to strings and store them in [parts]. | |
| 288 */ | |
| 289 void _iterablePartsToStrings(Iterable iterable, List parts) { | |
| 290 /* | |
| 291 * This is the complicated part of [iterableToShortString]. | |
| 292 * It is extracted as a separate function to avoid having too much code | |
| 293 * inside the try/finally. | |
| 294 */ | |
| 295 /// Try to stay below this many characters. | |
| 296 const int LENGTH_LIMIT = 80; | |
| 297 /// Always at least this many elements at the start. | |
| 298 const int HEAD_COUNT = 3; | |
| 299 /// Always at least this many elements at the end. | |
| 300 const int TAIL_COUNT = 2; | |
| 301 /// Stop iterating after this many elements. Iterables can be infinite. | |
| 302 const int MAX_COUNT = 100; | |
| 303 // Per entry length overhead. It's for ", " for all after the first entry, | |
| 304 // and for "(" and ")" for the initial entry. By pure luck, that's the same | |
| 305 // number. | |
| 306 const int OVERHEAD = 2; | |
| 307 const int ELLIPSIS_SIZE = 3; // "...".length. | |
| 308 | |
| 309 int length = 0; | |
| 310 int count = 0; | |
| 311 Iterator it = iterable.iterator; | |
| 312 // Initial run of elements, at least HEAD_COUNT, and then continue until | |
| 313 // passing at most LENGTH_LIMIT characters. | |
| 314 while (length < LENGTH_LIMIT || count < HEAD_COUNT) { | |
| 315 if (!it.moveNext()) return; | |
| 316 String next = "${it.current}"; | |
| 317 parts.add(next); | |
| 318 length += next.length + OVERHEAD; | |
| 319 count++; | |
| 320 } | |
| 321 | |
| 322 String penultimateString; | |
| 323 String ultimateString; | |
| 324 | |
| 325 // Find last two elements. One or more of them may already be in the | |
| 326 // parts array. Include their length in `length`. | |
| 327 var penultimate = null; | |
| 328 var ultimate = null; | |
| 329 if (!it.moveNext()) { | |
| 330 if (count <= HEAD_COUNT + TAIL_COUNT) return; | |
| 331 ultimateString = parts.removeLast(); | |
| 332 penultimateString = parts.removeLast(); | |
| 333 } else { | |
| 334 penultimate = it.current; | |
| 335 count++; | |
| 336 if (!it.moveNext()) { | |
| 337 if (count <= HEAD_COUNT + 1) { | |
| 338 parts.add("$penultimate"); | |
| 339 return; | |
| 340 } | |
| 341 ultimateString = "$penultimate"; | |
| 342 penultimateString = parts.removeLast(); | |
| 343 length += ultimateString.length + OVERHEAD; | |
| 344 } else { | |
| 345 ultimate = it.current; | |
| 346 count++; | |
| 347 // Then keep looping, keeping the last two elements in variables. | |
| 348 assert(count < MAX_COUNT); | |
| 349 while (it.moveNext()) { | |
| 350 penultimate = ultimate; | |
| 351 ultimate = it.current; | |
| 352 count++; | |
| 353 if (count > MAX_COUNT) { | |
| 354 // If we haven't found the end before MAX_COUNT, give up. | |
| 355 // This cannot happen in the code above because each entry | |
| 356 // increases length by at least two, so there is no way to | |
| 357 // visit more than ~40 elements before this loop. | |
| 358 | |
| 359 // Remove any surplus elements until length, including ", ...)", | |
| 360 // is at most LENGTH_LIMIT. | |
| 361 while (length > LENGTH_LIMIT - ELLIPSIS_SIZE - OVERHEAD && | |
| 362 count > HEAD_COUNT) { | |
| 363 length -= parts.removeLast().length + OVERHEAD; | |
| 364 count--; | |
| 365 } | |
| 366 parts.add("..."); | |
| 367 return; | |
| 368 } | |
| 369 } | |
| 370 penultimateString = "$penultimate"; | |
| 371 ultimateString = "$ultimate"; | |
| 372 length += | |
| 373 ultimateString.length + penultimateString.length + 2 * OVERHEAD; | |
| 374 } | |
| 375 } | |
| 376 | |
| 377 // If there is a gap between the initial run and the last two, | |
| 378 // prepare to add an ellipsis. | |
| 379 String elision = null; | |
| 380 if (count > parts.length + TAIL_COUNT) { | |
| 381 elision = "..."; | |
| 382 length += ELLIPSIS_SIZE + OVERHEAD; | |
| 383 } | |
| 384 | |
| 385 // If the last two elements were very long, and we have more than | |
| 386 // HEAD_COUNT elements in the initial run, drop some to make room for | |
| 387 // the last two. | |
| 388 while (length > LENGTH_LIMIT && parts.length > HEAD_COUNT) { | |
| 389 length -= parts.removeLast().length + OVERHEAD; | |
| 390 if (elision == null) { | |
| 391 elision = "..."; | |
| 392 length += ELLIPSIS_SIZE + OVERHEAD; | |
| 393 } | |
| 394 } | |
| 395 if (elision != null) { | |
| 396 parts.add(elision); | |
| 397 } | |
| 398 parts.add(penultimateString); | |
| 399 parts.add(ultimateString); | |
| 400 } | |
| OLD | NEW |