OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 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. |
| 4 |
| 5 part of serialization; |
| 6 |
| 7 /** |
| 8 * This writes out the state of the objects to an external format. It holds |
| 9 * all of the intermediate state needed. The primary API for it is the |
| 10 * [write] method. |
| 11 */ |
| 12 // TODO(alanknight): For simple serialization formats this does a lot of work |
| 13 // that isn't necessary, e.g. detecting cycles and maintaining references. |
| 14 // Consider having an abstract superclass with the basic functionality and |
| 15 // simple serialization subclasses where we know there aren't cycles. |
| 16 class Writer implements ReaderOrWriter { |
| 17 /** |
| 18 * The [serialization] holds onto the rules that define how objects |
| 19 * are serialized. |
| 20 */ |
| 21 final Serialization serialization; |
| 22 |
| 23 /** The [trace] object keeps track of the objects to be visited while finding |
| 24 * the full set of objects to be written.*/ |
| 25 Trace trace; |
| 26 |
| 27 /** |
| 28 * When we write out objects, should we also write out a description |
| 29 * of the rules for the serialization. This defaults to the corresponding |
| 30 * value on the Serialization. |
| 31 */ |
| 32 bool selfDescribing; |
| 33 |
| 34 final Format format; |
| 35 |
| 36 /** |
| 37 * Objects that cannot be represented in-place in the serialized form need |
| 38 * to have references to them stored. The [Reference] objects are computed |
| 39 * once and stored here for each object. This provides some space-saving, |
| 40 * but also serves to record which objects we have already seen. |
| 41 */ |
| 42 final Map<dynamic, Reference> references = |
| 43 new HashMap<Object, Reference>.identity(); |
| 44 |
| 45 /** |
| 46 * The state of objects that need to be serialized is stored here. |
| 47 * Each rule has a number, and rules keep track of the objects that they |
| 48 * serialize, in order. So the state of any object can be found by indexing |
| 49 * from the rule number and the object number within the rule. |
| 50 * The actual representation of the state is determined by the rule. Lists |
| 51 * and Maps are common, but it is arbitrary. |
| 52 */ |
| 53 final List<List> states = new List<List>(); |
| 54 |
| 55 /** Return the list of rules we use. */ |
| 56 List<SerializationRule> get rules => serialization.rules; |
| 57 |
| 58 /** |
| 59 * Creates a new [Writer] that uses the rules from its parent |
| 60 * [Serialization]. Serializations do not keep any state |
| 61 * related to a particular read/write, so the same one can be used |
| 62 * for multiple different Readers/Writers. |
| 63 */ |
| 64 Writer(this.serialization, [Format newFormat]) : |
| 65 format = (newFormat == null) ? const SimpleMapFormat() : newFormat { |
| 66 trace = new Trace(this); |
| 67 selfDescribing = serialization.selfDescribing; |
| 68 } |
| 69 |
| 70 /** |
| 71 * This is the main API for a [Writer]. It writes the objects and returns |
| 72 * the serialized representation, as determined by [format]. |
| 73 */ |
| 74 write(anObject) { |
| 75 trace.addRoot(anObject); |
| 76 trace.traceAll(); |
| 77 _flatten(); |
| 78 return format.generateOutput(this); |
| 79 } |
| 80 |
| 81 /** |
| 82 * Given that we have fully populated the list of [states], and more |
| 83 * importantly, the list of [references], go through each state and turn |
| 84 * anything that requires a [Reference] into one. Since only the rules |
| 85 * know the representation they use for state, delegate to them. |
| 86 */ |
| 87 void _flatten() { |
| 88 for (var eachRule in rules) { |
| 89 _growStates(eachRule); |
| 90 var index = eachRule.number; |
| 91 var statesForThisRule = states[index]; |
| 92 for (var i = 0; i < statesForThisRule.length; i++) { |
| 93 var eachState = statesForThisRule[i]; |
| 94 var newState = eachRule.flatten(eachState, this); |
| 95 if (newState != null) { |
| 96 statesForThisRule[i] = newState; |
| 97 } |
| 98 } |
| 99 } |
| 100 } |
| 101 |
| 102 /** |
| 103 * As the [trace] processes each object, it will call this method on us. |
| 104 * We find the rules for this object, and record the state of the object |
| 105 * as determined by each rule. |
| 106 */ |
| 107 void _process(object, Trace trace) { |
| 108 var real = (object is DesignatedRuleForObject) ? object.target : object; |
| 109 for (var eachRule in serialization.rulesFor(object, this)) { |
| 110 _record(real, eachRule); |
| 111 } |
| 112 } |
| 113 |
| 114 /** |
| 115 * Record the state of [object] as determined by [rule] and keep |
| 116 * track of it. Generate a [Reference] for this object if required. |
| 117 * When it's required is up to the particular rule, but generally everything |
| 118 * gets a reference except a primitive. |
| 119 * Note that at this point the states are just the same as the fields of the |
| 120 * object, and haven't been flattened. |
| 121 */ |
| 122 void _record(object, SerializationRule rule) { |
| 123 if (rule.shouldUseReferenceFor(object, this)) { |
| 124 references.putIfAbsent(object, () => |
| 125 new Reference(this, rule.number, _nextObjectNumberFor(rule))); |
| 126 var state = rule.extractState(object, trace.note, this); |
| 127 _addStateForRule(rule, state); |
| 128 } |
| 129 } |
| 130 |
| 131 /** |
| 132 * Should we store primitive objects directly or create references for them. |
| 133 * That depends on which format we're using, so a flat format will want |
| 134 * references, but the Map format can store them directly. |
| 135 */ |
| 136 bool get shouldUseReferencesForPrimitives |
| 137 => format.shouldUseReferencesForPrimitives; |
| 138 |
| 139 /** |
| 140 * Returns a serialized version of the [SerializationRule]s used to write |
| 141 * the data, if [selfDescribing] is true, otherwise returns null. |
| 142 */ |
| 143 serializedRules() { |
| 144 if (!selfDescribing) return null; |
| 145 var meta = serialization.ruleSerialization(); |
| 146 var writer = new Writer(meta, format); |
| 147 writer.selfDescribing = false; |
| 148 return writer.write(serialization.rules); |
| 149 } |
| 150 |
| 151 /** Record a [state] entry for a particular rule. */ |
| 152 void _addStateForRule(eachRule, state) { |
| 153 _growStates(eachRule); |
| 154 states[eachRule.number].add(state); |
| 155 } |
| 156 |
| 157 /** Find what the object number for the thing we're about to add will be.*/ |
| 158 int _nextObjectNumberFor(SerializationRule rule) { |
| 159 _growStates(rule); |
| 160 return states[rule.number].length; |
| 161 } |
| 162 |
| 163 /** |
| 164 * We store the states in a List, indexed by rule number. But rules can be |
| 165 * dynamically added, so we may have to grow the list. |
| 166 */ |
| 167 void _growStates(eachRule) { |
| 168 while (states.length <= eachRule.number) states.add(new List()); |
| 169 } |
| 170 |
| 171 /** |
| 172 * Return true if we have an object number for this object. This is used to |
| 173 * tell if we have processed the object or not. This relies on checking if we |
| 174 * have a reference or not. That saves some space by not having to keep track |
| 175 * of simple objects, but means that if someone refers to the identical string |
| 176 * from several places, we will process it several times, and store it |
| 177 * several times. That seems an acceptable tradeoff, and in cases where it |
| 178 * isn't, it's possible to apply a rule for String, or even for Strings larger |
| 179 * than x, which gives them references. |
| 180 */ |
| 181 bool _hasIndexFor(object) { |
| 182 return _objectNumberFor(object) != -1; |
| 183 } |
| 184 |
| 185 /** |
| 186 * Given an object, find what number it has. The number is valid only in |
| 187 * the context of a particular rule, and if the rule has more than one, |
| 188 * this will return the one for the primary rule, defined as the one that |
| 189 * is listed in its canonical reference. |
| 190 */ |
| 191 int _objectNumberFor(object) { |
| 192 var reference = references[object]; |
| 193 return (reference == null) ? -1 : reference.objectNumber; |
| 194 } |
| 195 |
| 196 /** |
| 197 * Return a list of [Reference] objects pointing to our roots. This will be |
| 198 * stored in the output under "roots" in the default format. |
| 199 */ |
| 200 List _rootReferences() => trace.roots.map(_referenceFor).toList(); |
| 201 |
| 202 /** |
| 203 * Given an object, return a reference for it if one exists. If there's |
| 204 * no reference, return the object itself. Once we have finished the tracing |
| 205 * step, all objects that should have a reference (roughly speaking, |
| 206 * non-primitives) can be relied on to have a reference. |
| 207 */ |
| 208 _referenceFor(object) { |
| 209 var result = references[object]; |
| 210 return (result == null) ? object : result; |
| 211 } |
| 212 |
| 213 /** |
| 214 * Return true if the [Serialization.namedObjects] collection has a |
| 215 * reference to [object]. |
| 216 */ |
| 217 // TODO(alanknight): Should the writer also have its own namedObjects |
| 218 // collection specific to the particular write, or is that just adding |
| 219 // complexity for little value? |
| 220 bool hasNameFor(object) => serialization._hasNameFor(object); |
| 221 |
| 222 /** |
| 223 * Return the name we have for this object in the [Serialization.namedObjects] |
| 224 * collection. |
| 225 */ |
| 226 String nameFor(object) => serialization._nameFor(object); |
| 227 |
| 228 // For debugging/testing purposes. Find what state a reference points to. |
| 229 stateForReference(Reference r) => states[r.ruleNumber][r.objectNumber]; |
| 230 |
| 231 /** Return the state pointed to by [reference]. */ |
| 232 resolveReference(reference) => stateForReference(reference); |
| 233 } |
| 234 |
| 235 /** |
| 236 * An abstract class for Reader and Writer, which primarily exists so we can |
| 237 * type things that will refer to one or the other, depending on which |
| 238 * operation we're doing. |
| 239 */ |
| 240 abstract class ReaderOrWriter { |
| 241 /** Return the list of serialization rules we are using.*/ |
| 242 List<SerializationRule> get rules; |
| 243 |
| 244 /** Return the internal collection of object state and [Reference] objects. */ |
| 245 List<List> get states; |
| 246 |
| 247 /** |
| 248 * Return the object, or state, that ref points to, depending on which |
| 249 * we're generating. |
| 250 */ |
| 251 resolveReference(Reference ref); |
| 252 } |
| 253 |
| 254 /** |
| 255 * The main class responsible for reading. It holds |
| 256 * onto the necessary state and to the objects that have been inflated. |
| 257 */ |
| 258 class Reader implements ReaderOrWriter { |
| 259 |
| 260 /** |
| 261 * The serialization that specifies how we read. Note that in contrast |
| 262 * to the Writer, this is not final. This is because we may be created |
| 263 * with an empty [Serialization] and then read the rules from the data, |
| 264 * if [selfDescribing] is true. |
| 265 */ |
| 266 Serialization serialization; |
| 267 |
| 268 /** |
| 269 * When we read objects, should we read a description of the rules if |
| 270 * present. This defaults to the corresponding value on the Serialization. |
| 271 */ |
| 272 bool selfDescribing; |
| 273 |
| 274 /** |
| 275 * The state of objects that have been serialized is stored here. |
| 276 * Each rule has a number, and rules keep track of the objects that they |
| 277 * serialize, in order. So the state of any object can be found by indexing |
| 278 * from the rule number and the object number within the rule. |
| 279 * The actual representation of the state is determined by the rule. Lists |
| 280 * and Maps are common, but it is arbitrary. See [Writer.states]. |
| 281 */ |
| 282 List<List> _data; |
| 283 |
| 284 /** Return the internal collection of object state and [Reference] objects. */ |
| 285 get states => _data; |
| 286 |
| 287 /** |
| 288 * The resulting objects, indexed according to the same scheme as |
| 289 * _data, where each rule has a number, and rules keep track of the objects |
| 290 * that they serialize, in order. |
| 291 */ |
| 292 List<List> objects; |
| 293 |
| 294 final Format format; |
| 295 |
| 296 /** |
| 297 * Creates a new [Reader] that uses the rules from its parent |
| 298 * [Serialization]. Serializations do not keep any state related to |
| 299 * a particular read or write operation, so the same one can be used |
| 300 * for multiple different Writers/Readers. |
| 301 */ |
| 302 Reader(this.serialization, [Format newFormat]) : |
| 303 format = (newFormat == null) ? const SimpleMapFormat() : newFormat { |
| 304 selfDescribing = serialization.selfDescribing; |
| 305 } |
| 306 |
| 307 /** |
| 308 * When we read, we may need to look up objects by name in order to link to |
| 309 * them. This is particularly true if we have references to classes, |
| 310 * functions, mirrors, or other non-portable entities. The map in which we |
| 311 * look things up can be provided as an argument to read, but we can also |
| 312 * provide a map here, and objects will be looked up in both places. |
| 313 */ |
| 314 Map namedObjects; |
| 315 |
| 316 /** |
| 317 * Look up the reference to an external object. This can be held either in |
| 318 * the reader-specific list of externals or in the serializer's |
| 319 */ |
| 320 objectNamed(key, [Function ifAbsent]) { |
| 321 var map = (namedObjects.containsKey(key)) |
| 322 ? namedObjects : serialization.namedObjects; |
| 323 if (!map.containsKey(key)) { |
| 324 (ifAbsent == null ? keyNotFound : ifAbsent)(key); |
| 325 } |
| 326 return map[key]; |
| 327 } |
| 328 |
| 329 void keyNotFound(key) { |
| 330 throw new SerializationException( |
| 331 'Cannot find named object to link to: $key'); |
| 332 } |
| 333 |
| 334 /** |
| 335 * Return the list of rules to be used when writing. These come from the |
| 336 * [serialization]. |
| 337 */ |
| 338 List<SerializationRule> get rules => serialization.rules; |
| 339 |
| 340 /** |
| 341 * Internal use only, for testing purposes. Set the data for this reader |
| 342 * to a List of Lists whose size must match the number of rules. |
| 343 */ |
| 344 // When we set the data, initialize the object storage to a matching size. |
| 345 void set data(List<List> newData) { |
| 346 _data = newData; |
| 347 objects = _data.map((x) => new List(x.length)).toList(); |
| 348 } |
| 349 |
| 350 /** |
| 351 * This is the primary method for a [Reader]. It takes the input data, |
| 352 * decodes it according to [format] and returns the root object. |
| 353 */ |
| 354 read(rawInput, [Map externals = const {}]) { |
| 355 namedObjects = externals; |
| 356 var input = format.read(rawInput, this); |
| 357 data = input["data"]; |
| 358 rules.forEach(inflateForRule); |
| 359 return inflateReference(input["roots"].first); |
| 360 } |
| 361 |
| 362 /** |
| 363 * If the data we are reading from has rules written to it, read them back |
| 364 * and set them as the rules we will use. |
| 365 */ |
| 366 void readRules(newRules) { |
| 367 // TODO(alanknight): Replacing the serialization is kind of confusing. |
| 368 if (newRules == null) return; |
| 369 var reader = serialization.ruleSerialization().newReader(format); |
| 370 List rulesWeRead = reader.read(newRules, namedObjects); |
| 371 if (rulesWeRead != null && !rulesWeRead.isEmpty) { |
| 372 serialization = new Serialization.blank(); |
| 373 rulesWeRead.forEach(serialization.addRule); |
| 374 } |
| 375 } |
| 376 |
| 377 /** |
| 378 * Inflate all of the objects for [rule]. Does the essential state for all |
| 379 * objects first, then the non-essential state. This avoids cycles in |
| 380 * non-essential state, because all the objects will have already been |
| 381 * created. |
| 382 */ |
| 383 void inflateForRule(rule) { |
| 384 var dataForThisRule = _data[rule.number]; |
| 385 keysAndValues(dataForThisRule).forEach((position, state) { |
| 386 inflateOne(rule, position, state); |
| 387 }); |
| 388 keysAndValues(dataForThisRule).forEach((position, state) { |
| 389 rule.inflateNonEssential(state, allObjectsForRule(rule)[position], this); |
| 390 }); |
| 391 } |
| 392 |
| 393 /** |
| 394 * Create a new object, based on [rule] and [state], which will |
| 395 * be stored in [position] in the storage for [rule]. This will |
| 396 * follow references and recursively inflate them, leaving Sentinel objects |
| 397 * to detect cycles. |
| 398 */ |
| 399 inflateOne(SerializationRule rule, position, state) { |
| 400 var existing = allObjectsForRule(rule)[position]; |
| 401 // We may already be in progress and hitting this in a cycle. |
| 402 if (existing is _Sentinel) { |
| 403 throw new SerializationException('Cycle in essential state'); |
| 404 } |
| 405 // We may have already inflated this object, at least its essential state. |
| 406 if (existing != null) return existing; |
| 407 |
| 408 // Put a sentinel there to mark this in case of recursion. |
| 409 allObjectsForRule(rule)[position] = const _Sentinel(); |
| 410 var newObject = rule.inflateEssential(state, this); |
| 411 allObjectsForRule(rule)[position] = newObject; |
| 412 return newObject; |
| 413 } |
| 414 |
| 415 /** |
| 416 * The parameter [possibleReference] might be a reference. If it isn't, just |
| 417 * return it. If it is, then inflate the target of the reference and return |
| 418 * the resulting object. |
| 419 */ |
| 420 inflateReference(possibleReference) { |
| 421 // If this is a primitive, return it directly. |
| 422 // TODO This seems too complicated. |
| 423 return asReference(possibleReference, |
| 424 ifReference: (reference) { |
| 425 var rule = ruleFor(reference); |
| 426 var state = _stateFor(reference); |
| 427 inflateOne(rule, reference.objectNumber, state); |
| 428 return _objectFor(reference); |
| 429 }); |
| 430 } |
| 431 |
| 432 /** Return the object pointed to by [reference]. */ |
| 433 resolveReference(reference) => inflateReference(reference); |
| 434 |
| 435 /** |
| 436 * Given [reference], return what we have stored as an object for it. Note |
| 437 * that, depending on the current state, this might be null or a Sentinel. |
| 438 */ |
| 439 _objectFor(Reference reference) => |
| 440 objects[reference.ruleNumber][reference.objectNumber]; |
| 441 |
| 442 /** Given [rule], return the storage for its objects. */ |
| 443 allObjectsForRule(SerializationRule rule) => objects[rule.number]; |
| 444 |
| 445 /** Given [reference], return the the state we have stored for it. */ |
| 446 _stateFor(Reference reference) => |
| 447 _data[reference.ruleNumber][reference.objectNumber]; |
| 448 |
| 449 /** Given a reference, return the rule it references. */ |
| 450 SerializationRule ruleFor(Reference reference) => |
| 451 serialization.rules[reference.ruleNumber]; |
| 452 |
| 453 /** |
| 454 * Return the primitive rule we are using. This is an ugly mechanism to |
| 455 * support the extra information to reconstruct objects in the |
| 456 * [SimpleJsonFormat]. |
| 457 */ |
| 458 SerializationRule _primitiveRule() { |
| 459 for (var each in rules) { |
| 460 if (each.runtimeType == PrimitiveRule) { |
| 461 return each; |
| 462 } |
| 463 } |
| 464 throw new SerializationException("No PrimitiveRule found"); |
| 465 } |
| 466 |
| 467 /** |
| 468 * Given a possible reference [anObject], call either [ifReference] or |
| 469 * [ifNotReference], depending if it's a reference or not. This is the |
| 470 * primary place that knows about the serialized representation of a |
| 471 * reference. |
| 472 */ |
| 473 asReference(anObject, {Function ifReference: doNothing, |
| 474 Function ifNotReference : doNothing}) { |
| 475 if (anObject is Reference) return ifReference(anObject); |
| 476 if (anObject is Map && anObject["__Ref"] != null) { |
| 477 var ref = |
| 478 new Reference(this, anObject["rule"], anObject["object"]); |
| 479 return ifReference(ref); |
| 480 } else { |
| 481 return ifNotReference(anObject); |
| 482 } |
| 483 } |
| 484 } |
| 485 |
| 486 /** |
| 487 * This serves as a marker to indicate a object that is in the process of |
| 488 * being de-serialized. So if we look for an object slot and find one of these, |
| 489 * we know we've hit a cycle. |
| 490 */ |
| 491 class _Sentinel { |
| 492 const _Sentinel(); |
| 493 } |
| 494 |
| 495 /** |
| 496 * This represents the transitive closure of the referenced objects to be |
| 497 * used for serialization. It works closely in conjunction with the Writer, |
| 498 * and is kept as a separate object primarily for the possibility of wanting |
| 499 * to plug in different sorts of tracing rules. |
| 500 */ |
| 501 class Trace { |
| 502 // TODO(alanknight): It seems likely that the mechanism for cutting off |
| 503 // tracings is by specifying rules. So is there any reason any more to have |
| 504 // this as a separate class? |
| 505 final Writer writer; |
| 506 |
| 507 /** |
| 508 * This class works by doing a breadth-first traversal of the objects, |
| 509 * with the traversal order maintained in [queue]. |
| 510 */ |
| 511 final Queue queue = new Queue(); |
| 512 |
| 513 /** The root objects from which we will be tracing. */ |
| 514 final List roots = []; |
| 515 |
| 516 Trace(this.writer); |
| 517 |
| 518 void addRoot(object) { |
| 519 roots.add(object); |
| 520 } |
| 521 |
| 522 /** A convenience method to add a single root and trace it in one step. */ |
| 523 void trace(object) { |
| 524 addRoot(object); |
| 525 traceAll(); |
| 526 } |
| 527 |
| 528 /** |
| 529 * Process all of the objects reachable from our roots via state that the |
| 530 * serialization rules access. |
| 531 */ |
| 532 void traceAll() { |
| 533 queue.addAll(roots); |
| 534 while (!queue.isEmpty) { |
| 535 var next = queue.removeFirst(); |
| 536 if (!hasProcessed(next)) writer._process(next, this); |
| 537 } |
| 538 } |
| 539 |
| 540 /** |
| 541 * Has this object been seen yet? We test for this by checking if the |
| 542 * writer has a reference for it. See comment for _hasIndexFor. |
| 543 */ |
| 544 bool hasProcessed(object) { |
| 545 return writer._hasIndexFor(object); |
| 546 } |
| 547 |
| 548 /** Note that we've seen [value], and add it to the queue to be processed. */ |
| 549 note(value) { |
| 550 if (value != null) { |
| 551 queue.add(value); |
| 552 } |
| 553 return value; |
| 554 } |
| 555 } |
| 556 |
| 557 /** |
| 558 * Any pointers to objects that can't be represented directly in the |
| 559 * serialization format has to be stored as a reference. A reference encodes |
| 560 * the rule number of the rule that saved it in the Serialization that was used |
| 561 * for writing, and the object number within that rule. |
| 562 */ |
| 563 class Reference { |
| 564 /** The [Reader] or [Writer] that owns this reference. */ |
| 565 final ReaderOrWriter parent; |
| 566 /** The position of the rule that controls this reference in [parent]. */ |
| 567 final int ruleNumber; |
| 568 /** The index of the referred-to object in the storage of [parent] */ |
| 569 final int objectNumber; |
| 570 |
| 571 Reference(this.parent, this.ruleNumber, this.objectNumber) { |
| 572 if (ruleNumber == null || objectNumber == null) { |
| 573 throw new SerializationException("Invalid Reference"); |
| 574 } |
| 575 if (parent.rules.length < ruleNumber) { |
| 576 throw new SerializationException("Invalid Reference"); |
| 577 } |
| 578 } |
| 579 |
| 580 /** |
| 581 * Return the thing this reference points to. Assumes that we have a valid |
| 582 * parent and that it is a Reader, as inflating is not meaningful when |
| 583 * writing. |
| 584 */ |
| 585 inflated() => parent.resolveReference(this); |
| 586 |
| 587 /** |
| 588 * Convert the reference to a map in JSON format. This is specific to the |
| 589 * custom JSON format we define, and must be consistent with the |
| 590 * [Reader.asReference] method. |
| 591 */ |
| 592 // TODO(alanknight): This is a hack both in defining a toJson specific to a |
| 593 // particular representation, and the use of a bogus sentinel "__Ref" |
| 594 Map<String, int> toJson() => { |
| 595 "__Ref" : 0, |
| 596 "rule" : ruleNumber, |
| 597 "object" : objectNumber |
| 598 }; |
| 599 |
| 600 /** Write our information to [list]. Useful in writing to flat formats.*/ |
| 601 void writeToList(List list) { |
| 602 list.add(ruleNumber); |
| 603 list.add(objectNumber); |
| 604 } |
| 605 |
| 606 String toString() => "Reference($ruleNumber, $objectNumber)"; |
| 607 } |
| 608 |
| 609 /** |
| 610 * This is used during tracing to indicate that an object should be processed |
| 611 * using a particular rule, rather than the one that might ordinarily be |
| 612 * found for it. This normally only makes sense if the object is uniquely |
| 613 * referenced, and is a more or less internal collection. See ListRuleEssential |
| 614 * for an example. It knows how to return its object and how to filter. |
| 615 */ |
| 616 class DesignatedRuleForObject { |
| 617 final Function rulePredicate; |
| 618 final target; |
| 619 |
| 620 DesignatedRuleForObject(this.target, this.rulePredicate); |
| 621 |
| 622 List possibleRules(List rules) => rules.where(rulePredicate).toList(); |
| 623 } |
OLD | NEW |