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