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

Unified Diff: pkg/serialization/lib/src/serialization_rule.dart

Issue 11578037: Replicating CL https://chromiumcodereview.appspot.com/11553012/ (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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();
}
« no previous file with comments | « pkg/serialization/lib/src/serialization_helpers.dart ('k') | pkg/serialization/test/serialization_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698