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

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

Issue 173473002: Adapting observe to use smoke (this is built on top of the previous change that (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 10 months 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
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 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 @MirrorsUsed(metaTargets: const [Reflectable, ObservableProperty],
11 override: 'observe.src.path_observer')
12 import 'dart:mirrors';
13 import 'package:logging/logging.dart' show Logger, Level; 10 import 'package:logging/logging.dart' show Logger, Level;
14 import 'package:observe/observe.dart'; 11 import 'package:observe/observe.dart';
15 import 'package:observe/src/observable.dart' show objectType; 12 import 'package:observe/src/observable.dart' show objectType;
13 import 'package:smoke/smoke.dart' as smoke;
16 14
17 /// A data-bound path starting from a view-model or model object, for example 15 /// A data-bound path starting from a view-model or model object, for example
18 /// `foo.bar.baz`. 16 /// `foo.bar.baz`.
19 /// 17 ///
20 /// When [open] is called, this will observe changes to the object and any 18 /// When [open] is called, this will observe changes to the object and any
21 /// intermediate object along the path, and send updated values accordingly. 19 /// intermediate object along the path, and send updated values accordingly.
22 /// When [close] is called it will stop observing the objects. 20 /// When [close] is called it will stop observing the objects.
23 /// 21 ///
24 /// This class is used to implement `Node.bind` and similar functionality in 22 /// This class is used to implement `Node.bind` and similar functionality in
25 /// the [template_binding](pub.dartlang.org/packages/template_binding) package. 23 /// the [template_binding](pub.dartlang.org/packages/template_binding) package.
26 class PathObserver extends _Observer implements Bindable { 24 class PathObserver extends _Observer implements Bindable {
27 PropertyPath _path; 25 PropertyPath _path;
28 Object _object; 26 Object _object;
29 _ObservedSet _directObserver; 27 _ObservedSet _directObserver;
30 28
31 /// Observes [path] on [object] for changes. This returns an object 29 /// Observes [path] on [object] for changes. This returns an object
32 /// that can be used to get the changes and get/set the value at this path. 30 /// that can be used to get the changes and get/set the value at this path.
33 /// 31 ///
34 /// The path can be a [PropertyPath], or a [String] used to construct it. 32 /// The path can be a [PropertyPath], or a [String] used to construct it.
35 /// 33 ///
36 /// See [open] and [value]. 34 /// See [open] and [value].
37 PathObserver(Object object, [path]) 35 PathObserver(Object object, [path])
38 : _object = object, 36 : _object = object,
39 _path = path is PropertyPath ? path : new PropertyPath(path); 37 _path = path is PropertyPath ? path : new PropertyPath(path);
40 38
41 bool get _isClosed => _path == null; 39 bool get _isClosed => _path == null;
42 40
43 /// Sets the value at this path. 41 /// Sets the value at this path.
44 @reflectable void set value(Object newValue) { 42 void set value(Object newValue) {
45 if (_path != null) _path.setValueFrom(_object, newValue); 43 if (_path != null) _path.setValueFrom(_object, newValue);
46 } 44 }
47 45
48 int get _reportArgumentCount => 2; 46 int get _reportArgumentCount => 2;
49 47
50 /// Initiates observation and returns the initial value. 48 /// Initiates observation and returns the initial value.
51 /// The callback will be passed the updated [value], and may optionally be 49 /// The callback will be passed the updated [value], and may optionally be
52 /// declared to take a second argument, which will contain the previous value. 50 /// declared to take a second argument, which will contain the previous value.
53 open(callback) => super.open(callback); 51 open(callback) => super.open(callback);
54 52
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after
141 139
142 PropertyPath._(this._segments); 140 PropertyPath._(this._segments);
143 141
144 int get length => _segments.length; 142 int get length => _segments.length;
145 bool get isEmpty => _segments.isEmpty; 143 bool get isEmpty => _segments.isEmpty;
146 bool get isValid => true; 144 bool get isValid => true;
147 145
148 String toString() { 146 String toString() {
149 if (!isValid) return '<invalid path>'; 147 if (!isValid) return '<invalid path>';
150 return _segments 148 return _segments
151 .map((s) => s is Symbol ? MirrorSystem.getName(s) : s) 149 .map((s) => s is Symbol ? smoke.symbolToName(s) : s)
152 .join('.'); 150 .join('.');
153 } 151 }
154 152
155 bool operator ==(other) { 153 bool operator ==(other) {
156 if (identical(this, other)) return true; 154 if (identical(this, other)) return true;
157 if (other is! PropertyPath) return false; 155 if (other is! PropertyPath) return false;
158 if (isValid != other.isValid) return false; 156 if (isValid != other.isValid) return false;
159 157
160 int len = _segments.length; 158 int len = _segments.length;
161 if (len != other._segments.length) return false; 159 if (len != other._segments.length) return false;
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
222 220
223 bool get isValid => false; 221 bool get isValid => false;
224 _InvalidPropertyPath() : super._([]); 222 _InvalidPropertyPath() : super._([]);
225 } 223 }
226 224
227 bool _changeRecordMatches(record, key) { 225 bool _changeRecordMatches(record, key) {
228 if (record is PropertyChangeRecord) { 226 if (record is PropertyChangeRecord) {
229 return (record as PropertyChangeRecord).name == key; 227 return (record as PropertyChangeRecord).name == key;
230 } 228 }
231 if (record is MapChangeRecord) { 229 if (record is MapChangeRecord) {
232 if (key is Symbol) key = MirrorSystem.getName(key); 230 if (key is Symbol) key = smoke.symbolToName(key);
233 return (record as MapChangeRecord).key == key; 231 return (record as MapChangeRecord).key == key;
234 } 232 }
235 return false; 233 return false;
236 } 234 }
237 235
238 _getObjectProperty(object, property) { 236 _getObjectProperty(object, property) {
239 if (object == null) return null; 237 if (object == null) return null;
240 238
241 if (property is int) { 239 if (property is int) {
242 if (object is List && property >= 0 && property < object.length) { 240 if (object is List && property >= 0 && property < object.length) {
243 return object[property]; 241 return object[property];
244 } 242 }
245 } else if (property is Symbol) { 243 } else if (property is Symbol) {
246 var mirror = reflect(object); 244 final type = object.runtimeType;
247 final type = mirror.type;
248 try { 245 try {
249 if (_maybeHasGetter(type, property)) { 246 if (smoke.hasGetter(type, property) || smoke.hasNoSuchMethod(type)) {
250 return mirror.getField(property).reflectee; 247 return smoke.read(object, property);
251 } 248 }
252 // Support indexer if available, e.g. Maps or polymer_expressions Scope. 249 // Support indexer if available, e.g. Maps or polymer_expressions Scope.
253 // This is the default syntax used by polymer/nodebind and 250 // This is the default syntax used by polymer/nodebind and
254 // polymer/observe-js PathObserver. 251 // polymer/observe-js PathObserver.
255 if (_hasMethod(type, const Symbol('[]'))) { 252 if (smoke.hasInstanceMethod(type, const Symbol('[]'))) {
256 return object[MirrorSystem.getName(property)]; 253 return object[smoke.symbolToName(property)];
257 } 254 }
258 } on NoSuchMethodError catch (e) { 255 } on NoSuchMethodError catch (e) {
259 // Rethrow, unless the type implements noSuchMethod, in which case we 256 // Rethrow, unless the type implements noSuchMethod, in which case we
260 // interpret the exception as a signal that the method was not found. 257 // interpret the exception as a signal that the method was not found.
261 if (!_hasMethod(type, #noSuchMethod)) rethrow; 258 if (!smoke.hasNoSuchMethod(type)) rethrow;
262 } 259 }
263 } 260 }
264 261
265 if (_logger.isLoggable(Level.FINER)) { 262 if (_logger.isLoggable(Level.FINER)) {
266 _logger.finer("can't get $property in $object"); 263 _logger.finer("can't get $property in $object");
267 } 264 }
268 return null; 265 return null;
269 } 266 }
270 267
271 bool _setObjectProperty(object, property, value) { 268 bool _setObjectProperty(object, property, value) {
272 if (object == null) return false; 269 if (object == null) return false;
273 270
274 if (property is int) { 271 if (property is int) {
275 if (object is List && property >= 0 && property < object.length) { 272 if (object is List && property >= 0 && property < object.length) {
276 object[property] = value; 273 object[property] = value;
277 return true; 274 return true;
278 } 275 }
279 } else if (property is Symbol) { 276 } else if (property is Symbol) {
280 var mirror = reflect(object); 277 final type = object.runtimeType;
281 final type = mirror.type;
282 try { 278 try {
283 if (_maybeHasSetter(type, property)) { 279 if (smoke.hasSetter(type, property) || smoke.hasNoSuchMethod(type)) {
284 mirror.setField(property, value); 280 smoke.write(object, property, value);
285 return true; 281 return true;
286 } 282 }
287 // Support indexer if available, e.g. Maps or polymer_expressions Scope. 283 // Support indexer if available, e.g. Maps or polymer_expressions Scope.
288 if (_hasMethod(type, const Symbol('[]='))) { 284 if (smoke.hasInstanceMethod(type, const Symbol('[]='))) {
289 object[MirrorSystem.getName(property)] = value; 285 object[smoke.symbolToName(property)] = value;
290 return true; 286 return true;
291 } 287 }
292 } on NoSuchMethodError catch (e) { 288 } on NoSuchMethodError catch (e) {
293 if (!_hasMethod(type, #noSuchMethod)) rethrow; 289 if (!smoke.hasNoSuchMethod(type)) rethrow;
294 } 290 }
295 } 291 }
296 292
297 if (_logger.isLoggable(Level.FINER)) { 293 if (_logger.isLoggable(Level.FINER)) {
298 _logger.finer("can't set $property in $object"); 294 _logger.finer("can't set $property in $object");
299 } 295 }
300 return false; 296 return false;
301 } 297 }
302 298
303 bool _maybeHasGetter(ClassMirror type, Symbol name) {
304 while (type != objectType) {
305 final members = type.declarations;
306 if (members.containsKey(name)) return true;
307 if (members.containsKey(#noSuchMethod)) return true;
308 type = _safeSuperclass(type);
309 }
310 return false;
311 }
312
313 // TODO(jmesserly): workaround for:
314 // https://code.google.com/p/dart/issues/detail?id=10029
315 Symbol _setterName(Symbol getter) =>
316 new Symbol('${MirrorSystem.getName(getter)}=');
317
318 bool _maybeHasSetter(ClassMirror type, Symbol name) {
319 var setterName = _setterName(name);
320 while (type != objectType) {
321 final members = type.declarations;
322 if (members[name] is VariableMirror) return true;
323 if (members.containsKey(setterName)) return true;
324 if (members.containsKey(#noSuchMethod)) return true;
325 type = _safeSuperclass(type);
326 }
327 return false;
328 }
329
330 /// True if the type has a method, other than on Object.
331 /// Doesn't consider noSuchMethod, unless [name] is `#noSuchMethod`.
332 bool _hasMethod(ClassMirror type, Symbol name) {
333 while (type != objectType) {
334 final member = type.declarations[name];
335 if (member is MethodMirror && member.isRegularMethod) return true;
336 type = _safeSuperclass(type);
337 }
338 return false;
339 }
340
341 ClassMirror _safeSuperclass(ClassMirror type) {
342 try {
343 return type.superclass;
344 } /*on UnsupportedError*/ catch (e) {
345 // Note: dart2js throws UnsupportedError when the type is not
346 // reflectable.
347 // TODO(jmesserly): dart2js also throws a NoSuchMethodError if the `type` is
348 // a bound generic, because they are not fully implemented. See
349 // https://code.google.com/p/dart/issues/detail?id=15573
350 return objectType;
351 }
352 }
353
354 // From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js 299 // From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
355 300
356 final _pathRegExp = () { 301 final _pathRegExp = () {
357 const identStart = '[\$_a-zA-Z]'; 302 const identStart = '[\$_a-zA-Z]';
358 const identPart = '[\$_a-zA-Z0-9]'; 303 const identPart = '[\$_a-zA-Z0-9]';
359 const ident = '$identStart+$identPart*'; 304 const ident = '$identStart+$identPart*';
360 const elementIndex = '(?:[0-9]|[1-9]+[0-9]+)'; 305 const elementIndex = '(?:[0-9]|[1-9]+[0-9]+)';
361 const identOrElementIndex = '(?:$ident|$elementIndex)'; 306 const identOrElementIndex = '(?:$ident|$elementIndex)';
362 const path = '(?:$identOrElementIndex)(?:\\.$identOrElementIndex)*'; 307 const path = '(?:$identOrElementIndex)(?:\\.$identOrElementIndex)*';
363 return new RegExp('^$path\$'); 308 return new RegExp('^$path\$');
(...skipping 199 matching lines...) Expand 10 before | Expand all | Expand 10 after
563 } 508 }
564 509
565 _notifyCallback = callback; 510 _notifyCallback = callback;
566 _notifyArgumentCount = min(_reportArgumentCount, 511 _notifyArgumentCount = min(_reportArgumentCount,
567 _maxArgumentCount(callback)); 512 _maxArgumentCount(callback));
568 513
569 _connect(); 514 _connect();
570 return _value; 515 return _value;
571 } 516 }
572 517
573 @reflectable get value { 518 get value {
574 _check(skipChanges: true); 519 _check(skipChanges: true);
575 return _value; 520 return _value;
576 } 521 }
577 522
578 void close() { 523 void close() {
579 if (!_isOpen) return; 524 if (!_isOpen) return;
580 525
581 _disconnect(); 526 _disconnect();
582 _value = null; 527 _value = null;
583 _notifyCallback = null; 528 _notifyCallback = null;
(...skipping 145 matching lines...) Expand 10 before | Expand all | Expand 10 after
729 for (var observer in _observers.values.toList(growable: false)) { 674 for (var observer in _observers.values.toList(growable: false)) {
730 if (observer._isOpen) observer._check(); 675 if (observer._isOpen) observer._check();
731 } 676 }
732 677
733 _resetNeeded = true; 678 _resetNeeded = true;
734 scheduleMicrotask(reset); 679 scheduleMicrotask(reset);
735 } 680 }
736 } 681 }
737 682
738 const int _MAX_DIRTY_CHECK_CYCLES = 1000; 683 const int _MAX_DIRTY_CHECK_CYCLES = 1000;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698