| 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 part of serialization; | 5 part of serialization; |
| 6 | 6 |
| 7 // TODO(alanknight): We should have an example and tests for subclassing | 7 // TODO(alanknight): We should have an example and tests for subclassing |
| 8 // serialization rule rather than using the hard-coded ClosureToMap rule. And | 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. | 9 // possibly an abstract superclass that's designed to be subclassed that way. |
| 10 /** | 10 /** |
| (...skipping 15 matching lines...) Expand all Loading... |
| 26 /** | 26 /** |
| 27 * Rules belong uniquely to a particular Serialization instance, and can | 27 * Rules belong uniquely to a particular Serialization instance, and can |
| 28 * be identified within it by number. | 28 * be identified within it by number. |
| 29 */ | 29 */ |
| 30 void set number(x) { | 30 void set number(x) { |
| 31 if (_number != null) throw | 31 if (_number != null) throw |
| 32 new SerializationException("Rule numbers cannot be changed, once set"); | 32 new SerializationException("Rule numbers cannot be changed, once set"); |
| 33 _number = x; | 33 _number = x; |
| 34 } | 34 } |
| 35 | 35 |
| 36 /** Return true if this rule applies to this object, false otherwise. */ | 36 /** |
| 37 bool appliesTo(object); | 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); |
| 38 | 41 |
| 39 /** | 42 /** |
| 40 * This extracts the state from the object, calling [f] for each value | 43 * This extracts the state from the object, calling [f] for each value |
| 41 * as it is extracted, and returning an object representing the whole | 44 * as it is extracted, and returning an object representing the whole |
| 42 * state at the end. The state that results will still have direct | 45 * state at the end. The state that results will still have direct |
| 43 * pointers to objects, rather than references. | 46 * pointers to objects, rather than references. |
| 44 */ | 47 */ |
| 45 Object extractState(object, void f(value)); | 48 extractState(object, void f(value)); |
| 46 | 49 |
| 47 /** | 50 /** |
| 48 * Given the variables representing the state of an object, flatten it | 51 * Given the variables representing the state of an object, flatten it |
| 49 * by turning object pointers into Reference objects where needed. This | 52 * by turning object pointers into Reference objects where needed. This |
| 50 * destructively modifies the state object. | 53 * destructively modifies the state object. |
| 51 * | 54 * |
| 52 * This has a default implementation which assumes that object is indexable, | 55 * This has a default implementation which assumes that object is indexable, |
| 53 * so either conforms to Map or List. Subclasses may override to do something | 56 * so either conforms to Map or List. Subclasses may override to do something |
| 54 * different. | 57 * different. |
| 55 */ | 58 */ |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 91 inflateEssential(state, Reader reader); | 94 inflateEssential(state, Reader reader); |
| 92 | 95 |
| 93 /** | 96 /** |
| 94 * The [object] has already been created. Set any of its non-essential | 97 * The [object] has already been created. Set any of its non-essential |
| 95 * variables from the representation in [state]. Where there are references | 98 * variables from the representation in [state]. Where there are references |
| 96 * to other objects they are resolved in the context of [reader]. | 99 * to other objects they are resolved in the context of [reader]. |
| 97 */ | 100 */ |
| 98 inflateNonEssential(state, object, Reader reader); | 101 inflateNonEssential(state, object, Reader reader); |
| 99 | 102 |
| 100 /** | 103 /** |
| 101 * If we have an object [o] as part of our state, should we represent that | 104 * If we have [object] as part of our state, should we represent that |
| 102 * directly, or should we make a reference for it. By default we use a | 105 * directly, or should we make a reference for it. By default we use a |
| 103 * reference for everything. | 106 * reference for everything. |
| 104 */ | 107 */ |
| 105 bool shouldUseReferenceFor(Object o, Writer w) => true; | 108 bool shouldUseReferenceFor(object, Writer w) => true; |
| 106 | 109 |
| 107 /** | 110 /** |
| 108 * This writes the data from our internal representation into a List. | 111 * This writes the data from our internal representation into a List. |
| 109 * It is used in order to write to a flat format, and is likely to be | 112 * It is used in order to write to a flat format, and is likely to be |
| 110 * folded into a more general mechanism for supporting different output | 113 * folded into a more general mechanism for supporting different output |
| 111 * formats. | 114 * formats. |
| 112 */ | 115 */ |
| 113 // TODO(alanknight): This really shouldn't exist, but is a temporary measure | 116 // TODO(alanknight): This really shouldn't exist, but is a temporary measure |
| 114 // for writing to a a flat format until that's more fleshed out. It takes | 117 // for writing to a a flat format until that's more fleshed out. It takes |
| 115 // the internal representation of the rule's state, which is particularly | 118 // the internal representation of the rule's state, which is particularly |
| 116 // bad. The default implementation treats the ruleData as a List of Lists | 119 // bad. The default implementation treats the ruleData as a List of Lists |
| 117 // of references. | 120 // of references. |
| 118 void dumpStateInto(List ruleData, List target) { | 121 void dumpStateInto(List ruleData, List target) { |
| 119 // Needing the intermediate is also bad for performance, but tricky | 122 // Needing the intermediate is also bad for performance, but tricky |
| 120 // to do otherwise without a mechanism to precalculate the size. | 123 // to do otherwise without a mechanism to precalculate the size. |
| 121 var intermediate = new List(); | 124 var intermediate = new List(); |
| 122 var totalLength = 0; | 125 var totalLength = 0; |
| 123 for (var eachList in ruleData) { | 126 for (var eachList in ruleData) { |
| 124 // TODO(alanknight): Abstract this out better, this really won't scale. | 127 if (writeLengthInFlatFormat) { |
| 125 if (this is ListRule) | |
| 126 intermediate.add(eachList.length); | 128 intermediate.add(eachList.length); |
| 129 } |
| 127 for (var eachRef in eachList) { | 130 for (var eachRef in eachList) { |
| 128 if (eachRef == null) { | 131 if (eachRef == null) { |
| 129 intermediate..add(null)..add(null); | 132 intermediate..add(null)..add(null); |
| 130 } else { | 133 } else { |
| 131 eachRef.writeToList(intermediate); | 134 eachRef.writeToList(intermediate); |
| 132 } | 135 } |
| 133 } | 136 } |
| 134 } | 137 } |
| 135 target.addAll(intermediate); | 138 target.addAll(intermediate); |
| 136 } | 139 } |
| 137 | 140 |
| 138 /** | 141 /** |
| 142 * Return true if this rule writes a length value before each entry in |
| 143 * the flat format. Return false if the results are fixed length. |
| 144 */ |
| 145 // TODO(alanknight): This should probably go away with more general formats. |
| 146 bool get writeLengthInFlatFormat => false; |
| 147 |
| 148 /** |
| 139 * The inverse of dumpStateInto, this reads the rule's state from an | 149 * The inverse of dumpStateInto, this reads the rule's state from an |
| 140 * iterator in a flat format. | 150 * iterator in a flat format. |
| 141 */ | 151 */ |
| 142 pullStateFrom(Iterator stream); | 152 pullStateFrom(Iterator stream) { |
| 153 var numberOfEntries = stream.next(); |
| 154 var ruleData = new List(); |
| 155 for (var i = 0; i < numberOfEntries; i++) { |
| 156 var subLength = dataLengthIn(stream); |
| 157 var subList = []; |
| 158 ruleData.add(subList); |
| 159 for (var j = 0; j < subLength; j++) { |
| 160 var a = stream.next(); |
| 161 var b = stream.next(); |
| 162 if (!(a is int)) { |
| 163 // This wasn't a reference, just use the first object as a literal. |
| 164 // particularly used for the case of null. |
| 165 subList.add(a); |
| 166 } else { |
| 167 subList.add(new Reference(this, a, b)); |
| 168 } |
| 169 } |
| 170 } |
| 171 return ruleData; |
| 172 } |
| 173 |
| 174 /** |
| 175 * Return the length of the list of data we expect to see on a particular |
| 176 * iterator in a flat format. This may have been encoded in the stream if we |
| 177 * are variable length, or it may be constant. Note that this is expressed in |
| 178 * |
| 179 */ |
| 180 dataLengthIn(Iterator stream) => |
| 181 writeLengthInFlatFormat ? stream.next() : dataLength; |
| 182 |
| 183 /** |
| 184 * If the data is fixed length, return it here. Unused in the non-flat |
| 185 * format, or if the data is variable length. |
| 186 */ |
| 187 int get dataLength => 0; |
| 143 } | 188 } |
| 144 | 189 |
| 145 /** | 190 /** |
| 146 * This rule handles things that implement List. It will recreate them as | 191 * This rule handles things that implement List. It will recreate them as |
| 147 * whatever the default implemenation of List is on the target platform. | 192 * whatever the default implemenation of List is on the target platform. |
| 148 */ | 193 */ |
| 149 class ListRule extends SerializationRule { | 194 class ListRule extends SerializationRule { |
| 150 | 195 |
| 151 appliesTo(object) => object is List; | 196 appliesTo(object, Writer w) => object is List; |
| 152 | 197 |
| 153 state(List list) => new List.from(list); | 198 state(List list) => new List.from(list); |
| 154 | 199 |
| 155 List extractState(List list, f) { | 200 List extractState(List list, f) { |
| 156 var result = new List(); | 201 var result = new List(); |
| 157 for (var each in list) { | 202 for (var each in list) { |
| 158 result.add(each); | 203 result.add(each); |
| 159 f(each); | 204 f(each); |
| 160 } | 205 } |
| 161 return result; | 206 return result; |
| (...skipping 17 matching lines...) Expand all Loading... |
| 179 * When reading from a flat format we are given [stream] and need to pull as | 224 * When reading from a flat format we are given [stream] and need to pull as |
| 180 * much data from it as we need. Our format is that we have an integer N | 225 * much data from it as we need. Our format is that we have an integer N |
| 181 * indicating the number of objects and then for each object a length M, | 226 * indicating the number of objects and then for each object a length M, |
| 182 * and then M references, where a reference is stored in the stream as two | 227 * and then M references, where a reference is stored in the stream as two |
| 183 * integers. Or, in the special case of null, two nulls. | 228 * integers. Or, in the special case of null, two nulls. |
| 184 */ | 229 */ |
| 185 pullStateFrom(Iterator stream) { | 230 pullStateFrom(Iterator stream) { |
| 186 // TODO(alanknight): This is much too close to the basicRule implementation, | 231 // TODO(alanknight): This is much too close to the basicRule implementation, |
| 187 // and I'd refactor them if I didn't think this whole mechanism needed to | 232 // and I'd refactor them if I didn't think this whole mechanism needed to |
| 188 // change soon. | 233 // change soon. |
| 189 var dataLength = stream.next(); | 234 var length = stream.next(); |
| 190 var ruleData = new List(); | 235 var ruleData = new List(); |
| 191 for (var i = 0; i < dataLength; i++) { | 236 for (var i = 0; i < length; i++) { |
| 192 var subLength = stream.next(); | 237 var subLength = stream.next(); |
| 193 var subList = new List(); | 238 var subList = new List(); |
| 194 ruleData.add(subList); | 239 ruleData.add(subList); |
| 195 for (var j = 0; j < subLength; j++) { | 240 for (var j = 0; j < subLength; j++) { |
| 196 var a = stream.next(); | 241 var a = stream.next(); |
| 197 var b = stream.next(); | 242 var b = stream.next(); |
| 198 if (!(a is int)) { | 243 if (!(a is int)) { |
| 199 // This wasn't a reference, just use the first object as a literal. | 244 // This wasn't a reference, just use the first object as a literal. |
| 200 // particularly used for the case of null. | 245 // particularly used for the case of null. |
| 201 subList.add(a); | 246 subList.add(a); |
| 202 } else { | 247 } else { |
| 203 subList.add(new Reference(this, a, b)); | 248 subList.add(new Reference(this, a, b)); |
| 204 } | 249 } |
| 205 } | 250 } |
| 206 } | 251 } |
| 207 return ruleData; | 252 return ruleData; |
| 208 } | 253 } |
| 254 |
| 255 /** |
| 256 * Return true because we need to write the length of each list in the flat |
| 257 * format. */ |
| 258 bool get writeLengthInFlatFormat => true; |
| 259 |
| 260 /** Return the length of the next list when reading the flat format. */ |
| 261 int dataLengthIn(Iterator stream) => stream.next(); |
| 209 } | 262 } |
| 210 | 263 |
| 211 /** | 264 /** |
| 212 * This is a subclass of ListRule where all of the list's contents are | 265 * This is a subclass of ListRule where all of the list's contents are |
| 213 * considered essential state. This is needed if an object X contains a List L, | 266 * considered essential state. This is needed if an object X contains a List L, |
| 214 * but it expects L's contents to be fixed when X's constructor is called. | 267 * but it expects L's contents to be fixed when X's constructor is called. |
| 215 */ | 268 */ |
| 216 class ListRuleEssential extends ListRule { | 269 class ListRuleEssential extends ListRule { |
| 217 | 270 |
| 218 /** Create the new List and also inflate all of its contents. */ | 271 /** Create the new List and also inflate all of its contents. */ |
| 219 inflateEssential(List state, Reader r) { | 272 inflateEssential(List state, Reader r) { |
| 220 var object = super.inflateEssential(state, r); | 273 var object = super.inflateEssential(state, r); |
| 221 populateContents(state, object, r); | 274 populateContents(state, object, r); |
| 222 return object; | 275 return object; |
| 223 } | 276 } |
| 224 | 277 |
| 225 /** Does nothing, because all the work has been done in inflateEssential. */ | 278 /** Does nothing, because all the work has been done in inflateEssential. */ |
| 226 inflateNonEssential(state, newList, reader) {} | 279 inflateNonEssential(state, newList, reader) {} |
| 227 | 280 |
| 228 bool get mustBePrimary => true; | 281 bool get mustBePrimary => true; |
| 229 } | 282 } |
| 230 | 283 |
| 231 /** | 284 /** |
| 232 * This rule handles primitive types, defined as those that we can normally | 285 * This rule handles primitive types, defined as those that we can normally |
| 233 * represent directly in the output format. We hard-code that to mean | 286 * represent directly in the output format. We hard-code that to mean |
| 234 * num, String, and bool. | 287 * num, String, and bool. |
| 235 */ | 288 */ |
| 236 class PrimitiveRule extends SerializationRule { | 289 class PrimitiveRule extends SerializationRule { |
| 237 appliesTo(object) { | 290 appliesTo(object, Writer w) { |
| 238 return isPrimitive(object); | 291 return isPrimitive(object); |
| 239 } | 292 } |
| 240 extractState(object, Function f) => object; | 293 extractState(object, Function f) => object; |
| 241 void flatten(object, Writer writer) {} | 294 void flatten(object, Writer writer) {} |
| 242 inflateEssential(state, Reader r) => state; | 295 inflateEssential(state, Reader r) => state; |
| 243 inflateNonEssential(object, _, Reader r) {} | 296 inflateNonEssential(object, _, Reader r) {} |
| 244 | 297 |
| 245 /** Indicate whether we should save pointers to this object as references | 298 /** |
| 299 * Indicate whether we should save pointers to this object as references |
| 246 * or store the object directly. For primitives this depends on the format, | 300 * or store the object directly. For primitives this depends on the format, |
| 247 * so we delegate to the writer. | 301 * so we delegate to the writer. |
| 248 */ | 302 */ |
| 249 bool shouldUseReferenceFor(Object o, Writer w) => | 303 bool shouldUseReferenceFor(object, Writer w) => |
| 250 w.shouldUseReferencesForPrimitives; | 304 w.shouldUseReferencesForPrimitives; |
| 251 | 305 |
| 252 /** | 306 /** |
| 253 * This writes the data from our internal representation into a List. | 307 * This writes the data from our internal representation into a List. |
| 254 * It is used in order to write to a flat format, and is likely to be | 308 * It is used in order to write to a flat format, and is likely to be |
| 255 * folded into a more general mechanism for supporting different output | 309 * folded into a more general mechanism for supporting different output |
| 256 * formats. For primitives, the ruleData is our list of all the | 310 * formats. For primitives, the ruleData is our list of all the |
| 257 * primitives and just add it into the target. | 311 * primitives and just add it into the target. |
| 258 */ | 312 */ |
| 259 void dumpStateInto(List ruleData, List target) { | 313 void dumpStateInto(List ruleData, List target) { |
| 260 target.addAll(ruleData); | 314 target.addAll(ruleData); |
| 261 } | 315 } |
| 262 | 316 |
| 263 /** | 317 /** |
| 264 * When reading from a flat format we are given [stream] and need to pull as | 318 * When reading from a flat format we are given [stream] and need to pull as |
| 265 * much data from it as we need. Our format is that we have an integer N | 319 * much data from it as we need. Our format is that we have an integer N |
| 266 * indicating the number of objects and then N simple objects. | 320 * indicating the number of objects and then N simple objects. |
| 267 */ | 321 */ |
| 268 pullStateFrom(Iterator stream) { | 322 pullStateFrom(Iterator stream) { |
| 269 var dataLength = stream.next(); | 323 var length = stream.next(); |
| 270 var ruleData = new List(); | 324 var ruleData = new List(); |
| 271 for (var i = 0; i < dataLength; i++) { | 325 for (var i = 0; i < length; i++) { |
| 272 ruleData.add(stream.next()); | 326 ruleData.add(stream.next()); |
| 273 } | 327 } |
| 274 return ruleData; | 328 return ruleData; |
| 275 } | 329 } |
| 276 } | 330 } |
| 277 | 331 |
| 278 /** Helper function for PrimitiveRule to tell which objects it applies to. */ | 332 /** Helper function for PrimitiveRule to tell which objects it applies to. */ |
| 279 bool isPrimitive(Object object) { | 333 bool isPrimitive(object) { |
| 280 return object is num || object is String || object is bool; | 334 return object is num || object is String || object is bool; |
| 281 } | 335 } |
| 282 | 336 |
| 283 /** Typedef for the object construction closure used in ClosureToMapRule. */ | 337 /** Typedef for the object construction closure used in ClosureRule. */ |
| 284 typedef Object ConstructType(Map m); | 338 typedef ConstructType(Map m); |
| 285 | 339 |
| 286 /** Typedef for the state-getting closure used in ClosureToMapRule. */ | 340 /** Typedef for the state-getting closure used in ClosureToMapRule. */ |
| 287 typedef Map<String, Object> GetStateType(Object o); | 341 typedef Map<String, dynamic> GetStateType(object); |
| 288 | 342 |
| 289 /** Typedef for the state-setting closure used in ClosureToMapRule. */ | 343 /** Typedef for the state-setting closure used in ClosureToMapRule. */ |
| 290 typedef void NonEssentialStateType(Object o, Map m); | 344 typedef void NonEssentialStateType(object, Map m); |
| 291 | 345 |
| 292 /** | 346 /** |
| 293 * This is a rule where the extraction and creation are hard-coded as | 347 * This is a rule where the extraction and creation are hard-coded as |
| 294 * closures. The result is expected to be a map indexed by field name. | 348 * closures. The result is expected to be a map indexed by field name. |
| 295 */ | 349 */ |
| 296 class ClosureToMapRule extends SerializationRule { | 350 class ClosureRule extends CustomRule { |
| 297 | 351 |
| 298 /** The runtimeType of objects that this rule applies to. Used in appliesTo.*/ | 352 /** The runtimeType of objects that this rule applies to. Used in appliesTo.*/ |
| 299 final Type type; | 353 final Type type; |
| 300 | 354 |
| 301 /** The function for constructing new objects when reading. */ | 355 /** The function for constructing new objects when reading. */ |
| 302 ConstructType construct; | 356 ConstructType construct; |
| 303 | 357 |
| 304 /** The function for returning an object's state as a Map. */ | 358 /** The function for returning an object's state as a Map. */ |
| 305 GetStateType getState; | 359 GetStateType getStateFunction; |
| 306 | 360 |
| 307 /** The function for setting an object's state from a Map. */ | 361 /** The function for setting an object's state from a Map. */ |
| 308 NonEssentialStateType setNonEssentialState; | 362 NonEssentialStateType setNonEssentialState; |
| 309 | 363 |
| 310 /** | 364 /** |
| 311 * Create a ClosureToMapRule for the given [type] which gets an object's | 365 * Create a ClosureToMapRule for the given [type] which gets an object's |
| 312 * state by calling [getState], creates a new object by calling [construct] | 366 * state by calling [getState], creates a new object by calling [construct] |
| 313 * and sets the new object's state by calling [setNonEssentialState]. | 367 * and sets the new object's state by calling [setNonEssentialState]. |
| 314 */ | 368 */ |
| 315 ClosureToMapRule(this.type, this.getState, this.construct, | 369 ClosureRule(this.type, this.getStateFunction, this.construct, |
| 316 this.setNonEssentialState); | 370 this.setNonEssentialState); |
| 317 | 371 |
| 318 /** | 372 bool appliesTo(object, Writer w) => object.runtimeType == type; |
| 319 * If we deserialize a ClosureToMapRule we can't actually use it, because | 373 |
| 320 * we don't have the closures, so generate a stub that just returns the | 374 getState(object) => getStateFunction(object); |
| 321 * raw state object. | 375 |
| 322 */ | 376 create(state) => construct(state); |
| 323 ClosureToMapRule.stub(this.type) { | 377 |
| 324 getState = (x) { throw new SerializationException( | 378 setState(object, state) { |
| 325 'Closures cannot be serialized'); }; | 379 if (setNonEssentialState == null) return; |
| 326 construct = (state) => state; | 380 setNonEssentialState(object, state); |
| 327 setNonEssentialState = (object, state) {}; | 381 } |
| 328 } | 382 } |
| 329 | 383 |
| 330 bool appliesTo(object) => object.runtimeType == type; | 384 /** |
| 331 | 385 * This rule handles things we can't pass directly, but only by reference. |
| 332 extractState(object, Function f) { | 386 * If objects are listed in the namedObjects in the writer or serialization, |
| 333 Map state = getState(object); | 387 * it will save the name rather than saving the state. |
| 334 values(state).forEach(f); | 388 */ |
| 389 class NamedObjectRule extends SerializationRule { |
| 390 /** |
| 391 * Return true if this rule applies to the object. Checked by looking up |
| 392 * in the namedObjects collection. |
| 393 */ |
| 394 bool appliesTo(object, Writer writer) { |
| 395 return writer.hasNameFor(object); |
| 396 } |
| 397 |
| 398 /** Extract the state of the named objects as just the object itself. */ |
| 399 extractState(object, Function f) => [object]; |
| 400 |
| 401 /** When we flatten the state we save it as the name. */ |
| 402 // TODO(alanknight): This seems questionable. In a truly flat format we may |
| 403 // want to have extracted the name as a string first and flatten it into a |
| 404 // reference to that. But that requires adding the Writer as a parameter to |
| 405 // extractState, and I'm reluctant to add yet another parameter until |
| 406 // proven necessary. |
| 407 void flatten(state, Writer writer) { |
| 408 state[0] = nameFor(state.first, writer); |
| 409 } |
| 410 |
| 411 /** Look up the named object and return it. */ |
| 412 inflateEssential(state, Reader r) => r.objectNamed(state.first); |
| 413 |
| 414 /** Set any non-essential state on the object. For this rule, a no-op. */ |
| 415 inflateNonEssential(state, object, Reader r) {} |
| 416 |
| 417 /** Return the name for this object in the Writer. */ |
| 418 nameFor(object, Writer writer) => writer.nameFor(object); |
| 419 } |
| 420 |
| 421 /** |
| 422 * This rule handles the special case of Mirrors, restricted to those that |
| 423 * have a simpleName. It knows that it applies to any such mirror and |
| 424 * automatically uses its simpleName as the key into the namedObjects. |
| 425 * When reading, the user is still responsible for adding the appropriate |
| 426 * mirrors to namedObject. |
| 427 */ |
| 428 class MirrorRule extends NamedObjectRule { |
| 429 bool appliesTo(object, Writer writer) => object is DeclarationMirror; |
| 430 nameFor(DeclarationMirror object, Writer writer) => object.simpleName; |
| 431 } |
| 432 |
| 433 /** |
| 434 * This provides an abstract superclass for writing your own rules specific to |
| 435 * a class. It makes some assumptions about behaviour, and so can have a |
| 436 * simpler set of methods that need to be implemented in order to subclass it. |
| 437 * |
| 438 */ |
| 439 abstract class CustomRule extends SerializationRule { |
| 440 // TODO(alanknight): It would be nice if we could provide an implementation |
| 441 // of appliesTo() here. If we add a type parameter to these classes |
| 442 // we can "is" test against it, but we need to be able to rule out subclasses. |
| 443 // => instance.runtimeType == T |
| 444 // should work. |
| 445 /** |
| 446 * Return true if this rule applies to this object, in the context |
| 447 * where we're writing it, false otherwise. |
| 448 */ |
| 449 bool appliesTo(instance, Writer w); |
| 450 |
| 451 /** |
| 452 * Subclasses should implement this to return a list of the important fields |
| 453 * in the object. The order of the fields doesn't matter, except that the |
| 454 * create and setState methods need to know how to use it. |
| 455 */ |
| 456 List getState(instance); |
| 457 |
| 458 /** |
| 459 * Given a [List] of the object's [state], re-create the object. This should |
| 460 * do the minimum needed to create the object, just calling the constructor. |
| 461 * Setting the remaining state of the object should be done in the [setState] |
| 462 * method, which will be called only once all the objects are created, so |
| 463 * it won't cause problems with cycles. |
| 464 */ |
| 465 create(List state); |
| 466 |
| 467 /** |
| 468 * Set any state in [object] which wasn't set in the constructor. Between |
| 469 * this method and [create] all of the information in [state] should be set |
| 470 * in the new object. |
| 471 */ |
| 472 void setState(object, List state); |
| 473 |
| 474 extractState(instance, Function f) { |
| 475 var state = getState(instance); |
| 476 for (var each in values(state)) { |
| 477 f(each); |
| 478 } |
| 335 return state; | 479 return state; |
| 336 } | 480 } |
| 337 | 481 |
| 338 // TODO(alanknight): We're inflating twice here. How to avoid doing | 482 inflateEssential(state, Reader r) => create(_lazy(state, r)); |
| 339 // that without giving the user even more stuff to specify. | 483 |
| 340 // Worse than that, by inflating everything in advance, we are are | 484 void inflateNonEssential(state, object, Reader r) => |
| 341 // forcing all the state to be essential. | 485 setState(object, _lazy(state, r)); |
| 342 Object inflateEssential(Map<String, Object> state, Reader r) { | 486 |
| 343 var inflated = values(state).map((x) => r.inflateReference(x)); | 487 // We don't want to have to make the end user tell us how long the list is |
| 344 return construct(inflated); | 488 // separately, so write it out for each object, even though they're all |
| 345 } | 489 // expected to be the same length. |
| 346 | 490 get writeLengthInFlatFormat => true; |
| 347 void inflateNonEssential(state, object, Reader r) { | 491 } |
| 348 if (setNonEssentialState == null) return; | 492 |
| 349 var inflated = values(state).map((x) => r.inflateReference(x)); | 493 /** Create a lazy list/map that will inflate its items on demand in [r]. */ |
| 350 setNonEssentialState(inflated, object); | 494 _lazy(l, Reader r) { |
| 351 } | 495 if (l is List) return new _LazyList(l, r); |
| 352 } | 496 if (l is Map) return new _LazyMap(l, r); |
| 353 | 497 throw new SerializationException("Invalid type: must be Map or List - $l"); |
| 354 /** | 498 } |
| 355 * This rule handles things we can't pass directly, but only by reference. | 499 |
| 356 * It extracts an identifier we can use to pass them. | 500 /** |
| 357 */ | 501 * This provides an implementation of Map that wraps a list which may |
| 358 class ClassMirrorRule extends SerializationRule { | 502 * contain references to (potentially) non-inflated objects. If these |
| 359 // TODO(alanknight): This probably generalizes to any named object. | 503 * are accessed it will inflate them. This allows us to pass something that |
| 360 bool appliesTo(object) { | 504 * looks like it's just a list of objects to a [CustomRule] without needing |
| 361 return object is ClassMirror; | 505 * to inflate all the references in advance. |
| 362 } | 506 */ |
| 363 extractState(object, Function f) => f(object.simpleName); | 507 class _LazyMap implements Map { |
| 364 void flatten(object, Writer writer) {} | 508 _LazyMap(this._raw, this._reader); |
| 365 inflateEssential(state, Reader r) => r.externalObjectNamed(state); | 509 |
| 366 inflateNonEssential(state, object, Reader r) {} | 510 Map _raw; |
| 511 Reader _reader; |
| 512 |
| 513 // This is the only operation that really matters. |
| 514 operator [](x) => _reader.inflateReference(_raw[x]); |
| 515 |
| 516 int get length => _raw.length; |
| 517 bool get isEmpty => _raw.isEmpty; |
| 518 List get keys => _raw.keys; |
| 519 bool containsKey(x) => _raw.containsKey(x); |
| 520 |
| 521 // These operations will work, but may be expensive, and are probably |
| 522 // best avoided. |
| 523 get _inflated => keysAndValues(_raw).map(_reader.inflateReference); |
| 524 bool containsValue(x) => _inflated.containsValue(x); |
| 525 List get values => _inflated.values; |
| 526 void forEach(f) => _inflated.forEach(f); |
| 527 |
| 528 // These operations are all invalid |
| 529 _throw() => throw new UnsupportedError("Not modifiable"); |
| 530 operator []=(x, y) => _throw(); |
| 531 putIfAbsent(x, y) => _throw(); |
| 532 remove(x) => _throw(); |
| 533 clear() => _throw(); |
| 534 } |
| 535 |
| 536 /** |
| 537 * This provides an implementation of List that wraps a list which may |
| 538 * contain references to (potentially) non-inflated objects. If these |
| 539 * are accessed it will inflate them. This allows us to pass something that |
| 540 * looks like it's just a list of objects to a [CustomRule] without needing |
| 541 * to inflate all the references in advance. |
| 542 */ |
| 543 class _LazyList implements List { |
| 544 _LazyList(this._raw, this._reader); |
| 545 |
| 546 List _raw; |
| 547 Reader _reader; |
| 548 |
| 549 // This is the only operation that really matters. |
| 550 operator [](x) => _reader.inflateReference(_raw[x]); |
| 551 |
| 552 int get length => _raw.length; |
| 553 bool get isEmpty => _raw.isEmpty; |
| 554 get first => _reader.inflateReference(_raw.first); |
| 555 get last => _reader.inflateReference(_raw.last); |
| 556 |
| 557 // These operations will work, but may be expensive, and are probably |
| 558 // best avoided. |
| 559 get _inflated => _raw.map(_reader.inflateReference); |
| 560 map(f) => _inflated.map(f); |
| 561 filter(f) => _inflated.filter(f); |
| 562 bool contains(element) => _inflated.filter(element); |
| 563 forEach(f) => _inflated.forEach(f); |
| 564 reduce(x, f) => _inflated.reduce(x, f); |
| 565 every(f) => _inflated(f); |
| 566 some(f) => _inflated(f); |
| 567 iterator() => _inflated.iterator(); |
| 568 indexOf(x, [pos = 0]) => _inflated.indexOf(x); |
| 569 lastIndexOf(x, [pos]) => _inflated.lastIndexOf(x); |
| 570 |
| 571 // These operations are all invalid |
| 572 _throw() => throw new UnsupportedError("Not modifiable"); |
| 573 operator []=(x, y) => _throw(); |
| 574 add(x) => _throw(); |
| 575 addLast(x) => _throw(); |
| 576 addAll(x) => _throw(); |
| 577 sort([f]) => _throw(); |
| 578 clear() => _throw(); |
| 579 removeAt(x) => _throw(); |
| 580 removeLast() => _throw(); |
| 581 getRange(x, y) => _throw(); |
| 582 setRange(x, y, z, [a]) => _throw(); |
| 583 removeRange(x, y) => _throw(); |
| 584 insertRange(x, y, [z]) => _throw(); |
| 585 void set length(x) => _throw(); |
| 367 } | 586 } |
| OLD | NEW |