OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library observe.src.path_observer; | 5 library observe.src.path_observer; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:collection'; | 8 import 'dart:collection'; |
9 import 'dart:math' show min; | 9 import 'dart:math' show min; |
10 | 10 |
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
125 throw new ArgumentError( | 125 throw new ArgumentError( |
126 'List must contain only ints, Strings, and Symbols'); | 126 'List must contain only ints, Strings, and Symbols'); |
127 } | 127 } |
128 } | 128 } |
129 return new PropertyPath._(copy); | 129 return new PropertyPath._(copy); |
130 } | 130 } |
131 | 131 |
132 var pathObj = _pathCache[path]; | 132 var pathObj = _pathCache[path]; |
133 if (pathObj != null) return pathObj; | 133 if (pathObj != null) return pathObj; |
134 | 134 |
135 | |
136 final segments = new _PathParser().parse(path); | 135 final segments = new _PathParser().parse(path); |
137 if (segments == null) return _InvalidPropertyPath._instance; | 136 if (segments == null) return _InvalidPropertyPath._instance; |
138 | 137 |
139 // TODO(jmesserly): we could use an UnmodifiableListView here, but that adds | 138 // TODO(jmesserly): we could use an UnmodifiableListView here, but that adds |
140 // memory overhead. | 139 // memory overhead. |
141 pathObj = new PropertyPath._(segments.toList(growable: false)); | 140 pathObj = new PropertyPath._(segments.toList(growable: false)); |
142 if (_pathCache.length >= _pathCacheLimit) { | 141 if (_pathCache.length >= _pathCacheLimit) { |
143 _pathCache.remove(_pathCache.keys.first); | 142 _pathCache.remove(_pathCache.keys.first); |
144 } | 143 } |
145 _pathCache[path] = pathObj; | 144 _pathCache[path] = pathObj; |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
194 /// [1]: http://en.wikipedia.org/wiki/Jenkins_hash_function | 193 /// [1]: http://en.wikipedia.org/wiki/Jenkins_hash_function |
195 // TODO(jmesserly): should reuse this instead, see | 194 // TODO(jmesserly): should reuse this instead, see |
196 // https://code.google.com/p/dart/issues/detail?id=11617 | 195 // https://code.google.com/p/dart/issues/detail?id=11617 |
197 int get hashCode { | 196 int get hashCode { |
198 int hash = 0; | 197 int hash = 0; |
199 for (int i = 0, len = _segments.length; i < len; i++) { | 198 for (int i = 0, len = _segments.length; i < len; i++) { |
200 hash = 0x1fffffff & (hash + _segments[i].hashCode); | 199 hash = 0x1fffffff & (hash + _segments[i].hashCode); |
201 hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); | 200 hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); |
202 hash = hash ^ (hash >> 6); | 201 hash = hash ^ (hash >> 6); |
203 } | 202 } |
204 hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); | 203 hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); |
205 hash = hash ^ (hash >> 11); | 204 hash = hash ^ (hash >> 11); |
206 return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); | 205 return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); |
207 } | 206 } |
208 | 207 |
209 /// Returns the current value of the path from the provided [obj]ect. | 208 /// Returns the current value of the path from the provided [obj]ect. |
210 getValueFrom(Object obj) { | 209 getValueFrom(Object obj) { |
211 if (!isValid) return null; | 210 if (!isValid) return null; |
212 for (var segment in _segments) { | 211 for (var segment in _segments) { |
213 if (obj == null) return null; | 212 if (obj == null) return null; |
214 obj = _getObjectProperty(obj, segment); | 213 obj = _getObjectProperty(obj, segment); |
(...skipping 23 matching lines...) Expand all Loading... |
238 observe(obj, _segments[i]); | 237 observe(obj, _segments[i]); |
239 | 238 |
240 if (i >= last) break; | 239 if (i >= last) break; |
241 obj = _getObjectProperty(obj, _segments[i++]); | 240 obj = _getObjectProperty(obj, _segments[i++]); |
242 } | 241 } |
243 } | 242 } |
244 | 243 |
245 // Dart note: it doesn't make sense to have compiledGetValueFromFn in Dart. | 244 // Dart note: it doesn't make sense to have compiledGetValueFromFn in Dart. |
246 } | 245 } |
247 | 246 |
248 | |
249 /// Visible only for testing: | 247 /// Visible only for testing: |
250 getSegmentsOfPropertyPathForTesting(p) => p._segments; | 248 getSegmentsOfPropertyPathForTesting(p) => p._segments; |
251 | 249 |
252 class _InvalidPropertyPath extends PropertyPath { | 250 class _InvalidPropertyPath extends PropertyPath { |
253 static final _instance = new _InvalidPropertyPath(); | 251 static final _instance = new _InvalidPropertyPath(); |
254 | 252 |
255 bool get isValid => false; | 253 bool get isValid => false; |
256 _InvalidPropertyPath() : super._([]); | 254 _InvalidPropertyPath() : super._([]); |
257 } | 255 } |
258 | 256 |
(...skipping 12 matching lines...) Expand all Loading... |
271 } | 269 } |
272 } else if (property is String) { | 270 } else if (property is String) { |
273 return object[property]; | 271 return object[property]; |
274 } else if (property is Symbol) { | 272 } else if (property is Symbol) { |
275 // Support indexer if available, e.g. Maps or polymer_expressions Scope. | 273 // Support indexer if available, e.g. Maps or polymer_expressions Scope. |
276 // This is the default syntax used by polymer/nodebind and | 274 // This is the default syntax used by polymer/nodebind and |
277 // polymer/observe-js PathObserver. | 275 // polymer/observe-js PathObserver. |
278 // TODO(sigmund): should we also support using checking dynamically for | 276 // TODO(sigmund): should we also support using checking dynamically for |
279 // whether the type practically implements the indexer API | 277 // whether the type practically implements the indexer API |
280 // (smoke.hasInstanceMethod(type, const Symbol('[]')))? | 278 // (smoke.hasInstanceMethod(type, const Symbol('[]')))? |
281 if (object is Indexable || object is Map && !_MAP_PROPERTIES.contains(proper
ty)) { | 279 if (object is Indexable || |
| 280 object is Map && !_MAP_PROPERTIES.contains(property)) { |
282 return object[smoke.symbolToName(property)]; | 281 return object[smoke.symbolToName(property)]; |
283 } | 282 } |
284 try { | 283 try { |
285 return smoke.read(object, property); | 284 return smoke.read(object, property); |
286 } on NoSuchMethodError catch (_) { | 285 } on NoSuchMethodError catch (_) { |
287 // Rethrow, unless the type implements noSuchMethod, in which case we | 286 // Rethrow, unless the type implements noSuchMethod, in which case we |
288 // interpret the exception as a signal that the method was not found. | 287 // interpret the exception as a signal that the method was not found. |
289 // Dart note: getting invalid properties is an error, unlike in JS where | 288 // Dart note: getting invalid properties is an error, unlike in JS where |
290 // it returns undefined. | 289 // it returns undefined. |
291 if (!smoke.hasNoSuchMethod(object.runtimeType)) rethrow; | 290 if (!smoke.hasNoSuchMethod(object.runtimeType)) rethrow; |
292 } | 291 } |
293 } | 292 } |
294 | 293 |
295 if (_logger.isLoggable(Level.FINER)) { | 294 if (_logger.isLoggable(Level.FINER)) { |
296 _logger.finer("can't get $property in $object"); | 295 _logger.finer("can't get $property in $object"); |
297 } | 296 } |
298 return null; | 297 return null; |
299 } | 298 } |
300 | 299 |
301 bool _setObjectProperty(object, property, value) { | 300 bool _setObjectProperty(object, property, value) { |
302 if (object == null) return false; | 301 if (object == null) return false; |
303 | 302 |
304 if (property is int) { | 303 if (property is int) { |
305 if (object is List && property >= 0 && property < object.length) { | 304 if (object is List && property >= 0 && property < object.length) { |
306 object[property] = value; | 305 object[property] = value; |
307 return true; | 306 return true; |
308 } | 307 } |
309 } else if (property is Symbol) { | 308 } else if (property is Symbol) { |
310 // Support indexer if available, e.g. Maps or polymer_expressions Scope. | 309 // Support indexer if available, e.g. Maps or polymer_expressions Scope. |
311 if (object is Indexable || object is Map && !_MAP_PROPERTIES.contains(proper
ty)) { | 310 if (object is Indexable || |
| 311 object is Map && !_MAP_PROPERTIES.contains(property)) { |
312 object[smoke.symbolToName(property)] = value; | 312 object[smoke.symbolToName(property)] = value; |
313 return true; | 313 return true; |
314 } | 314 } |
315 try { | 315 try { |
316 smoke.write(object, property, value); | 316 smoke.write(object, property, value); |
317 return true; | 317 return true; |
318 } on NoSuchMethodError catch (_) { | 318 } on NoSuchMethodError catch (_) { |
319 if (!smoke.hasNoSuchMethod(object.runtimeType)) rethrow; | 319 if (!smoke.hasNoSuchMethod(object.runtimeType)) rethrow; |
320 } | 320 } |
321 } | 321 } |
(...skipping 10 matching lines...) Expand all Loading... |
332 const identStart = '[\$_a-zA-Z]'; | 332 const identStart = '[\$_a-zA-Z]'; |
333 const identPart = '[\$_a-zA-Z0-9]'; | 333 const identPart = '[\$_a-zA-Z0-9]'; |
334 return new RegExp('^$identStart+$identPart*\$'); | 334 return new RegExp('^$identStart+$identPart*\$'); |
335 }(); | 335 }(); |
336 | 336 |
337 _isIdent(s) => _identRegExp.hasMatch(s); | 337 _isIdent(s) => _identRegExp.hasMatch(s); |
338 | 338 |
339 // Dart note: refactored to convert to codepoints once and operate on codepoints | 339 // Dart note: refactored to convert to codepoints once and operate on codepoints |
340 // rather than characters. | 340 // rather than characters. |
341 class _PathParser { | 341 class _PathParser { |
342 List keys = []; | 342 List<Object> keys = []; |
343 int index = -1; | 343 int index = -1; |
344 String key; | 344 String key; |
345 | 345 |
346 final Map<String, List<String>> _pathStateMachine = { | 346 final Map<String, Map<String, List<String>>> _pathStateMachine = { |
347 'beforePath': { | 347 'beforePath': { |
348 'ws': ['beforePath'], | 348 'ws': ['beforePath'], |
349 'ident': ['inIdent', 'append'], | 349 'ident': ['inIdent', 'append'], |
350 '[': ['beforeElement'], | 350 '[': ['beforeElement'], |
351 'eof': ['afterPath'] | 351 'eof': ['afterPath'] |
352 }, | 352 }, |
353 | |
354 'inPath': { | 353 'inPath': { |
355 'ws': ['inPath'], | 354 'ws': ['inPath'], |
356 '.': ['beforeIdent'], | 355 '.': ['beforeIdent'], |
357 '[': ['beforeElement'], | 356 '[': ['beforeElement'], |
358 'eof': ['afterPath'] | 357 'eof': ['afterPath'] |
359 }, | 358 }, |
360 | |
361 'beforeIdent': { | 359 'beforeIdent': { |
362 'ws': ['beforeIdent'], | 360 'ws': ['beforeIdent'], |
363 'ident': ['inIdent', 'append'] | 361 'ident': ['inIdent', 'append'] |
364 }, | 362 }, |
365 | |
366 'inIdent': { | 363 'inIdent': { |
367 'ident': ['inIdent', 'append'], | 364 'ident': ['inIdent', 'append'], |
368 '0': ['inIdent', 'append'], | 365 '0': ['inIdent', 'append'], |
369 'number': ['inIdent', 'append'], | 366 'number': ['inIdent', 'append'], |
370 'ws': ['inPath', 'push'], | 367 'ws': ['inPath', 'push'], |
371 '.': ['beforeIdent', 'push'], | 368 '.': ['beforeIdent', 'push'], |
372 '[': ['beforeElement', 'push'], | 369 '[': ['beforeElement', 'push'], |
373 'eof': ['afterPath', 'push'] | 370 'eof': ['afterPath', 'push'] |
374 }, | 371 }, |
375 | |
376 'beforeElement': { | 372 'beforeElement': { |
377 'ws': ['beforeElement'], | 373 'ws': ['beforeElement'], |
378 '0': ['afterZero', 'append'], | 374 '0': ['afterZero', 'append'], |
379 'number': ['inIndex', 'append'], | 375 'number': ['inIndex', 'append'], |
380 "'": ['inSingleQuote', 'append', ''], | 376 "'": ['inSingleQuote', 'append', ''], |
381 '"': ['inDoubleQuote', 'append', ''] | 377 '"': ['inDoubleQuote', 'append', ''] |
382 }, | 378 }, |
383 | |
384 'afterZero': { | 379 'afterZero': { |
385 'ws': ['afterElement', 'push'], | 380 'ws': ['afterElement', 'push'], |
386 ']': ['inPath', 'push'] | 381 ']': ['inPath', 'push'] |
387 }, | 382 }, |
388 | |
389 'inIndex': { | 383 'inIndex': { |
390 '0': ['inIndex', 'append'], | 384 '0': ['inIndex', 'append'], |
391 'number': ['inIndex', 'append'], | 385 'number': ['inIndex', 'append'], |
392 'ws': ['afterElement'], | 386 'ws': ['afterElement'], |
393 ']': ['inPath', 'push'] | 387 ']': ['inPath', 'push'] |
394 }, | 388 }, |
395 | |
396 'inSingleQuote': { | 389 'inSingleQuote': { |
397 "'": ['afterElement'], | 390 "'": ['afterElement'], |
398 'eof': ['error'], | 391 'eof': ['error'], |
399 'else': ['inSingleQuote', 'append'] | 392 'else': ['inSingleQuote', 'append'] |
400 }, | 393 }, |
401 | |
402 'inDoubleQuote': { | 394 'inDoubleQuote': { |
403 '"': ['afterElement'], | 395 '"': ['afterElement'], |
404 'eof': ['error'], | 396 'eof': ['error'], |
405 'else': ['inDoubleQuote', 'append'] | 397 'else': ['inDoubleQuote', 'append'] |
406 }, | 398 }, |
407 | |
408 'afterElement': { | 399 'afterElement': { |
409 'ws': ['afterElement'], | 400 'ws': ['afterElement'], |
410 ']': ['inPath', 'push'] | 401 ']': ['inPath', 'push'] |
411 } | 402 } |
412 }; | 403 }; |
413 | 404 |
414 /// From getPathCharType: determines the type of a given [code]point. | 405 /// From getPathCharType: determines the type of a given [code]point. |
415 String _getPathCharType(code) { | 406 String _getPathCharType(code) { |
416 if (code == null) return 'eof'; | 407 if (code == null) return 'eof'; |
417 switch(code) { | 408 switch (code) { |
418 case 0x5B: // [ | 409 case 0x5B: // [ |
419 case 0x5D: // ] | 410 case 0x5D: // ] |
420 case 0x2E: // . | 411 case 0x2E: // . |
421 case 0x22: // " | 412 case 0x22: // " |
422 case 0x27: // ' | 413 case 0x27: // ' |
423 case 0x30: // 0 | 414 case 0x30: // 0 |
424 return _char(code); | 415 return _char(code); |
425 | 416 |
426 case 0x5F: // _ | 417 case 0x5F: // _ |
427 case 0x24: // $ | 418 case 0x24: // $ |
428 return 'ident'; | 419 return 'ident'; |
429 | 420 |
430 case 0x20: // Space | 421 case 0x20: // Space |
431 case 0x09: // Tab | 422 case 0x09: // Tab |
432 case 0x0A: // Newline | 423 case 0x0A: // Newline |
433 case 0x0D: // Return | 424 case 0x0D: // Return |
434 case 0xA0: // No-break space | 425 case 0xA0: // No-break space |
435 case 0xFEFF: // Byte Order Mark | 426 case 0xFEFF: // Byte Order Mark |
436 case 0x2028: // Line Separator | 427 case 0x2028: // Line Separator |
437 case 0x2029: // Paragraph Separator | 428 case 0x2029: // Paragraph Separator |
438 return 'ws'; | 429 return 'ws'; |
439 } | 430 } |
440 | 431 |
441 // a-z, A-Z | 432 // a-z, A-Z |
442 if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A)) | 433 if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A)) |
443 return 'ident'; | 434 return 'ident'; |
444 | 435 |
445 // 1-9 | 436 // 1-9 |
446 if (0x31 <= code && code <= 0x39) | 437 if (0x31 <= code && code <= 0x39) return 'number'; |
447 return 'number'; | |
448 | 438 |
449 return 'else'; | 439 return 'else'; |
450 } | 440 } |
451 | 441 |
452 static String _char(int codepoint) => new String.fromCharCodes([codepoint]); | 442 static String _char(int codepoint) => new String.fromCharCodes([codepoint]); |
453 | 443 |
454 void push() { | 444 void push() { |
455 if (key == null) return; | 445 if (key == null) return; |
456 | 446 |
457 // Dart note: we store the keys with different types, rather than | 447 // Dart note: we store the keys with different types, rather than |
(...skipping 17 matching lines...) Expand all Loading... |
475 if ((mode == 'inSingleQuote' && nextChar == "'") || | 465 if ((mode == 'inSingleQuote' && nextChar == "'") || |
476 (mode == 'inDoubleQuote' && nextChar == '"')) { | 466 (mode == 'inDoubleQuote' && nextChar == '"')) { |
477 index++; | 467 index++; |
478 append(nextChar); | 468 append(nextChar); |
479 return true; | 469 return true; |
480 } | 470 } |
481 return false; | 471 return false; |
482 } | 472 } |
483 | 473 |
484 /// Returns the parsed keys, or null if there was a parse error. | 474 /// Returns the parsed keys, or null if there was a parse error. |
485 List<String> parse(String path) { | 475 List<Object> parse(String path) { |
486 var codePoints = stringToCodepoints(path); | 476 var codePoints = stringToCodepoints(path); |
487 var mode = 'beforePath'; | 477 var mode = 'beforePath'; |
488 | 478 |
489 while (mode != null) { | 479 while (mode != null) { |
490 index++; | 480 index++; |
491 var c = index >= codePoints.length ? null : codePoints[index]; | 481 var c = index >= codePoints.length ? null : codePoints[index]; |
492 | 482 |
493 if (c != null && | 483 if (c != null && |
494 _char(c) == '\\' && _maybeUnescapeQuote(mode, codePoints)) continue; | 484 _char(c) == '\\' && |
| 485 _maybeUnescapeQuote(mode, codePoints)) continue; |
495 | 486 |
496 var type = _getPathCharType(c); | 487 var type = _getPathCharType(c); |
497 if (mode == 'error') return null; | 488 if (mode == 'error') return null; |
498 | 489 |
499 var typeMap = _pathStateMachine[mode]; | 490 var typeMap = _pathStateMachine[mode]; |
500 var transition = typeMap[type]; | 491 var transition = typeMap[type]; |
501 if (transition == null) transition = typeMap['else']; | 492 if (transition == null) transition = typeMap['else']; |
502 if (transition == null) return null; // parse error; | 493 if (transition == null) return null; // parse error; |
503 | 494 |
504 mode = transition[0]; | 495 mode = transition[0]; |
505 var actionName = transition.length > 1 ? transition[1] : null; | 496 var actionName = transition.length > 1 ? transition[1] : null; |
506 if (actionName == 'push' && key != null) push(); | 497 if (actionName == 'push' && key != null) push(); |
507 if (actionName == 'append') { | 498 if (actionName == 'append') { |
508 var newChar = transition.length > 2 && transition[2] != null | 499 var newChar = transition.length > 2 && transition[2] != null |
509 ? transition[2] : _char(c); | 500 ? transition[2] |
| 501 : _char(c); |
510 append(newChar); | 502 append(newChar); |
511 } | 503 } |
512 | 504 |
513 if (mode == 'afterPath') return keys; | 505 if (mode == 'afterPath') return keys; |
514 } | 506 } |
515 return null; // parse error | 507 return null; // parse error |
516 } | 508 } |
517 } | 509 } |
518 | 510 |
519 final Logger _logger = new Logger('observe.PathObserver'); | 511 final Logger _logger = new Logger('observe.PathObserver'); |
520 | 512 |
521 | |
522 /// This is a simple cache. It's like LRU but we don't update an item on a | 513 /// This is a simple cache. It's like LRU but we don't update an item on a |
523 /// cache hit, because that would require allocation. Better to let it expire | 514 /// cache hit, because that would require allocation. Better to let it expire |
524 /// and reallocate the PropertyPath. | 515 /// and reallocate the PropertyPath. |
525 // TODO(jmesserly): this optimization is from observe-js, how valuable is it in | 516 // TODO(jmesserly): this optimization is from observe-js, how valuable is it in |
526 // practice? | 517 // practice? |
527 final _pathCache = new LinkedHashMap<String, PropertyPath>(); | 518 final _pathCache = new LinkedHashMap<String, PropertyPath>(); |
528 | 519 |
529 /// The size of a path like "foo.bar" is approximately 160 bytes, so this | 520 /// The size of a path like "foo.bar" is approximately 160 bytes, so this |
530 /// reserves ~16Kb of memory for recently used paths. Since paths are frequently | 521 /// reserves ~16Kb of memory for recently used paths. Since paths are frequently |
531 /// reused, the theory is that this ends up being a good tradeoff in practice. | 522 /// reused, the theory is that this ends up being a good tradeoff in practice. |
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
639 bool _check({bool skipChanges: false}) { | 630 bool _check({bool skipChanges: false}) { |
640 bool changed = false; | 631 bool changed = false; |
641 _value.length = _observed.length ~/ 2; | 632 _value.length = _observed.length ~/ 2; |
642 var oldValues = null; | 633 var oldValues = null; |
643 for (var i = 0; i < _observed.length; i += 2) { | 634 for (var i = 0; i < _observed.length; i += 2) { |
644 var object = _observed[i]; | 635 var object = _observed[i]; |
645 var path = _observed[i + 1]; | 636 var path = _observed[i + 1]; |
646 var value; | 637 var value; |
647 if (identical(object, _observerSentinel)) { | 638 if (identical(object, _observerSentinel)) { |
648 var observable = path as Bindable; | 639 var observable = path as Bindable; |
649 value = _state == _Observer._UNOPENED ? | 640 value = _state == _Observer._UNOPENED |
650 observable.open((_) => this.deliver()) : | 641 ? observable.open((_) => this.deliver()) |
651 observable.value; | 642 : observable.value; |
652 } else { | 643 } else { |
653 value = (path as PropertyPath).getValueFrom(object); | 644 value = (path as PropertyPath).getValueFrom(object); |
654 } | 645 } |
655 | 646 |
656 if (skipChanges) { | 647 if (skipChanges) { |
657 _value[i ~/ 2] = value; | 648 _value[i ~/ 2] = value; |
658 continue; | 649 continue; |
659 } | 650 } |
660 | 651 |
661 if (value == _value[i ~/ 2]) continue; | 652 if (value == _value[i ~/ 2]) continue; |
(...skipping 18 matching lines...) Expand all Loading... |
680 } | 671 } |
681 | 672 |
682 /// An object accepted by [PropertyPath] where properties are read and written | 673 /// An object accepted by [PropertyPath] where properties are read and written |
683 /// as indexing operations, just like a [Map]. | 674 /// as indexing operations, just like a [Map]. |
684 abstract class Indexable<K, V> { | 675 abstract class Indexable<K, V> { |
685 V operator [](K key); | 676 V operator [](K key); |
686 operator []=(K key, V value); | 677 operator []=(K key, V value); |
687 } | 678 } |
688 | 679 |
689 const _observerSentinel = const _ObserverSentinel(); | 680 const _observerSentinel = const _ObserverSentinel(); |
690 class _ObserverSentinel { const _ObserverSentinel(); } | 681 |
| 682 class _ObserverSentinel { |
| 683 const _ObserverSentinel(); |
| 684 } |
691 | 685 |
692 // Visible for testing | 686 // Visible for testing |
693 get observerSentinelForTesting => _observerSentinel; | 687 get observerSentinelForTesting => _observerSentinel; |
694 | 688 |
695 // A base class for the shared API implemented by PathObserver and | 689 // A base class for the shared API implemented by PathObserver and |
696 // CompoundObserver and used in _ObservedSet. | 690 // CompoundObserver and used in _ObservedSet. |
697 abstract class _Observer extends Bindable { | 691 abstract class _Observer extends Bindable { |
698 Function _notifyCallback; | 692 Function _notifyCallback; |
699 int _notifyArgumentCount; | 693 int _notifyArgumentCount; |
700 var _value; | 694 var _value; |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
757 var cycles = 0; | 751 var cycles = 0; |
758 while (cycles < _MAX_DIRTY_CHECK_CYCLES && _check()) { | 752 while (cycles < _MAX_DIRTY_CHECK_CYCLES && _check()) { |
759 cycles++; | 753 cycles++; |
760 } | 754 } |
761 return cycles > 0; | 755 return cycles > 0; |
762 } | 756 } |
763 | 757 |
764 void _report(newValue, oldValue, [extraArg]) { | 758 void _report(newValue, oldValue, [extraArg]) { |
765 try { | 759 try { |
766 switch (_notifyArgumentCount) { | 760 switch (_notifyArgumentCount) { |
767 case 0: _notifyCallback(); break; | 761 case 0: |
768 case 1: _notifyCallback(newValue); break; | 762 _notifyCallback(); |
769 case 2: _notifyCallback(newValue, oldValue); break; | 763 break; |
770 case 3: _notifyCallback(newValue, oldValue, extraArg); break; | 764 case 1: |
| 765 _notifyCallback(newValue); |
| 766 break; |
| 767 case 2: |
| 768 _notifyCallback(newValue, oldValue); |
| 769 break; |
| 770 case 3: |
| 771 _notifyCallback(newValue, oldValue, extraArg); |
| 772 break; |
771 } | 773 } |
772 } catch (e, s) { | 774 } catch (e, s) { |
773 // Deliver errors async, so if a single callback fails it doesn't prevent | 775 // Deliver errors async, so if a single callback fails it doesn't prevent |
774 // other things from working. | 776 // other things from working. |
775 new Completer().completeError(e, s); | 777 new Completer().completeError(e, s); |
776 } | 778 } |
777 } | 779 } |
778 } | 780 } |
779 | 781 |
780 /// The observedSet abstraction is a perf optimization which reduces the total | 782 /// The observedSet abstraction is a perf optimization which reduces the total |
(...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
896 } | 898 } |
897 } else { | 899 } else { |
898 // TODO(sigmund): consider adding object to MapChangeRecord, and make | 900 // TODO(sigmund): consider adding object to MapChangeRecord, and make |
899 // this more precise. | 901 // this more precise. |
900 return false; | 902 return false; |
901 } | 903 } |
902 } | 904 } |
903 return true; | 905 return true; |
904 } | 906 } |
905 | 907 |
906 void _callback(records) { | 908 void _callback(List<ChangeRecord> records) { |
907 if (_canIgnoreRecords(records)) return; | 909 if (_canIgnoreRecords(records)) return; |
908 for (var observer in _observers.toList(growable: false)) { | 910 for (var observer in _observers.toList(growable: false)) { |
909 if (observer._isOpen) observer._iterateObjects(observe); | 911 if (observer._isOpen) observer._iterateObjects(observe); |
910 } | 912 } |
911 | 913 |
912 for (var observer in _observers.toList(growable: false)) { | 914 for (var observer in _observers.toList(growable: false)) { |
913 if (observer._isOpen) observer._check(); | 915 if (observer._isOpen) observer._check(); |
914 } | 916 } |
915 } | 917 } |
916 } | 918 } |
917 | 919 |
918 const int _MAX_DIRTY_CHECK_CYCLES = 1000; | 920 const int _MAX_DIRTY_CHECK_CYCLES = 1000; |
OLD | NEW |