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

Side by Side Diff: pkg/serialization/lib/src/basic_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 // TODO(alanknight): Figure out how to reasonably separate out the things
8 // that require reflection without making the API more awkward. Or if that is
9 // in fact necessary. Maybe the tree-shaking will just remove it if unused.
10
11 /**
12 * This is the basic rule for handling "normal" objects, which have a list of
13 * fields and a constructor, as opposed to simple types or collections. It uses
14 * mirrors to access the state, and can also use them to figure out the list
15 * of fields and the constructor if it's not provided.
16 *
17 * If you call [Serialization.addRule], this is what you get.
18 *
19 */
20 class BasicRule extends SerializationRule {
21 /**
22 * The [type] is used both to find fields and to verify if the object is one
23 * that we handle.
24 */
25 ClassMirror type;
Jennifer Messerly 2012/11/15 08:02:14 can anything bad happen if this is mutated after c
Alan Knight 2012/11/15 20:51:03 I think everything would be ok, but there's also n
26
27 /** Used to create new objects when reading. */
28 Constructor constructor;
29
30 /** This holds onto our list of fields, and can also calculate them. */
31 _FieldList fields;
32
33 /**
34 * Instances can either use maps or lists to hold the object's state. The list
35 * representation is much more compact and used by default. The map
36 * representation is much easier to debug. The default is to use lists.
Jennifer Messerly 2012/11/15 08:02:14 would maps be an easier thing to use for common se
Alan Knight 2012/11/15 20:51:03 It might be, I'm not sure. Changed the text to say
37 */
38 bool useMaps = false;
39
40 // TODO(alanknight) Change the type parameter once we have class literals.
41 // TODO(alanknight) Does the comment for this format properly?
42 /**
43 * Create this rule. Right now the user is obliged to pass a ClassMirror,
44 * but once we allow class literals it will support that. The other parameters
45 * can all be left as null, and are optional on the [Serialization.addRule]
46 * method which is the normal caller for this.
47 * [constructorName] is the constructor, if not the default.
48 * [constructorFields] are the fields required to call the constructor, which
49 * is the essential state. They don't have to be actual fields,
50 * getter/setter pairs or getter/constructor pairs are fine. Note that
51 * the constructorFields do not need to be strings, they can be arbitrary
52 * values. For non-strings, these will be treated as constant values to be
53 * used instead of data read from the objects.
54 * [regularFields] are the non-essential fields. They don't have to be actual
55 * fields, getter/setter pairs are fine. If this is null, it's assumed
56 * that we should figure them out.
57 * [excludeFields] lets you tell it to find the fields automatically, but
58 * omit some that would otherwise be included.
59 */
60 BasicRule(ClassMirror this.type, String constructorName,
61 List constructorFields, List regularFields,
62 List excludeFields) {
63 _findFields(constructorFields, regularFields, excludeFields);
64 constructor = new Constructor(
65 type, constructorName, fields.constructorFieldIndices());
66 configureForLists();
67 }
68
69 /**
70 * Sometimes it's necessary to treat fields of an object differently, based
71 * on the containing object. For example, by default a list treats its
72 * contents as non-essential state, so it will be populated only after all
73 * objects have been created. An object may have a list which is used in its
74 * constructor and must be fully created before the owning object can be
75 * created. Alternatively, it may not be possible to set a field directly,
76 * and some other method must be called to set it, perhaps calling a method
77 * on the owning object to add each individual element.
78 *
79 * This method lets you designate a function to use to set the value of a
80 * field. It also makes the contents of that field be treated as essential,
81 * which currently only has meaning if the field is a list. This is done
82 * because you might set a list field's special treatment function to add
83 * each item individually and that will only work if those objects already
84 * exist.
85 *
86 * For example, to serialize a Serialization, we need its rules to be
87 * individually added rather than just setting the rules field.
88 * ..addRuleFor(new Serialization()).specialTreatmentFor('rules',
89 * (InstanceMirror s, List rules) {
90 * rules.forEach((x) => s.reflectee.addRule(x));
91 * Note that the function is passed the owning object as well as the field
92 * value, but that it is passed as a mirror.
93 */
94 specialTreatmentFor(String fieldName, SetWithFunction setWith) {
95 fields.addAllByName([fieldName]);
96 _Field field = fields.named(fieldName);
97 Function setter = (setWith == null) ? field.defaultSetter : setWith;
98 field.specialTreatment = setter;
99 }
100
101 // TODO(alanknight): Polyfill for the non-hashability of mirrors. Issue 6880.
102 get typeWrapped => new ClassMirrorWrapper(type);
103
104 /** Return the name of the constructor used to create new instances on read.*/
105 String get constructorName => constructor.name;
106
107 /** Return the list of field names to be passed to the constructor.*/
108 List<String> get constructorFields => fields.constructorFieldNames();
109
110 /** Return the list of field names not used in the constructor. */
111 List<String> get regularFields => fields.regularFieldNames();
112
113 String toString() => "Basic Rule for ${type.simpleName}";
114
115 /**
116 * Configure this instance to use maps by field name as its output.
117 * Instances can either produce maps or lists. The list representation
118 * is much more compact and used by default. The map representation is
119 * much easier to debug. The default is to use lists.
120 */
121 configureForMaps() {
122 useMaps = true;
123 }
124
125 /**
126 * Configure this instance to use lists accessing fields by index as its
127 * output. Instances can either produce maps or lists. The list representation
128 * is much more compact and used by default. The map representation is
129 * much easier to debug. The default is to use lists.
130 */
131 configureForLists() {
132 useMaps = false;
133 }
134
135 /** Create either a list or a map to hold the object's state, depending
136 * on the [useMaps] variable. If using a Map, we wrap it in order to keep
137 * the protocol compatible. See [configureForLists]/[configureForMaps].
138 */
139 createStateHolder() =>
140 useMaps ? new _MapWrapper(fields.contents) : new List(fields.length);
141
142 /** Wrap the state if it's passed in as a map. */
143 makeIndexableByNumber(state) =>
144 (state is Map) ? new _MapWrapper.fromMap(state, fields.contents) : state;
145
146 /**
147 * Extract the state from [object] using an instanceMirror and the field
148 * names in [fields]. Call the function [callback] on each value.
149 */
150 extractState(object, Function callback) {
151 var result = createStateHolder();
152 var mirror = reflect(object);
153
154 keysAndValues(fields).forEach(
155 (index, field) {
156 var value = _value(mirror, field);
157 callback(checkForEssentialLists(index, value));
158 result[index] = value;
159 });
160 return _unwrap(result);
161 }
162
163 /**
164 * If the value is a List, and the field is a constructor field or
165 * otherwise specially designated, we wrap it in something that indicates
166 * a restriction on the rules that can be used. Which in this case amounts
167 * to designating the rule, since we so far only have one rule per object.
168 */
169 checkForEssentialLists(index, value) {
170 if (value is List && fields.contents[index].isEssential) {
171 return new DesignatedRuleForObject(value,
172 (index) => index is ListRuleEssential);
173 } else {
174 return value;
175 }
176 }
177
178 /** Remove any MapWrapper from the extracted state. */
179 _unwrap(result) => (result is _MapWrapper) ? result.asMap() : result;
180
181 /**
182 * Call the designated constructor with the appropriate fields from [state],
183 * first resolving references in the context of [reader].
184 */
185 inflateEssential(state, Reader reader) {
186 InstanceMirror mirror = constructor.constructFrom(
187 makeIndexableByNumber(state), reader);
188 return mirror.reflectee;
189 }
190
191 /** For all [state] not required in the constructor, set it in the [object],
192 * resolving references in the context of [reader].
193 */
194 inflateNonEssential(rawState, object, Reader reader) {
195 InstanceMirror mirror = reflect(object);
196 var state = makeIndexableByNumber(rawState);
197 fields.forEachRegularField( (_Field field) {
198 var value = reader.inflateReference(state[field.index]);
199 field.setValue(mirror, value);
200 });
201 }
202
203 /**
204 * Determine if this rule applies to the object in question. In our case
205 * this is true if the type mirrors are the same.
206 */
207 // TODO(alanknight): This seems likely to be slow. Verify. Other options?
208 bool appliesTo(object) => reflect(object).type == type;
209
210 /**
211 * Given the various field lists provided by the user, construct the list
212 * of field names that we want.
213 */
214 void _findFields(List constructorFields, List regularFields,
215 List excludeFields) {
216 fields = new _FieldList(type);
217 fields.constructorFields = constructorFields;
218 fields.regular = regularFields;
219 // TODO(alanknight): The order of this matters. It shouldn't.
220 fields.exclude = excludeFields;
221 fields.figureOutFields();
222 }
223
224 /**
225 * Extract the value of the field [fieldName] from the object reflected
226 * by [mirror].
227 */
228 // TODO (alanknight): The "is String" mechanism here should be more tightly
229 // controlled to work on just constructor fields.
230 // TODO(alanknight): The framework should be resilient if there are fields
231 // it expects that are missing, either for the case of de-serializing to a
232 // different definition, or for the case that tree-shaking has removed state.
233 // TODO(alanknight): This, and other places, rely on synchronous access to
234 // mirrors. Should be changed to use a synchronous API once one is available,
235 // or to be async, but that would be extremely ugly.
236 _value(InstanceMirror mirror, _Field field) {
237 if (field.name is String) {
238 return mirror.getField(field.name).value.reflectee;
239 } else {
240 return field.name;
241 }
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 for each object N fields, which
248 * are references, where a reference is stored in the stream as two integers.
249 * Or, in the special case of null, two nulls.
250 */
251 pullStateFrom(Iterator stream) {
252 var dataLength = stream.next();
253 var ruleData = new List();
254 for (var i = 0; i < dataLength; i++) {
255 var subList = new List();
256 ruleData.add(subList);
257 for (var j = 0; j < fields.length; j++) {
258 var a = stream.next();
259 var b = stream.next();
260 if (!(a is int)) {
261 // This wasn't a reference, so just use the first object as a literal.
262 // particularly used for the case of null.
263 subList.add(a);
264 } else {
265 subList.add(new Reference(this, a, b));
266 }
267 }
268 }
269 return ruleData;
270 }
271 }
272
273 /**
274 * This represents a field in an object. It is intended to be used as part of
275 * a [_FieldList].
276 */
277 class _Field implements Comparable {
278 /** The name of the field (or getter) */
279 final name;
280
281 /** The FieldList that contains us. */
282 final _FieldList fieldList;
283
284 /**
285 * Our position in the [contents] collection of [fieldList]. This is used
286 * to index into the state, so it's extremely important.
287 */
288 int index;
289
290 /** Is this field used in the constructor? */
291 bool usedInConstructor = false;
292
293 /** The special way to set this value registered, if this has a value. */
294 Function specialTreatment;
295
296 _Field(this.name, this.fieldList);
297
298 operator ==(x) => x is _Field && (name == x.name);
299 int get hashCode => name.hashCode;
300
301 // Note that the field 'name' may be an arbitrary constant value, so we have
302 // to convert it to a string for sorting.
303 compareTo(x) => name.toString().compareTo(x.name.toString());
304
305 // TODO(alanknight): Is this the right name, or is it confusing that essential
306 // is not the inverse of regular.
Jennifer Messerly 2012/11/15 08:02:14 either way, doc comments for these two properties
Alan Knight 2012/11/15 20:51:03 Done.
307 bool get isRegular => !usedInConstructor;
308 bool get isEssential => usedInConstructor || specialTreatment != null;
309
310 /** Set the [value] of our field in the given mirrored [object]. */
311 void setValue(InstanceMirror object, value) {
312 setter(object, value);
313 }
314
315 /** Return the function to use to set our value. */
316 Function get setter =>
317 (specialTreatment != null) ? specialTreatment : defaultSetter;
318
319 /** Return a default setter function. */
320 void defaultSetter(InstanceMirror object, value) {
321 object.setField(name, reflect(value));
322 }
323
324 String toString() => 'Field($name)';
325 }
326
327 /**
328 * The organization of fields in an object can be reasonably complex, so they
329 * are kept in a separate object, which also has the ability to compute the
330 * default fields to use reflectively.
331 */
332 class _FieldList {
333 /**
334 * All of our fields, indexed by name. Note that the names are not
335 * necessarily strings.
336 */
337 Map<dynamic, _Field> allFields = new Map<dynamic, _Field>();
338
339 /**
340 * The fields which are used in the constructor. The fields themselves also
341 * know if they are constructor fields or not, but we need to keep this
342 * information here because the order matters.
343 */
344 List _constructorFields = const [];
345
346 /** The list of fields to exclude if we are computing the list ourselves. */
347 List<String> _excludeFields = const [];
348
349 /** The mirror we will use to compute the fields. */
350 final ClassMirror mirror;
351
352 /** Cached, sorted list of fields. */
353 List<_Field> _contents;
354
355 /** Should we compute the fields or just use whatever we were given. */
356 bool _shouldFigureOutFields = true;
357
358 _FieldList(this.mirror);
359
360 /** Look up a field by [name]. */
361 _Field named(String name) => allFields[name];
362
363 /** Set the fields to be used in the constructor. */
364 set constructorFields(List fields) {
365 if (fields == null || fields.isEmpty) return;
366 _constructorFields = [];
367 for (var each in fields) {
368 var field = new _Field(each, this)..usedInConstructor = true;
369 allFields[each] = field;
370 _constructorFields.add(field);
371 }
372 invalidate();
373 }
374
375 /** Set the fields that aren't used in the constructor. */
376 set regular(List<String> fields) {
377 if (fields == null) return;
378 _shouldFigureOutFields = false;
379 addAllByName(fields);
380 }
381
382 /** Set the fields to be excluded. This is mutually exclusive with setting
383 * the regular fields.
384 */
385 set exclude(List<String> fields) {
386 // TODO(alanknight): This isn't well tested.
387 if (fields == null || fields.isEmpty) return;
388 if (allFields.length > _constructorFields.length) {
389 throw "You can't specify both excludeFields and regular fields";
390 }
391 _excludeFields = fields;
392 }
393
394 int get length => allFields.length;
395
396 /** Add all the fields which aren't on the exclude list. */
397 void addAllNotExplicitlyExcluded(List<String> aCollection) {
398 if (aCollection == null) return;
399 var names = aCollection;
400 names = names.filter((x) => x is String && !_excludeFields.contains(x));
401 addAllByName(names);
402 }
403
404 /** Add all the fields with the given names without any special properties. */
405 void addAllByName(List<String> names) {
406 for (var each in names) {
407 allFields.putIfAbsent(each, () => new _Field(each, this));
408 }
409 invalidate();
410 }
411
412 /**
413 * Fields have been added. In case we had already forced calculation of the
414 * list of contents, re-set it.
415 */
416 void invalidate() {
417 _contents = null;
418 contents;
419 }
420
421 Iterator iterator() => contents.iterator();
422
423 /** Return a cached, sorted list of all the fields. */
424 List<_Field> get contents {
425 if (_contents == null) {
426 _contents = sorted(allFields.values);
427 for (var i = 0; i < _contents.length; i++)
428 _contents[i].index = i;
429 }
430 return _contents;
431 }
432
433 /** Iterate over the regular fields, i.e. those not used in the constructor.*/
434 void forEachRegularField(Function f) {
435 for (var each in contents) {
436 if (each.isRegular) {
437 f(each);
438 }
439 }
440 }
441
442 /** Iterate over the fields used in the constructor. */
443 void forEachConstructorField(Function f) {
444 for (var each in contents) {
445 if (each.usedInConstructor) {
446 f(each);
447 }
448 }
449 }
450
451 List get constructorFields => _constructorFields;
452 List constructorFieldNames() => constructorFields.map((x) => x.name);
453 List constructorFieldIndices() => constructorFields.map((x) => x.index);
454 List regularFields() => contents.filter((x) => !x.usedInConstructor);
455 List regularFieldNames() => regularFields().map((x) => x.name);
456 List regularFieldIndices() => regularFields().map((x) => x.index);
457
458
459 /**
460 * If we weren't given any non-constructor fields to use, figure out what
461 * we think they ought to be, based on the class definition.
462 * We find public fields, getters that have corresponding setters, and getters
463 * that are listed in the constructor fields.
464 */
465 figureOutFields() {
Jennifer Messerly 2012/11/15 08:02:14 void?
Alan Knight 2012/11/15 20:51:03 Done.
466 List names(Collection<DeclarationMirror> mirrors) =>
467 mirrors.map((each) => each.simpleName);
468
469 if (!_shouldFigureOutFields || !regularFields().isEmpty) return;
470 var publicFields = publicFields(mirror);
471 var getters = publicGetters(mirror);
472 var gettersWithSetters = getters.filter( (each)
473 => mirror.setters["${each.simpleName}="] != null);
474 var gettersThatMatchConstructor = getters.filter((each)
475 => (named(each.simpleName) != null) &&
476 (named(each.simpleName).usedInConstructor));
477 addAllNotExplicitlyExcluded(names(publicFields));
478 addAllNotExplicitlyExcluded(names(gettersWithSetters));
479 addAllNotExplicitlyExcluded(names(gettersThatMatchConstructor));
480 }
481 }
482
483 /**
484 * Provide a typedef for the setWith argument to specialTreatmentFor. It would
485 * be nice if we could put this closer to the definition.
486 */
487 typedef SetWithFunction(InstanceMirror m, Object o);
488
489 /**
490 * This represents a constructor that is to be used when re-creating a
491 * serialized object.
492 */
493 class Constructor {
494 /** The mirror of the class we construct. */
495 final ClassMirror type;
496
497 /** The name of the constructor to use, if not the default constructor.*/
498 String name;
499
500 /**
501 * The indices of the fields used as constructor arguments. We will look
502 * these up in the state by number. These correspond to the index in the
503 * [contents] of the FieldList, which will be alphabetically sorted.
504 */
505 List<int> fieldNumbers;
506
507 /**
508 * Creates a new constructor for the [type] with the constructor named [name]
509 * and the [fieldNumbers] of the constructor fields.
510 */
511 Constructor(this.type, this.name, this.fieldNumbers) {
512 if (name == null) name = '';
513 if (fieldNumbers == null) fieldNumbers = const [];
514 }
515
516 /**
517 * Find the field values in [state] and pass them to the constructor.
518 * If any of [fieldNumbers] is not an int, then use it as a literal value.
519 */
520 constructFrom(state, Reader r) {
521 // TODO(alanknight): Handle named parameters
522 Collection inflated = fieldNumbers.map(
523 (x) => (x is int) ? reflect(r.inflateReference(state[x])) : reflect(x));
524 var result = type.newInstance(name, inflated);
525 return result.value;
526 }
527 }
528
529 /**
530 * This wraps a map to make it indexable by integer field numbers. It translates
531 * from the index into a field name and then looks it up in the map.
532 */
533 class _MapWrapper {
534 Map<String, dynamic> _map = new Map<String, dynamic>();
535 List fieldList;
536 _MapWrapper(this.fieldList);
537 _MapWrapper.fromMap(this._map, this.fieldList);
538
539 operator [](key) => _map[fieldList[key].name];
540 operator []=(key, value) { _map[fieldList[key].name] = value; }
541 get length => _map.length;
542
543 asMap() => _map;
544 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698