| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 part of serialization; |
| 6 |
| 7 // TODO(alanknight): We should have an example and tests for subclassing |
| 8 // serialization rule rather than using the hard-coded ClosureToMap rule. And |
| 9 // possibly an abstract superclass that's designed to be subclassed that way. |
| 10 /** |
| 11 * The abstract superclass for serialization rules. |
| 12 */ |
| 13 abstract class SerializationRule { |
| 14 /** |
| 15 * Rules belong uniquely to a particular Serialization instance, and can |
| 16 * be identified within it by number. |
| 17 */ |
| 18 int _number; |
| 19 |
| 20 /** |
| 21 * Rules belong uniquely to a particular Serialization instance, and can |
| 22 * be identified within it by number. |
| 23 */ |
| 24 int get number => _number; |
| 25 |
| 26 /** |
| 27 * Rules belong uniquely to a particular Serialization instance, and can |
| 28 * be identified within it by number. |
| 29 */ |
| 30 void set number(x) { |
| 31 if (_number != null) throw |
| 32 new SerializationException("Rule numbers cannot be changed, once set"); |
| 33 _number = x; |
| 34 } |
| 35 |
| 36 /** Return true if this rule applies to this object, false otherwise. */ |
| 37 bool appliesTo(object); |
| 38 |
| 39 /** |
| 40 * This extracts the state from the object, calling [f] for each value |
| 41 * as it is extracted, and returning an object representing the whole |
| 42 * state at the end. The state that results will still have direct |
| 43 * pointers to objects, rather than references. |
| 44 */ |
| 45 Object extractState(object, void f(value)); |
| 46 |
| 47 /** |
| 48 * Given the variables representing the state of an object, flatten it |
| 49 * by turning object pointers into Reference objects where needed. This |
| 50 * destructively modifies the state object. |
| 51 * |
| 52 * 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 |
| 54 * different. |
| 55 */ |
| 56 // This has to be a separate operation from extracting, because we extract |
| 57 // as we are traversing the objects, so we don't yet have the objects to |
| 58 // generate references for them. It might be possible to avoid that by |
| 59 // doing a depth-first rather than breadth-first traversal, but I'm not |
| 60 // sure it's worth it. |
| 61 void flatten(state, Writer writer) { |
| 62 keysAndValues(state).forEach((key, value) { |
| 63 var reference = writer._referenceFor(value); |
| 64 state[key] = (reference == null) ? value : reference; |
| 65 }); |
| 66 } |
| 67 |
| 68 /** Return true if this rule should only be applied when we are the first |
| 69 * rule found that applies to this object. This may or may not be a hack |
| 70 * that will disappear once we have better support for multiple rules. |
| 71 * We want to have multiple different rules that apply to the same object. We |
| 72 * also want to have multiple different rules that might exclusively apply |
| 73 * to the same object. So, we want either ListRule or ListRuleEssential, and |
| 74 * only one of them can be there. But on the other hand, we may want both |
| 75 * ListRule and BasicRule. So we identify the kinds of rules that can share. |
| 76 * If mustBePrimary returns true, then this rule will only be chosen if no |
| 77 * other rule has been found yet. This means that the ordering of rules in |
| 78 * the serialization is significant, which is unpleasant, but we'll have |
| 79 * to see how bad it is. |
| 80 */ |
| 81 // TODO(alanknight): Reconsider whether this should be handled differently. |
| 82 get mustBePrimary => false; |
| 83 |
| 84 /** |
| 85 * Create the new object corresponding to [state] using the rules |
| 86 * from [reader]. This may involve recursively inflating "essential" |
| 87 * references in the state, which are those that are required for the |
| 88 * object's constructor. It is up to the rule what state is considered |
| 89 * essential. |
| 90 */ |
| 91 inflateEssential(state, Reader reader); |
| 92 |
| 93 /** |
| 94 * The [object] has already been created. Set any of its non-essential |
| 95 * variables from the representation in [state]. Where there are references |
| 96 * to other objects they are resolved in the context of [reader]. |
| 97 */ |
| 98 inflateNonEssential(state, object, Reader reader); |
| 99 |
| 100 /** |
| 101 * If we have an object [o] as part of our state, should we represent that |
| 102 * directly, or should we make a reference for it. By default we use a |
| 103 * reference for everything. |
| 104 */ |
| 105 bool shouldUseReferenceFor(Object o, Writer w) => true; |
| 106 |
| 107 /** |
| 108 * 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 |
| 110 * folded into a more general mechanism for supporting different output |
| 111 * formats. |
| 112 */ |
| 113 // 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 |
| 115 // the internal representation of the rule's state, which is particularly |
| 116 // bad. The default implementation treats the ruleData as a List of Lists |
| 117 // of references. |
| 118 void dumpStateInto(List ruleData, List target) { |
| 119 // Needing the intermediate is also bad for performance, but tricky |
| 120 // to do otherwise without a mechanism to precalculate the size. |
| 121 var intermediate = new List(); |
| 122 var totalLength = 0; |
| 123 for (var eachList in ruleData) { |
| 124 // TODO(alanknight): Abstract this out better, this really won't scale. |
| 125 if (this is ListRule) |
| 126 intermediate.add(eachList.length); |
| 127 for (var eachRef in eachList) { |
| 128 if (eachRef == null) { |
| 129 intermediate..add(null)..add(null); |
| 130 } else { |
| 131 eachRef.writeToList(intermediate); |
| 132 } |
| 133 } |
| 134 } |
| 135 target.addAll(intermediate); |
| 136 } |
| 137 |
| 138 /** |
| 139 * The inverse of dumpStateInto, this reads the rule's state from an |
| 140 * iterator in a flat format. |
| 141 */ |
| 142 pullStateFrom(Iterator stream); |
| 143 } |
| 144 |
| 145 /** |
| 146 * This rule handles things that implement List. It will recreate them as |
| 147 * whatever the default implemenation of List is on the target platform. |
| 148 */ |
| 149 class ListRule extends SerializationRule { |
| 150 |
| 151 appliesTo(object) => object is List; |
| 152 |
| 153 state(List list) => new List.from(list); |
| 154 |
| 155 List extractState(List list, f) { |
| 156 var result = new List(); |
| 157 for (var each in list) { |
| 158 result.add(each); |
| 159 f(each); |
| 160 } |
| 161 return result; |
| 162 } |
| 163 |
| 164 inflateEssential(List state, Reader r) => new List(); |
| 165 |
| 166 // For a list, we consider all of its state non-essential and add it |
| 167 // after creation. |
| 168 inflateNonEssential(List state, List newList, Reader r) { |
| 169 populateContents(state, newList, r); |
| 170 } |
| 171 |
| 172 void populateContents(List state, List newList, Reader r) { |
| 173 for(var each in state) { |
| 174 newList.add(r.inflateReference(each)); |
| 175 } |
| 176 } |
| 177 |
| 178 /** |
| 179 * 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 |
| 181 * 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 |
| 183 * integers. Or, in the special case of null, two nulls. |
| 184 */ |
| 185 pullStateFrom(Iterator stream) { |
| 186 // 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 |
| 188 // change soon. |
| 189 var dataLength = stream.next(); |
| 190 var ruleData = new List(); |
| 191 for (var i = 0; i < dataLength; i++) { |
| 192 var subLength = stream.next(); |
| 193 var subList = new List(); |
| 194 ruleData.add(subList); |
| 195 for (var j = 0; j < subLength; j++) { |
| 196 var a = stream.next(); |
| 197 var b = stream.next(); |
| 198 if (!(a is int)) { |
| 199 // This wasn't a reference, just use the first object as a literal. |
| 200 // particularly used for the case of null. |
| 201 subList.add(a); |
| 202 } else { |
| 203 subList.add(new Reference(this, a, b)); |
| 204 } |
| 205 } |
| 206 } |
| 207 return ruleData; |
| 208 } |
| 209 } |
| 210 |
| 211 /** |
| 212 * 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, |
| 214 * but it expects L's contents to be fixed when X's constructor is called. |
| 215 */ |
| 216 class ListRuleEssential extends ListRule { |
| 217 |
| 218 /** Create the new List and also inflate all of its contents. */ |
| 219 inflateEssential(List state, Reader r) { |
| 220 var object = super.inflateEssential(state, r); |
| 221 populateContents(state, object, r); |
| 222 return object; |
| 223 } |
| 224 |
| 225 /** Does nothing, because all the work has been done in inflateEssential. */ |
| 226 inflateNonEssential(state, newList, reader) {} |
| 227 |
| 228 bool get mustBePrimary => true; |
| 229 } |
| 230 |
| 231 /** |
| 232 * 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 |
| 234 * num, String, and bool. |
| 235 */ |
| 236 class PrimitiveRule extends SerializationRule { |
| 237 appliesTo(object) { |
| 238 return isPrimitive(object); |
| 239 } |
| 240 extractState(object, Function f) => object; |
| 241 void flatten(object, Writer writer) {} |
| 242 inflateEssential(state, Reader r) => state; |
| 243 inflateNonEssential(object, _, Reader r) {} |
| 244 |
| 245 /** Indicate whether we should save pointers to this object as references |
| 246 * or store the object directly. For primitives this depends on the format, |
| 247 * so we delegate to the writer. |
| 248 */ |
| 249 bool shouldUseReferenceFor(Object o, Writer w) => |
| 250 w.shouldUseReferencesForPrimitives; |
| 251 |
| 252 /** |
| 253 * 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 |
| 255 * folded into a more general mechanism for supporting different output |
| 256 * formats. For primitives, the ruleData is our list of all the |
| 257 * primitives and just add it into the target. |
| 258 */ |
| 259 void dumpStateInto(List ruleData, List target) { |
| 260 target.addAll(ruleData); |
| 261 } |
| 262 |
| 263 /** |
| 264 * 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 |
| 266 * indicating the number of objects and then N simple objects. |
| 267 */ |
| 268 pullStateFrom(Iterator stream) { |
| 269 var dataLength = stream.next(); |
| 270 var ruleData = new List(); |
| 271 for (var i = 0; i < dataLength; i++) { |
| 272 ruleData.add(stream.next()); |
| 273 } |
| 274 return ruleData; |
| 275 } |
| 276 } |
| 277 |
| 278 /** Helper function for PrimitiveRule to tell which objects it applies to. */ |
| 279 bool isPrimitive(Object object) { |
| 280 return object is num || object is String || object is bool; |
| 281 } |
| 282 |
| 283 /** Typedef for the object construction closure used in ClosureToMapRule. */ |
| 284 typedef Object ConstructType(Map m); |
| 285 |
| 286 /** Typedef for the state-getting closure used in ClosureToMapRule. */ |
| 287 typedef Map<String, Object> GetStateType(Object o); |
| 288 |
| 289 /** Typedef for the state-setting closure used in ClosureToMapRule. */ |
| 290 typedef void NonEssentialStateType(Object o, Map m); |
| 291 |
| 292 /** |
| 293 * 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. |
| 295 */ |
| 296 class ClosureToMapRule extends SerializationRule { |
| 297 |
| 298 /** The runtimeType of objects that this rule applies to. Used in appliesTo.*/ |
| 299 final Type type; |
| 300 |
| 301 /** The function for constructing new objects when reading. */ |
| 302 ConstructType construct; |
| 303 |
| 304 /** The function for returning an object's state as a Map. */ |
| 305 GetStateType getState; |
| 306 |
| 307 /** The function for setting an object's state from a Map. */ |
| 308 NonEssentialStateType setNonEssentialState; |
| 309 |
| 310 /** |
| 311 * Create a ClosureToMapRule for the given [type] which gets an object's |
| 312 * state by calling [getState], creates a new object by calling [construct] |
| 313 * and sets the new object's state by calling [setNonEssentialState]. |
| 314 */ |
| 315 ClosureToMapRule(this.type, this.getState, this.construct, |
| 316 this.setNonEssentialState); |
| 317 |
| 318 /** |
| 319 * If we deserialize a ClosureToMapRule we can't actually use it, because |
| 320 * we don't have the closures, so generate a stub that just returns the |
| 321 * raw state object. |
| 322 */ |
| 323 ClosureToMapRule.stub(this.type) { |
| 324 getState = (x) { throw new SerializationException( |
| 325 'Closures cannot be serialized'); }; |
| 326 construct = (state) => state; |
| 327 setNonEssentialState = (object, state) {}; |
| 328 } |
| 329 |
| 330 bool appliesTo(object) => object.runtimeType == type; |
| 331 |
| 332 extractState(object, Function f) { |
| 333 Map state = getState(object); |
| 334 values(state).forEach(f); |
| 335 return state; |
| 336 } |
| 337 |
| 338 // TODO(alanknight): We're inflating twice here. How to avoid doing |
| 339 // that without giving the user even more stuff to specify. |
| 340 // Worse than that, by inflating everything in advance, we are are |
| 341 // forcing all the state to be essential. |
| 342 Object inflateEssential(Map<String, Object> state, Reader r) { |
| 343 var inflated = values(state).map((x) => r.inflateReference(x)); |
| 344 return construct(inflated); |
| 345 } |
| 346 |
| 347 void inflateNonEssential(state, object, Reader r) { |
| 348 if (setNonEssentialState == null) return; |
| 349 var inflated = values(state).map((x) => r.inflateReference(x)); |
| 350 setNonEssentialState(inflated, object); |
| 351 } |
| 352 } |
| 353 |
| 354 /** |
| 355 * This rule handles things we can't pass directly, but only by reference. |
| 356 * It extracts an identifier we can use to pass them. |
| 357 */ |
| 358 class ClassMirrorRule extends SerializationRule { |
| 359 // TODO(alanknight): This probably generalizes to any named object. |
| 360 bool appliesTo(object) { |
| 361 return object is ClassMirror; |
| 362 } |
| 363 extractState(object, Function f) => f(object.simpleName); |
| 364 void flatten(object, Writer writer) {} |
| 365 inflateEssential(state, Reader r) => r.externalObjectNamed(state); |
| 366 inflateNonEssential(state, object, Reader r) {} |
| 367 } |
| OLD | NEW |