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

Side by Side Diff: pkg/serialization/lib/serialization.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/README.md ('k') | pkg/serialization/lib/src/basic_rule.dart » ('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 /**
6 * A general-purpose serialization facility for Dart objects.
7 *
8 * A [Serialization] is defined in terms of [SerializationRule]s and supports
9 * reading and writing to different formats.
10 *
11 * For information on installing and importing this library, see the
12 * [serialization package on pub.dartlang.org]
13 * (http://pub.dartlang.org/packages/serialization).
14 *
15 * ## Setup
16 *
17 * A simple example of usage is
18 *
19 * var address = new Address();
20 * address.street = 'N 34th';
21 * address.city = 'Seattle';
22 * var serialization = new Serialization()
23 * ..addRuleFor(Address);
24 * Map output = serialization.write(address);
25 *
26 * This creates a new serialization and adds a rule for address objects.
27 * Then we ask the [Serialization] to write the address
28 * and we get back a Map which is a [json]able representation of the state of
29 * the address and related objects. Note that while the output in this case
30 * is a [Map], the type will vary depending on which output format we've told
31 * the [Serialization] to use.
32 *
33 * The version above used reflection to automatically identify the public
34 * fields of the address object. We can also specify those fields explicitly.
35 *
36 * var serialization = new Serialization()
37 * ..addRuleFor(Address,
38 * constructor: "create",
39 * constructorFields: ["number", "street"],
40 * fields: ["city"]);
41 *
42 * This rule still uses reflection to access the fields, but does not try to
43 * identify which fields to use, but instead uses only the "number" and "street"
44 * fields that we specified. We may also want to tell it to identify the
45 * fields, but to specifically omit certain fields that we don't want
46 * serialized.
47 *
48 * var serialization = new Serialization()
49 * ..addRuleFor(Address,
50 * constructor: "",
51 * excludeFields: ["other", "stuff"]);
52 *
53 * ## Writing rules
54 *
55 * We can also use a completely non-reflective rule to serialize and
56 * de-serialize objects. This can be more work, but it does work in
57 * dart2js, where mirrors are not yet implemented. We can specify this in two
58 * ways. First, we can write our own SerializationRule class that has methods
59 * for our Address class.
60 *
61 * class AddressRule extends CustomRule {
62 * bool appliesTo(instance, Writer w) => instance.runtimeType == Address;
63 * getState(instance) => [instance.street, instance.city];
64 * create(state) => new Address();
65 * setState(Address a, List state) {
66 * a.street = state[0];
67 * a.city = state[1];
68 * }
69 * }
70 *
71 * The class needs four different methods. The [CustomRule.appliesTo]
72 * method tells us if
73 * the rule should be used to write an object. In this example we use a test
74 * based on runtimeType. We could also use an "is Address" test, but if Address
75 * has subclasses that would find those as well, and we want a separate rule
76 * for each. The [CustomRule.getState] method should
77 * return all the state of the object that we want to recreate,
78 * and should be either a Map or a List. If you want to write to human-readable
79 * formats where it's useful to be able to look at the data as a map from
80 * field names to values, then it's better to return it as a map. Otherwise it's
81 * more efficient to return it as a list. You just need to be sure that the
82 * [CustomRule.create] and [CustomRule.setState] methods interpret the data the
83 * same way as [CustomRule.getState] does.
84 *
85 * The [CustomRule.create] method will create the new object and return it. Whil e it's
86 * possible to create the object and set all its state in this one method, that
87 * increases the likelihood of problems with cycles. So it's better to use the
88 * minimum necessary information in [CustomRule.create] and do more of the work
89 * in [CustomRule.setState].
90 *
91 * The other way to do this is not creating a subclass, but by using a
92 * [ClosureRule] and giving it functions for how to create
93 * the address.
94 *
95 * addressToMap(a) => {"number" : a.number, "street" : a.street,
96 * "city" : a.city};
97 * createAddress(Map m) => new Address.create(m["number"], m["street"]);
98 * fillInAddress(Address a, Map m) => a.city = m["city"];
99 * var serialization = new Serialization()
100 * ..addRule(
101 * new ClosureRule(anAddress.runtimeType,
102 * addressToMap, createAddress, fillInAddress);
103 *
104 * In this case we have created standalone functions rather than
105 * methods in a subclass and we pass them to the constructor of
106 * [ClosureRule]. In this case we've also had them use maps rather than
107 * lists for the state, but either would work as long as the rule is
108 * consistent with the representation it uses. We pass it the runtimeType
109 * of the object, and functions equivalent to the methods on [CustomRule]
110 *
111 * ## Constant values
112 *
113 * There are cases where the constructor needs values that we can't easily get
114 * from the serialized object. For example, we may just want to pass null, or a
115 * constant value. To support this, we can specify as constructor fields
116 * values that aren't field names. If any value isn't a String, or is a string
117 * that doesn't correspond to a field name, it will be
118 * treated as a constant and passed unaltered to the constructor.
119 *
120 * In some cases a non-constructor field should not be set using field
121 * access or a setter, but should be done by calling a method. For example, it
122 * may not be possible to set a List field "foo", and you need to call an
123 * addFoo() method for each entry in the list. In these cases, if you are using
124 * a BasicRule for the object you can call the setFieldWith() method.
125 *
126 * s..addRuleFor(FooHolder).setFieldWith("foo",
127 * (parent, value) => for (var each in value) parent.addFoo(value));
128 *
129 * ## Writing
130 *
131 * To write objects, we use the write() method.
132 *
133 * var output = serialization.write(someObject);
134 *
135 * By default this uses a representation in which objects are represented as
136 * maps keyed by field name, but in which references between objects have been
137 * converted into Reference objects. This is then typically encoded as
138 * a [json] string, but can also be used in other ways, e.g. sent to another
139 * isolate.
140 *
141 * We can write objects in different formats by passing a [Format] object to
142 * the [Serialization.write] method or by getting a [Writer] object.
143 * The available formats
144 * include the default, a simple "flat" format that doesn't include field names,
145 * and a simple JSON format that produces output more suitable for talking to
146 * services that expect JSON in a predefined format. Examples of these are
147 *
148 * Map output = serialization.write(address, new SimpleMapFormat());
149 * List output = serialization.write(address, new SimpleFlatFormat());
150 * var output = serialization.write(address, new SimpleJsonFormat());
151 * Or, using a [Writer] explicitly
152 * var writer = serialization.newWriter(new SimpleFlatFormat());
153 * List output = writer.write(address);
154 *
155 * These representations are not yet considered stable.
156 *
157 * ## Reading
158 *
159 * To read objects, the corresponding [Serialization.read] method can be used.
160 *
161 * Address input = serialization.read(input);
162 *
163 * When reading, the serialization instance doing the reading must be configured
164 * with compatible rules to the one doing the writing. It's possible for the
165 * rules to be different, but they need to be able to read the same
166 * representation. For most practical purposes right now they should be the
167 * same. The simplest way to achieve this is by having the serialization
168 * variable [Serialization.selfDescribing] be true. In that case the rules
169 * themselves are also
170 * stored along with the serialized data, and can be read back on the receiving
171 * end. Note that this may not work for all rules or all formats. The
172 * [Serialization.selfDescribing] variable is true by default, but the
173 * [SimpleJsonFormat] does not support it, since the point is to provide a
174 * representation in a form
175 * other services might expect. Using CustomRule or ClosureRule also does not
176 * yet work with the [Serialization.selfDescribing] variable.
177 *
178 * ## Named objects
179 *
180 * When reading, some object references should not be serialized, but should be
181 * connected up to other instances on the receiving side. A notable example of
182 * this is when serialization rules have been stored. Instances of BasicRule
183 * take a [ClassMirror] in their constructor, and we cannot serialize those. So
184 * when we read the rules, we must provide a Map<String, Object> which maps from
185 * the simple name of classes we are interested in to a [ClassMirror]. This can
186 * be provided either in the [Serialization.namedObjects],
187 * or as an additional parameter to the reading and writing methods on the
188 * [Reader] or [Writer] respectively.
189 *
190 * new Serialization()
191 * ..addRuleFor(Person, constructorFields: ["name"])
192 * ..namedObjects['Person'] = reflect(new Person()).type;
193 */
194 library serialization;
195
196 import 'src/mirrors_helpers.dart';
197 import 'src/serialization_helpers.dart';
198 import 'dart:collection';
199
200 part 'src/reader_writer.dart';
201 part 'src/serialization_rule.dart';
202 part 'src/basic_rule.dart';
203 part 'src/format.dart';
204
205 /**
206 * This class defines a particular serialization scheme, in terms of
207 * [SerializationRule] instances, and supports reading and writing them.
208 * See library comment for examples of usage.
209 */
210 class Serialization {
211 final List<SerializationRule> _rules;
212
213 /**
214 * The serialization is controlled by the list of Serialization rules. These
215 * are most commonly added via [addRuleFor].
216 */
217 final UnmodifiableListView<SerializationRule> rules;
218
219 /**
220 * When reading, we may need to resolve references to existing objects in
221 * the system. The right action may not be to create a new instance of
222 * something, but rather to find an existing instance and connect to it.
223 * For example, if we have are serializing an Email message and it has a
224 * link to the owning account, it may not be appropriate to try and serialize
225 * the account. Instead we should just connect the de-serialized message
226 * object to the account object that already exists there.
227 */
228 Map<String, dynamic> namedObjects = {};
229
230 /**
231 * When we write out data using this serialization, should we also write
232 * out a description of the rules. This is on by default unless using
233 * CustomRule subclasses, in which case it requires additional setup and
234 * is off by default.
235 */
236 bool _selfDescribing;
237
238 /**
239 * When we write out data using this serialization, should we also write
240 * out a description of the rules. This is on by default unless using
241 * CustomRule subclasses, in which case it requires additional setup and
242 * is off by default.
243 */
244 bool get selfDescribing {
245 // TODO(alanknight): Should this be moved to the format?
246 // TODO(alanknight): Allow self-describing in the presence of CustomRule.
247 // TODO(alanknight): Don't do duplicate work creating a writer and
248 // serialization here and then re-creating when we actually serialize.
249 if (_selfDescribing != null) return _selfDescribing;
250 var meta = ruleSerialization();
251 var w = meta.newWriter();
252 _selfDescribing = !rules.any((rule) =>
253 meta.rulesFor(rule, w, create: false).isEmpty);
254 return _selfDescribing;
255 }
256
257 /**
258 * When we write out data using this serialization, should we also write
259 * out a description of the rules. This is on by default unless using
260 * CustomRule subclasses, in which case it requires additional setup and
261 * is off by default.
262 */
263 void set selfDescribing(bool value) {
264 _selfDescribing = value;
265 }
266
267 /**
268 * Creates a new serialization with a default set of rules for primitives
269 * and lists.
270 */
271 factory Serialization() =>
272 new Serialization.blank()
273 ..addDefaultRules();
274
275 /**
276 * Creates a new serialization with no default rules at all. The most common
277 * use for this is if we are reading self-describing serialized data and
278 * will populate the rules from that data.
279 */
280 factory Serialization.blank()
281 => new Serialization._(new List<SerializationRule>());
282
283 Serialization._(List<SerializationRule> rules) :
284 this._rules = rules,
285 this.rules = new UnmodifiableListView(rules);
286
287 /**
288 * Create a [BasicRule] rule for [instanceOrType]. Normally this will be
289 * a type, but for backward compatibilty we also allow you to pass an
290 * instance (except an instance of Type), and the rule will be created
291 * for its runtimeType. Optionally
292 * allows specifying a [constructor] name, the list of [constructorFields],
293 * and the list of [fields] not used in the constructor. Returns the new
294 * rule. Note that [BasicRule] uses reflection, and so will not work with the
295 * current state of dartj2s. If you need to run there, consider using
296 * [CustomRule] instead.
297 *
298 * If the optional parameters aren't specified, the default constructor will
299 * be used, and the list of fields will be computed. Alternatively, you can
300 * omit [fields] and provide [excludeFields], which will then compute the
301 * list of fields specifically excluding those listed.
302 *
303 * The fields can be actual public fields, but can also be getter/setter
304 * pairs or getters whose value is provided in the constructor. For the
305 * [constructorFields] they can also be arbitrary objects. Anything that is
306 * not a String will be treated as a constant value to be used in any
307 * construction of these objects.
308 *
309 * If the list of fields is computed, fields from the superclass will be
310 * included. However, each subclass needs its own rule, since the constructors
311 * are not inherited, and so may need to be specified separately for each
312 * subclass.
313 */
314 BasicRule addRuleFor(
315 instanceOrType,
316 {String constructor,
317 List constructorFields,
318 List<String> fields,
319 List<String> excludeFields}) {
320
321 var rule = new BasicRule(
322 turnInstanceIntoSomethingWeCanUse(
323 instanceOrType),
324 constructor, constructorFields, fields, excludeFields);
325 addRule(rule);
326 return rule;
327 }
328
329 /** Set up the default rules, for lists and primitives. */
330 void addDefaultRules() {
331 addRule(new PrimitiveRule());
332 addRule(new ListRule());
333 // Both these rules apply to lists, so unless otherwise indicated,
334 // it will always find the first one.
335 addRule(new ListRuleEssential());
336 addRule(new MapRule());
337 addRule(new SymbolRule());
338 addRule(new DateTimeRule());
339 }
340
341 /**
342 * Add a new SerializationRule [rule]. The addRuleFor method will probably
343 * handle most simple cases, but for adding an arbitrary rule, including
344 * a SerializationRule subclass which you have created, you can use this
345 * method.
346 */
347 void addRule(SerializationRule rule) {
348 rule.number = _rules.length;
349 _rules.add(rule);
350 }
351
352 /**
353 * This writes out an object graph rooted at [object] and returns the result.
354 * The [format] parameter determines the form of the result. The default
355 * format is [SimpleMapFormat] and returns a Map.
356 */
357 write(Object object, {Format format}) {
358 return newWriter(format).write(object);
359 }
360
361 /**
362 * Return a new [Writer] object for this serialization. This is useful if you
363 * want to do something more complex with the writer than just returning
364 * the final result.
365 */
366 Writer newWriter([Format format]) => new Writer(this, format);
367
368 /**
369 * Read the serialized data from [input] and return the root object
370 * from the result. The [input] can be of any type that the [Format]
371 * reads/writes, but normally will be a [List], [Map], or a simple type.
372 * The [format] parameter determines the form of the result. The default
373 * format is [SimpleMapFormat] and expects a Map as input.
374 * If there are objects that need to be resolved
375 * in the current context, they should be provided in [externals] as a
376 * Map from names to values. In particular, in the current implementation
377 * any class mirrors needed should be provided in [externals] using the
378 * class name as a key. In addition to the [externals] map provided here,
379 * values will be looked up in the [namedObjects] map.
380 */
381 read(input, {Format format, Map externals: const {}}) {
382 return newReader(format).read(input, externals);
383 }
384
385 /**
386 * Return a new [Reader] object for this serialization. This is useful if
387 * you want to do something more complex with the reader than just returning
388 * the final result.
389 */
390 Reader newReader([Format format]) => new Reader(this, format);
391
392 /**
393 * Return the list of SerializationRule that apply to [object]. For
394 * internal use, but public because it's used in testing.
395 */
396 Iterable<SerializationRule> rulesFor(object, Writer w, {create : true}) {
397 // This has a couple of edge cases.
398 // 1) The owning object may have indicated we should use a different
399 // rule than the default.
400 // 2) We may not have a rule, in which case we lazily create a BasicRule.
401 // 3) Rules are allowed to say mustBePrimary, meaning that they can be used
402 // iff no other rule was chosen first.
403 // TODO(alanknight): Can the mustBePrimary mechanism be removed or changed.
404 // It adds an order dependency to the rules, and is messy. Reconsider in the
405 // light of a more general mechanism for multiple rules per object.
406 // TODO(alanknight): Finding which rules apply seems likely to be a
407 // bottleneck, particularly with the current reflective implementation.
408 // Consider how to improve it. e.g. cache the list of rules by class. But
409 // be careful of issues like rules which have arbitrary predicates. Or
410 // consider having the arbitrary predicates be secondary to an initial
411 // class-based lookup mechanism.
412 var target, candidateRules;
413 if (object is DesignatedRuleForObject) {
414 target = object.target;
415 candidateRules = object.possibleRules(rules);
416 } else {
417 target = object;
418 candidateRules = rules;
419 }
420 Iterable applicable = candidateRules.where(
421 (each) => each.appliesTo(target, w));
422
423 if (applicable.isEmpty) {
424 return create ? [addRuleFor(target)] : applicable;
425 }
426
427 if (applicable.length == 1) return applicable;
428 var first = applicable.first;
429 var finalRules = applicable.where(
430 (x) => !x.mustBePrimary || (x == first));
431
432 if (finalRules.isEmpty) throw new SerializationException(
433 'No valid rule found for object $object');
434 return finalRules;
435 }
436
437 /**
438 * Create a Serialization for serializing SerializationRules. This is used
439 * to save the rules in a self-describing format along with the data.
440 * If there are new rule classes created, they will need to be described
441 * here.
442 */
443 Serialization ruleSerialization() {
444 // TODO(alanknight): There's an extensibility issue here with new rules.
445 // TODO(alanknight): How to handle rules with closures? They have to
446 // exist on the other side, but we might be able to hook them up by name,
447 // or we might just be able to validate that they're correctly set up
448 // on the other side.
449
450 var meta = new Serialization()
451 ..selfDescribing = false
452 ..addRuleFor(ListRule)
453 ..addRuleFor(MapRule)
454 ..addRuleFor(PrimitiveRule)
455 ..addRuleFor(ListRuleEssential)
456 ..addRuleFor(BasicRule,
457 constructorFields: ['type',
458 'constructorName',
459 'constructorFields', 'regularFields', []],
460 fields: [])
461 ..addRule(new NamedObjectRule())
462 ..addRule(new MirrorRule())
463 ..addRuleFor(MirrorRule)
464 ..addRuleFor(SymbolRule)
465 ..addRuleFor(DateTimeRule);
466 meta.namedObjects = namedObjects;
467 return meta;
468 }
469
470 /** Return true if our [namedObjects] collection has an entry for [object].*/
471 bool _hasNameFor(object) {
472 var sentinel = const _Sentinel();
473 return _nameFor(object, () => sentinel) != sentinel;
474 }
475
476 /**
477 * Return the name we have for [object] in our [namedObjects] collection or
478 * the result of evaluating [ifAbsent] if there is no entry.
479 */
480 _nameFor(object, [ifAbsent]) {
481 for (var key in namedObjects.keys) {
482 if (identical(namedObjects[key], object)) return key;
483 }
484 return ifAbsent == null ? null : ifAbsent();
485 }
486 }
487
488 /**
489 * An exception class for errors during serialization.
490 */
491 class SerializationException implements Exception {
492 final String message;
493 const SerializationException(this.message);
494 String toString() => "SerializationException($message)";
495 }
OLDNEW
« no previous file with comments | « pkg/serialization/README.md ('k') | pkg/serialization/lib/src/basic_rule.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698