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

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

Powered by Google App Engine
This is Rietveld 408576698