OLD | NEW |
---|---|
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * This provides a general-purpose serialization facility for Dart objects. A | 6 * This provides a general-purpose serialization facility for Dart objects. A |
7 * [Serialization] is defined in terms of [SerializationRule]s and supports | 7 * [Serialization] is defined in terms of [SerializationRule]s and supports |
8 * reading and writing to different formats. | 8 * reading and writing to different formats. |
9 * | 9 * |
10 * Setup | 10 * Setup |
11 * ===== | 11 * ===== |
12 * A simple example of usage is | 12 * A simple example of usage is |
13 * | 13 * |
14 * var address = new Address(); | 14 * var address = new Address(); |
15 * address.street = 'N 34th'; | 15 * address.street = 'N 34th'; |
16 * address.city = 'Seattle'; | 16 * address.city = 'Seattle'; |
17 * var serialization = new Serialization() | 17 * var serialization = new Serialization() |
18 * ..addRuleFor(address); | 18 * ..addRuleFor(address); |
19 * String output = serialization.write(address); | 19 * String output = serialization.write(address); |
20 * | 20 * |
21 * This creates a new serialization and adds a rule for address objects. Right | 21 * This creates a new serialization and adds a rule for address objects. Right |
22 * now it has to be passed an address instance because we can't write Address | 22 * now it has to be passed an address instance because of limitations using |
23 * as a literal. Then we ask the Serialization to write the address and we get | 23 * Address as a literal. Then we ask the Serialization to write the address |
24 * back a String which is a [JSON] representation of the state of it and related | 24 * and we get back a String which is a [JSON] representation of the state of |
25 * objects. | 25 * it and related objects. |
26 * | 26 * |
27 * The version above used reflection to automatically identify the public | 27 * The version above used reflection to automatically identify the public |
28 * fields of the address object. We can also specify those fields explicitly. | 28 * fields of the address object. We can also specify those fields explicitly. |
29 * | 29 * |
30 * var serialization = new Serialization() | 30 * var serialization = new Serialization() |
31 * ..addRuleFor(address, | 31 * ..addRuleFor(address, |
32 * constructor: "create", | 32 * constructor: "create", |
33 * constructorFields: ["number", "street"], | 33 * constructorFields: ["number", "street"], |
34 * fields: ["city"]); | 34 * fields: ["city"]); |
35 * | 35 * |
36 * This rule still uses reflection to access the fields, but does not try to | 36 * This rule still uses reflection to access the fields, but does not try to |
37 * identify which fields to use, but instead uses only the "number" and "street" | 37 * identify which fields to use, but instead uses only the "number" and "street" |
38 * fields that we specified. We may also want to tell it to identify the | 38 * fields that we specified. We may also want to tell it to identify the |
39 * fields, but to specifically omit certain fields that we don't want | 39 * fields, but to specifically omit certain fields that we don't want |
40 * serialized. | 40 * serialized. |
41 * | 41 * |
42 * var serialization = new Serialization() | 42 * var serialization = new Serialization() |
43 * ..addRuleFor(address, | 43 * ..addRuleFor(address, |
44 * constructor: "", | 44 * constructor: "", |
45 * excludeFields: ["other", "stuff"]); | 45 * excludeFields: ["other", "stuff"]); |
46 * | 46 * |
47 * Writing Rules | |
48 * ============= | |
47 * We can also use a completely non-reflective rule to serialize and | 49 * We can also use a completely non-reflective rule to serialize and |
48 * de-serialize objects. This can be more cumbersome, but it does work in | 50 * de-serialize objects. This can be more work, but it does work in |
49 * dart2js, where mirrors are not yet implemented. | 51 * dart2js, where mirrors are not yet implemented. We can specify this in two |
52 * ways. First, we can write our own SerializationRule class that has methods | |
53 * for our Address class. | |
54 * | |
55 * class AddressRule extends CustomRule { | |
56 * bool appliesTo(instance, Writer w) => instance.runtimeType == Address; | |
Jennifer Messerly
2013/01/11 02:21:53
"instance is Address"?
Alan Knight
2013/01/11 19:18:11
I actually did that on purpose, because if we have
Jennifer Messerly
2013/01/11 21:54:25
Yeah, comment would be nice. That makes sense.
Do
Alan Knight
2013/01/11 22:35:00
null will get caught by the PrimitiveRule that co
| |
57 * getState(instance) => [instance.street, instance.city]; | |
58 * create(state) => new Address(); | |
59 * setState(Address a, List state) { | |
60 * a.street = state[0]; | |
61 * a.city = state[1]; | |
62 * } | |
63 * } | |
64 * | |
65 * The class needs four different methods. The [appliesTo] method tells us if | |
66 * the rule should be used to write an object. The [getState] method should | |
67 * return all the state of the object that we want to recreate, | |
68 * and should be either a Map or a List. If you want to write to human-readable | |
69 * formats where it's useful to be able to look at the data as a map from | |
70 * field names to values, then it's better to return it as a map. Otherwise it's | |
71 * more efficient to return it as a list. You just need to be sure that the | |
72 * [create] and [setState] methods interpret the same way as [getState] does. | |
73 * | |
74 * The [create] method will create the new object and return it. While it's | |
75 * possible to create the object and set all its state in this one method, that | |
76 * increases the likelihood of problems with cycles. So it's better to use the | |
77 * minimum necessary information in [create] and do more of the work in | |
78 * [setState]. | |
79 * | |
80 * The other way to do this is not creating a subclass, but by using a | |
81 * [ClosureRule] and giving it functions for how to create | |
82 * the address. | |
50 * | 83 * |
51 * addressToMap(a) => {"number" : a.number, "street" : a.street, | 84 * addressToMap(a) => {"number" : a.number, "street" : a.street, |
52 * "city" : a.city}; | 85 * "city" : a.city}; |
53 * createAddress(Map m) => new Address.create(m["number"], m["street"]); | 86 * createAddress(Map m) => new Address.create(m["number"], m["street"]); |
54 * fillInAddress(Map m, Address a) => a.city = m["city"]; | 87 * fillInAddress(Map m, Address a) => a.city = m["city"]; |
55 * var serialization = new Serialization() | 88 * var serialization = new Serialization() |
56 * ..addRule( | 89 * ..addRule( |
57 * new ClosureToMapRule(anAddress.runtimeType, | 90 * new ClosureRule(anAddress.runtimeType, |
58 * addressToMap, createAddress, fillInAddress); | 91 * addressToMap, createAddress, fillInAddress); |
59 * | 92 * |
60 * Note that there are three different functions provided. The addressToMap | 93 * In this case we have created standalone functions rather than |
61 * function takes the fields we want serialized from the Address and puts them | 94 * methods in a subclass and we pass them to the constructor of |
62 * into a map. The createAddress function creates a new address using a map like | 95 * [ClosureRule]. In this case we've also had them use maps rather than |
63 * the one returned by the first function. And the fillInAddress function fills | 96 * lists for the state, but either would work as long as the rule is |
64 * in any remaining state in the created object. | 97 * consistent with the representation it uses. We pass it the runtimeType |
98 * of the object, and functions equivalent to the methods on [CustomRule] | |
65 * | 99 * |
66 * At the moment, however, using this rule increases the probability of problems | 100 * Constant Values |
67 * with cycles. The problem is that before passing values to the user-supplied | 101 * =============== |
68 * functions it has to inflate any references to be the real objects. Since it | |
69 * doesn't know which ones the creation function uses it has to inflate all of | |
70 * them. For example, consider a Node class with parent, leftChild, and | |
71 * rightChild, and the parent field was final and set in the constructor. When | |
72 * we inflate all of the values we will end up with a cycle and can't | |
73 * de-serialize. If we know which fields are used by the constructor we can | |
74 * inflate only those, which is what BasicRule does. We expect to make a richer | |
75 * API for rules not using reflection, but there's a tension between providing | |
76 * the serialization process with enough information and making it more work | |
77 * to specify. | |
78 * | |
79 * There are cases where the constructor needs values that we can't easily get | 102 * There are cases where the constructor needs values that we can't easily get |
80 * the serialized object. For example, we may just want to pass null, or a | 103 * from the serialized object. For example, we may just want to pass null, or a |
81 * constant value. To support this, we can specify as constructor fields | 104 * constant value. To support this, we can specify as constructor fields |
82 * values that aren't field names. If any value isn't a String, it will be | 105 * values that aren't field names. If any value isn't a String, it will be |
83 * treated as a constant and passed unaltered to the constructor. | 106 * treated as a constant and passed unaltered to the constructor. |
84 * | 107 * |
85 * In some cases a non-constructor field should not be set using field | 108 * In some cases a non-constructor field should not be set using field |
86 * access or a setter, but should be done by calling a method. For example, it | 109 * access or a setter, but should be done by calling a method. For example, it |
87 * may not be possible to set a List field "foo", and you need to call an | 110 * may not be possible to set a List field "foo", and you need to call an |
88 * addFoo() method for each entry in the list. In these cases, if you are using | 111 * addFoo() method for each entry in the list. In these cases, if you are using |
89 * a BasicRule for the object you can call the setFieldWith() method. | 112 * a BasicRule for the object you can call the setFieldWith() method. |
90 * | 113 * |
91 * s..addRuleFor(fooHolderInstance).setFieldWith("foo", | 114 * s..addRuleFor(fooHolderInstance).setFieldWith("foo", |
92 * (parent, value) => for (var each in value) parent.addFoo(value)); | 115 * (parent, value) => for (var each in value) parent.addFoo(value)); |
93 * | 116 * |
94 * Writing | 117 * Writing |
95 * ======= | 118 * ======= |
96 * To write objects, we use the write() methods. There are two variations. | 119 * To write objects, we use the write() method. |
97 * | 120 * |
98 * String output = serialization.write(someObject); | 121 * var output = serialization.write(someObject); |
99 * List output = serialization.writeFlat(someObject); | |
100 * | 122 * |
101 * The first uses a representation in which objects are represented as maps | 123 * By default this uses a representation in which objects are represented as |
102 * keyed by field name, but in which references between objects have been | 124 * maps keyed by field name, but in which references between objects have been |
103 * converted into Reference objects. This is then encoded as a [JSON] string. | 125 * converted into Reference objects. This is then encoded as a [json] string. |
104 * | 126 * |
105 * The second representation holds all the objects as a List of simple types. | 127 * We can write objects in different formats by passing a [Format] object to |
106 * For practical use you may want to convert that to a [JSON] or other encoded | 128 * the [write] method or by getting a [Writer] object. The available formats |
107 * representation as well. | 129 * include the default, a simple "flat" format that doesn't include field names, |
130 * and a simple JSON format that produces output more suitable for talking to | |
131 * services that expect JSON in a predefined format. Examples of these are | |
108 * | 132 * |
109 * Both representations are primarily intended as proofs of concept for | 133 * String output = serialization.write(address, new SimpleMapFormat()); |
110 * different types of representation, and we expect to generalize that to a | 134 * List output = serialization.write(address, new SimpleFlatFormat()); |
111 * pluggable mechanism for different representations. | 135 * Map output = serialization.write(address, new SimpleJSONFormat()); |
136 * Or, using a [Writer] explicitly | |
137 * var writer = serialization.newWriter(new SimpleFlatFormat()); | |
138 * var output = writer.write(address); | |
139 * | |
140 * These representations are not yet considered stable. | |
112 * | 141 * |
113 * Reading | 142 * Reading |
114 * ======= | 143 * ======= |
115 * To read objects, the corresponding methods are [read] and [readFlat]. | 144 * To read objects, the corresponding [read] method can be used. |
116 * | 145 * |
117 * List input = serialization.read(aString); | 146 * Address input = serialization.read(aString); |
118 * List input = serialization.readFlat(aList); | |
119 * | |
120 * There is also a convenience method for the case of reading a single object. | |
121 * | |
122 * Object result = serialization.readOne(aString); | |
123 * Object result = serialization.readOneFlat(aString); | |
124 * | 147 * |
125 * When reading, the serialization instance doing the reading must be configured | 148 * When reading, the serialization instance doing the reading must be configured |
126 * with compatible rules to the one doing the writing. It's possible for the | 149 * with compatible rules to the one doing the writing. It's possible for the |
127 * rules to be different, but they need to be able to read the same | 150 * rules to be different, but they need to be able to read the same |
128 * representation. For most practical purposes right now they should be the | 151 * representation. For most practical purposes right now they should be the |
129 * same. The simplest way to achieve this is by having the serialization | 152 * same. The simplest way to achieve this is by having the serialization |
130 * variable [selfDescribing] be true. In that case the rules themselves are also | 153 * variable [selfDescribing] be true. In that case the rules themselves are also |
131 * stored along with the serialized data, and can be read back on the receiving | 154 * stored along with the serialized data, and can be read back on the receiving |
132 * end. Note that this does not yet work for [ClosureToMapRule]. The | 155 * end. Note that this may not work for all rules or all formats. The |
133 * [selfDescribing] variable is true by default. | 156 * [selfDescribing] variable is true by default, but the SimpleJSONFormat does |
Jennifer Messerly
2013/01/11 02:21:53
SimpleJsonFormat?
I think we do camel case (e.g.
Alan Knight
2013/01/11 19:18:11
Done.
| |
157 * not support it, since the point is to provide a representation in a form | |
158 * other services might expect. Using CustomRule or ClosureRule also does not | |
159 * yet work with the [selfDescribing] variable. | |
134 * | 160 * |
161 * Named Objects | |
Jennifer Messerly
2013/01/11 02:21:53
like these new sections!
| |
162 * ============= | |
135 * When reading, some object references should not be serialized, but should be | 163 * When reading, some object references should not be serialized, but should be |
136 * connected up to other instances on the receiving side. A notable example of | 164 * connected up to other instances on the receiving side. A notable example of |
137 * this is when serialization rules have been stored. Instances of BasicRule | 165 * this is when serialization rules have been stored. Instances of BasicRule |
138 * take a [ClassMirror] in their constructor, and we cannot serialize those. So | 166 * take a [ClassMirror] in their constructor, and we cannot serialize those. So |
139 * when we read the rules, we must provide a Map<String, Object> which maps from | 167 * when we read the rules, we must provide a Map<String, Object> which maps from |
140 * the simple name of classes we are interested in to a [ClassMirror]. This can | 168 * the simple name of classes we are interested in to a [ClassMirror]. This can |
141 * be provided either in the [externalObjects] variable of the Serialization, | 169 * be provided either in the [namedObjects] variable of the Serialization, |
142 * or as an additional parameter to the reading methods. | 170 * or as an additional parameter to the reading and writing methods on the |
171 * [Reader] or [Writer] respectively. | |
143 * | 172 * |
144 * new Serialization() | 173 * new Serialization() |
145 * ..addRuleFor(new Person(), constructorFields: ["name"]) | 174 * ..addRuleFor(new Person(), constructorFields: ["name"]) |
146 * ..externalObjects['Person'] = reflect(new Person()).type; | 175 * ..namedObjects['Person'] = reflect(new Person()).type; |
147 */ | 176 */ |
148 library serialization; | 177 library serialization; |
149 | 178 |
150 import 'src/mirrors_helpers.dart'; | 179 import 'src/mirrors_helpers.dart'; |
151 import 'src/serialization_helpers.dart'; | 180 import 'src/serialization_helpers.dart'; |
152 import 'dart:async'; | 181 import 'dart:async'; |
153 import 'dart:json' as json; | 182 import 'dart:json' as json; |
154 | 183 |
155 part 'src/reader_writer.dart'; | 184 part 'src/reader_writer.dart'; |
156 part 'src/serialization_rule.dart'; | 185 part 'src/serialization_rule.dart'; |
157 part 'src/basic_rule.dart'; | 186 part 'src/basic_rule.dart'; |
187 part 'src/format.dart'; | |
158 | 188 |
159 /** | 189 /** |
160 * This class defines a particular serialization scheme, in terms of | 190 * This class defines a particular serialization scheme, in terms of |
161 * [SerializationRule] instances, and supports reading and writing them. | 191 * [SerializationRule] instances, and supports reading and writing them. |
162 * See library comment for examples of usage. | 192 * See library comment for examples of usage. |
163 */ | 193 */ |
164 class Serialization { | 194 class Serialization { |
165 | 195 |
166 /** | 196 /** |
167 * The serialization is controlled by the list of Serialization rules. These | 197 * The serialization is controlled by the list of Serialization rules. These |
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
284 * handle most simple cases, but for adding an arbitrary rule, including | 314 * handle most simple cases, but for adding an arbitrary rule, including |
285 * a SerializationRule subclass which you have created, you can use this | 315 * a SerializationRule subclass which you have created, you can use this |
286 * method. | 316 * method. |
287 */ | 317 */ |
288 void addRule(SerializationRule rule) { | 318 void addRule(SerializationRule rule) { |
289 rule.number = _rules.length; | 319 rule.number = _rules.length; |
290 _rules.add(rule); | 320 _rules.add(rule); |
291 } | 321 } |
292 | 322 |
293 /** | 323 /** |
294 * This is the basic method to write out an object graph rooted at | 324 * This writes out an object graph rooted at [object] and returns the result. |
295 * [object] and return the result. Right now this is hard-coded to return | 325 * The [format] parameter determines the form of the result. The default |
296 * a String from a custom [JSON] format, but that is likely to change to be | 326 * format returns a String in [json] format. |
297 * more pluggable in the near future. | |
298 */ | 327 */ |
299 String write(Object object) { | 328 write(Object object, [Format format]) { |
300 return newWriter().write(object); | 329 return newWriter(format).write(object); |
301 } | 330 } |
302 | 331 |
303 /** | 332 /** |
304 * Return a new [Writer] object for this serialization. This is useful if you | 333 * Return a new [Writer] object for this serialization. This is useful if you |
305 * want to do something more complex with the writer than just returning | 334 * want to do something more complex with the writer than just returning |
306 * the final result. | 335 * the final result. |
307 */ | 336 */ |
308 Writer newWriter() => new Writer(this); | 337 Writer newWriter([Format format]) => |
309 | 338 new Writer(this, format); |
310 /** | |
311 * Write out the tree in a custom flat format, returning a list containing | |
312 * only "simple" types: num, String, and bool. | |
313 */ | |
314 List writeFlat(Object object) { | |
315 return newWriter().writeFlat(object); | |
316 } | |
317 | 339 |
318 /** | 340 /** |
319 * Read the serialized data from [input] and return the root object | 341 * Read the serialized data from [input] and return the root object |
320 * from the result. If there are objects that need to be resolved | 342 * from the result. If there are objects that need to be resolved |
321 * in the current context, they should be provided in [externals] as a | 343 * in the current context, they should be provided in [externals] as a |
322 * Map from names to values. In particular, in the current implementation | 344 * Map from names to values. In particular, in the current implementation |
323 * any class mirrors needed should be provided in [externals] using the | 345 * any class mirrors needed should be provided in [externals] using the |
324 * class name as a key. In addition to the [externals] map provided here, | 346 * class name as a key. In addition to the [externals] map provided here, |
325 * values will be looked up in the [externalObjects] map. | 347 * values will be looked up in the [externalObjects] map. |
326 */ | 348 */ |
327 read(String input, [Map externals = const {}]) { | 349 read(String input, [Map externals = const {}]) { |
328 return newReader().read(input, externals); | 350 return newReader().read(input, externals); |
329 } | 351 } |
330 | 352 |
331 /** | 353 /** |
332 * Return a new [Reader] object for this serialization. This is useful if | 354 * Return a new [Reader] object for this serialization. This is useful if |
333 * you want to do something more complex with the reader than just returning | 355 * you want to do something more complex with the reader than just returning |
334 * the final result. | 356 * the final result. |
335 */ | 357 */ |
336 Reader newReader() => new Reader(this); | 358 Reader newReader([Format format]) => new Reader(this, format); |
337 | 359 |
338 /** | 360 /** |
339 * Return the list of SerializationRule that apply to [object]. For | 361 * Return the list of SerializationRule that apply to [object]. For |
340 * internal use, but public because it's used in testing. | 362 * internal use, but public because it's used in testing. |
341 */ | 363 */ |
342 List<SerializationRule> rulesFor(object, Writer w) { | 364 Iterable<SerializationRule> rulesFor(object, Writer w) { |
343 // This has a couple of edge cases. | 365 // This has a couple of edge cases. |
344 // 1) The owning object may have indicated we should use a different | 366 // 1) The owning object may have indicated we should use a different |
345 // rule than the default. | 367 // rule than the default. |
346 // 2) We may not have a rule, in which case we lazily create a BasicRule. | 368 // 2) We may not have a rule, in which case we lazily create a BasicRule. |
347 // 3) Rules are allowed to say mustBePrimary, meaning that they can be used | 369 // 3) Rules are allowed to say mustBePrimary, meaning that they can be used |
348 // iff no other rule was chosen first. | 370 // iff no other rule was chosen first. |
349 // TODO(alanknight): Can the mustBePrimary mechanism be removed or changed. | 371 // TODO(alanknight): Can the mustBePrimary mechanism be removed or changed. |
350 // It adds an order dependency to the rules, and is messy. Reconsider in the | 372 // It adds an order dependency to the rules, and is messy. Reconsider in the |
351 // light of a more general mechanism for multiple rules per object. | 373 // light of a more general mechanism for multiple rules per object. |
352 // TODO(alanknight): Finding which rules apply seems likely to be a | 374 // TODO(alanknight): Finding which rules apply seems likely to be a |
353 // bottleneck, particularly with the current reflective implementation. | 375 // bottleneck, particularly with the current reflective implementation. |
354 // Consider how to improve it. e.g. cache the list of rules by class. But | 376 // Consider how to improve it. e.g. cache the list of rules by class. But |
355 // be careful of issues like rules which have arbitrary predicates. Or | 377 // be careful of issues like rules which have arbitrary predicates. Or |
356 // consider having the arbitrary predicates be secondary to an initial | 378 // consider having the arbitrary predicates be secondary to an initial |
357 // class-based lookup mechanism. | 379 // class-based lookup mechanism. |
358 var target, candidateRules; | 380 var target, candidateRules; |
359 if (object is DesignatedRuleForObject) { | 381 if (object is DesignatedRuleForObject) { |
360 target = object.target; | 382 target = object.target; |
361 candidateRules = object.possibleRules(_rules); | 383 candidateRules = object.possibleRules(_rules); |
362 } else { | 384 } else { |
363 target = object; | 385 target = object; |
364 candidateRules = _rules; | 386 candidateRules = _rules; |
365 } | 387 } |
366 List applicable = | 388 Iterable applicable = candidateRules.where( |
367 candidateRules.where((each) => each.appliesTo(target, w)).toList(); | 389 (each) => each.appliesTo(target, w)); |
368 | 390 |
369 if (applicable.isEmpty) { | 391 if (applicable.isEmpty) { |
370 return [addRuleFor(target)]; | 392 return [addRuleFor(target)]; |
371 } | 393 } |
372 | 394 |
373 if (applicable.length == 1) return applicable; | 395 if (applicable.length == 1) return applicable; |
374 var first = applicable[0]; | 396 var first = applicable.first; |
375 var finalRules = applicable.where( | 397 var finalRules = applicable.where( |
376 (x) => !x.mustBePrimary || (x == first)).toList(); | 398 (x) => !x.mustBePrimary || (x == first)); |
377 | 399 |
378 if (finalRules.isEmpty) throw new SerializationException( | 400 if (finalRules.isEmpty) throw new SerializationException( |
379 'No valid rule found for object $object'); | 401 'No valid rule found for object $object'); |
380 return finalRules; | 402 return finalRules; |
381 } | 403 } |
382 | 404 |
383 /** | 405 /** |
384 * Create a Serialization for serializing SerializationRules. This is used | 406 * Create a Serialization for serializing SerializationRules. This is used |
385 * to save the rules in a self-describing format along with the data. | 407 * to save the rules in a self-describing format along with the data. |
386 * If there are new rule classes created, they will need to be described | 408 * If there are new rule classes created, they will need to be described |
387 * here. | 409 * here. |
388 */ | 410 */ |
389 Serialization _ruleSerialization() { | 411 Serialization ruleSerialization() { |
390 // TODO(alanknight): There's an extensibility issue here with new rules. | 412 // TODO(alanknight): There's an extensibility issue here with new rules. |
391 // TODO(alanknight): How to handle rules with closures? They have to | 413 // TODO(alanknight): How to handle rules with closures? They have to |
392 // exist on the other side, but we might be able to hook them up by name, | 414 // exist on the other side, but we might be able to hook them up by name, |
393 // or we might just be able to validate that they're correctly set up | 415 // or we might just be able to validate that they're correctly set up |
394 // on the other side. | 416 // on the other side. |
395 | 417 |
396 // Make some bogus rule instances so we have something to feed rule creation | 418 // Make some bogus rule instances so we have something to feed rule creation |
397 // and get their types. If only we had class literals implemented... | 419 // and get their types. If only we had class literals implemented... |
398 var basicRule = new BasicRule(reflect(null).type, '', [], [], []); | 420 var basicRule = new BasicRule(reflect(null).type, '', [], [], []); |
399 | 421 |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
431 } | 453 } |
432 } | 454 } |
433 | 455 |
434 /** | 456 /** |
435 * An exception class for errors during serialization. | 457 * An exception class for errors during serialization. |
436 */ | 458 */ |
437 class SerializationException implements Exception { | 459 class SerializationException implements Exception { |
438 final String message; | 460 final String message; |
439 const SerializationException([this.message]); | 461 const SerializationException([this.message]); |
440 } | 462 } |
OLD | NEW |