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

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

Issue 584473004: Revert "remove serialization. it's moved to github" (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 3 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
« no previous file with comments | « pkg/serialization/lib/src/serialization_helpers.dart ('k') | pkg/serialization/pubspec.yaml » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 // TODO(alanknight): We should have an example and tests for subclassing
8 // serialization rule rather than using the hard-coded ClosureToMap rule. And
9 // possibly an abstract superclass that's designed to be subclassed that way.
10 /**
11 * The abstract superclass for serialization rules.
12 */
13 abstract class SerializationRule {
14 /**
15 * Rules belong uniquely to a particular Serialization instance, and can
16 * be identified within it by number.
17 */
18 int _number;
19
20 /**
21 * Rules belong uniquely to a particular Serialization instance, and can
22 * be identified within it by number.
23 */
24 int get number => _number;
25
26 /**
27 * Rules belong uniquely to a particular Serialization instance, and can
28 * be identified within it by number.
29 */
30 void set number(int value) {
31 if (_number != null && _number != value) throw
32 new SerializationException("Rule numbers cannot be changed, once set");
33 _number = value;
34 }
35
36 /**
37 * Return true if this rule applies to this object, in the context
38 * where we're writing it, false otherwise.
39 */
40 bool appliesTo(object, Writer writer);
41
42 /**
43 * This extracts the state from the object, calling [f] for each value
44 * as it is extracted, and returning an object representing the whole
45 * state at the end. The state that results will still have direct
46 * pointers to objects, rather than references.
47 */
48 extractState(object, void f(value), Writer w);
49
50 /**
51 * Allows rules to tell us how they expect to store their state. If this
52 * isn't specified we can also just look at the data to tell.
53 */
54 bool get storesStateAsLists => false;
55 bool get storesStateAsMaps => false;
56 bool get storesStateAsPrimitives => false;
57
58 /**
59 * Given the variables representing the state of an object, flatten it
60 * by turning object pointers into Reference objects where needed. This
61 * destructively modifies the state object.
62 *
63 * This has a default implementation which assumes that object is indexable,
64 * so either conforms to Map or List. Subclasses may override to do something
65 * different, including returning a new state object to be used in place
66 * of the original.
67 */
68 // This has to be a separate operation from extracting, because we extract
69 // as we are traversing the objects, so we don't yet have the objects to
70 // generate references for them. It might be possible to avoid that by
71 // doing a depth-first rather than breadth-first traversal, but I'm not
72 // sure it's worth it.
73 flatten(state, Writer writer) {
74 keysAndValues(state).forEach((key, value) {
75 var reference = writer._referenceFor(value);
76 state[key] = reference;
77 });
78 }
79
80 /** Return true if this rule should only be applied when we are the first
81 * rule found that applies to this object. This may or may not be a hack
82 * that will disappear once we have better support for multiple rules.
83 * We want to have multiple different rules that apply to the same object. We
84 * also want to have multiple different rules that might exclusively apply
85 * to the same object. So, we want either ListRule or ListRuleEssential, and
86 * only one of them can be there. But on the other hand, we may want both
87 * ListRule and BasicRule. So we identify the kinds of rules that can share.
88 * If mustBePrimary returns true, then this rule will only be chosen if no
89 * other rule has been found yet. This means that the ordering of rules in
90 * the serialization is significant, which is unpleasant, but we'll have
91 * to see how bad it is.
92 */
93 // TODO(alanknight): Reconsider whether this should be handled differently.
94 bool get mustBePrimary => false;
95
96 /**
97 * Create the new object corresponding to [state] using the rules
98 * from [reader]. This may involve recursively inflating "essential"
99 * references in the state, which are those that are required for the
100 * object's constructor. It is up to the rule what state is considered
101 * essential.
102 */
103 inflateEssential(state, Reader reader);
104
105 /**
106 * The [object] has already been created. Set any of its non-essential
107 * variables from the representation in [state]. Where there are references
108 * to other objects they are resolved in the context of [reader].
109 */
110 void inflateNonEssential(state, object, Reader reader);
111
112 /**
113 * If we have [object] as part of our state, should we represent that
114 * directly, or should we make a reference for it. By default, true.
115 * This may also delegate to [writer].
116 */
117 bool shouldUseReferenceFor(object, Writer writer) => true;
118
119 /**
120 * Return true if the data this rule returns is variable length, so a
121 * length needs to be written for it if the format requires that. Return
122 * false if the results are always the same length.
123 */
124 bool get hasVariableLengthEntries => true;
125
126 /**
127 * If the data is fixed length, return it here. The format may or may not
128 * make use of this, depending on whether it already has enough information
129 * to determine the length on its own. If [hasVariableLengthEntries] is true
130 * this is ignored.
131 */
132 int get dataLength => 0;
133 }
134
135 /**
136 * This rule handles things that implement List. It will recreate them as
137 * whatever the default implemenation of List is on the target platform.
138 */
139 class ListRule extends SerializationRule {
140
141 bool appliesTo(object, Writer w) => object is List;
142
143 bool get storesStateAsLists => true;
144
145 List extractState(List list, f, Writer w) {
146 var result = new List();
147 for (var each in list) {
148 result.add(each);
149 f(each);
150 }
151 return result;
152 }
153
154 inflateEssential(List state, Reader r) => new List();
155
156 // For a list, we consider all of its state non-essential and add it
157 // after creation.
158 void inflateNonEssential(List state, List newList, Reader r) {
159 populateContents(state, newList, r);
160 }
161
162 void populateContents(List state, List newList, Reader r) {
163 for(var each in state) {
164 newList.add(r.inflateReference(each));
165 }
166 }
167
168 bool get hasVariableLengthEntries => true;
169 }
170
171 /**
172 * This is a subclass of ListRule where all of the list's contents are
173 * considered essential state. This is needed if an object X contains a List L,
174 * but it expects L's contents to be fixed when X's constructor is called.
175 */
176 class ListRuleEssential extends ListRule {
177
178 /** Create the new List and also inflate all of its contents. */
179 inflateEssential(List state, Reader r) {
180 var object = super.inflateEssential(state, r);
181 populateContents(state, object, r);
182 return object;
183 }
184
185 /** Does nothing, because all the work has been done in inflateEssential. */
186 void inflateNonEssential(state, newList, reader) {}
187
188 bool get mustBePrimary => true;
189 }
190
191 /**
192 * This rule handles things that implement Map. It will recreate them as
193 * whatever the default implemenation of Map is on the target platform. If a
194 * map has string keys it will attempt to retain it as a map for JSON formats,
195 * otherwise it will store it as a list of references to keys and values.
196 */
197 class MapRule extends SerializationRule {
198
199 bool appliesTo(object, Writer w) => object is Map;
200
201 bool get storesStateAsMaps => true;
202
203 extractState(Map map, f, Writer w) {
204 // Note that we make a copy here because flattening may be destructive.
205 var newMap = new Map.from(map);
206 newMap.forEach((key, value) {
207 f(key);
208 f(value);
209 });
210 return newMap;
211 }
212
213 /**
214 * Change the keys and values of [state] into references in [writer].
215 * If [state] is a map whose keys are all strings then we leave the keys
216 * as is so that JSON formats will be more readable. If the keys are
217 * arbitrary then we need to turn them into references and replace the
218 * state with a new Map whose keys are the references.
219 */
220 flatten(Map state, Writer writer) {
221 bool keysAreAllStrings = state.keys.every((x) => x is String);
222 if (keysAreAllStrings && !writer.shouldUseReferencesForPrimitives) {
223 keysAndValues(state).forEach(
224 (key, value) => state[key] = writer._referenceFor(value));
225 return state;
226 } else {
227 var newState = [];
228 keysAndValues(state).forEach((key, value) {
229 newState.add(writer._referenceFor(key));
230 newState.add(writer._referenceFor(value));
231 });
232 return newState;
233 }
234 }
235
236 inflateEssential(state, Reader r) => new Map();
237
238 // For a map, we consider all of its state non-essential and add it
239 // after creation.
240 void inflateNonEssential(state, Map newMap, Reader r) {
241 if (state is List) {
242 inflateNonEssentialFromList(state, newMap, r);
243 } else {
244 inflateNonEssentialFromMap(state, newMap, r);
245 }
246 }
247
248 void inflateNonEssentialFromList(List state, Map newMap, Reader r) {
249 var key;
250 for (var each in state) {
251 if (key == null) {
252 key = each;
253 } else {
254 newMap[r.inflateReference(key)] = r.inflateReference(each);
255 key = null;
256 }
257 }
258 }
259
260 void inflateNonEssentialFromMap(Map state, Map newMap, Reader r) {
261 state.forEach((key, value) {
262 newMap[r.inflateReference(key)] = r.inflateReference(value);
263 });
264 }
265
266 bool get hasVariableLengthEntries => true;
267 }
268
269 /**
270 * This rule handles primitive types, defined as those that we can normally
271 * represent directly in the output format. We hard-code that to mean
272 * num, String, and bool.
273 */
274 class PrimitiveRule extends SerializationRule {
275 bool appliesTo(object, Writer w) => isPrimitive(object);
276 extractState(object, Function f, Writer w) => object;
277 flatten(object, Writer writer) {}
278 inflateEssential(state, Reader r) => state;
279 void inflateNonEssential(object, _, Reader r) {}
280
281 bool get storesStateAsPrimitives => true;
282
283 /**
284 * Indicate whether we should save pointers to this object as references
285 * or store the object directly. For primitives this depends on the format,
286 * so we delegate to the writer.
287 */
288 bool shouldUseReferenceFor(object, Writer w) =>
289 w.shouldUseReferencesForPrimitives;
290
291 bool get hasVariableLengthEntries => false;
292 }
293
294 /** Typedef for the object construction closure used in ClosureRule. */
295 typedef ConstructType(Map m);
296
297 /** Typedef for the state-getting closure used in ClosureToMapRule. */
298 typedef Map<String, dynamic> GetStateType(object);
299
300 /** Typedef for the state-setting closure used in ClosureToMapRule. */
301 typedef void NonEssentialStateType(object, Map m);
302
303 /**
304 * This is a rule where the extraction and creation are hard-coded as
305 * closures. The result is expected to be a map indexed by field name.
306 */
307 class ClosureRule extends CustomRule {
308
309 /** The runtimeType of objects that this rule applies to. Used in appliesTo.*/
310 final Type type;
311
312 /** The function for constructing new objects when reading. */
313 final ConstructType construct;
314
315 /** The function for returning an object's state as a Map. */
316 final GetStateType getStateFunction;
317
318 /** The function for setting an object's state from a Map. */
319 final NonEssentialStateType setNonEssentialState;
320
321 /**
322 * Create a ClosureToMapRule for the given [type] which gets an object's
323 * state by calling [getState], creates a new object by calling [construct]
324 * and sets the new object's state by calling [setNonEssentialState].
325 */
326 ClosureRule(this.type, this.getStateFunction, this.construct,
327 this.setNonEssentialState);
328
329 bool appliesTo(object, Writer w) => object.runtimeType == type;
330
331 getState(object) => getStateFunction(object);
332
333 create(state) => construct(state);
334
335 void setState(object, state) {
336 if (setNonEssentialState == null) return;
337 setNonEssentialState(object, state);
338 }
339 }
340
341 /**
342 * This rule handles things we can't pass directly, but only by reference.
343 * If objects are listed in the namedObjects in the writer or serialization,
344 * it will save the name rather than saving the state.
345 */
346 class NamedObjectRule extends SerializationRule {
347 /**
348 * Return true if this rule applies to the object. Checked by looking up
349 * in the namedObjects collection.
350 */
351 bool appliesTo(object, Writer writer) {
352 return writer.hasNameFor(object);
353 }
354
355 /** Extract the state of the named objects as just the object itself. */
356 extractState(object, Function f, Writer writer) {
357 var result = [nameFor(object, writer)];
358 f(result.first);
359 return result;
360 }
361
362 /** When we flatten the state we save it as the name. */
363 // TODO(alanknight): This seems questionable. In a truly flat format we may
364 // want to have extracted the name as a string first and flatten it into a
365 // reference to that. But that requires adding the Writer as a parameter to
366 // extractState, and I'm reluctant to add yet another parameter until
367 // proven necessary.
368 flatten(state, Writer writer) {
369 state[0] = writer._referenceFor(state[0]);
370 }
371
372 /** Look up the named object and return it. */
373 inflateEssential(state, Reader r) =>
374 r.objectNamed(r.resolveReference(state.first));
375
376 /** Set any non-essential state on the object. For this rule, a no-op. */
377 void inflateNonEssential(state, object, Reader r) {}
378
379 /** Return the name for this object in the Writer. */
380 String nameFor(object, Writer writer) => writer.nameFor(object);
381 }
382
383 /**
384 * This rule handles the special case of Mirrors. It stores the mirror by its
385 * qualifiedName and attempts to look it up in both the namedObjects
386 * collection, or if it's not found there, by looking it up in the mirror
387 * system. When reading, the user is responsible for supplying the appropriate
388 * values in [Serialization.namedObjects] or in the [externals] paramter to
389 * [Serialization.read].
390 */
391 class MirrorRule extends NamedObjectRule {
392 bool appliesTo(object, Writer writer) => object is DeclarationMirror;
393
394 String nameFor(DeclarationMirror object, Writer writer) =>
395 MirrorSystem.getName(object.qualifiedName);
396
397 inflateEssential(state, Reader r) {
398 var qualifiedName = r.resolveReference(state.first);
399 var lookupFull = r.objectNamed(qualifiedName, (x) => null);
400 if (lookupFull != null) return lookupFull;
401 var separatorIndex = qualifiedName.lastIndexOf(".");
402 var type = qualifiedName.substring(separatorIndex + 1);
403 var lookup = r.objectNamed(type, (x) => null);
404 if (lookup != null) return lookup;
405 var name = qualifiedName.substring(0, separatorIndex);
406 // This is very ugly. The library name for an unnamed library is its URI.
407 // That can't be constructed as a Symbol, so we can't use findLibrary.
408 // So follow one or the other path depending if it has a colon, which we
409 // assume is in any URI and can't be in a Symbol.
410 if (name.contains(":")) {
411 var uri = Uri.parse(name);
412 var libMirror = currentMirrorSystem().libraries[uri];
413 var candidate = libMirror.declarations[new Symbol(type)];
414 return candidate is ClassMirror ? candidate : null;
415 } else {
416 var symbol = new Symbol(name);
417 var typeSymbol = new Symbol(type);
418 for (var libMirror in currentMirrorSystem().libraries.values) {
419 if (libMirror.simpleName != symbol) continue;
420 var candidate = libMirror.declarations[typeSymbol];
421 if (candidate != null && candidate is ClassMirror) return candidate;
422 }
423 return null;
424 }
425 }
426 }
427
428 /**
429 * This provides an abstract superclass for writing your own rules specific to
430 * a class. It makes some assumptions about behaviour, and so can have a
431 * simpler set of methods that need to be implemented in order to subclass it.
432 *
433 */
434 abstract class CustomRule extends SerializationRule {
435 // TODO(alanknight): It would be nice if we could provide an implementation
436 // of appliesTo() here. If we add a type parameter to these classes
437 // we can "is" test against it, but we need to be able to rule out subclasses.
438 // => instance.runtimeType == T
439 // should work.
440 /**
441 * Return true if this rule applies to this object, in the context
442 * where we're writing it, false otherwise.
443 */
444 bool appliesTo(instance, Writer w);
445
446 /**
447 * Subclasses should implement this to return a list of the important fields
448 * in the object. The order of the fields doesn't matter, except that the
449 * create and setState methods need to know how to use it.
450 */
451 List getState(instance);
452
453 /**
454 * Given a [List] of the object's [state], re-create the object. This should
455 * do the minimum needed to create the object, just calling the constructor.
456 * Setting the remaining state of the object should be done in the [setState]
457 * method, which will be called only once all the objects are created, so
458 * it won't cause problems with cycles.
459 */
460 create(List state);
461
462 /**
463 * Set any state in [object] which wasn't set in the constructor. Between
464 * this method and [create] all of the information in [state] should be set
465 * in the new object.
466 */
467 void setState(object, List state);
468
469 extractState(instance, Function f, Writer w) {
470 var state = getState(instance);
471 for (var each in values(state)) {
472 f(each);
473 }
474 return state;
475 }
476
477 inflateEssential(state, Reader r) => create(_lazy(state, r));
478
479 void inflateNonEssential(state, object, Reader r) {
480 setState(object, _lazy(state, r));
481 }
482
483 // We don't want to have to make the end user tell us how long the list is
484 // separately, so write it out for each object, even though they're all
485 // expected to be the same length.
486 bool get hasVariableLengthEntries => true;
487 }
488
489 /** A hard-coded rule for serializing Symbols. */
490 class SymbolRule extends CustomRule {
491 bool appliesTo(instance, _) => instance is Symbol;
492 getState(instance) => [MirrorSystem.getName(instance)];
493 create(state) => new Symbol(state[0]);
494 void setState(symbol, state) {}
495 int get dataLength => 1;
496 bool get hasVariableLengthEntries => false;
497 }
498
499 /** A hard-coded rule for DateTime. */
500 class DateTimeRule extends CustomRule {
501 bool appliesTo(instance, _) => instance is DateTime;
502 List getState(DateTime date) => [date.millisecondsSinceEpoch, date.isUtc];
503 DateTime create(List state) =>
504 new DateTime.fromMillisecondsSinceEpoch(state[0], isUtc: state[1]);
505 void setState(date, state) {}
506 // Let the system know we don't have to store a length for these.
507 int get dataLength => 2;
508 bool get hasVariableLengthEntries => false;
509 }
510
511 /** Create a lazy list/map that will inflate its items on demand in [r]. */
512 _lazy(l, Reader r) {
513 if (l is List) return new _LazyList(l, r);
514 if (l is Map) return new _LazyMap(l, r);
515 throw new SerializationException("Invalid type: must be Map or List - $l");
516 }
517
518 /**
519 * This provides an implementation of Map that wraps a list which may
520 * contain references to (potentially) non-inflated objects. If these
521 * are accessed it will inflate them. This allows us to pass something that
522 * looks like it's just a list of objects to a [CustomRule] without needing
523 * to inflate all the references in advance.
524 */
525 class _LazyMap implements Map {
526 _LazyMap(this._raw, this._reader);
527
528 final Map _raw;
529 final Reader _reader;
530
531 // This is the only operation that really matters.
532 operator [](x) => _reader.inflateReference(_raw[x]);
533
534 int get length => _raw.length;
535 bool get isEmpty => _raw.isEmpty;
536 bool get isNotEmpty => _raw.isNotEmpty;
537 Iterable get keys => _raw.keys;
538 bool containsKey(x) => _raw.containsKey(x);
539
540 // These operations will work, but may be expensive, and are probably
541 // best avoided.
542 get _inflated => mapValues(_raw, _reader.inflateReference);
543 bool containsValue(x) => _inflated.containsValue(x);
544 Iterable get values => _inflated.values;
545 void forEach(f) => _inflated.forEach(f);
546
547 // These operations are all invalid
548 _throw() {
549 throw new UnsupportedError("Not modifiable");
550 }
551 operator []=(x, y) => _throw();
552 putIfAbsent(x, y) => _throw();
553 bool remove(x) => _throw();
554 void clear() => _throw();
555 void addAll(Map other) => _throw();
556 }
557
558 /**
559 * This provides an implementation of List that wraps a list which may
560 * contain references to (potentially) non-inflated objects. If these
561 * are accessed it will inflate them. This allows us to pass something that
562 * looks like it's just a list of objects to a [CustomRule] without needing
563 * to inflate all the references in advance.
564 */
565 class _LazyList extends ListBase {
566 _LazyList(this._raw, this._reader);
567
568 final List _raw;
569 final Reader _reader;
570
571 operator [](int x) => _reader.inflateReference(_raw[x]);
572 int get length => _raw.length;
573
574 void set length(int value) => _throw();
575
576 void operator []=(int index, value) => _throw();
577
578 void _throw() {
579 throw new UnsupportedError("Not modifiable");
580 }
581 }
OLDNEW
« no previous file with comments | « pkg/serialization/lib/src/serialization_helpers.dart ('k') | pkg/serialization/pubspec.yaml » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698