Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(210)

Side by Side Diff: pkg/serialization/lib/src/reader_writer.dart

Issue 11293283: Initial version of a serialization framework (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 Map<Object, Reference>();
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.
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;
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
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();
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
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)));
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
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) {
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
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));
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;
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));
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.noDefaultRules();
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];
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)) {
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 {
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();
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};
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698