| OLD | NEW |
| (Empty) | |
| 1 library angular.watch_group; |
| 2 |
| 3 import 'dart:mirrors'; |
| 4 import 'package:angular/change_detection/change_detection.dart'; |
| 5 |
| 6 part 'linked_list.dart'; |
| 7 part 'ast.dart'; |
| 8 part 'prototype_map.dart'; |
| 9 |
| 10 typedef ReactionFn(value, previousValue); |
| 11 typedef ChangeLog(String expression, current, previous); |
| 12 |
| 13 /** |
| 14 * Extend this class if you wish to pretend to be a function, but you don't know |
| 15 * number of arguments with which the function will get called. |
| 16 */ |
| 17 abstract class FunctionApply { |
| 18 // dartbug.com/16401 |
| 19 // dynamic call() { throw new StateError('Use apply()'); } |
| 20 dynamic apply(List arguments); |
| 21 } |
| 22 |
| 23 /** |
| 24 * [WatchGroup] is a logical grouping of a set of watches. [WatchGroup]s are |
| 25 * organized into a hierarchical tree parent-children configuration. |
| 26 * [WatchGroup] builds upon [ChangeDetector] and adds expression (field chains |
| 27 * as in `a.b.c`) support as well as support function/closure/method (function |
| 28 * invocation as in `a.b()`) watching. |
| 29 */ |
| 30 class WatchGroup implements _EvalWatchList, _WatchGroupList { |
| 31 /** A unique ID for the WatchGroup */ |
| 32 final String id; |
| 33 /** |
| 34 * A marker to be inserted when a group has no watches. We need the marker to |
| 35 * hold our position information in the linked list of all [Watch]es. |
| 36 */ |
| 37 final _EvalWatchRecord _marker = new _EvalWatchRecord.marker(); |
| 38 |
| 39 /** All Expressions are evaluated against a context object. */ |
| 40 final Object context; |
| 41 |
| 42 /** [ChangeDetector] used for field watching */ |
| 43 final ChangeDetectorGroup<_Handler> _changeDetector; |
| 44 /** A cache for sharing sub expression watching. Watching `a` and `a.b` will |
| 45 * watch `a` only once. */ |
| 46 final Map<String, WatchRecord<_Handler>> _cache; |
| 47 final RootWatchGroup _rootGroup; |
| 48 |
| 49 /// STATS: Number of field watchers which are in use. |
| 50 int _fieldCost = 0; |
| 51 int _collectionCost = 0; |
| 52 int _evalCost = 0; |
| 53 |
| 54 /// STATS: Number of field watchers which are in use including child [WatchGro
up]s. |
| 55 int get fieldCost => _fieldCost; |
| 56 int get totalFieldCost { |
| 57 var cost = _fieldCost; |
| 58 WatchGroup group = _watchGroupHead; |
| 59 while(group != null) { |
| 60 cost += group.totalFieldCost; |
| 61 group = group._nextWatchGroup; |
| 62 } |
| 63 return cost; |
| 64 } |
| 65 |
| 66 /// STATS: Number of collection watchers which are in use including child [Wat
chGroup]s. |
| 67 int get collectionCost => _collectionCost; |
| 68 int get totalCollectionCost { |
| 69 var cost = _collectionCost; |
| 70 WatchGroup group = _watchGroupHead; |
| 71 while(group != null) { |
| 72 cost += group.totalCollectionCost; |
| 73 group = group._nextWatchGroup; |
| 74 } |
| 75 return cost; |
| 76 } |
| 77 |
| 78 /// STATS: Number of invocation watchers (closures/methods) which are in use. |
| 79 int get evalCost => _evalCost; |
| 80 |
| 81 /// STATS: Number of invocation watchers which are in use including child [Wat
chGroup]s. |
| 82 int get totalEvalCost { |
| 83 var cost = _evalCost; |
| 84 WatchGroup group = _watchGroupHead; |
| 85 while(group != null) { |
| 86 cost += group.evalCost; |
| 87 group = group._nextWatchGroup; |
| 88 } |
| 89 return cost; |
| 90 } |
| 91 |
| 92 int _nextChildId = 0; |
| 93 _EvalWatchRecord _evalWatchHead, _evalWatchTail; |
| 94 /// Pointer for creating tree of [WatchGroup]s. |
| 95 WatchGroup _watchGroupHead, _watchGroupTail, _previousWatchGroup, |
| 96 _nextWatchGroup; |
| 97 WatchGroup _parentWatchGroup; |
| 98 |
| 99 WatchGroup._child(_parentWatchGroup, this._changeDetector, this.context, |
| 100 this._cache, this._rootGroup) |
| 101 : _parentWatchGroup = _parentWatchGroup, |
| 102 id = '${_parentWatchGroup.id}.${_parentWatchGroup._nextChildId++}' |
| 103 { |
| 104 _marker.watchGrp = this; |
| 105 _evalWatchTail = _evalWatchHead = _marker; |
| 106 } |
| 107 |
| 108 WatchGroup._root(this._changeDetector, this.context) |
| 109 : id = '', |
| 110 _rootGroup = null, |
| 111 _parentWatchGroup = null, |
| 112 _cache = new Map<String, WatchRecord<_Handler>>() |
| 113 { |
| 114 _marker.watchGrp = this; |
| 115 _evalWatchTail = _evalWatchHead = _marker; |
| 116 } |
| 117 |
| 118 get isAttached { |
| 119 var group = this; |
| 120 var root = _rootGroup; |
| 121 while(group != null) { |
| 122 if (group == root){ |
| 123 return true; |
| 124 } |
| 125 group = group._parentWatchGroup; |
| 126 } |
| 127 return false; |
| 128 } |
| 129 |
| 130 Watch watch(AST expression, ReactionFn reactionFn) { |
| 131 WatchRecord<_Handler> watchRecord = |
| 132 _cache.putIfAbsent(expression.expression, |
| 133 () => expression.setupWatch(this)); |
| 134 return watchRecord.handler.addReactionFn(reactionFn); |
| 135 } |
| 136 |
| 137 /** |
| 138 * Watch a [name] field on [lhs] represented by [expression]. |
| 139 * |
| 140 * - [name] the field to watch. |
| 141 * - [lhs] left-hand-side of the field. |
| 142 */ |
| 143 WatchRecord<_Handler> addFieldWatch(AST lhs, String name, String expression) { |
| 144 var fieldHandler = new _FieldHandler(this, expression); |
| 145 |
| 146 // Create a ChangeRecord for the current field and assign the change record |
| 147 // to the handler. |
| 148 var watchRecord = _changeDetector.watch(null, name, fieldHandler); |
| 149 _fieldCost++; |
| 150 fieldHandler.watchRecord = watchRecord; |
| 151 |
| 152 WatchRecord<_Handler> lhsWR = _cache.putIfAbsent(lhs.expression, |
| 153 () => lhs.setupWatch(this)); |
| 154 |
| 155 // We set a field forwarding handler on LHS. This will allow the change |
| 156 // objects to propagate to the current WatchRecord. |
| 157 lhsWR.handler.addForwardHandler(fieldHandler); |
| 158 |
| 159 // propagate the value from the LHS to here |
| 160 fieldHandler.acceptValue(lhsWR.currentValue); |
| 161 return watchRecord; |
| 162 } |
| 163 |
| 164 WatchRecord<_Handler> addCollectionWatch(AST ast) { |
| 165 var collectionHandler = new _CollectionHandler(this, ast.expression); |
| 166 var watchRecord = _changeDetector.watch(null, null, collectionHandler); |
| 167 _collectionCost++; |
| 168 collectionHandler.watchRecord = watchRecord; |
| 169 WatchRecord<_Handler> astWR = _cache.putIfAbsent(ast.expression, |
| 170 () => ast.setupWatch(this)); |
| 171 |
| 172 // We set a field forwarding handler on LHS. This will allow the change |
| 173 // objects to propagate to the current WatchRecord. |
| 174 astWR.handler.addForwardHandler(collectionHandler); |
| 175 |
| 176 // propagate the value from the LHS to here |
| 177 collectionHandler.acceptValue(astWR.currentValue); |
| 178 return watchRecord; |
| 179 } |
| 180 |
| 181 /** |
| 182 * Watch a [fn] function represented by an [expression]. |
| 183 * |
| 184 * - [fn] function to evaluate. |
| 185 * - [argsAST] list of [AST]es which represent arguments passed to function. |
| 186 * - [expression] normalized expression used for caching. |
| 187 */ |
| 188 _EvalWatchRecord addFunctionWatch(/* dartbug.com/16401 Function */ fn, List<AS
T> argsAST, |
| 189 String expression) => |
| 190 _addEvalWatch(null, fn, null, argsAST, expression); |
| 191 |
| 192 /** |
| 193 * Watch a method [name]ed represented by an [expression]. |
| 194 * |
| 195 * - [lhs] left-hand-side of the method. |
| 196 * - [name] name of the method. |
| 197 * - [argsAST] list of [AST]es which represent arguments passed to method. |
| 198 * - [expression] normalized expression used for caching. |
| 199 */ |
| 200 _EvalWatchRecord addMethodWatch(AST lhs, String name, List<AST> argsAST, |
| 201 String expression) => |
| 202 _addEvalWatch(lhs, null, name, argsAST, expression); |
| 203 |
| 204 |
| 205 |
| 206 _EvalWatchRecord _addEvalWatch(AST lhsAST, /* dartbug.com/16401 Function */ fn
, String name, |
| 207 List<AST> argsAST, String expression) { |
| 208 _InvokeHandler invokeHandler = new _InvokeHandler(this, expression); |
| 209 var evalWatchRecord = new _EvalWatchRecord(this, invokeHandler, fn, name, |
| 210 argsAST.length); |
| 211 invokeHandler.watchRecord = evalWatchRecord; |
| 212 |
| 213 if (lhsAST != null) { |
| 214 var lhsWR = _cache.putIfAbsent(lhsAST.expression, |
| 215 () => lhsAST.setupWatch(this)); |
| 216 lhsWR.handler.addForwardHandler(invokeHandler); |
| 217 invokeHandler.acceptValue(lhsWR.currentValue); |
| 218 } |
| 219 |
| 220 // Convert the args from AST to WatchRecords |
| 221 var i = 0; |
| 222 argsAST. |
| 223 map((ast) => _cache.putIfAbsent(ast.expression, |
| 224 () => ast.setupWatch(this))).forEach((WatchRecord<_Handler> record) { |
| 225 var argHandler = new _ArgHandler(this, evalWatchRecord, i++); |
| 226 _ArgHandlerList._add(invokeHandler, argHandler); |
| 227 record.handler.addForwardHandler(argHandler); |
| 228 argHandler.acceptValue(record.currentValue); |
| 229 }); |
| 230 |
| 231 // Must be done last |
| 232 _EvalWatchList._add(this, evalWatchRecord); |
| 233 _evalCost++; |
| 234 |
| 235 return evalWatchRecord; |
| 236 } |
| 237 |
| 238 WatchGroup get _childWatchGroupTail { |
| 239 var tail = this, nextTail; |
| 240 while ((nextTail = tail._watchGroupTail) != null) { |
| 241 tail = nextTail; |
| 242 } |
| 243 return tail; |
| 244 } |
| 245 |
| 246 /** |
| 247 * Create a new child [WatchGroup]. |
| 248 * |
| 249 * - [context] if present the the child [WatchGroup] expressions will evaluate |
| 250 * against the new [context]. If not present than child expressions will |
| 251 * evaluate on same context allowing the reuse of the expression cache. |
| 252 */ |
| 253 WatchGroup newGroup([Object context]) { |
| 254 _EvalWatchRecord prev = _childWatchGroupTail._evalWatchTail; |
| 255 _EvalWatchRecord next = prev._nextEvalWatch; |
| 256 var childGroup = new WatchGroup._child( |
| 257 this, |
| 258 _changeDetector.newGroup(), |
| 259 context == null ? this.context : context, |
| 260 context == null ? this._cache: <String, WatchRecord<_Handler>>{}, |
| 261 _rootGroup == null ? this : _rootGroup); |
| 262 _WatchGroupList._add(this, childGroup); |
| 263 var marker = childGroup._marker; |
| 264 |
| 265 marker._previousEvalWatch = prev; |
| 266 marker._nextEvalWatch = next; |
| 267 if (prev != null) prev._nextEvalWatch = marker; |
| 268 if (next != null) next._previousEvalWatch = marker; |
| 269 |
| 270 return childGroup; |
| 271 } |
| 272 |
| 273 /** |
| 274 * Remove/destroy [WatchGroup] and all of its [Watches]. |
| 275 */ |
| 276 void remove() { |
| 277 // TODO:(misko) This code is not right. |
| 278 // 1) It fails to release [ChangeDetector] [WatchRecord]s. |
| 279 // 2) it needs to cleanup caches if the cache is being shared. |
| 280 |
| 281 _WatchGroupList._remove(_parentWatchGroup, this); |
| 282 _changeDetector.remove(); |
| 283 _rootGroup._removeCount++; |
| 284 _parentWatchGroup = null; |
| 285 |
| 286 // Unlink the _watchRecord |
| 287 _EvalWatchRecord firstEvalWatch = _evalWatchHead; |
| 288 _EvalWatchRecord lastEvalWatch = |
| 289 (_watchGroupTail == null ? this : _watchGroupTail)._evalWatchTail; |
| 290 _EvalWatchRecord previous = firstEvalWatch._previousEvalWatch; |
| 291 _EvalWatchRecord next = lastEvalWatch._nextEvalWatch; |
| 292 if (previous != null) previous._nextEvalWatch = next; |
| 293 if (next != null) next._previousEvalWatch = previous; |
| 294 } |
| 295 |
| 296 toString() { |
| 297 var lines = []; |
| 298 if (this == _rootGroup) { |
| 299 var allWatches = []; |
| 300 var watch = _evalWatchHead; |
| 301 var prev = null; |
| 302 while (watch != null) { |
| 303 allWatches.add(watch.toString()); |
| 304 assert(watch._previousEvalWatch == prev); |
| 305 prev = watch; |
| 306 watch = watch._nextEvalWatch; |
| 307 } |
| 308 lines.add('WATCHES: ${allWatches.join(', ')}'); |
| 309 } |
| 310 |
| 311 var watches = []; |
| 312 var watch = _evalWatchHead; |
| 313 while (watch != _evalWatchTail) { |
| 314 watches.add(watch.toString()); |
| 315 watch = watch._nextEvalWatch; |
| 316 } |
| 317 watches.add(watch.toString()); |
| 318 |
| 319 lines.add('WatchGroup[$id](watches: ${watches.join(', ')})'); |
| 320 var childGroup = _watchGroupHead; |
| 321 while (childGroup != null) { |
| 322 lines.add(' ' + childGroup.toString().split('\n').join('\n ')); |
| 323 childGroup = childGroup._nextWatchGroup; |
| 324 } |
| 325 return lines.join('\n'); |
| 326 } |
| 327 } |
| 328 |
| 329 /** |
| 330 * [RootWatchGroup] |
| 331 */ |
| 332 class RootWatchGroup extends WatchGroup { |
| 333 Watch _dirtyWatchHead, _dirtyWatchTail; |
| 334 |
| 335 /** |
| 336 * Every time a [WatchGroup] is destroyed we increment the counter. During |
| 337 * [detectChanges] we reset the count. Before calling the reaction function, |
| 338 * we check [_removeCount] and if it is unchanged we can safely call the |
| 339 * reaction function. If it is changed we only call the reaction function |
| 340 * if the [WatchGroup] is still attached. |
| 341 */ |
| 342 int _removeCount = 0; |
| 343 |
| 344 |
| 345 RootWatchGroup(ChangeDetector changeDetector, Object context): |
| 346 super._root(changeDetector, context); |
| 347 |
| 348 RootWatchGroup get _rootGroup => this; |
| 349 |
| 350 /** |
| 351 * Detect changes and process the [ReactionFn]s. |
| 352 * |
| 353 * Algorithm: |
| 354 * 1) process the [ChangeDetector#collectChanges]. |
| 355 * 2) process function/closure/method changes |
| 356 * 3) call an [ReactionFn]s |
| 357 * |
| 358 * Each step is called in sequence. ([ReactionFn]s are not called until all |
| 359 * previous steps are completed). |
| 360 */ |
| 361 int detectChanges({ EvalExceptionHandler exceptionHandler, |
| 362 ChangeLog changeLog, |
| 363 AvgStopwatch fieldStopwatch, |
| 364 AvgStopwatch evalStopwatch, |
| 365 AvgStopwatch processStopwatch}) { |
| 366 // Process the ChangeRecords from the change detector |
| 367 ChangeRecord<_Handler> changeRecord = |
| 368 (_changeDetector as ChangeDetector<_Handler>).collectChanges( |
| 369 exceptionHandler:exceptionHandler, |
| 370 stopwatch: fieldStopwatch); |
| 371 if (processStopwatch != null) processStopwatch.start(); |
| 372 while (changeRecord != null) { |
| 373 if (changeLog != null) changeLog(changeRecord.handler.expression, |
| 374 changeRecord.currentValue, |
| 375 changeRecord.previousValue); |
| 376 changeRecord.handler.onChange(changeRecord); |
| 377 changeRecord = changeRecord.nextChange; |
| 378 } |
| 379 if (processStopwatch != null) processStopwatch.stop(); |
| 380 |
| 381 if (evalStopwatch != null) evalStopwatch.start(); |
| 382 // Process our own function evaluations |
| 383 _EvalWatchRecord evalRecord = _evalWatchHead; |
| 384 int evalCount = 0; |
| 385 while (evalRecord != null) { |
| 386 try { |
| 387 if (evalStopwatch != null) evalCount++; |
| 388 var change = evalRecord.check(); |
| 389 if (change != null && changeLog != null) { |
| 390 changeLog(evalRecord.handler.expression, |
| 391 evalRecord.currentValue, |
| 392 evalRecord.previousValue); |
| 393 } |
| 394 } catch (e, s) { |
| 395 if (exceptionHandler == null) rethrow; else exceptionHandler(e, s); |
| 396 } |
| 397 evalRecord = evalRecord._nextEvalWatch; |
| 398 } |
| 399 if (evalStopwatch != null) evalStopwatch..stop()..increment(evalCount); |
| 400 |
| 401 // Because the handler can forward changes between each other synchronously |
| 402 // We need to call reaction functions asynchronously. This processes the |
| 403 // asynchronous reaction function queue. |
| 404 int count = 0; |
| 405 if (processStopwatch != null) processStopwatch.stop(); |
| 406 Watch dirtyWatch = _dirtyWatchHead; |
| 407 RootWatchGroup root = _rootGroup; |
| 408 root._removeCount = 0; |
| 409 while(dirtyWatch != null) { |
| 410 count++; |
| 411 try { |
| 412 if (root._removeCount == 0 || dirtyWatch._watchGroup.isAttached) { |
| 413 dirtyWatch.invoke(); |
| 414 } |
| 415 } catch (e, s) { |
| 416 if (exceptionHandler == null) rethrow; else exceptionHandler(e, s); |
| 417 } |
| 418 dirtyWatch = dirtyWatch._nextDirtyWatch; |
| 419 } |
| 420 _dirtyWatchHead = _dirtyWatchTail = null; |
| 421 if (processStopwatch != null) processStopwatch..stop()..increment(count); |
| 422 return count; |
| 423 } |
| 424 |
| 425 /** |
| 426 * Add Watch into the asynchronous queue for later processing. |
| 427 */ |
| 428 Watch _addDirtyWatch(Watch watch) { |
| 429 if (!watch._dirty) { |
| 430 watch._dirty = true; |
| 431 if (_dirtyWatchTail == null) { |
| 432 _dirtyWatchHead = _dirtyWatchTail = watch; |
| 433 } else { |
| 434 _dirtyWatchTail._nextDirtyWatch = watch; |
| 435 _dirtyWatchTail = watch; |
| 436 } |
| 437 watch._nextDirtyWatch = null; |
| 438 } |
| 439 return watch; |
| 440 } |
| 441 } |
| 442 |
| 443 /** |
| 444 * [Watch] corresponds to an individual [watch] registration on the watchGrp. |
| 445 */ |
| 446 class Watch { |
| 447 Watch _previousWatch, _nextWatch; |
| 448 |
| 449 final Record<_Handler> _record; |
| 450 final ReactionFn reactionFn; |
| 451 final WatchGroup _watchGroup; |
| 452 |
| 453 bool _dirty = false; |
| 454 bool _deleted = false; |
| 455 Watch _nextDirtyWatch; |
| 456 |
| 457 Watch(this._watchGroup, this._record, this.reactionFn); |
| 458 |
| 459 get expression => _record.handler.expression; |
| 460 void invoke() { |
| 461 if (_deleted || !_dirty) return; |
| 462 _dirty = false; |
| 463 reactionFn(_record.currentValue, _record.previousValue); |
| 464 } |
| 465 |
| 466 void remove() { |
| 467 if (_deleted) throw new StateError('Already deleted!'); |
| 468 _deleted = true; |
| 469 var handler = _record.handler; |
| 470 _WatchList._remove(handler, this); |
| 471 handler.release(); |
| 472 } |
| 473 } |
| 474 |
| 475 /** |
| 476 * This class processes changes from the change detector. The changes are |
| 477 * forwarded onto the next [_Handler] or queued up in case of reaction function. |
| 478 * |
| 479 * Given these two expression: 'a.b.c' => rfn1 and 'a.b' => rfn2 |
| 480 * The resulting data structure is: |
| 481 * |
| 482 * _Handler +--> _Handler +--> _Handler |
| 483 * - delegateHandler -+ - delegateHandler -+ - delegateHandler = nul
l |
| 484 * - expression: 'a' - expression: 'a.b' - expression: 'a.b.c' |
| 485 * - watchObject: context - watchObject: context.a - watchObject: context.
a.b |
| 486 * - watchRecord: 'a' - watchRecord 'b' - watchRecord 'c' |
| 487 * - reactionFn: null - reactionFn: rfn1 - reactionFn: rfn2 |
| 488 * |
| 489 * Notice how the [_Handler]s coalesce their watching. Also notice that any |
| 490 * changes detected at one handler are propagated to the next handler. |
| 491 */ |
| 492 abstract class _Handler implements _LinkedList, _LinkedListItem, _WatchList { |
| 493 _Handler _head, _tail; |
| 494 _Handler _next, _previous; |
| 495 Watch _watchHead, _watchTail; |
| 496 |
| 497 final String expression; |
| 498 final WatchGroup watchGrp; |
| 499 |
| 500 WatchRecord<_Handler> watchRecord; |
| 501 _Handler forwardingHandler; |
| 502 |
| 503 _Handler(this.watchGrp, this.expression) { |
| 504 assert(watchGrp != null); |
| 505 assert(expression != null); |
| 506 } |
| 507 |
| 508 Watch addReactionFn(ReactionFn reactionFn) { |
| 509 assert(_next != this); // verify we are not detached |
| 510 return watchGrp._rootGroup._addDirtyWatch(_WatchList._add(this, |
| 511 new Watch(watchGrp, watchRecord, reactionFn))); |
| 512 } |
| 513 |
| 514 void addForwardHandler(_Handler forwardToHandler) { |
| 515 assert(forwardToHandler.forwardingHandler == null); |
| 516 _LinkedList._add(this, forwardToHandler); |
| 517 forwardToHandler.forwardingHandler = this; |
| 518 } |
| 519 |
| 520 void release() { |
| 521 if (_WatchList._isEmpty(this) && _LinkedList._isEmpty(this)) { |
| 522 _releaseWatch(); |
| 523 // Remove ourselves from cache, or else new registrations will go to us, |
| 524 // but we are dead |
| 525 watchGrp._cache.remove(expression); |
| 526 |
| 527 if (forwardingHandler != null) { |
| 528 // TODO(misko): why do we need this check? |
| 529 _LinkedList._remove(forwardingHandler, this); |
| 530 forwardingHandler.release(); |
| 531 } |
| 532 |
| 533 // We can remove ourselves |
| 534 assert((_next = _previous = this) == this); // mark ourselves as detached |
| 535 } |
| 536 } |
| 537 |
| 538 void _releaseWatch() { |
| 539 watchRecord.remove(); |
| 540 watchGrp._fieldCost--; |
| 541 } |
| 542 acceptValue(object) => null; |
| 543 |
| 544 void onChange(ChangeRecord<_Handler> record) { |
| 545 assert(_next != this); // verify we are not detached |
| 546 // If we have reaction functions than queue them up for asynchronous |
| 547 // processing. |
| 548 Watch watch = _watchHead; |
| 549 while(watch != null) { |
| 550 watchGrp._rootGroup._addDirtyWatch(watch); |
| 551 watch = watch._nextWatch; |
| 552 } |
| 553 // If we have a delegateHandler then forward the new value to it. |
| 554 _Handler delegateHandler = _head; |
| 555 while (delegateHandler != null) { |
| 556 delegateHandler.acceptValue(record.currentValue); |
| 557 delegateHandler = delegateHandler._next; |
| 558 } |
| 559 } |
| 560 } |
| 561 |
| 562 class _ConstantHandler extends _Handler { |
| 563 _ConstantHandler(WatchGroup watchGroup, String expression, dynamic constantVal
ue) |
| 564 : super(watchGroup, expression) |
| 565 { |
| 566 watchRecord = new _EvalWatchRecord.constant(this, constantValue); |
| 567 } |
| 568 release() => null; |
| 569 } |
| 570 |
| 571 class _FieldHandler extends _Handler { |
| 572 _FieldHandler(watchGrp, expression): super(watchGrp, expression); |
| 573 |
| 574 /** |
| 575 * This function forwards the watched object to the next [_Handler] |
| 576 * synchronously. |
| 577 */ |
| 578 void acceptValue(object) { |
| 579 watchRecord.object = object; |
| 580 var changeRecord = watchRecord.check(); |
| 581 if (changeRecord != null) onChange(changeRecord); |
| 582 } |
| 583 } |
| 584 |
| 585 class _CollectionHandler extends _Handler { |
| 586 _CollectionHandler(WatchGroup watchGrp, String expression) |
| 587 : super(watchGrp, expression); |
| 588 /** |
| 589 * This function forwards the watched object to the next [_Handler] synchronou
sly. |
| 590 */ |
| 591 void acceptValue(object) { |
| 592 watchRecord.object = object; |
| 593 var changeRecord = watchRecord.check(); |
| 594 if (changeRecord != null) onChange(changeRecord); |
| 595 } |
| 596 |
| 597 void _releaseWatch() { |
| 598 watchRecord.remove(); |
| 599 watchGrp._collectionCost--; |
| 600 } |
| 601 } |
| 602 |
| 603 class _ArgHandler extends _Handler { |
| 604 _ArgHandler _previousArgHandler, _nextArgHandler; |
| 605 |
| 606 // TODO(misko): Why do we override parent? |
| 607 final _EvalWatchRecord watchRecord; |
| 608 final int index; |
| 609 |
| 610 _releaseWatch() => null; |
| 611 |
| 612 _ArgHandler(WatchGroup watchGrp, this.watchRecord, int index) |
| 613 : index = index, |
| 614 super(watchGrp, 'arg[$index]'); |
| 615 |
| 616 void acceptValue(object) { |
| 617 watchRecord.dirtyArgs = true; |
| 618 watchRecord.args[index] = object; |
| 619 } |
| 620 } |
| 621 |
| 622 class _InvokeHandler extends _Handler implements _ArgHandlerList { |
| 623 _ArgHandler _argHandlerHead, _argHandlerTail; |
| 624 |
| 625 _InvokeHandler(WatchGroup watchGrp, String expression) |
| 626 : super(watchGrp, expression); |
| 627 |
| 628 void acceptValue(object) { |
| 629 watchRecord.object = object; |
| 630 } |
| 631 |
| 632 void onChange(ChangeRecord<_Handler> record) { |
| 633 super.onChange(record); |
| 634 } |
| 635 |
| 636 void _releaseWatch() { |
| 637 (watchRecord as _EvalWatchRecord).remove(); |
| 638 } |
| 639 |
| 640 void release() { |
| 641 super.release(); |
| 642 _ArgHandler current = _argHandlerHead; |
| 643 while(current != null) { |
| 644 current.release(); |
| 645 current = current._nextArgHandler; |
| 646 } |
| 647 } |
| 648 } |
| 649 |
| 650 |
| 651 class _EvalWatchRecord implements WatchRecord<_Handler>, ChangeRecord<_Handler>
{ |
| 652 static const int _MODE_DELETED_ = -1; |
| 653 static const int _MODE_MARKER_ = 0; |
| 654 static const int _MODE_FUNCTION_ = 1; |
| 655 static const int _MODE_FUNCTION_APPLY_ = 2; |
| 656 static const int _MODE_NULL_ = 3; |
| 657 static const int _MODE_FIELD_CLOSURE_ = 4; |
| 658 static const int _MODE_MAP_CLOSURE_ = 5; |
| 659 static const int _MODE_METHOD_ = 6; |
| 660 WatchGroup watchGrp; |
| 661 final _Handler handler; |
| 662 final List args; |
| 663 final Symbol symbol; |
| 664 final String name; |
| 665 int mode; |
| 666 /* dartbug.com/16401 Function*/ var fn; |
| 667 InstanceMirror _instanceMirror; |
| 668 bool dirtyArgs = true; |
| 669 |
| 670 dynamic currentValue, previousValue, _object; |
| 671 _EvalWatchRecord _previousEvalWatch, _nextEvalWatch; |
| 672 |
| 673 _EvalWatchRecord(this.watchGrp, this.handler, this.fn, name, int arity) |
| 674 : args = new List(arity), |
| 675 name = name, |
| 676 symbol = name == null ? null : new Symbol(name) { |
| 677 if (fn is FunctionApply) { |
| 678 mode = _MODE_FUNCTION_APPLY_; |
| 679 } else if (fn is Function) { |
| 680 mode = _MODE_FUNCTION_; |
| 681 } else { |
| 682 mode = _MODE_NULL_; |
| 683 } |
| 684 } |
| 685 |
| 686 _EvalWatchRecord.marker() |
| 687 : mode = _MODE_MARKER_, |
| 688 watchGrp = null, |
| 689 handler = null, |
| 690 args = null, |
| 691 fn = null, |
| 692 symbol = null, |
| 693 name = null; |
| 694 |
| 695 _EvalWatchRecord.constant(_Handler handler, dynamic constantValue) |
| 696 : mode = _MODE_MARKER_, |
| 697 handler = handler, |
| 698 currentValue = constantValue, |
| 699 watchGrp = null, |
| 700 args = null, |
| 701 fn = null, |
| 702 symbol = null, |
| 703 name = null; |
| 704 |
| 705 get field => '()'; |
| 706 |
| 707 get object => _object; |
| 708 |
| 709 set object(value) { |
| 710 assert(mode != _MODE_DELETED_); |
| 711 assert(mode != _MODE_MARKER_); |
| 712 assert(mode != _MODE_FUNCTION_); |
| 713 assert(mode != _MODE_FUNCTION_APPLY_); |
| 714 assert(symbol != null); |
| 715 _object = value; |
| 716 |
| 717 if (value == null) { |
| 718 mode = _MODE_NULL_; |
| 719 } else { |
| 720 if (value is Map) { |
| 721 mode = _MODE_MAP_CLOSURE_; |
| 722 } else { |
| 723 _instanceMirror = reflect(value); |
| 724 mode = _hasMethod(_instanceMirror.type, symbol) |
| 725 ? _MODE_METHOD_ |
| 726 : _MODE_FIELD_CLOSURE_; |
| 727 } |
| 728 } |
| 729 } |
| 730 |
| 731 ChangeRecord<_Handler> check() { |
| 732 var value; |
| 733 switch (mode) { |
| 734 case _MODE_MARKER_: |
| 735 case _MODE_NULL_: |
| 736 return null; |
| 737 case _MODE_FUNCTION_: |
| 738 if (!dirtyArgs) return null; |
| 739 value = Function.apply(fn, args); |
| 740 dirtyArgs = false; |
| 741 break; |
| 742 case _MODE_FUNCTION_APPLY_: |
| 743 if (!dirtyArgs) return null; |
| 744 value = (fn as FunctionApply).apply(args); |
| 745 dirtyArgs = false; |
| 746 break; |
| 747 case _MODE_FIELD_CLOSURE_: |
| 748 var closure = _instanceMirror.getField(symbol).reflectee; |
| 749 value = closure == null ? null : Function.apply(closure, args); |
| 750 break; |
| 751 case _MODE_MAP_CLOSURE_: |
| 752 var closure = object[name]; |
| 753 value = closure == null ? null : Function.apply(closure, args); |
| 754 break; |
| 755 case _MODE_METHOD_: |
| 756 value = _instanceMirror.invoke(symbol, args).reflectee; |
| 757 break; |
| 758 default: |
| 759 assert(false); |
| 760 } |
| 761 |
| 762 var current = currentValue; |
| 763 if (!identical(current, value)) { |
| 764 if (value is String && current is String && value == current) { |
| 765 // it is really the same, recover and save so next time identity is same |
| 766 current = value; |
| 767 } else { |
| 768 previousValue = current; |
| 769 currentValue = value; |
| 770 handler.onChange(this); |
| 771 return this; |
| 772 } |
| 773 } |
| 774 return null; |
| 775 } |
| 776 |
| 777 get nextChange => null; |
| 778 |
| 779 void remove() { |
| 780 assert(mode != _MODE_DELETED_); |
| 781 assert((mode = _MODE_DELETED_) == _MODE_DELETED_); // Mark as deleted. |
| 782 watchGrp._evalCost--; |
| 783 _EvalWatchList._remove(watchGrp, this); |
| 784 } |
| 785 |
| 786 String toString() { |
| 787 if (mode == _MODE_MARKER_) return 'MARKER[$currentValue]'; |
| 788 return '${watchGrp.id}:${handler.expression}'; |
| 789 } |
| 790 |
| 791 static final Function _hasMethod = (() { |
| 792 var objectClassMirror = reflectClass(Object); |
| 793 Set<Symbol> objectClassInstanceMethods = |
| 794 new Set<Symbol>.from([#toString, #noSuchMethod]); |
| 795 try { |
| 796 // Use ClassMirror.instanceMembers if available. It contains local |
| 797 // as well as inherited members. |
| 798 objectClassMirror.instanceMembers; |
| 799 // For SDK 1.2 we have to use a somewhat complicated helper for this |
| 800 // to work around bugs in the dart2js implementation. |
| 801 return (type, symbol) { |
| 802 // Always allow instance methods found in the Object class. This makes |
| 803 // it easier to work around a few bugs in the dart2js implementation. |
| 804 if (objectClassInstanceMethods.contains(symbol)) return true; |
| 805 // Work around http://dartbug.com/16309 which causes access to the |
| 806 // instance members of certain builtin types to throw exceptions |
| 807 // while traversing the superclass chain. |
| 808 var mirror; |
| 809 try { |
| 810 mirror = type.instanceMembers[symbol]; |
| 811 } on UnsupportedError catch (e) { |
| 812 mirror = type.declarations[symbol]; |
| 813 } |
| 814 // Work around http://dartbug.com/15760 which causes noSuchMethod |
| 815 // forwarding stubs to be treated as members of all classes. We have |
| 816 // already checked for the real instance methods in Object, so if the |
| 817 // owner of this method is Object we simply filter it out. |
| 818 if (mirror is !MethodMirror) return false; |
| 819 return mirror.owner != objectClassMirror; |
| 820 }; |
| 821 } on NoSuchMethodError catch (e) { |
| 822 // For SDK 1.0 we fall back to just using the local members. |
| 823 return (type, symbol) => type.members[symbol] is MethodMirror; |
| 824 } on UnimplementedError catch (e) { |
| 825 // For SDK 1.1 we fall back to just using the local declarations. |
| 826 return (type, symbol) => type.declarations[symbol] is MethodMirror; |
| 827 } |
| 828 return null; |
| 829 })(); |
| 830 |
| 831 } |
| OLD | NEW |