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

Side by Side Diff: pkg/serialization/lib/src/serialization_rule.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 * The abstract superclass for serialization rules.
9 */
10 abstract class SerializationRule {
11 /**
12 * Rules belong uniquely to a particular Serialization instance, and can
13 * be identified within it by number.
14 */
15 int number;
16
17 /** Return true if this rule applies to this object, false otherwise. */
18 bool appliesTo(object);
19
20 /**
21 * This extracts the state from the object, calling [f] for each value
22 * as it is extracted, and returning an object representing the whole
23 * state at the end. The state that results will still have direct
24 * pointers to objects, rather than references.
25 */
26 Object extractState(object, void f(value));
27
28 /**
29 * Given the variables representing the state of an object, flatten it
30 * by turning object pointers into Reference objects where needed. This
31 * destructively modifies the state object.
32 *
33 * This has a default implementation which assumes that object is indexable,
34 * so either conforms to Map or List. Subclasses may override to do something
35 * different.
36 */
37 // This has to be a separate operation from extracting, because we extract
38 // as we are traversing the objects, so we don't yet have the objects to
39 // generate references for them. It might be possible to avoid that by
40 // doing a depth-first rather than breadth-first traversal, but I'm not
41 // sure it's worth it.
42 void flatten(state, Writer writer) {
43 keysAndValues(state).forEach((key, value) {
44 var reference = writer._referenceFor(value);
45 state[key] = (reference == null) ? value : reference;
46 });
47 }
48
49 /** Return true if this rule should only be applied when we are the first
50 * rule found that applies to this object. This may or may not be a hack
51 * that will disappear once we have better support for multiple rules.
52 * We want to have multiple different rules that apply to the same object. We
53 * also want to have multiple different rules that might exclusively apply
54 * to the same object. So, we want either ListRule or ListRuleEssential, and
55 * only one of them can be there. But on the other hand, we may want both
56 * ListRule and BasicRule. So we identify the kinds of rules that can share.
57 * If mustBePrimary returns true, then this rule will only be chosen if no
58 * other rule has been found yet. This means that the ordering of rules in
59 * the serialization is significant, which is unpleasant, but we'll have
60 * to see how bad it is.
61 */
62 // TODO(alanknight): Reconsider whether this should be handled differently.
63 get mustBePrimary => false;
64
65 /**
66 * Create the new object corresponding to [state] using the rules
67 * from [reader]. This may involve recursively inflating "essential"
68 * references in the state, which are those that are required for the
69 * object's constructor. It is up to the rule what state is considered
70 * essential.
71 */
72 inflateEssential(state, reader);
73
74 /**
75 * The [object] has already been created. Set any of its non-essential
76 * variables from the representation in [state]. Where there are references
77 * to other objects they are resolved in the context of [reader].
78 */
79 inflateNonEssential(state, object, Reader reader);
80
81 /**
82 * If we have an object [o] as part of our state, should we represent that
83 * directly, or should we make a reference for it. By default we use a
84 * reference for everything.
85 */
86 bool shouldUseReferenceFor(Object o, Writer w) => true;
87
88 /**
89 * This writes the data from our internal representation into a List.
90 * It is used in order to write to a flat format, and is likely to be
91 * folded into a more general mechanism for supporting different output
92 * formats.
93 */
94 // TODO(alanknight): This really shouldn't exist, but is a temporary measure
95 // for writing to a a flat format until that's more fleshed out. It takes
96 // the internal representation of the rule's state, which is particularly
97 // bad. The default implementation treats the ruleData as a List of Lists
98 // of references.
99 void dumpStateInto(List ruleData, List target) {
100 // Needing the intermediate is also bad for performance, but tricky
101 // to do otherwise without a mechanism to precalculate the size.
102 var intermediate = new List();
103 var totalLength = 0;
104 for (var eachList in ruleData) {
105 // TODO(alanknight): Abstract this out better, this really won't scale.
106 if (this is ListRule)
107 intermediate.add(eachList.length);
108 for (var eachRef in eachList) {
109 if (eachRef == null) {
110 intermediate..add(null)..add(null);
111 } else {
112 eachRef.writeToList(intermediate);
113 }
114 }
115 }
116 target.addAll(intermediate);
117 }
118
119 /**
120 * The inverse of dumpStateInto, this reads the rule's state from an
121 * iterator in a flat format.
122 */
123 pullStateFrom(Iterator stream);
124 }
125
126 /**
127 * This rule handles things that implement List. It will recreate them as
128 * whatever the default implemenation of List is on the target platform.
129 */
130 class ListRule extends SerializationRule {
131
132 appliesTo(object) => object is List;
133
134 state(List list) => new List.from(list);
135
136 List extractState(List list, f) {
137 var result = new List();
138 for (var each in list) {
139 result.add(each);
140 f(each);
141 }
142 return result;
143 }
144
145 inflateEssential(List state, Reader r) => new List();
146
147 // For a list, we consider all of its state non-essential and add it
148 // after creation.
149 inflateNonEssential(List state, List newList, Reader r) {
150 populateContents(state, newList, r);
151 }
152
153 void populateContents(List state, List newList, Reader r) {
154 for(var each in state) {
155 newList.add(r.inflateReference(each));
156 }
157 }
158
159 /**
160 * When reading from a flat format we are given [stream] and need to pull as
161 * much data from it as we need. Our format is that we have an integer N
162 * indicating the number of objects and then for each object a length M,
163 * and then M references, where a reference is stored in the stream as two
164 * integers. Or, in the special case of null, two nulls.
165 */
166 pullStateFrom(Iterator stream) {
167 // TODO(alanknight): This is much too close to the basicRule implementation,
168 // and I'd refactor them if I didn't think this whole mechanism needed to
169 // change soon.
170 var dataLength = stream.next();
171 var ruleData = new List();
172 for (var i = 0; i < dataLength; i++) {
173 var subLength = stream.next();
174 var subList = new List();
175 ruleData.add(subList);
176 for (var j = 0; j < subLength; j++) {
177 var a = stream.next();
178 var b = stream.next();
179 if (!(a is int)) {
180 // This wasn't a reference, just use the first object as a literal.
181 // particularly used for the case of null.
182 subList.add(a);
183 } else {
184 subList.add(new Reference(this, a, b));
185 }
186 }
187 }
188 return ruleData;
189 }
190 }
191
192 /**
193 * This is a subclass of ListRule where all of the list's contents are
194 * considered essential state. This is needed if an object X contains a List L,
195 * but it expects L's contents to be fixed when X's constructor is called.
196 */
197 class ListRuleEssential extends ListRule {
198
199 /** Create the new List and also inflate all of its contents. */
200 inflateEssential(List state, Reader r) {
201 var object = super.inflateEssential(state, r);
202 populateContents(state, object, r);
203 return object;
204 }
205
206 /** Does nothing, because all the work has been done in inflateEssential. */
207 inflateNonEssential(state, newList, reader) {}
208
209 bool get mustBePrimary => true;
210 }
211
212 /**
213 * This rule handles primitive types, defined as those that we can normally
214 * represent directly in the output format. We hard-code that to mean
215 * num, String, and bool.
216 */
217 class PrimitiveRule extends SerializationRule {
218 appliesTo(object) {
219 return isPrimitive(object);
220 }
221 extractState(object, Function f) => object;
222 void flatten(object, Writer writer) {}
223 inflateEssential(state, Reader r) => state;
224 inflateNonEssential(object, _, Reader r) {}
225
226 /** Indicate whether we should save pointers to this object as references
227 * or store the object directly. For primitives this depends on the format,
228 * so we delegate to the writer.
229 */
230 bool shouldUseReferenceFor(Object o, Writer w) =>
231 w.shouldUseReferencesForPrimitives;
232
233 /**
234 * This writes the data from our internal representation into a List.
235 * It is used in order to write to a flat format, and is likely to be
236 * folded into a more general mechanism for supporting different output
237 * formats. For primitives, the ruleData is our list of all the
238 * primitives and just add it into the target.
239 */
240 void dumpStateInto(List ruleData, List target) {
241 target.addAll(ruleData);
242 }
243
244 /**
245 * When reading from a flat format we are given [stream] and need to pull as
246 * much data from it as we need. Our format is that we have an integer N
247 * indicating the number of objects and then N simple objects.
248 */
249 pullStateFrom(Iterator stream) {
250 var dataLength = stream.next();
251 var ruleData = new List();
252 for (var i = 0; i < dataLength; i++) {
253 ruleData.add(stream.next());
254 }
255 return ruleData;
256 }
257 }
258
259 /** Helper function for PrimitiveRule to tell which objects it applies to. */
260 bool isPrimitive(Object object) {
261 return (object is num) || (object is String) || (object is bool);
262 }
263
264 /** Typedef for the object construction closure used in ClosureToMapRule. */
265 typedef Object ConstructType(Map);
266
267 /** Typedef for the state-getting closure used in ClosureToMapRule. */
268 typedef Map<String, Object> GetStateType(Object);
269
270 /** Typedef for the state-setting closure used in ClosureToMapRule. */
271 typedef void NonEssentialStateType(Object, Map);
272
273 /**
274 * This is a rule where the extraction and creation are hard-coded as
275 * closures. The result is expected to be a map indexed by field name.
276 */
277 class ClosureToMapRule extends SerializationRule {
278
279 /** The runtimeType of objects that this rule applies to. Used in appliesTo.*/
280 Type type;
281
282 /** The function for constructing new objects when reading. */
283 ConstructType construct;
284
285 /** The function for returning an object's state as a Map. */
286 GetStateType getState;
287
288 /** The function for setting an object's state from a Map. */
289 NonEssentialStateType setNonEssentialState;
290
291 /**
292 * Create a ClosureToMapRule for the given [type] which gets an object's
293 * state by calling [getState], creates a new object by calling [construct]
294 * and sets the new object's state by calling [setNonEssentialState].
295 */
296 ClosureToMapRule(this.type, this.getState, this.construct,
297 this.setNonEssentialState);
298
299 /**
300 * If we deserialize a ClosureToMapRule we can't actually use it, because
301 * we don't have the closures, so generate a stub that just returns the
302 * raw state object.
303 */
304 ClosureToMapRule.stub(this.type) {
305 getState = (x) { throw new SerializationException(
306 'Closures cannot be serialized'); };
307 construct = (state) => state;
308 setNonEssentialState = (object, state) {};
309 }
310
311 bool appliesTo(object) => object.runtimeType == type;
312
313 extractState(object, Function f) {
314 Map state = getState(object);
315 values(state).forEach(f);
316 return state;
317 }
318
319 // TODO(alanknight): We're inflating twice here. How to avoid doing
320 // that without giving the user even more stuff to specify.
321 // Worse than that, by inflating everything in advance, we are are
322 // forcing all the state to be essential.
323 Object inflateEssential(Map<String, Object> state, Reader r) {
324 var inflated = values(state).map((x) => r.inflateReference(x));
325 return construct(inflated);
326 }
327
328 void inflateNonEssential(state, object, Reader r) {
329 if (setNonEssentialState == null) return;
330 var inflated = values(state).map((x) => r.inflateReference(x));
331 setNonEssentialState(inflated, object);
332 }
333 }
334
335 /**
336 * This rule handles things we can't pass directly, but only by reference.
337 * It extracts an identifier we can use to pass them.
338 */
339 class ClassMirrorRule extends SerializationRule {
340 // TODO(alanknight): This probably generalizes to any named object.
341 bool appliesTo(object) {
342 return object is ClassMirror;
343 }
344 extractState(object, Function f) => f(object.simpleName);
345 void flatten(object, Writer writer) {}
346 inflateEssential(state, Reader r) => r.externalObjectNamed(state);
347 inflateNonEssential(object, _, Reader r) {}
348 }
349
350 /**
351 * This is polyfill because we can't hash ClassMirror right now. We
352 * don't bother implementing most of its methods because we don't need them.
353 */
354 // TODO(alanknight): Remove this when you can hash mirrors directly
355 class ClassMirrorWrapper implements ClassMirror {
356 ClassMirror mirror;
357 ClassMirrorWrapper(this.mirror);
358 get simpleName => mirror.simpleName;
359 get hashCode => simpleName.hashCode;
360 operator ==(x) => x is ClassMirror && simpleName == x.simpleName;
361 }
362
363
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698