Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(806)

Side by Side Diff: pkg/observe/lib/src/path_observer.dart

Issue 51483002: fix PathObserver to avoid try+catch (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: rm meta Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/observe/lib/src/observable_map.dart ('k') | pkg/observe/lib/src/to_observable.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 part of observe; 5 library observe.src.path_observer;
6
7 import 'dart:async';
8 @MirrorsUsed(metaTargets: const [Reflectable, ObservableProperty],
9 override: 'observe.src.path_observer')
10 import 'dart:mirrors';
11 import 'package:logging/logging.dart' show Logger, Level;
12 import 'package:observe/observe.dart';
13 import 'package:observe/src/observable.dart' show objectType;
6 14
7 // This code is inspired by ChangeSummary: 15 // This code is inspired by ChangeSummary:
8 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js 16 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
9 // ...which underlies MDV. Since we don't need the functionality of 17 // ...which underlies MDV. Since we don't need the functionality of
10 // ChangeSummary, we just implement what we need for data bindings. 18 // ChangeSummary, we just implement what we need for data bindings.
11 // This allows our implementation to be much simpler. 19 // This allows our implementation to be much simpler.
12 20
13 /** 21 /**
14 * A data-bound path starting from a view-model or model object, for example 22 * A data-bound path starting from a view-model or model object, for example
15 * `foo.bar.baz`. 23 * `foo.bar.baz`.
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
68 return _values.last; 76 return _values.last;
69 } 77 }
70 78
71 /** Sets the value at this path. */ 79 /** Sets the value at this path. */
72 @reflectable void set value(Object value) { 80 @reflectable void set value(Object value) {
73 int len = _segments.length; 81 int len = _segments.length;
74 82
75 // TODO(jmesserly): throw if property cannot be set? 83 // TODO(jmesserly): throw if property cannot be set?
76 // MDV seems tolerant of these errors. 84 // MDV seems tolerant of these errors.
77 if (len == 0) return; 85 if (len == 0) return;
78 if (!hasObservers) _updateValues(); 86 if (!hasObservers) _updateValues(end: len - 1);
79 87
80 if (_setObjectProperty(_values[len - 1], _segments[len - 1], value)) { 88 if (_setObjectProperty(_values[len - 1], _segments[len - 1], value)) {
81 // Technically, this would get updated asynchronously via a change record. 89 // Technically, this would get updated asynchronously via a change record.
82 // However, it is nice if calling the getter will yield the same value 90 // However, it is nice if calling the getter will yield the same value
83 // that was just set. So we use this opportunity to update our cache. 91 // that was just set. So we use this opportunity to update our cache.
84 _values[len] = value; 92 _values[len] = value;
85 } 93 }
86 } 94 }
87 95
88 /** 96 /**
89 * Invokes the [callback] immediately with the current [value], and every time 97 * Invokes the [callback] immediately with the current [value], and every time
90 * the value changes. This is useful for bindings, which want to be up-to-date 98 * the value changes. This is useful for bindings, which want to be up-to-date
91 * immediately and stay bound to the value of the path. 99 * immediately and stay bound to the value of the path.
92 */ 100 */
93 StreamSubscription bindSync(void callback(value)) { 101 StreamSubscription bindSync(void callback(value)) {
94 var result = changes.listen((records) { callback(value); }); 102 var result = changes.listen((records) { callback(value); });
95 callback(value); 103 callback(value);
96 return result; 104 return result;
97 } 105 }
98 106
99 void _observed() { 107 void observed() {
100 super._observed(); 108 super.observed();
101 _updateValues(); 109 _updateValues();
102 _observePath(); 110 _observePath();
103 } 111 }
104 112
105 void _unobserved() { 113 void unobserved() {
106 for (int i = 0; i < _subs.length; i++) { 114 for (int i = 0; i < _subs.length; i++) {
107 if (_subs[i] != null) { 115 if (_subs[i] != null) {
108 _subs[i].cancel(); 116 _subs[i].cancel();
109 _subs[i] = null; 117 _subs[i] = null;
110 } 118 }
111 } 119 }
120 super.unobserved();
112 } 121 }
113 122
114 // TODO(jmesserly): should we be caching these values if not observing? 123 // TODO(jmesserly): should we be caching these values if not observing?
115 void _updateValues() { 124 void _updateValues({int end}) {
116 for (int i = 0; i < _segments.length; i++) { 125 if (end == null) end = _segments.length;
126 for (int i = 0; i < end; i++) {
117 _values[i + 1] = _getObjectProperty(_values[i], _segments[i]); 127 _values[i + 1] = _getObjectProperty(_values[i], _segments[i]);
118 } 128 }
119 } 129 }
120 130
121 void _updateObservedValues([int start = 0]) { 131 void _updateObservedValues({int start: 0}) {
122 var oldValue, newValue; 132 var oldValue, newValue;
123 for (int i = start; i < _segments.length; i++) { 133 for (int i = start; i < _segments.length; i++) {
124 oldValue = _values[i + 1]; 134 oldValue = _values[i + 1];
125 newValue = _getObjectProperty(_values[i], _segments[i]); 135 newValue = _getObjectProperty(_values[i], _segments[i]);
126 if (identical(oldValue, newValue)) { 136 if (identical(oldValue, newValue)) {
127 _observePath(start, i); 137 _observePath(start, i);
128 return; 138 return;
129 } 139 }
130 _values[i + 1] = newValue; 140 _values[i + 1] = newValue;
131 } 141 }
(...skipping 20 matching lines...) Expand all
152 // PropertyChangeRecord is modified to includes the sender object), so 162 // PropertyChangeRecord is modified to includes the sender object), so
153 // we would need to re-evaluate the entire path. Need to evaluate perf. 163 // we would need to re-evaluate the entire path. Need to evaluate perf.
154 _subs[i] = object.changes.listen((List<ChangeRecord> records) { 164 _subs[i] = object.changes.listen((List<ChangeRecord> records) {
155 if (!identical(_values[i], object)) { 165 if (!identical(_values[i], object)) {
156 // Ignore this object if we're now tracking something else. 166 // Ignore this object if we're now tracking something else.
157 return; 167 return;
158 } 168 }
159 169
160 for (var record in records) { 170 for (var record in records) {
161 if (_changeRecordMatches(record, _segments[i])) { 171 if (_changeRecordMatches(record, _segments[i])) {
162 _updateObservedValues(i); 172 _updateObservedValues(start: i);
163 return; 173 return;
164 } 174 }
165 } 175 }
166 }); 176 });
167 } 177 }
168 } 178 }
169 } 179 }
170 180
171 bool _changeRecordMatches(record, key) { 181 bool _changeRecordMatches(record, key) {
172 if (record is ListChangeRecord) { 182 if (record is ListChangeRecord) {
173 return key is int && (record as ListChangeRecord).indexChanged(key); 183 return key is int && (record as ListChangeRecord).indexChanged(key);
174 } 184 }
175 if (record is PropertyChangeRecord) { 185 if (record is PropertyChangeRecord) {
176 return (record as PropertyChangeRecord).name == key; 186 return (record as PropertyChangeRecord).name == key;
177 } 187 }
178 if (record is MapChangeRecord) { 188 if (record is MapChangeRecord) {
179 if (key is Symbol) key = MirrorSystem.getName(key); 189 if (key is Symbol) key = MirrorSystem.getName(key);
180 return (record as MapChangeRecord).key == key; 190 return (record as MapChangeRecord).key == key;
181 } 191 }
182 return false; 192 return false;
183 } 193 }
184 194
185 _getObjectProperty(object, property) { 195 _getObjectProperty(object, property) {
186 if (object == null) { 196 if (object == null) return null;
187 return null;
188 }
189 197
190 if (object is List && property is int) { 198 if (property is int) {
191 if (property >= 0 && property < object.length) { 199 if (object is List && property >= 0 && property < object.length) {
192 return object[property]; 200 return object[property];
193 } else { 201 }
194 return null; 202 } else if (property is Symbol) {
203 var mirror = reflect(object);
204 final type = mirror.type;
205 try {
206 if (_canGetProperty(type, property)) {
207 return mirror.getField(property).reflectee;
208 }
209 // Support indexer if available, e.g. Maps or polymer_expressions Scope.
210 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.
211 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
212 }
213 } on NoSuchMethodError catch (e) {
214 // Rethrow, unless the type implements noSuchMethod, in which case we
215 // interpret the exception as a signal that the method was not found.
216 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
195 } 217 }
196 } 218 }
197 219
198 if (property is Symbol) { 220 if (_logger.isLoggable(Level.FINER)) {
199 var mirror = reflect(object); 221 _logger.log("can't get $property in $object");
200 var result = _tryGetField(mirror, property);
201 if (result != null) return result.reflectee;
202 } 222 }
203
204 if (object is Map) {
205 if (property is Symbol) property = MirrorSystem.getName(property);
206 return object[property];
207 }
208
209 return null; 223 return null;
210 } 224 }
211 225
212 bool _setObjectProperty(object, property, value) { 226 bool _setObjectProperty(object, property, value) {
213 if (object is List && property is int) { 227 if (object == null) return false;
214 if (property >= 0 && property < object.length) { 228
229 if (property is int) {
230 if (object is List && property >= 0 && property < object.length) {
215 object[property] = value; 231 object[property] = value;
216 return true; 232 return true;
217 } else { 233 }
218 return false; 234 } else if (property is Symbol) {
235 var mirror = reflect(object);
236 final type = mirror.type;
237 try {
238 if (_canSetProperty(type, property)) {
239 mirror.setField(property, value);
240 return true;
241 }
242 // Support indexer if available, e.g. Maps or polymer_expressions Scope.
243 if (_hasMethod(type, const Symbol('[]='))) {
244 object[MirrorSystem.getName(property)] = value;
245 return true;
246 }
247 } on NoSuchMethodError catch (e) {
248 if (!_hasMethod(type, #noSuchMethod)) rethrow;
219 } 249 }
220 } 250 }
221 251
222 if (property is Symbol) { 252 if (_logger.isLoggable(Level.FINER)) {
223 var mirror = reflect(object); 253 _logger.log("can't set $property in $object");
224 if (_trySetField(mirror, property, value)) return true;
225 } 254 }
226
227 if (object is Map) {
228 if (property is Symbol) property = MirrorSystem.getName(property);
229 object[property] = value;
230 return true;
231 }
232
233 return false; 255 return false;
234 } 256 }
235 257
236 InstanceMirror _tryGetField(InstanceMirror mirror, Symbol name) { 258 bool _canGetProperty(ClassMirror type, Symbol name) {
237 try { 259 while (type != objectType) {
238 return mirror.getField(name); 260 final members = type.members;
239 } on NoSuchMethodError catch (e) { 261 if (members.containsKey(name)) return true;
240 if (_hasMember(mirror, name, (m) => 262 if (members.containsKey(#noSuchMethod)) return true;
241 m is VariableMirror || m is MethodMirror && m.isGetter)) { 263 type = _safeSuperclass(type);
242 // The field/getter is there but threw a NoSuchMethod exception.
243 // This is a legitimate error in the code so rethrow.
244 rethrow;
245 }
246 // The field isn't there. PathObserver does not treat this as an error.
247 return null;
248 } 264 }
249 } 265 return false;
250
251 bool _trySetField(InstanceMirror mirror, Symbol name, Object value) {
252 try {
253 mirror.setField(name, value);
254 return true;
255 } on NoSuchMethodError catch (e) {
256 if (_hasMember(mirror, name, (m) => m is VariableMirror) ||
257 _hasMember(mirror, _setterName(name))) {
258 // The field/setter is there but threw a NoSuchMethod exception.
259 // This is a legitimate error in the code so rethrow.
260 rethrow;
261 }
262 // The field isn't there. PathObserver does not treat this as an error.
263 return false;
264 }
265 } 266 }
266 267
267 // TODO(jmesserly): workaround for: 268 // TODO(jmesserly): workaround for:
268 // https://code.google.com/p/dart/issues/detail?id=10029 269 // https://code.google.com/p/dart/issues/detail?id=10029
269 Symbol _setterName(Symbol getter) => 270 Symbol _setterName(Symbol getter) =>
270 new Symbol('${MirrorSystem.getName(getter)}='); 271 new Symbol('${MirrorSystem.getName(getter)}=');
271 272
272 bool _hasMember(InstanceMirror mirror, Symbol name, [bool test(member)]) { 273 bool _canSetProperty(ClassMirror type, Symbol name) {
273 var type = mirror.type; 274 var setterName = _setterName(name);
274 while (type != null) { 275 while (type != objectType) {
275 final member = type.members[name]; 276 final members = type.members;
276 if (member != null && (test == null || test(member))) return true; 277 if (members[name] is VariableMirror) return true;
277 278 if (members.containsKey(setterName)) return true;
278 try { 279 if (members.containsKey(#noSuchMethod)) return true;
279 type = type.superclass; 280 type = _safeSuperclass(type);
280 } on UnsupportedError catch (e) {
281 // TODO(jmesserly): dart2js throws this error when the type is not
282 // reflectable.
283 return false;
284 }
285 } 281 }
286 return false; 282 return false;
287 } 283 }
288 284
285 /**
286 * True if the type has a method, other than on Object.
287 * Doesn't consider noSuchMethod, unless [name] is `#noSuchMethod`.
288 */
289 bool _hasMethod(ClassMirror type, Symbol name) {
290 while (type != objectType) {
291 final member = type.members[name];
292 if (member is MethodMirror && member.isRegularMethod) return true;
293 type = _safeSuperclass(type);
294 }
295 return false;
296 }
297
298 ClassMirror _safeSuperclass(ClassMirror type) {
299 try {
300 return type.superclass;
301 } on UnsupportedError catch (e) {
302 // TODO(jmesserly): dart2js throws this error when the type is not
303 // reflectable.
304 return objectType;
305 }
306 }
307
289 // From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js 308 // From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
290 309
291 final _pathRegExp = () { 310 final _pathRegExp = () {
292 const identStart = '[\$_a-zA-Z]'; 311 const identStart = '[\$_a-zA-Z]';
293 const identPart = '[\$_a-zA-Z0-9]'; 312 const identPart = '[\$_a-zA-Z0-9]';
294 const ident = '$identStart+$identPart*'; 313 const ident = '$identStart+$identPart*';
295 const elementIndex = '(?:[0-9]|[1-9]+[0-9]+)'; 314 const elementIndex = '(?:[0-9]|[1-9]+[0-9]+)';
296 const identOrElementIndex = '(?:$ident|$elementIndex)'; 315 const identOrElementIndex = '(?:$ident|$elementIndex)';
297 const path = '(?:$identOrElementIndex)(?:\\.$identOrElementIndex)*'; 316 const path = '(?:$identOrElementIndex)(?:\\.$identOrElementIndex)*';
298 return new RegExp('^$path\$'); 317 return new RegExp('^$path\$');
299 }(); 318 }();
300 319
301 final _spacesRegExp = new RegExp(r'\s'); 320 final _spacesRegExp = new RegExp(r'\s');
302 321
303 bool _isPathValid(String s) { 322 bool _isPathValid(String s) {
304 s = s.replaceAll(_spacesRegExp, ''); 323 s = s.replaceAll(_spacesRegExp, '');
305 324
306 if (s == '') return true; 325 if (s == '') return true;
307 if (s[0] == '.') return false; 326 if (s[0] == '.') return false;
308 return _pathRegExp.hasMatch(s); 327 return _pathRegExp.hasMatch(s);
309 } 328 }
329
330 final _logger = new Logger('observe.PathObserver');
OLDNEW
« no previous file with comments | « pkg/observe/lib/src/observable_map.dart ('k') | pkg/observe/lib/src/to_observable.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698