OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 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 | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of serialization; | 5 part of serialization; |
6 | 6 |
7 /** | 7 /** |
8 * This writes out the state of the objects to an external format. It holds | 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 | 9 * all of the intermediate state needed. The primary API for it is the |
10 * [write] method. | 10 * [write] method. |
(...skipping 13 matching lines...) Expand all Loading... |
24 * the full set of objects to be written.*/ | 24 * the full set of objects to be written.*/ |
25 Trace trace; | 25 Trace trace; |
26 | 26 |
27 /** | 27 /** |
28 * When we write out objects, should we also write out a description | 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 | 29 * of the rules for the serialization. This defaults to the corresponding |
30 * value on the Serialization. | 30 * value on the Serialization. |
31 */ | 31 */ |
32 bool selfDescribing; | 32 bool selfDescribing; |
33 | 33 |
| 34 Format format = new SimpleMapFormat(); |
| 35 |
34 /** | 36 /** |
35 * Objects that cannot be represented in-place in the serialized form need | 37 * Objects that cannot be represented in-place in the serialized form need |
36 * to have references to them stored. The [Reference] objects are computed | 38 * to have references to them stored. The [Reference] objects are computed |
37 * once and stored here for each object. This provides some space-saving, | 39 * once and stored here for each object. This provides some space-saving, |
38 * but also serves to record which objects we have already seen. | 40 * but also serves to record which objects we have already seen. |
39 */ | 41 */ |
40 final Map<dynamic, Reference> references = | 42 final Map<dynamic, Reference> references = |
41 new IdentityMap<Object, Reference>(); | 43 new IdentityMap<Object, Reference>(); |
42 | 44 |
43 /** | 45 /** |
44 * The state of objects that need to be serialized is stored here. | 46 * 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 | 47 * 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 | 48 * 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. | 49 * from the rule number and the object number within the rule. |
48 * The actual representation of the state is determined by the rule. Lists | 50 * The actual representation of the state is determined by the rule. Lists |
49 * and Maps are common, but it is arbitrary. | 51 * and Maps are common, but it is arbitrary. |
50 */ | 52 */ |
51 final List<List> states = new List<List>(); | 53 final List<List> states = new List<List>(); |
52 | 54 |
53 /** Return the list of rules we use. */ | 55 /** Return the list of rules we use. */ |
54 List<SerializationRule> get rules => serialization._rules; | 56 List<SerializationRule> get rules => serialization._rules; |
55 | 57 |
56 /** | 58 /** |
57 * Creates a new [Writer] that uses the rules from its parent | 59 * Creates a new [Writer] that uses the rules from its parent |
58 * [Serialization]. Serializations do not keep any state | 60 * [Serialization]. Serializations do not keep any state |
59 * related to a particular read/write, so the same one can be used | 61 * related to a particular read/write, so the same one can be used |
60 * for multiple different Readers/Writers. | 62 * for multiple different Readers/Writers. |
61 */ | 63 */ |
62 Writer(this.serialization) { | 64 Writer(this.serialization, [Format newFormat]) { |
63 trace = new Trace(this); | 65 trace = new Trace(this); |
64 selfDescribing = serialization.selfDescribing; | 66 selfDescribing = serialization.selfDescribing; |
| 67 if (newFormat != null) format = newFormat; |
65 } | 68 } |
66 | 69 |
67 /** | 70 /** |
68 * This is the main API for a [Writer]. It writes the objects and returns | 71 * 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 | 72 * the serialized representation, as determined by [format]. |
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 */ |
73 // TODO(alanknight): Generalize the output representation. Probably requires | 74 write(anObject) { |
74 // introducing some sort of OutputFormat object. | |
75 String write(anObject) { | |
76 trace.addRoot(anObject); | 75 trace.addRoot(anObject); |
77 trace.traceAll(); | 76 trace.traceAll(); |
78 _flatten(); | 77 _flatten(); |
79 return toStringFormat(); | 78 return format.generateOutput(this); |
80 } | 79 } |
81 | 80 |
82 /** | 81 /** |
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 | 82 * Given that we have fully populated the list of [states], and more |
129 * importantly, the list of [references], go through each state and turn | 83 * importantly, the list of [references], go through each state and turn |
130 * anything that requires a [Reference] into one. Since only the rules | 84 * anything that requires a [Reference] into one. Since only the rules |
131 * know the representation they use for state, delegate to them. | 85 * know the representation they use for state, delegate to them. |
132 */ | 86 */ |
133 void _flatten() { | 87 void _flatten() { |
134 for (var eachRule in rules) { | 88 for (var eachRule in rules) { |
135 _growStates(eachRule); | 89 _growStates(eachRule); |
136 var index = eachRule.number; | 90 var index = eachRule.number; |
137 for (var eachState in states[index]) { | 91 for (var eachState in states[index]) { |
(...skipping 29 matching lines...) Expand all Loading... |
167 var state = rule.extractState(object, trace.note); | 121 var state = rule.extractState(object, trace.note); |
168 _addStateForRule(rule, state); | 122 _addStateForRule(rule, state); |
169 } | 123 } |
170 } | 124 } |
171 | 125 |
172 /** | 126 /** |
173 * Should we store primitive objects directly or create references for them. | 127 * 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 | 128 * That depends on which format we're using, so a flat format will want |
175 * references, but the Map format can store them directly. | 129 * references, but the Map format can store them directly. |
176 */ | 130 */ |
177 bool shouldUseReferencesForPrimitives = false; | 131 bool get shouldUseReferencesForPrimitives |
| 132 => format.shouldUseReferencesForPrimitives; |
| 133 |
| 134 /** |
| 135 * Returns a serialized version of the [SerializationRule]s used to write |
| 136 * the data, if [selfDescribing] is true, otherwise returns null. |
| 137 */ |
| 138 serializedRules() { |
| 139 if (!selfDescribing) return null; |
| 140 var meta = serialization.ruleSerialization(); |
| 141 var writer = new Writer(meta); |
| 142 writer.selfDescribing = false; |
| 143 return writer.write(serialization._rules); |
| 144 } |
178 | 145 |
179 /** Record a [state] entry for a particular rule. */ | 146 /** Record a [state] entry for a particular rule. */ |
180 void _addStateForRule(eachRule, state) { | 147 void _addStateForRule(eachRule, state) { |
181 _growStates(eachRule); | 148 _growStates(eachRule); |
182 states[eachRule.number].add(state); | 149 states[eachRule.number].add(state); |
183 } | 150 } |
184 | 151 |
185 /** Find what the object number for the thing we're about to add will be.*/ | 152 /** Find what the object number for the thing we're about to add will be.*/ |
186 int _nextObjectNumberFor(SerializationRule rule) { | 153 int _nextObjectNumberFor(SerializationRule rule) { |
187 _growStates(rule); | 154 _growStates(rule); |
(...skipping 27 matching lines...) Expand all Loading... |
215 * the context of a particular rule, and if the rule has more than one, | 182 * 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 | 183 * this will return the one for the primary rule, defined as the one that |
217 * is listed in its canonical reference. | 184 * is listed in its canonical reference. |
218 */ | 185 */ |
219 int _objectNumberFor(object) { | 186 int _objectNumberFor(object) { |
220 var reference = references[object]; | 187 var reference = references[object]; |
221 return (reference == null) ? -1 : reference.objectNumber; | 188 return (reference == null) ? -1 : reference.objectNumber; |
222 } | 189 } |
223 | 190 |
224 /** | 191 /** |
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 | 192 * Return a list of [Reference] objects pointing to our roots. This will be |
260 * stored in the output under "roots" in the default format. | 193 * stored in the output under "roots" in the default format. |
261 */ | 194 */ |
262 _rootReferences(roots) => | 195 _rootReferences() => trace.roots.mappedBy(_referenceFor).toList(); |
263 roots.mappedBy(_referenceFor).toList(); | |
264 | 196 |
265 /** | 197 /** |
266 * Given an object, return a reference for it if one exists. If there's | 198 * 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 | 199 * no reference, return null. Once we have finished the tracing step, all |
268 * objects that should have a reference (roughly speaking, non-primitives) | 200 * objects that should have a reference (roughly speaking, non-primitives) |
269 * can be relied on to have a reference. | 201 * can be relied on to have a reference. |
270 */ | 202 */ |
271 _referenceFor(object) => references[object]; | 203 _referenceFor(object) => references[object]; |
272 | 204 |
273 /** | 205 /** |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
317 */ | 249 */ |
318 List<List> _data; | 250 List<List> _data; |
319 | 251 |
320 /** | 252 /** |
321 * The resulting objects, indexed according to the same scheme as | 253 * 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 | 254 * [data], where each rule has a number, and rules keep track of the objects |
323 * that they serialize, in order. | 255 * that they serialize, in order. |
324 */ | 256 */ |
325 List<List> objects; | 257 List<List> objects; |
326 | 258 |
| 259 Format format = new SimpleMapFormat(); |
| 260 |
327 /** | 261 /** |
328 * Creates a new [Reader] that uses the rules from its parent | 262 * Creates a new [Reader] that uses the rules from its parent |
329 * [Serialization]. Serializations do not keep any state related to | 263 * [Serialization]. Serializations do not keep any state related to |
330 * a particular read or write operation, so the same one can be used | 264 * a particular read or write operation, so the same one can be used |
331 * for multiple different Writers/Readers. | 265 * for multiple different Writers/Readers. |
332 */ | 266 */ |
333 Reader(this.serialization) { | 267 Reader(this.serialization, [Format newFormat]) { |
334 selfDescribing = serialization.selfDescribing; | 268 selfDescribing = serialization.selfDescribing; |
| 269 if (newFormat != null) format = newFormat; |
335 } | 270 } |
336 | 271 |
337 /** | 272 /** |
338 * When we read, we may need to look up objects by name in order to link to | 273 * 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, | 274 * them. This is particularly true if we have references to classes, |
340 * functions, mirrors, or other non-portable entities. The map in which we | 275 * 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 | 276 * 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. | 277 * provide a map here, and objects will be looked up in both places. |
343 */ | 278 */ |
344 Map namedObjects; | 279 Map namedObjects; |
(...skipping 22 matching lines...) Expand all Loading... |
367 * to a List of Lists whose size must match the number of rules. | 302 * to a List of Lists whose size must match the number of rules. |
368 */ | 303 */ |
369 // When we set the data, initialize the object storage to a matching size. | 304 // When we set the data, initialize the object storage to a matching size. |
370 void set data(List<List> newData) { | 305 void set data(List<List> newData) { |
371 _data = newData; | 306 _data = newData; |
372 objects = _data.mappedBy((x) => new List(x.length)).toList(); | 307 objects = _data.mappedBy((x) => new List(x.length)).toList(); |
373 } | 308 } |
374 | 309 |
375 /** | 310 /** |
376 * This is the primary method for a [Reader]. It takes the input data, | 311 * This is the primary method for a [Reader]. It takes the input data, |
377 * currently hard-coded to expect our custom JSON format, and returns | 312 * decodes it according to [format] and returns the root object. |
378 * the root object. | |
379 */ | 313 */ |
380 read(String input, [Map externals = const {}]) { | 314 read(rawInput, [Map externals = const {}]) { |
381 namedObjects = externals; | 315 namedObjects = externals; |
382 var topLevel = json.parse(input); | 316 var input = format.read(rawInput, this); |
383 var ruleString = topLevel["rules"]; | 317 data = input["data"]; |
384 readRules(ruleString, externals); | |
385 data = topLevel["data"]; | |
386 rules.forEach(inflateForRule); | 318 rules.forEach(inflateForRule); |
387 var roots = topLevel["roots"]; | 319 return inflateReference(input["roots"].first); |
388 return inflateReference(roots.first); | |
389 } | 320 } |
390 | 321 |
391 /** | 322 /** |
392 * If the data we are reading from has rules written to it, read them back | 323 * If the data we are reading from has rules written to it, read them back |
393 * and set them as the rules we will use. | 324 * and set them as the rules we will use. |
394 */ | 325 */ |
395 void readRules(String newRules, Map externals) { | 326 void readRules(String newRules) { |
396 // TODO(alanknight): Replacing the serialization is kind of confusing. | 327 // TODO(alanknight): Replacing the serialization is kind of confusing. |
397 List rulesWeRead = (newRules == null) ? | 328 List rulesWeRead = (newRules == null) ? |
398 null : serialization._ruleSerialization().read(newRules, externals); | 329 null : serialization.ruleSerialization().read(newRules, namedObjects); |
399 if (rulesWeRead != null && !rulesWeRead.isEmpty) { | 330 if (rulesWeRead != null && !rulesWeRead.isEmpty) { |
400 serialization = new Serialization.blank(); | 331 serialization = new Serialization.blank(); |
401 rulesWeRead.forEach(serialization.addRule); | 332 rulesWeRead.forEach(serialization.addRule); |
402 } | 333 } |
403 } | 334 } |
404 | 335 |
405 /** | 336 /** |
406 * This is a hard-coded read method for a vaguely flat format. It's just a | |
407 * proof of concept of handling more flat formats right now, and needs a lot | |
408 * of fixing and generalization. | |
409 */ | |
410 readFlat(List input, [Map externals = const {}]) { | |
411 // TODO(alanknight): Way too much code duplication with read. Numerous | |
412 // code smells. | |
413 namedObjects = externals; | |
414 var topLevel = input; | |
415 var ruleString = topLevel[0]; | |
416 readRules(ruleString, externals); | |
417 var flatData = topLevel[1]; | |
418 var stream = flatData.iterator; | |
419 var tempData = new List(rules.length); | |
420 for (var eachRule in rules) { | |
421 tempData[eachRule.number] = eachRule.pullStateFrom(stream); | |
422 } | |
423 data = tempData; | |
424 for (var eachRule in rules) { | |
425 inflateForRule(eachRule); | |
426 } | |
427 var rootsAsInts = topLevel[2]; | |
428 var rootStream = rootsAsInts.iterator; | |
429 var roots = new List(); | |
430 while (rootStream.moveNext()) { | |
431 var first = rootStream.current; | |
432 rootStream.moveNext(); | |
433 var second = rootStream.current; | |
434 roots.add(new Reference(this, first, second)); | |
435 } | |
436 var x = inflateReference(roots[0]); | |
437 return inflateReference(roots.first); | |
438 } | |
439 | |
440 /** | |
441 * Inflate all of the objects for [rule]. Does the essential state for all | 337 * Inflate all of the objects for [rule]. Does the essential state for all |
442 * objects first, then the non-essential state. This avoids cycles in | 338 * objects first, then the non-essential state. This avoids cycles in |
443 * non-essential state, because all the objects will have already been | 339 * non-essential state, because all the objects will have already been |
444 * created. | 340 * created. |
445 */ | 341 */ |
446 inflateForRule(rule) { | 342 inflateForRule(rule) { |
447 var dataForThisRule = _data[rule.number]; | 343 var dataForThisRule = _data[rule.number]; |
448 keysAndValues(dataForThisRule).forEach((position, state) { | 344 keysAndValues(dataForThisRule).forEach((position, state) { |
449 inflateOne(rule, position, state); | 345 inflateOne(rule, position, state); |
450 }); | 346 }); |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
504 | 400 |
505 /** Given [reference], return the the state we have stored for it. */ | 401 /** Given [reference], return the the state we have stored for it. */ |
506 _stateFor(Reference reference) => | 402 _stateFor(Reference reference) => |
507 _data[reference.ruleNumber][reference.objectNumber]; | 403 _data[reference.ruleNumber][reference.objectNumber]; |
508 | 404 |
509 /** Given a reference, return the rule it references. */ | 405 /** Given a reference, return the rule it references. */ |
510 SerializationRule ruleFor(Reference reference) => | 406 SerializationRule ruleFor(Reference reference) => |
511 serialization._rules[reference.ruleNumber]; | 407 serialization._rules[reference.ruleNumber]; |
512 | 408 |
513 /** | 409 /** |
| 410 * Return the primitive rule we are using. This is an ugly mechanism to |
| 411 * support the extra information to reconstruct objects in the |
| 412 * [SimpleJsonFormat]. |
| 413 */ |
| 414 SerializationRule _primitiveRule() { |
| 415 for (var each in rules) { |
| 416 if (each.runtimeType == PrimitiveRule) { |
| 417 return each; |
| 418 } |
| 419 } |
| 420 throw new SerializationException("No PrimitiveRule found"); |
| 421 } |
| 422 |
| 423 /** |
514 * Given a possible reference [anObject], call either [ifReference] or | 424 * Given a possible reference [anObject], call either [ifReference] or |
515 * [ifNotReference], depending if it's a reference or not. This is the | 425 * [ifNotReference], depending if it's a reference or not. This is the |
516 * primary place that knows about the serialized representation of a | 426 * primary place that knows about the serialized representation of a |
517 * reference. | 427 * reference. |
518 */ | 428 */ |
519 asReference(anObject, {Function ifReference: doNothing, | 429 asReference(anObject, {Function ifReference: doNothing, |
520 Function ifNotReference : doNothing}) { | 430 Function ifNotReference : doNothing}) { |
521 if (anObject is Reference) return ifReference(anObject); | 431 if (anObject is Reference) return ifReference(anObject); |
522 if (anObject is Map && anObject["__Ref"] == true) { | 432 if (anObject is Map && anObject["__Ref"] == true) { |
523 var ref = | 433 var ref = |
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
627 "__Ref" : true, | 537 "__Ref" : true, |
628 "rule" : ruleNumber, | 538 "rule" : ruleNumber, |
629 "object" : objectNumber | 539 "object" : objectNumber |
630 }; | 540 }; |
631 | 541 |
632 /** Write our information to [list]. Useful in writing to flat formats.*/ | 542 /** Write our information to [list]. Useful in writing to flat formats.*/ |
633 writeToList(List list) { | 543 writeToList(List list) { |
634 list.add(ruleNumber); | 544 list.add(ruleNumber); |
635 list.add(objectNumber); | 545 list.add(objectNumber); |
636 } | 546 } |
| 547 |
| 548 toString() => "Reference $ruleNumber, $objectNumber"; |
637 } | 549 } |
638 | 550 |
639 /** | 551 /** |
640 * This is used during tracing to indicate that an object should be processed | 552 * This is used during tracing to indicate that an object should be processed |
641 * using a particular rule, rather than the one that might ordinarily be | 553 * using a particular rule, rather than the one that might ordinarily be |
642 * found for it. This normally only makes sense if the object is uniquely | 554 * found for it. This normally only makes sense if the object is uniquely |
643 * referenced, and is a more or less internal collection. See ListRuleEssential | 555 * referenced, and is a more or less internal collection. See ListRuleEssential |
644 * for an example. It knows how to return its object and how to filter. | 556 * for an example. It knows how to return its object and how to filter. |
645 */ | 557 */ |
646 class DesignatedRuleForObject { | 558 class DesignatedRuleForObject { |
647 Function rulePredicate; | 559 Function rulePredicate; |
648 final target; | 560 final target; |
649 | 561 |
650 DesignatedRuleForObject(this.target, this.rulePredicate); | 562 DesignatedRuleForObject(this.target, this.rulePredicate); |
651 | 563 |
652 possibleRules(List rules) => rules.where(rulePredicate).toList(); | 564 possibleRules(List rules) => rules.where(rulePredicate).toList(); |
653 } | 565 } |
OLD | NEW |