Chromium Code Reviews| Index: pkg/observe/lib/src/path_observer.dart |
| diff --git a/pkg/observe/lib/src/path_observer.dart b/pkg/observe/lib/src/path_observer.dart |
| index 1b6e1a34dc1a80b7638bf918a0897b763616e81a..b3aece1f3e9c0b75dbca8c749a84458e4b3808a5 100644 |
| --- a/pkg/observe/lib/src/path_observer.dart |
| +++ b/pkg/observe/lib/src/path_observer.dart |
| @@ -2,7 +2,15 @@ |
| // 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 observe; |
| +library observe.src.path_observer; |
| + |
| +import 'dart:async'; |
| +@MirrorsUsed(metaTargets: const [Reflectable, ObservableProperty], |
| + override: 'observe.src.path_observer') |
| +import 'dart:mirrors'; |
| +import 'package:logging/logging.dart' show Logger, Level; |
| +import 'package:observe/observe.dart'; |
| +import 'package:observe/src/observable.dart' show objectType; |
| // This code is inspired by ChangeSummary: |
| // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js |
| @@ -75,7 +83,7 @@ class PathObserver extends ChangeNotifier { |
| // TODO(jmesserly): throw if property cannot be set? |
| // MDV seems tolerant of these errors. |
| if (len == 0) return; |
| - if (!hasObservers) _updateValues(); |
| + if (!hasObservers) _updateValues(end: len - 1); |
| if (_setObjectProperty(_values[len - 1], _segments[len - 1], value)) { |
| // Technically, this would get updated asynchronously via a change record. |
| @@ -96,29 +104,31 @@ class PathObserver extends ChangeNotifier { |
| return result; |
| } |
| - void _observed() { |
| - super._observed(); |
| + void observed() { |
| + super.observed(); |
| _updateValues(); |
| _observePath(); |
| } |
| - void _unobserved() { |
| + void unobserved() { |
| for (int i = 0; i < _subs.length; i++) { |
| if (_subs[i] != null) { |
| _subs[i].cancel(); |
| _subs[i] = null; |
| } |
| } |
| + super.unobserved(); |
| } |
| // TODO(jmesserly): should we be caching these values if not observing? |
| - void _updateValues() { |
| - for (int i = 0; i < _segments.length; i++) { |
| + void _updateValues({int end}) { |
| + if (end == null) end = _segments.length; |
| + for (int i = 0; i < end; i++) { |
| _values[i + 1] = _getObjectProperty(_values[i], _segments[i]); |
| } |
| } |
| - void _updateObservedValues([int start = 0]) { |
| + void _updateObservedValues({int start: 0}) { |
| var oldValue, newValue; |
| for (int i = start; i < _segments.length; i++) { |
| oldValue = _values[i + 1]; |
| @@ -159,7 +169,7 @@ class PathObserver extends ChangeNotifier { |
| for (var record in records) { |
| if (_changeRecordMatches(record, _segments[i])) { |
| - _updateObservedValues(i); |
| + _updateObservedValues(start: i); |
| return; |
| } |
| } |
| @@ -183,85 +193,76 @@ bool _changeRecordMatches(record, key) { |
| } |
| _getObjectProperty(object, property) { |
| - if (object == null) { |
| - return null; |
| - } |
| + if (object == null) return null; |
| - if (object is List && property is int) { |
| - if (property >= 0 && property < object.length) { |
| + if (property is int) { |
| + if (object is List && property >= 0 && property < object.length) { |
| return object[property]; |
| - } else { |
| - return null; |
| } |
| - } |
| - |
| - if (property is Symbol) { |
| + } else if (property is Symbol) { |
| var mirror = reflect(object); |
| - var result = _tryGetField(mirror, property); |
| - if (result != null) return result.reflectee; |
| + final type = mirror.type; |
| + try { |
| + if (_canGetProperty(type, property)) { |
| + return mirror.getField(property).reflectee; |
| + } |
| + // Support indexer if available, e.g. Maps or polymer_expressions Scope. |
| + if (_hasMethod(type, const Symbol('[]'))) { |
|
Siggi Cherem (dart-lang)
2013/10/29 21:48:38
does the # grammar support `#[]`?
Jennifer Messerly
2013/10/29 22:03:30
crazy. apparently yes. changed.
|
| + return object[MirrorSystem.getName(property)]; |
|
Siggi Cherem (dart-lang)
2013/10/29 21:48:38
interesting, this will let people use:
$.id.foo
Jennifer Messerly
2013/10/29 22:03:30
yes. This has always been the case with PathObserv
Jennifer Messerly
2013/10/29 22:10:33
added a comment
|
| + } |
| + } on NoSuchMethodError catch (e) { |
| + // Rethrow, unless the type implements noSuchMethod, in which case we |
| + // interpret the exception as a signal that the method was not found. |
| + if (!_hasMethod(type, #noSuchMethod)) rethrow; |
|
Siggi Cherem (dart-lang)
2013/10/29 21:48:38
given your earlier check with _canGetProperty, wha
Jennifer Messerly
2013/10/29 22:03:30
consider the following:
* the object has noSuchMet
Jennifer Messerly
2013/10/29 22:10:33
renamed to _maybeHasGetter/_maybeHasSetter
|
| + } |
| } |
| - if (object is Map) { |
| - if (property is Symbol) property = MirrorSystem.getName(property); |
| - return object[property]; |
| + if (_logger.isLoggable(Level.FINER)) { |
| + _logger.log("can't get $property in $object"); |
| } |
| - |
| return null; |
| } |
| bool _setObjectProperty(object, property, value) { |
| - if (object is List && property is int) { |
| - if (property >= 0 && property < object.length) { |
| + if (object == null) return false; |
| + |
| + if (property is int) { |
| + if (object is List && property >= 0 && property < object.length) { |
| object[property] = value; |
| return true; |
| - } else { |
| - return false; |
| } |
| - } |
| - |
| - if (property is Symbol) { |
| + } else if (property is Symbol) { |
| var mirror = reflect(object); |
| - if (_trySetField(mirror, property, value)) return true; |
| + final type = mirror.type; |
| + try { |
| + if (_canSetProperty(type, property)) { |
| + mirror.setField(property, value); |
| + return true; |
| + } |
| + // Support indexer if available, e.g. Maps or polymer_expressions Scope. |
| + if (_hasMethod(type, const Symbol('[]='))) { |
| + object[MirrorSystem.getName(property)] = value; |
| + return true; |
| + } |
| + } on NoSuchMethodError catch (e) { |
| + if (!_hasMethod(type, #noSuchMethod)) rethrow; |
| + } |
| } |
| - if (object is Map) { |
| - if (property is Symbol) property = MirrorSystem.getName(property); |
| - object[property] = value; |
| - return true; |
| + if (_logger.isLoggable(Level.FINER)) { |
| + _logger.log("can't set $property in $object"); |
| } |
| - |
| return false; |
| } |
| -InstanceMirror _tryGetField(InstanceMirror mirror, Symbol name) { |
| - try { |
| - return mirror.getField(name); |
| - } on NoSuchMethodError catch (e) { |
| - if (_hasMember(mirror, name, (m) => |
| - m is VariableMirror || m is MethodMirror && m.isGetter)) { |
| - // The field/getter is there but threw a NoSuchMethod exception. |
| - // This is a legitimate error in the code so rethrow. |
| - rethrow; |
| - } |
| - // The field isn't there. PathObserver does not treat this as an error. |
| - return null; |
| - } |
| -} |
| - |
| -bool _trySetField(InstanceMirror mirror, Symbol name, Object value) { |
| - try { |
| - mirror.setField(name, value); |
| - return true; |
| - } on NoSuchMethodError catch (e) { |
| - if (_hasMember(mirror, name, (m) => m is VariableMirror) || |
| - _hasMember(mirror, _setterName(name))) { |
| - // The field/setter is there but threw a NoSuchMethod exception. |
| - // This is a legitimate error in the code so rethrow. |
| - rethrow; |
| - } |
| - // The field isn't there. PathObserver does not treat this as an error. |
| - return false; |
| +bool _canGetProperty(ClassMirror type, Symbol name) { |
| + while (type != objectType) { |
| + final members = type.members; |
| + if (members.containsKey(name)) return true; |
| + if (members.containsKey(#noSuchMethod)) return true; |
| + type = _safeSuperclass(type); |
| } |
| + return false; |
| } |
| // TODO(jmesserly): workaround for: |
| @@ -269,23 +270,41 @@ bool _trySetField(InstanceMirror mirror, Symbol name, Object value) { |
| Symbol _setterName(Symbol getter) => |
| new Symbol('${MirrorSystem.getName(getter)}='); |
| -bool _hasMember(InstanceMirror mirror, Symbol name, [bool test(member)]) { |
| - var type = mirror.type; |
| - while (type != null) { |
| - final member = type.members[name]; |
| - if (member != null && (test == null || test(member))) return true; |
| +bool _canSetProperty(ClassMirror type, Symbol name) { |
| + var setterName = _setterName(name); |
| + while (type != objectType) { |
| + final members = type.members; |
| + if (members[name] is VariableMirror) return true; |
| + if (members.containsKey(setterName)) return true; |
| + if (members.containsKey(#noSuchMethod)) return true; |
| + type = _safeSuperclass(type); |
| + } |
| + return false; |
| +} |
| - try { |
| - type = type.superclass; |
| - } on UnsupportedError catch (e) { |
| - // TODO(jmesserly): dart2js throws this error when the type is not |
| - // reflectable. |
| - return false; |
| - } |
| +/** |
| + * True if the type has a method, other than on Object. |
| + * Doesn't consider noSuchMethod, unless [name] is `#noSuchMethod`. |
| + */ |
| +bool _hasMethod(ClassMirror type, Symbol name) { |
| + while (type != objectType) { |
| + final member = type.members[name]; |
| + if (member is MethodMirror && member.isRegularMethod) return true; |
| + type = _safeSuperclass(type); |
| } |
| return false; |
| } |
| +ClassMirror _safeSuperclass(ClassMirror type) { |
| + try { |
| + return type.superclass; |
| + } on UnsupportedError catch (e) { |
| + // TODO(jmesserly): dart2js throws this error when the type is not |
| + // reflectable. |
| + return objectType; |
| + } |
| +} |
| + |
| // From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js |
| final _pathRegExp = () { |
| @@ -307,3 +326,5 @@ bool _isPathValid(String s) { |
| if (s[0] == '.') return false; |
| return _pathRegExp.hasMatch(s); |
| } |
| + |
| +final _logger = new Logger('observe.PathObserver'); |