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

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, 9 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
« no previous file with comments | « pkg/observe/lib/src/observable.dart ('k') | pkg/observe/pubspec.yaml » ('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 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], 10 @MirrorsUsed(metaTargets: const [Reflectable, ObservableProperty],
11 override: 'observe.src.path_observer') 11 override: 'smoke.mirrors')
12 import 'dart:mirrors'; 12 import 'dart:mirrors' show MirrorsUsed;
13
13 import 'package:logging/logging.dart' show Logger, Level; 14 import 'package:logging/logging.dart' show Logger, Level;
14 import 'package:observe/observe.dart'; 15 import 'package:observe/observe.dart';
15 import 'package:observe/src/observable.dart' show objectType; 16 import 'package:observe/src/observable.dart' show objectType;
17 import 'package:smoke/smoke.dart' as smoke;
16 18
17 /// A data-bound path starting from a view-model or model object, for example 19 /// A data-bound path starting from a view-model or model object, for example
18 /// `foo.bar.baz`. 20 /// `foo.bar.baz`.
19 /// 21 ///
20 /// When [open] is called, this will observe changes to the object and any 22 /// When [open] is called, this will observe changes to the object and any
21 /// intermediate object along the path, and send updated values accordingly. 23 /// intermediate object along the path, and send updated values accordingly.
22 /// When [close] is called it will stop observing the objects. 24 /// When [close] is called it will stop observing the objects.
23 /// 25 ///
24 /// This class is used to implement `Node.bind` and similar functionality in 26 /// This class is used to implement `Node.bind` and similar functionality in
25 /// the [template_binding](pub.dartlang.org/packages/template_binding) package. 27 /// the [template_binding](pub.dartlang.org/packages/template_binding) package.
26 class PathObserver extends _Observer implements Bindable { 28 class PathObserver extends _Observer implements Bindable {
27 PropertyPath _path; 29 PropertyPath _path;
28 Object _object; 30 Object _object;
29 _ObservedSet _directObserver; 31 _ObservedSet _directObserver;
30 32
31 /// Observes [path] on [object] for changes. This returns an object 33 /// 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. 34 /// that can be used to get the changes and get/set the value at this path.
33 /// 35 ///
34 /// The path can be a [PropertyPath], or a [String] used to construct it. 36 /// The path can be a [PropertyPath], or a [String] used to construct it.
35 /// 37 ///
36 /// See [open] and [value]. 38 /// See [open] and [value].
37 PathObserver(Object object, [path]) 39 PathObserver(Object object, [path])
38 : _object = object, 40 : _object = object,
39 _path = path is PropertyPath ? path : new PropertyPath(path); 41 _path = path is PropertyPath ? path : new PropertyPath(path);
40 42
41 bool get _isClosed => _path == null; 43 bool get _isClosed => _path == null;
42 44
43 /// Sets the value at this path. 45 /// Sets the value at this path.
44 @reflectable void set value(Object newValue) { 46 void set value(Object newValue) {
45 if (_path != null) _path.setValueFrom(_object, newValue); 47 if (_path != null) _path.setValueFrom(_object, newValue);
46 } 48 }
47 49
48 int get _reportArgumentCount => 2; 50 int get _reportArgumentCount => 2;
49 51
50 /// Initiates observation and returns the initial value. 52 /// Initiates observation and returns the initial value.
51 /// The callback will be passed the updated [value], and may optionally be 53 /// 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. 54 /// declared to take a second argument, which will contain the previous value.
53 open(callback) => super.open(callback); 55 open(callback) => super.open(callback);
54 56
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after
141 143
142 PropertyPath._(this._segments); 144 PropertyPath._(this._segments);
143 145
144 int get length => _segments.length; 146 int get length => _segments.length;
145 bool get isEmpty => _segments.isEmpty; 147 bool get isEmpty => _segments.isEmpty;
146 bool get isValid => true; 148 bool get isValid => true;
147 149
148 String toString() { 150 String toString() {
149 if (!isValid) return '<invalid path>'; 151 if (!isValid) return '<invalid path>';
150 return _segments 152 return _segments
151 .map((s) => s is Symbol ? MirrorSystem.getName(s) : s) 153 .map((s) => s is Symbol ? smoke.symbolToName(s) : s)
152 .join('.'); 154 .join('.');
153 } 155 }
154 156
155 bool operator ==(other) { 157 bool operator ==(other) {
156 if (identical(this, other)) return true; 158 if (identical(this, other)) return true;
157 if (other is! PropertyPath) return false; 159 if (other is! PropertyPath) return false;
158 if (isValid != other.isValid) return false; 160 if (isValid != other.isValid) return false;
159 161
160 int len = _segments.length; 162 int len = _segments.length;
161 if (len != other._segments.length) return false; 163 if (len != other._segments.length) return false;
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
222 224
223 bool get isValid => false; 225 bool get isValid => false;
224 _InvalidPropertyPath() : super._([]); 226 _InvalidPropertyPath() : super._([]);
225 } 227 }
226 228
227 bool _changeRecordMatches(record, key) { 229 bool _changeRecordMatches(record, key) {
228 if (record is PropertyChangeRecord) { 230 if (record is PropertyChangeRecord) {
229 return (record as PropertyChangeRecord).name == key; 231 return (record as PropertyChangeRecord).name == key;
230 } 232 }
231 if (record is MapChangeRecord) { 233 if (record is MapChangeRecord) {
232 if (key is Symbol) key = MirrorSystem.getName(key); 234 if (key is Symbol) key = smoke.symbolToName(key);
233 return (record as MapChangeRecord).key == key; 235 return (record as MapChangeRecord).key == key;
234 } 236 }
235 return false; 237 return false;
236 } 238 }
237 239
238 _getObjectProperty(object, property) { 240 _getObjectProperty(object, property) {
239 if (object == null) return null; 241 if (object == null) return null;
240 242
241 if (property is int) { 243 if (property is int) {
242 if (object is List && property >= 0 && property < object.length) { 244 if (object is List && property >= 0 && property < object.length) {
243 return object[property]; 245 return object[property];
244 } 246 }
245 } else if (property is Symbol) { 247 } else if (property is Symbol) {
246 var mirror = reflect(object); 248 final type = object.runtimeType;
247 final type = mirror.type;
248 try { 249 try {
249 if (_maybeHasGetter(type, property)) { 250 if (smoke.hasGetter(type, property) || smoke.hasNoSuchMethod(type)) {
250 return mirror.getField(property).reflectee; 251 return smoke.read(object, property);
251 } 252 }
252 // Support indexer if available, e.g. Maps or polymer_expressions Scope. 253 // Support indexer if available, e.g. Maps or polymer_expressions Scope.
253 // This is the default syntax used by polymer/nodebind and 254 // This is the default syntax used by polymer/nodebind and
254 // polymer/observe-js PathObserver. 255 // polymer/observe-js PathObserver.
255 if (_hasMethod(type, const Symbol('[]'))) { 256 if (smoke.hasInstanceMethod(type, const Symbol('[]'))) {
256 return object[MirrorSystem.getName(property)]; 257 return object[smoke.symbolToName(property)];
257 } 258 }
258 } on NoSuchMethodError catch (e) { 259 } on NoSuchMethodError catch (e) {
259 // Rethrow, unless the type implements noSuchMethod, in which case we 260 // Rethrow, unless the type implements noSuchMethod, in which case we
260 // interpret the exception as a signal that the method was not found. 261 // interpret the exception as a signal that the method was not found.
261 if (!_hasMethod(type, #noSuchMethod)) rethrow; 262 if (!smoke.hasNoSuchMethod(type)) rethrow;
262 } 263 }
263 } 264 }
264 265
265 if (_logger.isLoggable(Level.FINER)) { 266 if (_logger.isLoggable(Level.FINER)) {
266 _logger.finer("can't get $property in $object"); 267 _logger.finer("can't get $property in $object");
267 } 268 }
268 return null; 269 return null;
269 } 270 }
270 271
271 bool _setObjectProperty(object, property, value) { 272 bool _setObjectProperty(object, property, value) {
272 if (object == null) return false; 273 if (object == null) return false;
273 274
274 if (property is int) { 275 if (property is int) {
275 if (object is List && property >= 0 && property < object.length) { 276 if (object is List && property >= 0 && property < object.length) {
276 object[property] = value; 277 object[property] = value;
277 return true; 278 return true;
278 } 279 }
279 } else if (property is Symbol) { 280 } else if (property is Symbol) {
280 var mirror = reflect(object); 281 final type = object.runtimeType;
281 final type = mirror.type;
282 try { 282 try {
283 if (_maybeHasSetter(type, property)) { 283 if (smoke.hasSetter(type, property) || smoke.hasNoSuchMethod(type)) {
284 mirror.setField(property, value); 284 smoke.write(object, property, value);
285 return true; 285 return true;
286 } 286 }
287 // Support indexer if available, e.g. Maps or polymer_expressions Scope. 287 // Support indexer if available, e.g. Maps or polymer_expressions Scope.
288 if (_hasMethod(type, const Symbol('[]='))) { 288 if (smoke.hasInstanceMethod(type, const Symbol('[]='))) {
289 object[MirrorSystem.getName(property)] = value; 289 object[smoke.symbolToName(property)] = value;
290 return true; 290 return true;
291 } 291 }
292 } on NoSuchMethodError catch (e) { 292 } on NoSuchMethodError catch (e) {
293 if (!_hasMethod(type, #noSuchMethod)) rethrow; 293 if (!smoke.hasNoSuchMethod(type)) rethrow;
294 } 294 }
295 } 295 }
296 296
297 if (_logger.isLoggable(Level.FINER)) { 297 if (_logger.isLoggable(Level.FINER)) {
298 _logger.finer("can't set $property in $object"); 298 _logger.finer("can't set $property in $object");
299 } 299 }
300 return false; 300 return false;
301 } 301 }
302 302
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 303 // From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
355 304
356 final _pathRegExp = () { 305 final _pathRegExp = () {
357 const identStart = '[\$_a-zA-Z]'; 306 const identStart = '[\$_a-zA-Z]';
358 const identPart = '[\$_a-zA-Z0-9]'; 307 const identPart = '[\$_a-zA-Z0-9]';
359 const ident = '$identStart+$identPart*'; 308 const ident = '$identStart+$identPart*';
360 const elementIndex = '(?:[0-9]|[1-9]+[0-9]+)'; 309 const elementIndex = '(?:[0-9]|[1-9]+[0-9]+)';
361 const identOrElementIndex = '(?:$ident|$elementIndex)'; 310 const identOrElementIndex = '(?:$ident|$elementIndex)';
362 const path = '(?:$identOrElementIndex)(?:\\.$identOrElementIndex)*'; 311 const path = '(?:$identOrElementIndex)(?:\\.$identOrElementIndex)*';
363 return new RegExp('^$path\$'); 312 return new RegExp('^$path\$');
(...skipping 199 matching lines...) Expand 10 before | Expand all | Expand 10 after
563 } 512 }
564 513
565 _notifyCallback = callback; 514 _notifyCallback = callback;
566 _notifyArgumentCount = min(_reportArgumentCount, 515 _notifyArgumentCount = min(_reportArgumentCount,
567 _maxArgumentCount(callback)); 516 _maxArgumentCount(callback));
568 517
569 _connect(); 518 _connect();
570 return _value; 519 return _value;
571 } 520 }
572 521
573 @reflectable get value { 522 get value {
574 _check(skipChanges: true); 523 _check(skipChanges: true);
575 return _value; 524 return _value;
576 } 525 }
577 526
578 void close() { 527 void close() {
579 if (!_isOpen) return; 528 if (!_isOpen) return;
580 529
581 _disconnect(); 530 _disconnect();
582 _value = null; 531 _value = null;
583 _notifyCallback = null; 532 _notifyCallback = null;
(...skipping 145 matching lines...) Expand 10 before | Expand all | Expand 10 after
729 for (var observer in _observers.values.toList(growable: false)) { 678 for (var observer in _observers.values.toList(growable: false)) {
730 if (observer._isOpen) observer._check(); 679 if (observer._isOpen) observer._check();
731 } 680 }
732 681
733 _resetNeeded = true; 682 _resetNeeded = true;
734 scheduleMicrotask(reset); 683 scheduleMicrotask(reset);
735 } 684 }
736 } 685 }
737 686
738 const int _MAX_DIRTY_CHECK_CYCLES = 1000; 687 const int _MAX_DIRTY_CHECK_CYCLES = 1000;
OLDNEW
« no previous file with comments | « pkg/observe/lib/src/observable.dart ('k') | pkg/observe/pubspec.yaml » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698