| Index: pkg/serialization/lib/src/reader_writer.dart
|
| diff --git a/pkg/serialization/lib/src/reader_writer.dart b/pkg/serialization/lib/src/reader_writer.dart
|
| index 8892aa716985fbd0e243f91602c9f9c796f43286..87535cea9173d849e3fb46234263281496b90afd 100644
|
| --- a/pkg/serialization/lib/src/reader_writer.dart
|
| +++ b/pkg/serialization/lib/src/reader_writer.dart
|
| @@ -31,6 +31,8 @@ class Writer {
|
| */
|
| bool selfDescribing;
|
|
|
| + Format format = new SimpleMapFormat();
|
| +
|
| /**
|
| * Objects that cannot be represented in-place in the serialized form need
|
| * to have references to them stored. The [Reference] objects are computed
|
| @@ -59,69 +61,21 @@ class Writer {
|
| * related to a particular read/write, so the same one can be used
|
| * for multiple different Readers/Writers.
|
| */
|
| - Writer(this.serialization) {
|
| + Writer(this.serialization, [Format newFormat]) {
|
| trace = new Trace(this);
|
| selfDescribing = serialization.selfDescribing;
|
| + if (newFormat != null) format = newFormat;
|
| }
|
|
|
| /**
|
| * This is the main API for a [Writer]. It writes the objects and returns
|
| - * the serialized representation, currently a JSON format of a map
|
| - * whose data is either lists indexed by field position or maps indexed
|
| - * by field name, and holding either primitives or references. See [toMaps]
|
| + * the serialized representation, as determined by [format].
|
| */
|
| - // TODO(alanknight): Generalize the output representation. Probably requires
|
| - // introducing some sort of OutputFormat object.
|
| - String write(anObject) {
|
| + write(anObject) {
|
| trace.addRoot(anObject);
|
| trace.traceAll();
|
| _flatten();
|
| - return toStringFormat();
|
| - }
|
| -
|
| - /**
|
| - * This is an alternate writing API that writes the objects and returns
|
| - * the serialized representation as a List of simple objects.
|
| - * See [toFlatFormat].
|
| - */
|
| - List writeFlat(anObject) {
|
| - shouldUseReferencesForPrimitives = true;
|
| - trace.addRoot(anObject);
|
| - trace.traceAll();
|
| - _flatten();
|
| - return toFlatFormat();
|
| - }
|
| -
|
| - /**
|
| - * Write to a simple flat format. This format is at the proof of concept
|
| - * stage, so details are not finalized and are likely to change in the future.
|
| - * Right now this produces a List containing null, int, and String. This is
|
| - * more space-efficient than the map format created by [toStringFormat] or
|
| - * [toMaps], but is much less human-readable.
|
| - */
|
| - List toFlatFormat() {
|
| - var result = new List(3);
|
| - // TODO(alanknight): Don't make it call toMaps in order to make non-maps.
|
| - // As part of that, if writing flat, the rule serialization should be flat.
|
| - var stuff = toMaps();
|
| - result[0] = stuff["rules"];
|
| - var roots = new List();
|
| - stuff["roots"].forEach((x) => x.writeToList(roots));
|
| - result[2] = roots;
|
| -
|
| - // TODO(alanknight): This needs serious generalization. Do we introduce
|
| - // an output format object that the rules talk to? Do we make use of the
|
| - // fact that rules talk to something that looks to them like a List. Do
|
| - // we then mandate that instead of saying they have complete charge of
|
| - // their own storage?
|
| - var flatData = [];
|
| - for (var eachRule in rules) {
|
| - var ruleData = stuff["data"][eachRule.number];
|
| - flatData.add(ruleData.length);
|
| - eachRule.dumpStateInto(ruleData, flatData);
|
| - }
|
| - result[1] = flatData;
|
| - return result;
|
| + return format.generateOutput(this);
|
| }
|
|
|
| /**
|
| @@ -174,7 +128,20 @@ class Writer {
|
| * That depends on which format we're using, so a flat format will want
|
| * references, but the Map format can store them directly.
|
| */
|
| - bool shouldUseReferencesForPrimitives = false;
|
| + bool get shouldUseReferencesForPrimitives
|
| + => format.shouldUseReferencesForPrimitives;
|
| +
|
| + /**
|
| + * Returns a serialized version of the [SerializationRule]s used to write
|
| + * the data, if [selfDescribing] is true, otherwise returns null.
|
| + */
|
| + serializedRules() {
|
| + if (!selfDescribing) return null;
|
| + var meta = serialization.ruleSerialization();
|
| + var writer = new Writer(meta);
|
| + writer.selfDescribing = false;
|
| + return writer.write(serialization._rules);
|
| + }
|
|
|
| /** Record a [state] entry for a particular rule. */
|
| void _addStateForRule(eachRule, state) {
|
| @@ -222,45 +189,10 @@ class Writer {
|
| }
|
|
|
| /**
|
| - * Return the serialized data in string format. Currently hard-coded to
|
| - * our custom JSON format.
|
| - */
|
| - String toStringFormat() {
|
| - return json.stringify(toMaps());
|
| - }
|
| -
|
| - /**
|
| - * Returns the full serialized structure as nested maps. The top-level
|
| - * has 3 fields, "rules" which may hold a definition of the rules used,
|
| - * "data" which holds the serialized data, and "roots", which holds
|
| - * [Reference] objects indicating the root objects. Note that roots are
|
| - * necessary because the data is organized in the same way as the object
|
| - * structure, it's a list of lists holding self-contained maps which only
|
| - * refer to other parts via [Reference] objects.
|
| - * This effectively defines a custom JSON serialization format, although
|
| - * the details of the format vary depending which rules were used.
|
| - */
|
| - Map toMaps() {
|
| - var result = new Map();
|
| - var savedRules;
|
| - if (selfDescribing) {
|
| - var meta = serialization._ruleSerialization();
|
| - var writer = new Writer(meta);
|
| - writer.selfDescribing = false;
|
| - savedRules = writer.write(serialization._rules);
|
| - }
|
| - result["rules"] = savedRules;
|
| - result["data"] = states;
|
| - result["roots"] = _rootReferences(trace.roots);
|
| - return result;
|
| - }
|
| -
|
| - /**
|
| * Return a list of [Reference] objects pointing to our roots. This will be
|
| * stored in the output under "roots" in the default format.
|
| */
|
| - _rootReferences(roots) =>
|
| - roots.mappedBy(_referenceFor).toList();
|
| + _rootReferences() => trace.roots.mappedBy(_referenceFor).toList();
|
|
|
| /**
|
| * Given an object, return a reference for it if one exists. If there's
|
| @@ -324,14 +256,17 @@ class Reader {
|
| */
|
| List<List> objects;
|
|
|
| + Format format = new SimpleMapFormat();
|
| +
|
| /**
|
| * Creates a new [Reader] that uses the rules from its parent
|
| * [Serialization]. Serializations do not keep any state related to
|
| * a particular read or write operation, so the same one can be used
|
| * for multiple different Writers/Readers.
|
| */
|
| - Reader(this.serialization) {
|
| + Reader(this.serialization, [Format newFormat]) {
|
| selfDescribing = serialization.selfDescribing;
|
| + if (newFormat != null) format = newFormat;
|
| }
|
|
|
| /**
|
| @@ -374,28 +309,24 @@ class Reader {
|
|
|
| /**
|
| * This is the primary method for a [Reader]. It takes the input data,
|
| - * currently hard-coded to expect our custom JSON format, and returns
|
| - * the root object.
|
| + * decodes it according to [format] and returns the root object.
|
| */
|
| - read(String input, [Map externals = const {}]) {
|
| + read(rawInput, [Map externals = const {}]) {
|
| namedObjects = externals;
|
| - var topLevel = json.parse(input);
|
| - var ruleString = topLevel["rules"];
|
| - readRules(ruleString, externals);
|
| - data = topLevel["data"];
|
| + var input = format.read(rawInput, this);
|
| + data = input["data"];
|
| rules.forEach(inflateForRule);
|
| - var roots = topLevel["roots"];
|
| - return inflateReference(roots.first);
|
| + return inflateReference(input["roots"].first);
|
| }
|
|
|
| /**
|
| * If the data we are reading from has rules written to it, read them back
|
| * and set them as the rules we will use.
|
| */
|
| - void readRules(String newRules, Map externals) {
|
| + void readRules(String newRules) {
|
| // TODO(alanknight): Replacing the serialization is kind of confusing.
|
| List rulesWeRead = (newRules == null) ?
|
| - null : serialization._ruleSerialization().read(newRules, externals);
|
| + null : serialization.ruleSerialization().read(newRules, namedObjects);
|
| if (rulesWeRead != null && !rulesWeRead.isEmpty) {
|
| serialization = new Serialization.blank();
|
| rulesWeRead.forEach(serialization.addRule);
|
| @@ -403,41 +334,6 @@ class Reader {
|
| }
|
|
|
| /**
|
| - * This is a hard-coded read method for a vaguely flat format. It's just a
|
| - * proof of concept of handling more flat formats right now, and needs a lot
|
| - * of fixing and generalization.
|
| - */
|
| - readFlat(List input, [Map externals = const {}]) {
|
| - // TODO(alanknight): Way too much code duplication with read. Numerous
|
| - // code smells.
|
| - namedObjects = externals;
|
| - var topLevel = input;
|
| - var ruleString = topLevel[0];
|
| - readRules(ruleString, externals);
|
| - var flatData = topLevel[1];
|
| - var stream = flatData.iterator;
|
| - var tempData = new List(rules.length);
|
| - for (var eachRule in rules) {
|
| - tempData[eachRule.number] = eachRule.pullStateFrom(stream);
|
| - }
|
| - data = tempData;
|
| - for (var eachRule in rules) {
|
| - inflateForRule(eachRule);
|
| - }
|
| - var rootsAsInts = topLevel[2];
|
| - var rootStream = rootsAsInts.iterator;
|
| - var roots = new List();
|
| - while (rootStream.moveNext()) {
|
| - var first = rootStream.current;
|
| - rootStream.moveNext();
|
| - var second = rootStream.current;
|
| - roots.add(new Reference(this, first, second));
|
| - }
|
| - var x = inflateReference(roots[0]);
|
| - return inflateReference(roots.first);
|
| - }
|
| -
|
| - /**
|
| * Inflate all of the objects for [rule]. Does the essential state for all
|
| * objects first, then the non-essential state. This avoids cycles in
|
| * non-essential state, because all the objects will have already been
|
| @@ -511,6 +407,20 @@ class Reader {
|
| serialization._rules[reference.ruleNumber];
|
|
|
| /**
|
| + * Return the primitive rule we are using. This is an ugly mechanism to
|
| + * support the extra information to reconstruct objects in the
|
| + * [SimpleJsonFormat].
|
| + */
|
| + SerializationRule _primitiveRule() {
|
| + for (var each in rules) {
|
| + if (each.runtimeType == PrimitiveRule) {
|
| + return each;
|
| + }
|
| + }
|
| + throw new SerializationException("No PrimitiveRule found");
|
| + }
|
| +
|
| + /**
|
| * Given a possible reference [anObject], call either [ifReference] or
|
| * [ifNotReference], depending if it's a reference or not. This is the
|
| * primary place that knows about the serialized representation of a
|
| @@ -634,6 +544,8 @@ class Reference {
|
| list.add(ruleNumber);
|
| list.add(objectNumber);
|
| }
|
| +
|
| + toString() => "Reference $ruleNumber, $objectNumber";
|
| }
|
|
|
| /**
|
|
|