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 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
74 bool _check({bool skipChanges: false}) { | 74 bool _check({bool skipChanges: false}) { |
75 var oldValue = _value; | 75 var oldValue = _value; |
76 _value = _path.getValueFrom(_object); | 76 _value = _path.getValueFrom(_object); |
77 if (skipChanges || _value == oldValue) return false; | 77 if (skipChanges || _value == oldValue) return false; |
78 | 78 |
79 _report(_value, oldValue); | 79 _report(_value, oldValue); |
80 return true; | 80 return true; |
81 } | 81 } |
82 } | 82 } |
83 | 83 |
84 /// A dot-delimieted property path such as "foo.bar" or "foo.10.bar". | 84 /// A dot-delimieted property path such as "foo.bar" or "foo.10.bar". |
Jennifer Messerly
2014/03/25 22:32:44
this was an issue before but: do you mind adding a
Siggi Cherem (dart-lang)
2014/03/26 00:32:07
Done.
| |
85 /// The path specifies how to get a particular value from an object graph, where | 85 /// The path specifies how to get a particular value from an object graph, where |
86 /// the graph can include arrays. | 86 /// the graph can include arrays and maps. Each segment of the path describes |
87 /// how to take a single step in the object graph. Properties like 'foo' or | |
88 /// 'bar' are read as properties on objects, or as keys if the object is a Map | |
89 /// or an [StringIndexer], while integer values are read as indexes in an array. | |
Jennifer Messerly
2014/03/25 22:32:44
should be "a [StringIndexer]"
also, this was an i
Siggi Cherem (dart-lang)
2014/03/26 00:32:07
Done.
| |
87 // TODO(jmesserly): consider specialized subclasses for: | 90 // TODO(jmesserly): consider specialized subclasses for: |
88 // * empty path | 91 // * empty path |
89 // * "value" | 92 // * "value" |
90 // * single token in path, e.g. "foo" | 93 // * single token in path, e.g. "foo" |
91 class PropertyPath { | 94 class PropertyPath { |
92 /// The segments of the path. | 95 /// The segments of the path. |
93 final List<Object> _segments; | 96 final List<Object> _segments; |
94 | 97 |
95 /// Creates a new [PropertyPath]. These can be stored to avoid excessive | 98 /// Creates a new [PropertyPath]. These can be stored to avoid excessive |
96 /// parsing of path strings. | 99 /// parsing of path strings. |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
173 for (int i = 0, len = _segments.length; i < len; i++) { | 176 for (int i = 0, len = _segments.length; i < len; i++) { |
174 hash = 0x1fffffff & (hash + _segments[i].hashCode); | 177 hash = 0x1fffffff & (hash + _segments[i].hashCode); |
175 hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); | 178 hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); |
176 hash = hash ^ (hash >> 6); | 179 hash = hash ^ (hash >> 6); |
177 } | 180 } |
178 hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); | 181 hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); |
179 hash = hash ^ (hash >> 11); | 182 hash = hash ^ (hash >> 11); |
180 return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); | 183 return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); |
181 } | 184 } |
182 | 185 |
183 /// Returns the current of the path from the provided [obj]ect. | 186 /// Returns the current value of the path from the provided [obj]ect. |
184 getValueFrom(Object obj) { | 187 getValueFrom(Object obj) { |
185 if (!isValid) return null; | 188 if (!isValid) return null; |
186 for (var segment in _segments) { | 189 for (var segment in _segments) { |
187 if (obj == null) return null; | 190 if (obj == null) return null; |
188 obj = _getObjectProperty(obj, segment); | 191 obj = _getObjectProperty(obj, segment); |
189 } | 192 } |
190 return obj; | 193 return obj; |
191 } | 194 } |
192 | 195 |
193 /// Attempts to set the [value] of the path from the provided [obj]ect. | 196 /// Attempts to set the [value] of the path from the provided [obj]ect. |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
226 if (record is PropertyChangeRecord) { | 229 if (record is PropertyChangeRecord) { |
227 return (record as PropertyChangeRecord).name == key; | 230 return (record as PropertyChangeRecord).name == key; |
228 } | 231 } |
229 if (record is MapChangeRecord) { | 232 if (record is MapChangeRecord) { |
230 if (key is Symbol) key = smoke.symbolToName(key); | 233 if (key is Symbol) key = smoke.symbolToName(key); |
231 return (record as MapChangeRecord).key == key; | 234 return (record as MapChangeRecord).key == key; |
232 } | 235 } |
233 return false; | 236 return false; |
234 } | 237 } |
235 | 238 |
239 /// Properties in [Map] that need to be read as properties and not as keys in | |
240 /// the map. We exclude methods ('containsValue', 'containsKey', 'putIfAbsent', | |
241 /// 'addAll', 'remove', 'clear', 'forEach') because there is no use in reading | |
242 /// them as part of path-observer segments. | |
243 const _MAP_PROPERTIES = | |
244 const ['keys', 'values', 'length', 'isEmpty', 'isNotEmpty']; | |
245 | |
236 _getObjectProperty(object, property) { | 246 _getObjectProperty(object, property) { |
237 if (object == null) return null; | 247 if (object == null) return null; |
238 | 248 |
239 if (property is int) { | 249 if (property is int) { |
240 if (object is List && property >= 0 && property < object.length) { | 250 if (object is List && property >= 0 && property < object.length) { |
241 return object[property]; | 251 return object[property]; |
242 } | 252 } |
243 } else if (property is Symbol) { | 253 } else if (property is Symbol) { |
244 final type = object.runtimeType; | 254 var name = smoke.symbolToName(property); |
Jennifer Messerly
2014/03/25 22:32:44
move this inside the if, below? and make _MAP_PROP
Siggi Cherem (dart-lang)
2014/03/26 00:32:07
Done.
| |
255 // Support indexer if available, e.g. Maps or polymer_expressions Scope. | |
256 // This is the default syntax used by polymer/nodebind and | |
257 // polymer/observe-js PathObserver. | |
258 // TODO(sigmund): should we also support using checking dynamically for | |
259 // whether the type practically implements the indexer API | |
260 // (smoke.hasInstanceMethod(type, const Symbol('[]')))? | |
261 if (object is StringIndexer || | |
262 object is Map && !_MAP_PROPERTIES.contains(name)) { | |
263 return object[name]; | |
264 } | |
245 try { | 265 try { |
246 if (smoke.hasGetter(type, property) || smoke.hasNoSuchMethod(type)) { | 266 return smoke.read(object, property); |
247 return smoke.read(object, property); | 267 } on NoSuchMethodError catch (e, s) { |
248 } | |
249 // Support indexer if available, e.g. Maps or polymer_expressions Scope. | |
250 // This is the default syntax used by polymer/nodebind and | |
251 // polymer/observe-js PathObserver. | |
252 if (smoke.hasInstanceMethod(type, const Symbol('[]'))) { | |
253 return object[smoke.symbolToName(property)]; | |
254 } | |
255 } on NoSuchMethodError catch (e) { | |
256 // Rethrow, unless the type implements noSuchMethod, in which case we | 268 // Rethrow, unless the type implements noSuchMethod, in which case we |
257 // interpret the exception as a signal that the method was not found. | 269 // interpret the exception as a signal that the method was not found. |
258 if (!smoke.hasNoSuchMethod(type)) rethrow; | 270 if (!smoke.hasNoSuchMethod(object.runtimeType)) { |
271 new Completer().completeError(e, s); | |
Jennifer Messerly
2014/03/25 22:32:44
hmmm. Upon second look I'm not sure this is the ri
Siggi Cherem (dart-lang)
2014/03/26 00:32:07
No worries, changed it back to sync. Thanks for fi
| |
272 } | |
259 } | 273 } |
260 } | 274 } |
261 | 275 |
262 if (_logger.isLoggable(Level.FINER)) { | 276 if (_logger.isLoggable(Level.FINER)) { |
263 _logger.finer("can't get $property in $object"); | 277 _logger.finer("can't get $property in $object"); |
264 } | 278 } |
265 return null; | 279 return null; |
266 } | 280 } |
267 | 281 |
268 bool _setObjectProperty(object, property, value) { | 282 bool _setObjectProperty(object, property, value) { |
269 if (object == null) return false; | 283 if (object == null) return false; |
270 | 284 |
271 if (property is int) { | 285 if (property is int) { |
272 if (object is List && property >= 0 && property < object.length) { | 286 if (object is List && property >= 0 && property < object.length) { |
273 object[property] = value; | 287 object[property] = value; |
274 return true; | 288 return true; |
275 } | 289 } |
276 } else if (property is Symbol) { | 290 } else if (property is Symbol) { |
277 final type = object.runtimeType; | 291 // Support indexer if available, e.g. Maps or polymer_expressions Scope. |
292 var name = smoke.symbolToName(property); | |
293 if (object is StringIndexer || | |
294 object is Map && !_MAP_PROPERTIES.contains(name)) { | |
295 object[name] = value; | |
296 return true; | |
297 } | |
278 try { | 298 try { |
279 if (smoke.hasSetter(type, property) || smoke.hasNoSuchMethod(type)) { | 299 smoke.write(object, property, value); |
280 smoke.write(object, property, value); | 300 return true; |
281 return true; | 301 } on NoSuchMethodError catch (e, s) { |
302 if (!smoke.hasNoSuchMethod(object.runtimeType)) { | |
303 new Completer().completeError(e, s); | |
282 } | 304 } |
283 // Support indexer if available, e.g. Maps or polymer_expressions Scope. | |
284 if (smoke.hasInstanceMethod(type, const Symbol('[]='))) { | |
285 object[smoke.symbolToName(property)] = value; | |
286 return true; | |
287 } | |
288 } on NoSuchMethodError catch (e) { | |
289 if (!smoke.hasNoSuchMethod(type)) rethrow; | |
290 } | 305 } |
291 } | 306 } |
292 | 307 |
293 if (_logger.isLoggable(Level.FINER)) { | 308 if (_logger.isLoggable(Level.FINER)) { |
294 _logger.finer("can't set $property in $object"); | 309 _logger.finer("can't set $property in $object"); |
295 } | 310 } |
296 return false; | 311 return false; |
297 } | 312 } |
298 | 313 |
299 // From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js | 314 // From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js |
(...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
463 | 478 |
464 if (!changed) return false; | 479 if (!changed) return false; |
465 | 480 |
466 // TODO(rafaelw): Having _observed as the third callback arg here is | 481 // TODO(rafaelw): Having _observed as the third callback arg here is |
467 // pretty lame API. Fix. | 482 // pretty lame API. Fix. |
468 _report(_value, oldValues, _observed); | 483 _report(_value, oldValues, _observed); |
469 return true; | 484 return true; |
470 } | 485 } |
471 } | 486 } |
472 | 487 |
488 /// An object accepted by [PropertyPath] where properties are read and written | |
489 /// as indexing operations, just like a [Map]. | |
490 abstract class StringIndexer { | |
Siggi Cherem (dart-lang)
2014/03/25 21:44:25
I was not sure about the name. Other ideas/suggest
Jennifer Messerly
2014/03/25 22:32:44
Indexable<K, V>? then do the is check for Indexabl
Siggi Cherem (dart-lang)
2014/03/26 00:32:07
Done.
| |
491 operator [](String name); | |
492 operator []=(index, value); | |
493 } | |
494 | |
473 const _observerSentinel = const _ObserverSentinel(); | 495 const _observerSentinel = const _ObserverSentinel(); |
474 class _ObserverSentinel { const _ObserverSentinel(); } | 496 class _ObserverSentinel { const _ObserverSentinel(); } |
475 | 497 |
476 // A base class for the shared API implemented by PathObserver and | 498 // A base class for the shared API implemented by PathObserver and |
477 // CompoundObserver and used in _ObservedSet. | 499 // CompoundObserver and used in _ObservedSet. |
478 abstract class _Observer extends Bindable { | 500 abstract class _Observer extends Bindable { |
479 static int _nextBirthId = 0; | 501 static int _nextBirthId = 0; |
480 | 502 |
481 /// A number indicating when the object was created. | 503 /// A number indicating when the object was created. |
482 final int _birthId = _nextBirthId++; | 504 final int _birthId = _nextBirthId++; |
(...skipping 169 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
652 for (var observer in _observers.values.toList(growable: false)) { | 674 for (var observer in _observers.values.toList(growable: false)) { |
653 if (observer._isOpen) observer._check(); | 675 if (observer._isOpen) observer._check(); |
654 } | 676 } |
655 | 677 |
656 _resetNeeded = true; | 678 _resetNeeded = true; |
657 scheduleMicrotask(reset); | 679 scheduleMicrotask(reset); |
658 } | 680 } |
659 } | 681 } |
660 | 682 |
661 const int _MAX_DIRTY_CHECK_CYCLES = 1000; | 683 const int _MAX_DIRTY_CHECK_CYCLES = 1000; |
OLD | NEW |