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

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

Issue 11820032: Make input/output formats pluggable, adapt to new libraries (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 11 months 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 part of serialization;
2
3 /**
4 * An abstract class for serialization formats. Subclasses define how data
5 * is read or written to a particular output mechanism.
6 */
7 abstract class Format {
8 /**
9 * Return true if this format stores primitives in their own area and uses
10 * references to them (e.g. [SimpleFlatFormat]) and false if primitives
11 * are stored directly (e.g. [SimpleJSONFormat], [SimpleMapFormat]).
12 */
13 bool get shouldUseReferencesForPrimitives => false;
14
15 /**
16 * Generate output for [w] and return it. The particular form of the output
17 * will depend on the format. The format can assume that [w] has data
18 * generated by rules in a series of lists, and that each list will contain
19 * either primitives (null, bool, num, String), Lists or Maps. The Lists or
20 * Maps may contain any of the same things recursively, or may contain
21 * Reference objects. For lists and maps the rule will tell us if they can
22 * be of variable length or not. The format is allowed to operate
23 * destructively on the rule data.
24 */
25 generateOutput(Writer w);
26
27 /**
28 * Read the data from [input] in the context of [reader] and return it as a
29 * Map with entries for "roots", "data" and "rules", which the reader knows
30 * how to interpret. The type of [input] will depend on the particular format.
31 */
32 Map<String, dynamic> read(input, Reader reader);
33 }
34
35 /**
36 * A format that stores the data in maps which are converted into a JSON
37 * string. Note that the maps aren't nested, and it handles cyclic references
38 * by converting object references to [Reference] objects. If you want simple
39 * acyclic JSON look at [SimpleJSONFormat].
40 */
41 class SimpleMapFormat extends Format {
42
43 /**
44 * Generate output for this format from [w] and return it as a String which
45 * is the [json] representation of a nested Map structure. The top level has
46 * 3 fields, "rules" which may hold a definition of the rules used,
47 * "data" which holds the serialized data, and "roots", which holds
48 * [Reference] objects indicating the root objects. Note that roots are
49 * necessary because the data is organized in the same way as the object
50 * structure, it's a list of lists holding self-contained maps which only
51 * refer to other parts via [Reference] objects.
52 * This effectively defines a custom JSON serialization format, although
53 * the details of the format vary depending which rules were used.
54 */
55 String generateOutput(Writer w) {
56 var result = new Map();
Jennifer Messerly 2013/01/11 02:21:53 consider map literal since keys are all const stri
Alan Knight 2013/01/11 19:18:11 I forget I can do that. Done.
57 result["rules"] = w.serializedRules();
58 result["data"] = w.states;
59 result["roots"] = w._rootReferences();
60 return json.stringify(result);
61 }
62
63 /**
64 * Read a [json] encoded string representing serialized data in this format
65 * and return the nested Map representation described in [generateOutput]. If
66 * the data also includes rule definitions, then these will replace the rules
67 * in the [Serialization] for [reader].
68 */
69 Map<String, dynamic>read(String input, Reader reader) {
Jennifer Messerly 2013/01/11 02:21:53 space after >
Alan Knight 2013/01/11 19:18:11 Done.
70 var topLevel = json.parse(input);
71 var ruleString = topLevel["rules"];
72 reader.readRules(ruleString);
73 return topLevel;
74 }
75 }
76
77 /**
78 * A format for "normal" JSON representation of objects. It stores
79 * the fields of the objects as nested maps, and doesn't allow cycles. This can
80 * be useful in talking to existing APIs that expect JSON format data. However,
81 * note that since the classes of objects aren't stored, this isn't enough
82 * information to read back the objects. This format also doesn't support the
83 * [selfDescriptive] option on the [Serialization], as storing the rules.
84 * If the [storeAdditionalData] field of the format is set to true, then this
85 * will store the rule number along with the data, allowing reconstruction.
86 */
87 class SimpleJSONFormat extends Format {
88
89 /**
90 * Indicate if we should store rule numbers with map/list data so that we
91 * will know how to reconstruct it with a read operation. If we don't, this
92 * will be more compliant with things that expect known format JSON as input,
93 * but we won't be able to read back the objects.
94 */
95 final bool storeAdditionalData;
Jennifer Messerly 2013/01/11 02:21:53 this name is good, but I wonder if we could make i
Alan Knight 2013/01/11 19:18:11 Done.
96
97 SimpleJSONFormat([this.storeAdditionalData = false]);
Jennifer Messerly 2013/01/11 02:21:53 make this a named arg?
Alan Knight 2013/01/11 19:18:11 Done.
98
99 /**
100 * Generate output for this format from [w] and return it as a String which
101 * is the [json] representation of a nested Map structure.
102 */
103 String generateOutput(Writer w) {
104 jsonify(w);
105 return json.stringify(w.stateForReference(w._rootReferences().first));
106 }
107
108 /**
109 * Convert the data generated by the rules to have nested maps instead
110 * of Reference objects and to add rule numbers if [storeAdditionalData]
111 * is true.
112 */
113 jsonify(Writer w) {
114 for (var eachRule in w.rules) {
115 var ruleData = w.states[eachRule.number];
116 jsonifyForRule(ruleData, w, eachRule);
117 }
118 }
119
120 /**
121 * For a particular [rule] modify the [ruleData] to conform to this format.
122 */
123 jsonifyForRule(List ruleData, Writer w, SerializationRule rule) {
124 for (var i = 0; i < ruleData.length; i++) {
125 var each = ruleData[i];
126 if (each is List) {
127 jsonifyEntry(each, w);
128 if (storeAdditionalData) ruleData[i].add(rule.number);
129 } else if (each is Map) {
130 jsonifyEntry(each, w);
131 if (storeAdditionalData) each["__rule"] = rule.number;
132 }
133 }
134 }
135
136 /**
137 * For one particular entry, which is either a Map or a List, update it
138 * to turn References into a nested List/Map.
139 */
140 jsonifyEntry(map, Writer w) {
141 keysAndValues(map).forEach((key, value) {
142 if (value is Reference) map[key] = w.stateForReference(value);
Jennifer Messerly 2013/01/11 02:21:53 -2 indent
Alan Knight 2013/01/11 19:18:11 Done.
143 });
144 }
145
146 /**
147 * Read a [json] encoded string representing serialized data in this format
148 * and return the Map representation that the reader expects, with top-level
149 * entries for "rules", "data", and "roots". Nested lists/maps will be
150 * converted into Reference objects. Note that if the data was not written
151 * with [storeAdditionalState] true this will fail.
152 */
153 Map<String, dynamic>read(String input, Reader r) {
Jennifer Messerly 2013/01/11 02:21:53 also space here after >
Alan Knight 2013/01/11 19:18:11 Done.
154 var data = json.parse(input);
155 var result = {};
156 result["rules"] = null;
157 var ruleData =
158 new List(r.serialization.rules.length).mappedBy((x) => []).toList();
159 var rootRule = data["__rule"];
160 var top = recursivelyFixUp(data, r, ruleData);
161 result["data"] = ruleData;
162 result["roots"] = [top];
163 return result;
164 }
165
166 /**
167 * Convert nested references in [data] into [Reference] objects.
168 */
169 recursivelyFixUp(data, Reader r, List result) {
170 if (isPrimitive(data)) {
171 result[r._primitiveRule().number].add(data);
172 return data;
173 }
174 var ruleNumber =
175 (data is List) ? data.removeLast() : data.remove("__rule");
176 var newData = values(data).mappedBy(
177 (x) => recursivelyFixUp(x, r, result));
178 result[ruleNumber].add(newData);
179 return new Reference(this, ruleNumber, result[ruleNumber].length - 1);
180 }
181 }
182
183 /**
184 * Writes to a simple mostly-flat format. Details are subject to change.
185 * Right now this produces a List containing null, num, and String. This is
186 * more space-efficient than the map formats, but much less human-readable.
187 * Simple usage is to turn this into JSON for transmission.
188 */
189 class SimpleFlatFormat extends Format {
190 bool get shouldUseReferencesForPrimitives => true;
191
192 /**
193 * For each rule we store data to indicate whether it will be reconstructed
194 * as a primitive, a list or a map.
195 */
196 static final int STORED_AS_LIST = 1;
197 static final int STORED_AS_MAP = 2;
198 static final int STORED_AS_PRIMITIVE = 3;
199
200 /**
201 * Generate output for this format from [w]. This will return a List with
202 * three entries, corresponding to the "rules", "data", and "roots" from
203 * [SimpleMapFormat]. The data is stored as a single List containing
204 * primitives.
205 */
206 List generateOutput(Writer w) {
207 var result = new List(3);
208 var flatData = [];
209 for (var eachRule in w.rules) {
210 var ruleData = w.states[eachRule.number];
211 flatData.add(ruleData.length);
212 writeStateInto(eachRule, ruleData, flatData);
213 }
214 result[0] = w.serializedRules();
215 result[1] = flatData;
216 result[2] = new List();
217 w._rootReferences().forEach((x) => x.writeToList(result[2]));
218 return result;
219 }
220
221 /**
222 * Writes the data from [rule] into the [target] list.
223 */
224 void writeStateInto(SerializationRule rule, List ruleData, List target) {
225 if (!ruleData.isEmpty) {
226 var sample = ruleData.first;
227 if (sample is List) {
228 writeLists(rule, ruleData, target);
229 } else if (sample is Map) {
230 writeMaps(rule, ruleData, target);
231 } else {
232 writeObjects(ruleData, target);
233 }
234 } else {
235 // If there is no data, write a zero for the length.
236 target.add(0);
237 }
238 }
239
240 /**
241 * Write [entries], which contains Lists. Either the lists are variable
242 * length, in which case we add a length field, or they are fixed length, in
243 * which case we don't, and assume the [rule] will know how to read the
244 * right length when we read it back. We expect everything in the list to be
245 * a reference, which is stored as two numbers.
246 */
247 writeLists(SerializationRule rule, List<List> entries, List target) {
248 target.add(STORED_AS_LIST);
249 for (var eachEntry in entries) {
250 if (rule.hasVariableLengthEntries) {
251 target.add(eachEntry.length);
252 }
253 for (var eachReference in eachEntry) {
254 writeReference(eachReference, target);
255 }
256 }
257 }
258
259 /**
260 * Write [entries], which contains Maps. Either the Maps are variable
261 * length, in which case we add a length field, or they are fixed length, in
262 * which case we don't, and assume the [rule] will know how to read the
263 * right length when we read it back. Then we write alternating keys and
264 * values. We expect the values to be references, which we store as
265 * two numbers.
266 */
267 writeMaps(SerializationRule rule, List<Map> entries, List target) {
268 target.add(STORED_AS_MAP);
269 for (var eachEntry in entries) {
270 if (rule.hasVariableLengthEntries) {
271 target.add(eachEntry.length);
272 }
273 // We take advantage of this being only a semi-flat format, and expecting
274 // that the keys here are field names, i.e. strings. So we write
275 // the keys as literals and the values as references. This duplicates the
276 // keys, so is quite inefficient. But generating maps rather than lists is
277 // not very efficient in the first place.
278 eachEntry.forEach((key, value) {
279 target.add(key);
280 writeReference(value, target);
281 });
282 }
283 }
284
285 /**
286 * Write [entries], which contains simple objects which we can put directly
287 * into [target].
288 */
289 writeObjects(List entries, List target) {
290 target.add(STORED_AS_PRIMITIVE);
291 target.addAll(entries);
292 }
293
294 /**
295 * Write [eachRef] to [target]. It will be written as two ints. If [eachRef]
296 * is null it will be written as two nulls.
297 */
298 void writeReference(Reference eachRef, List target) {
299 // TODO(alanknight): Writing nulls is problematic in a real flat format.
300 if (eachRef == null) {
301 target..add(null)..add(null);
302 } else {
303 eachRef.writeToList(target);
304 }
305 }
306
307 /**
308 * Read the data from [rawInput] in the context of [r] and return it as a
309 * Map with entries for "roots", "data" and "rules", which the reader knows
310 * how to interpret. We expect [rawInput] to have been generated from this
311 * format.
312 */
313 Map<String, dynamic> read(List rawInput, Reader r) {
314 var input = {};
315 input["rules"] = rawInput[0];
316 r.readRules(input["rules"]);
317
318 var flatData = rawInput[1];
319 var stream = flatData.iterator;
320 var tempData = new List(r.rules.length);
321 for (var eachRule in r.rules) {
322 tempData[eachRule.number] = readRuleDataFrom(stream, eachRule);
323 }
324 input["data"] = tempData;
325
326 var roots = [];
327 var rootsAsInts = rawInput[2].iterator;
328 do {
329 roots.add(nextReferenceFrom(rootsAsInts));
330 } while (rootsAsInts.current != null);
331
332 input["roots"] = roots;
333 return input;
334 }
335
336 /**
337 * Read the data for [rule] from [input] and return it.
338 */
339 readRuleDataFrom(Iterator input, SerializationRule rule) {
340 var numberOfEntries = _next(input);
341 var entryType = _next(input);
342 if (entryType == STORED_AS_LIST) {
343 return readLists(input, rule, numberOfEntries);
344 }
345 if (entryType == STORED_AS_MAP) {
346 return readMaps(input, rule, numberOfEntries);
347 }
348 if (entryType == STORED_AS_PRIMITIVE) {
349 return readPrimitives(input, rule, numberOfEntries);
350 }
351 if (numberOfEntries == 0) {
352 return [];
353 } else {
354 throw new SerializationException("Invalid data in serialization");
355 }
356 }
357
358 /**
359 * Read data for [rule] from [input] with [length] number of entries,
360 * creating lists from the results.
361 */
362 readLists(Iterator input, SerializationRule rule, int length) {
363 var ruleData = [];
364 for (var i = 0; i < length; i++) {
365 var subLength =
366 rule.hasVariableLengthEntries ? _next(input) : rule.dataLength;
367 var subList = [];
368 ruleData.add(subList);
369 for (var j = 0; j < subLength; j++) {
370 subList.add(nextReferenceFrom(input));
371 }
372 }
373 return ruleData;
374 }
375
376 /**
377 * Read data for [rule] from [input] with [length] number of entries,
378 * creating maps from the results.
379 */
380 readMaps(Iterator input, SerializationRule rule, int length) {
381 var ruleData = [];
382 for (var i = 0; i < length; i++) {
383 var subLength =
384 rule.hasVariableLengthEntries ? _next(input) : rule.dataLength;
385 var map = {};
386 ruleData.add(map);
387 for (var j = 0; j < subLength; j++) {
388 map[_next(input)] = nextReferenceFrom(input);
389 }
390 }
391 return ruleData;
392 }
393
394 /**
395 * Read data for [rule] from [input] with [length] number of entries,
396 * treating the data as primitives that can be returned directly.
397 */
398 readPrimitives(Iterator input, SerializationRule rule, int length) {
399 var ruleData = [];
400 for (var i = 0; i < length; i++) {
401 ruleData.add(_next(input));
402 }
403 return ruleData;
404 }
405
406 /** Read the next Reference from the input. */
407 nextReferenceFrom(Iterator input) {
408 var a = _next(input);
409 var b = _next(input);
410 if (a == null) {
411 return null;
412 } else {
413 return new Reference(this, a, b);
414 }
415 }
416
417 /** Return the next element from the input. */
418 _next(Iterator input) {
419 input.moveNext();
420 return input.current;
421 }
422 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698