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