| Index: pkg/serialization/lib/src/serialization_rule.dart
|
| diff --git a/pkg/serialization/lib/src/serialization_rule.dart b/pkg/serialization/lib/src/serialization_rule.dart
|
| index a4602373c1f33f40ff2264d3bfe3a6e143f64d6c..1207b7379953a2ba165c5f316f155300a1661de4 100644
|
| --- a/pkg/serialization/lib/src/serialization_rule.dart
|
| +++ b/pkg/serialization/lib/src/serialization_rule.dart
|
| @@ -33,8 +33,11 @@ abstract class SerializationRule {
|
| _number = x;
|
| }
|
|
|
| - /** Return true if this rule applies to this object, false otherwise. */
|
| - bool appliesTo(object);
|
| + /**
|
| + * Return true if this rule applies to this object, in the context
|
| + * where we're writing it, false otherwise.
|
| + */
|
| + bool appliesTo(object, Writer writer);
|
|
|
| /**
|
| * This extracts the state from the object, calling [f] for each value
|
| @@ -42,7 +45,7 @@ abstract class SerializationRule {
|
| * state at the end. The state that results will still have direct
|
| * pointers to objects, rather than references.
|
| */
|
| - Object extractState(object, void f(value));
|
| + extractState(object, void f(value));
|
|
|
| /**
|
| * Given the variables representing the state of an object, flatten it
|
| @@ -98,11 +101,11 @@ abstract class SerializationRule {
|
| inflateNonEssential(state, object, Reader reader);
|
|
|
| /**
|
| - * If we have an object [o] as part of our state, should we represent that
|
| + * If we have [object] as part of our state, should we represent that
|
| * directly, or should we make a reference for it. By default we use a
|
| * reference for everything.
|
| */
|
| - bool shouldUseReferenceFor(Object o, Writer w) => true;
|
| + bool shouldUseReferenceFor(object, Writer w) => true;
|
|
|
| /**
|
| * This writes the data from our internal representation into a List.
|
| @@ -121,9 +124,9 @@ abstract class SerializationRule {
|
| var intermediate = new List();
|
| var totalLength = 0;
|
| for (var eachList in ruleData) {
|
| - // TODO(alanknight): Abstract this out better, this really won't scale.
|
| - if (this is ListRule)
|
| + if (writeLengthInFlatFormat) {
|
| intermediate.add(eachList.length);
|
| + }
|
| for (var eachRef in eachList) {
|
| if (eachRef == null) {
|
| intermediate..add(null)..add(null);
|
| @@ -136,10 +139,52 @@ abstract class SerializationRule {
|
| }
|
|
|
| /**
|
| + * Return true if this rule writes a length value before each entry in
|
| + * the flat format. Return false if the results are fixed length.
|
| + */
|
| + // TODO(alanknight): This should probably go away with more general formats.
|
| + bool get writeLengthInFlatFormat => false;
|
| +
|
| + /**
|
| * The inverse of dumpStateInto, this reads the rule's state from an
|
| * iterator in a flat format.
|
| */
|
| - pullStateFrom(Iterator stream);
|
| + pullStateFrom(Iterator stream) {
|
| + var numberOfEntries = stream.next();
|
| + var ruleData = new List();
|
| + for (var i = 0; i < numberOfEntries; i++) {
|
| + var subLength = dataLengthIn(stream);
|
| + var subList = [];
|
| + ruleData.add(subList);
|
| + for (var j = 0; j < subLength; j++) {
|
| + var a = stream.next();
|
| + var b = stream.next();
|
| + if (!(a is int)) {
|
| + // This wasn't a reference, just use the first object as a literal.
|
| + // particularly used for the case of null.
|
| + subList.add(a);
|
| + } else {
|
| + subList.add(new Reference(this, a, b));
|
| + }
|
| + }
|
| + }
|
| + return ruleData;
|
| + }
|
| +
|
| + /**
|
| + * Return the length of the list of data we expect to see on a particular
|
| + * iterator in a flat format. This may have been encoded in the stream if we
|
| + * are variable length, or it may be constant. Note that this is expressed in
|
| + *
|
| + */
|
| + dataLengthIn(Iterator stream) =>
|
| + writeLengthInFlatFormat ? stream.next() : dataLength;
|
| +
|
| + /**
|
| + * If the data is fixed length, return it here. Unused in the non-flat
|
| + * format, or if the data is variable length.
|
| + */
|
| + int get dataLength => 0;
|
| }
|
|
|
| /**
|
| @@ -148,7 +193,7 @@ abstract class SerializationRule {
|
| */
|
| class ListRule extends SerializationRule {
|
|
|
| - appliesTo(object) => object is List;
|
| + appliesTo(object, Writer w) => object is List;
|
|
|
| state(List list) => new List.from(list);
|
|
|
| @@ -186,9 +231,9 @@ class ListRule extends SerializationRule {
|
| // TODO(alanknight): This is much too close to the basicRule implementation,
|
| // and I'd refactor them if I didn't think this whole mechanism needed to
|
| // change soon.
|
| - var dataLength = stream.next();
|
| + var length = stream.next();
|
| var ruleData = new List();
|
| - for (var i = 0; i < dataLength; i++) {
|
| + for (var i = 0; i < length; i++) {
|
| var subLength = stream.next();
|
| var subList = new List();
|
| ruleData.add(subList);
|
| @@ -206,6 +251,14 @@ class ListRule extends SerializationRule {
|
| }
|
| return ruleData;
|
| }
|
| +
|
| + /**
|
| + * Return true because we need to write the length of each list in the flat
|
| + * format. */
|
| + bool get writeLengthInFlatFormat => true;
|
| +
|
| + /** Return the length of the next list when reading the flat format. */
|
| + int dataLengthIn(Iterator stream) => stream.next();
|
| }
|
|
|
| /**
|
| @@ -234,7 +287,7 @@ class ListRuleEssential extends ListRule {
|
| * num, String, and bool.
|
| */
|
| class PrimitiveRule extends SerializationRule {
|
| - appliesTo(object) {
|
| + appliesTo(object, Writer w) {
|
| return isPrimitive(object);
|
| }
|
| extractState(object, Function f) => object;
|
| @@ -242,11 +295,12 @@ class PrimitiveRule extends SerializationRule {
|
| inflateEssential(state, Reader r) => state;
|
| inflateNonEssential(object, _, Reader r) {}
|
|
|
| - /** Indicate whether we should save pointers to this object as references
|
| + /**
|
| + * Indicate whether we should save pointers to this object as references
|
| * or store the object directly. For primitives this depends on the format,
|
| * so we delegate to the writer.
|
| */
|
| - bool shouldUseReferenceFor(Object o, Writer w) =>
|
| + bool shouldUseReferenceFor(object, Writer w) =>
|
| w.shouldUseReferencesForPrimitives;
|
|
|
| /**
|
| @@ -266,9 +320,9 @@ class PrimitiveRule extends SerializationRule {
|
| * indicating the number of objects and then N simple objects.
|
| */
|
| pullStateFrom(Iterator stream) {
|
| - var dataLength = stream.next();
|
| + var length = stream.next();
|
| var ruleData = new List();
|
| - for (var i = 0; i < dataLength; i++) {
|
| + for (var i = 0; i < length; i++) {
|
| ruleData.add(stream.next());
|
| }
|
| return ruleData;
|
| @@ -276,24 +330,24 @@ class PrimitiveRule extends SerializationRule {
|
| }
|
|
|
| /** Helper function for PrimitiveRule to tell which objects it applies to. */
|
| -bool isPrimitive(Object object) {
|
| +bool isPrimitive(object) {
|
| return object is num || object is String || object is bool;
|
| }
|
|
|
| -/** Typedef for the object construction closure used in ClosureToMapRule. */
|
| -typedef Object ConstructType(Map m);
|
| +/** Typedef for the object construction closure used in ClosureRule. */
|
| +typedef ConstructType(Map m);
|
|
|
| /** Typedef for the state-getting closure used in ClosureToMapRule. */
|
| -typedef Map<String, Object> GetStateType(Object o);
|
| +typedef Map<String, dynamic> GetStateType(object);
|
|
|
| /** Typedef for the state-setting closure used in ClosureToMapRule. */
|
| -typedef void NonEssentialStateType(Object o, Map m);
|
| +typedef void NonEssentialStateType(object, Map m);
|
|
|
| /**
|
| * This is a rule where the extraction and creation are hard-coded as
|
| * closures. The result is expected to be a map indexed by field name.
|
| */
|
| -class ClosureToMapRule extends SerializationRule {
|
| +class ClosureRule extends CustomRule {
|
|
|
| /** The runtimeType of objects that this rule applies to. Used in appliesTo.*/
|
| final Type type;
|
| @@ -302,7 +356,7 @@ class ClosureToMapRule extends SerializationRule {
|
| ConstructType construct;
|
|
|
| /** The function for returning an object's state as a Map. */
|
| - GetStateType getState;
|
| + GetStateType getStateFunction;
|
|
|
| /** The function for setting an object's state from a Map. */
|
| NonEssentialStateType setNonEssentialState;
|
| @@ -312,56 +366,221 @@ class ClosureToMapRule extends SerializationRule {
|
| * state by calling [getState], creates a new object by calling [construct]
|
| * and sets the new object's state by calling [setNonEssentialState].
|
| */
|
| - ClosureToMapRule(this.type, this.getState, this.construct,
|
| + ClosureRule(this.type, this.getStateFunction, this.construct,
|
| this.setNonEssentialState);
|
|
|
| + bool appliesTo(object, Writer w) => object.runtimeType == type;
|
| +
|
| + getState(object) => getStateFunction(object);
|
| +
|
| + create(state) => construct(state);
|
| +
|
| + setState(object, state) {
|
| + if (setNonEssentialState == null) return;
|
| + setNonEssentialState(object, state);
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * This rule handles things we can't pass directly, but only by reference.
|
| + * If objects are listed in the namedObjects in the writer or serialization,
|
| + * it will save the name rather than saving the state.
|
| + */
|
| +class NamedObjectRule extends SerializationRule {
|
| /**
|
| - * If we deserialize a ClosureToMapRule we can't actually use it, because
|
| - * we don't have the closures, so generate a stub that just returns the
|
| - * raw state object.
|
| + * Return true if this rule applies to the object. Checked by looking up
|
| + * in the namedObjects collection.
|
| */
|
| - ClosureToMapRule.stub(this.type) {
|
| - getState = (x) { throw new SerializationException(
|
| - 'Closures cannot be serialized'); };
|
| - construct = (state) => state;
|
| - setNonEssentialState = (object, state) {};
|
| + bool appliesTo(object, Writer writer) {
|
| + return writer.hasNameFor(object);
|
| }
|
|
|
| - bool appliesTo(object) => object.runtimeType == type;
|
| + /** Extract the state of the named objects as just the object itself. */
|
| + extractState(object, Function f) => [object];
|
|
|
| - extractState(object, Function f) {
|
| - Map state = getState(object);
|
| - values(state).forEach(f);
|
| - return state;
|
| + /** When we flatten the state we save it as the name. */
|
| + // TODO(alanknight): This seems questionable. In a truly flat format we may
|
| + // want to have extracted the name as a string first and flatten it into a
|
| + // reference to that. But that requires adding the Writer as a parameter to
|
| + // extractState, and I'm reluctant to add yet another parameter until
|
| + // proven necessary.
|
| + void flatten(state, Writer writer) {
|
| + state[0] = nameFor(state.first, writer);
|
| }
|
|
|
| - // TODO(alanknight): We're inflating twice here. How to avoid doing
|
| - // that without giving the user even more stuff to specify.
|
| - // Worse than that, by inflating everything in advance, we are are
|
| - // forcing all the state to be essential.
|
| - Object inflateEssential(Map<String, Object> state, Reader r) {
|
| - var inflated = values(state).map((x) => r.inflateReference(x));
|
| - return construct(inflated);
|
| - }
|
| + /** Look up the named object and return it. */
|
| + inflateEssential(state, Reader r) => r.objectNamed(state.first);
|
|
|
| - void inflateNonEssential(state, object, Reader r) {
|
| - if (setNonEssentialState == null) return;
|
| - var inflated = values(state).map((x) => r.inflateReference(x));
|
| - setNonEssentialState(inflated, object);
|
| - }
|
| + /** Set any non-essential state on the object. For this rule, a no-op. */
|
| + inflateNonEssential(state, object, Reader r) {}
|
| +
|
| + /** Return the name for this object in the Writer. */
|
| + nameFor(object, Writer writer) => writer.nameFor(object);
|
| }
|
|
|
| /**
|
| - * This rule handles things we can't pass directly, but only by reference.
|
| - * It extracts an identifier we can use to pass them.
|
| + * This rule handles the special case of Mirrors, restricted to those that
|
| + * have a simpleName. It knows that it applies to any such mirror and
|
| + * automatically uses its simpleName as the key into the namedObjects.
|
| + * When reading, the user is still responsible for adding the appropriate
|
| + * mirrors to namedObject.
|
| + */
|
| +class MirrorRule extends NamedObjectRule {
|
| + bool appliesTo(object, Writer writer) => object is DeclarationMirror;
|
| + nameFor(DeclarationMirror object, Writer writer) => object.simpleName;
|
| +}
|
| +
|
| +/**
|
| + * This provides an abstract superclass for writing your own rules specific to
|
| + * a class. It makes some assumptions about behaviour, and so can have a
|
| + * simpler set of methods that need to be implemented in order to subclass it.
|
| + *
|
| */
|
| -class ClassMirrorRule extends SerializationRule {
|
| - // TODO(alanknight): This probably generalizes to any named object.
|
| - bool appliesTo(object) {
|
| - return object is ClassMirror;
|
| +abstract class CustomRule extends SerializationRule {
|
| + // TODO(alanknight): It would be nice if we could provide an implementation
|
| + // of appliesTo() here. If we add a type parameter to these classes
|
| + // we can "is" test against it, but we need to be able to rule out subclasses.
|
| + // => instance.runtimeType == T
|
| + // should work.
|
| + /**
|
| + * Return true if this rule applies to this object, in the context
|
| + * where we're writing it, false otherwise.
|
| + */
|
| + bool appliesTo(instance, Writer w);
|
| +
|
| + /**
|
| + * Subclasses should implement this to return a list of the important fields
|
| + * in the object. The order of the fields doesn't matter, except that the
|
| + * create and setState methods need to know how to use it.
|
| + */
|
| + List getState(instance);
|
| +
|
| + /**
|
| + * Given a [List] of the object's [state], re-create the object. This should
|
| + * do the minimum needed to create the object, just calling the constructor.
|
| + * Setting the remaining state of the object should be done in the [setState]
|
| + * method, which will be called only once all the objects are created, so
|
| + * it won't cause problems with cycles.
|
| + */
|
| + create(List state);
|
| +
|
| + /**
|
| + * Set any state in [object] which wasn't set in the constructor. Between
|
| + * this method and [create] all of the information in [state] should be set
|
| + * in the new object.
|
| + */
|
| + void setState(object, List state);
|
| +
|
| + extractState(instance, Function f) {
|
| + var state = getState(instance);
|
| + for (var each in values(state)) {
|
| + f(each);
|
| + }
|
| + return state;
|
| }
|
| - extractState(object, Function f) => f(object.simpleName);
|
| - void flatten(object, Writer writer) {}
|
| - inflateEssential(state, Reader r) => r.externalObjectNamed(state);
|
| - inflateNonEssential(state, object, Reader r) {}
|
| +
|
| + inflateEssential(state, Reader r) => create(_lazy(state, r));
|
| +
|
| + void inflateNonEssential(state, object, Reader r) =>
|
| + setState(object, _lazy(state, r));
|
| +
|
| + // We don't want to have to make the end user tell us how long the list is
|
| + // separately, so write it out for each object, even though they're all
|
| + // expected to be the same length.
|
| + get writeLengthInFlatFormat => true;
|
| +}
|
| +
|
| +/** Create a lazy list/map that will inflate its items on demand in [r]. */
|
| +_lazy(l, Reader r) {
|
| + if (l is List) return new _LazyList(l, r);
|
| + if (l is Map) return new _LazyMap(l, r);
|
| + throw new SerializationException("Invalid type: must be Map or List - $l");
|
| +}
|
| +
|
| +/**
|
| + * This provides an implementation of Map that wraps a list which may
|
| + * contain references to (potentially) non-inflated objects. If these
|
| + * are accessed it will inflate them. This allows us to pass something that
|
| + * looks like it's just a list of objects to a [CustomRule] without needing
|
| + * to inflate all the references in advance.
|
| + */
|
| +class _LazyMap implements Map {
|
| + _LazyMap(this._raw, this._reader);
|
| +
|
| + Map _raw;
|
| + Reader _reader;
|
| +
|
| + // This is the only operation that really matters.
|
| + operator [](x) => _reader.inflateReference(_raw[x]);
|
| +
|
| + int get length => _raw.length;
|
| + bool get isEmpty => _raw.isEmpty;
|
| + List get keys => _raw.keys;
|
| + bool containsKey(x) => _raw.containsKey(x);
|
| +
|
| + // These operations will work, but may be expensive, and are probably
|
| + // best avoided.
|
| + get _inflated => keysAndValues(_raw).map(_reader.inflateReference);
|
| + bool containsValue(x) => _inflated.containsValue(x);
|
| + List get values => _inflated.values;
|
| + void forEach(f) => _inflated.forEach(f);
|
| +
|
| + // These operations are all invalid
|
| + _throw() => throw new UnsupportedError("Not modifiable");
|
| + operator []=(x, y) => _throw();
|
| + putIfAbsent(x, y) => _throw();
|
| + remove(x) => _throw();
|
| + clear() => _throw();
|
| +}
|
| +
|
| +/**
|
| + * This provides an implementation of List that wraps a list which may
|
| + * contain references to (potentially) non-inflated objects. If these
|
| + * are accessed it will inflate them. This allows us to pass something that
|
| + * looks like it's just a list of objects to a [CustomRule] without needing
|
| + * to inflate all the references in advance.
|
| + */
|
| +class _LazyList implements List {
|
| + _LazyList(this._raw, this._reader);
|
| +
|
| + List _raw;
|
| + Reader _reader;
|
| +
|
| + // This is the only operation that really matters.
|
| + operator [](x) => _reader.inflateReference(_raw[x]);
|
| +
|
| + int get length => _raw.length;
|
| + bool get isEmpty => _raw.isEmpty;
|
| + get first => _reader.inflateReference(_raw.first);
|
| + get last => _reader.inflateReference(_raw.last);
|
| +
|
| + // These operations will work, but may be expensive, and are probably
|
| + // best avoided.
|
| + get _inflated => _raw.map(_reader.inflateReference);
|
| + map(f) => _inflated.map(f);
|
| + filter(f) => _inflated.filter(f);
|
| + bool contains(element) => _inflated.filter(element);
|
| + forEach(f) => _inflated.forEach(f);
|
| + reduce(x, f) => _inflated.reduce(x, f);
|
| + every(f) => _inflated(f);
|
| + some(f) => _inflated(f);
|
| + iterator() => _inflated.iterator();
|
| + indexOf(x, [pos = 0]) => _inflated.indexOf(x);
|
| + lastIndexOf(x, [pos]) => _inflated.lastIndexOf(x);
|
| +
|
| + // These operations are all invalid
|
| + _throw() => throw new UnsupportedError("Not modifiable");
|
| + operator []=(x, y) => _throw();
|
| + add(x) => _throw();
|
| + addLast(x) => _throw();
|
| + addAll(x) => _throw();
|
| + sort([f]) => _throw();
|
| + clear() => _throw();
|
| + removeAt(x) => _throw();
|
| + removeLast() => _throw();
|
| + getRange(x, y) => _throw();
|
| + setRange(x, y, z, [a]) => _throw();
|
| + removeRange(x, y) => _throw();
|
| + insertRange(x, y, [z]) => _throw();
|
| + void set length(x) => _throw();
|
| }
|
|
|