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

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 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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698