| Index: pkg/serialization/lib/serialization.dart
|
| diff --git a/pkg/serialization/lib/serialization.dart b/pkg/serialization/lib/serialization.dart
|
| deleted file mode 100644
|
| index b2d3dce6f835815efabceca0fd35197c94aecb6d..0000000000000000000000000000000000000000
|
| --- a/pkg/serialization/lib/serialization.dart
|
| +++ /dev/null
|
| @@ -1,495 +0,0 @@
|
| -// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
| -// for details. All rights reserved. Use of this source code is governed by a
|
| -// BSD-style license that can be found in the LICENSE file.
|
| -
|
| -/**
|
| - * A general-purpose serialization facility for Dart objects.
|
| - *
|
| - * A [Serialization] is defined in terms of [SerializationRule]s and supports
|
| - * reading and writing to different formats.
|
| - *
|
| - * For information on installing and importing this library, see the
|
| - * [serialization package on pub.dartlang.org]
|
| - * (http://pub.dartlang.org/packages/serialization).
|
| - *
|
| - * ## Setup
|
| - *
|
| - * A simple example of usage is
|
| - *
|
| - * var address = new Address();
|
| - * address.street = 'N 34th';
|
| - * address.city = 'Seattle';
|
| - * var serialization = new Serialization()
|
| - * ..addRuleFor(Address);
|
| - * Map output = serialization.write(address);
|
| - *
|
| - * This creates a new serialization and adds a rule for address objects.
|
| - * Then we ask the [Serialization] to write the address
|
| - * and we get back a Map which is a [json]able representation of the state of
|
| - * the address and related objects. Note that while the output in this case
|
| - * is a [Map], the type will vary depending on which output format we've told
|
| - * the [Serialization] to use.
|
| - *
|
| - * The version above used reflection to automatically identify the public
|
| - * fields of the address object. We can also specify those fields explicitly.
|
| - *
|
| - * var serialization = new Serialization()
|
| - * ..addRuleFor(Address,
|
| - * constructor: "create",
|
| - * constructorFields: ["number", "street"],
|
| - * fields: ["city"]);
|
| - *
|
| - * This rule still uses reflection to access the fields, but does not try to
|
| - * identify which fields to use, but instead uses only the "number" and "street"
|
| - * fields that we specified. We may also want to tell it to identify the
|
| - * fields, but to specifically omit certain fields that we don't want
|
| - * serialized.
|
| - *
|
| - * var serialization = new Serialization()
|
| - * ..addRuleFor(Address,
|
| - * constructor: "",
|
| - * excludeFields: ["other", "stuff"]);
|
| - *
|
| - * ## Writing rules
|
| - *
|
| - * We can also use a completely non-reflective rule to serialize and
|
| - * de-serialize objects. This can be more work, but it does work in
|
| - * dart2js, where mirrors are not yet implemented. We can specify this in two
|
| - * ways. First, we can write our own SerializationRule class that has methods
|
| - * for our Address class.
|
| - *
|
| - * class AddressRule extends CustomRule {
|
| - * bool appliesTo(instance, Writer w) => instance.runtimeType == Address;
|
| - * getState(instance) => [instance.street, instance.city];
|
| - * create(state) => new Address();
|
| - * setState(Address a, List state) {
|
| - * a.street = state[0];
|
| - * a.city = state[1];
|
| - * }
|
| - * }
|
| - *
|
| - * The class needs four different methods. The [CustomRule.appliesTo]
|
| - * method tells us if
|
| - * the rule should be used to write an object. In this example we use a test
|
| - * based on runtimeType. We could also use an "is Address" test, but if Address
|
| - * has subclasses that would find those as well, and we want a separate rule
|
| - * for each. The [CustomRule.getState] method should
|
| - * return all the state of the object that we want to recreate,
|
| - * and should be either a Map or a List. If you want to write to human-readable
|
| - * formats where it's useful to be able to look at the data as a map from
|
| - * field names to values, then it's better to return it as a map. Otherwise it's
|
| - * more efficient to return it as a list. You just need to be sure that the
|
| - * [CustomRule.create] and [CustomRule.setState] methods interpret the data the
|
| - * same way as [CustomRule.getState] does.
|
| - *
|
| - * The [CustomRule.create] method will create the new object and return it. While it's
|
| - * possible to create the object and set all its state in this one method, that
|
| - * increases the likelihood of problems with cycles. So it's better to use the
|
| - * minimum necessary information in [CustomRule.create] and do more of the work
|
| - * in [CustomRule.setState].
|
| - *
|
| - * The other way to do this is not creating a subclass, but by using a
|
| - * [ClosureRule] and giving it functions for how to create
|
| - * the address.
|
| - *
|
| - * addressToMap(a) => {"number" : a.number, "street" : a.street,
|
| - * "city" : a.city};
|
| - * createAddress(Map m) => new Address.create(m["number"], m["street"]);
|
| - * fillInAddress(Address a, Map m) => a.city = m["city"];
|
| - * var serialization = new Serialization()
|
| - * ..addRule(
|
| - * new ClosureRule(anAddress.runtimeType,
|
| - * addressToMap, createAddress, fillInAddress);
|
| - *
|
| - * In this case we have created standalone functions rather than
|
| - * methods in a subclass and we pass them to the constructor of
|
| - * [ClosureRule]. In this case we've also had them use maps rather than
|
| - * lists for the state, but either would work as long as the rule is
|
| - * consistent with the representation it uses. We pass it the runtimeType
|
| - * of the object, and functions equivalent to the methods on [CustomRule]
|
| - *
|
| - * ## Constant values
|
| - *
|
| - * There are cases where the constructor needs values that we can't easily get
|
| - * from the serialized object. For example, we may just want to pass null, or a
|
| - * constant value. To support this, we can specify as constructor fields
|
| - * values that aren't field names. If any value isn't a String, or is a string
|
| - * that doesn't correspond to a field name, it will be
|
| - * treated as a constant and passed unaltered to the constructor.
|
| - *
|
| - * In some cases a non-constructor field should not be set using field
|
| - * access or a setter, but should be done by calling a method. For example, it
|
| - * may not be possible to set a List field "foo", and you need to call an
|
| - * addFoo() method for each entry in the list. In these cases, if you are using
|
| - * a BasicRule for the object you can call the setFieldWith() method.
|
| - *
|
| - * s..addRuleFor(FooHolder).setFieldWith("foo",
|
| - * (parent, value) => for (var each in value) parent.addFoo(value));
|
| - *
|
| - * ## Writing
|
| - *
|
| - * To write objects, we use the write() method.
|
| - *
|
| - * var output = serialization.write(someObject);
|
| - *
|
| - * By default this uses a representation in which objects are represented as
|
| - * maps keyed by field name, but in which references between objects have been
|
| - * converted into Reference objects. This is then typically encoded as
|
| - * a [json] string, but can also be used in other ways, e.g. sent to another
|
| - * isolate.
|
| - *
|
| - * We can write objects in different formats by passing a [Format] object to
|
| - * the [Serialization.write] method or by getting a [Writer] object.
|
| - * The available formats
|
| - * include the default, a simple "flat" format that doesn't include field names,
|
| - * and a simple JSON format that produces output more suitable for talking to
|
| - * services that expect JSON in a predefined format. Examples of these are
|
| - *
|
| - * Map output = serialization.write(address, new SimpleMapFormat());
|
| - * List output = serialization.write(address, new SimpleFlatFormat());
|
| - * var output = serialization.write(address, new SimpleJsonFormat());
|
| - * Or, using a [Writer] explicitly
|
| - * var writer = serialization.newWriter(new SimpleFlatFormat());
|
| - * List output = writer.write(address);
|
| - *
|
| - * These representations are not yet considered stable.
|
| - *
|
| - * ## Reading
|
| - *
|
| - * To read objects, the corresponding [Serialization.read] method can be used.
|
| - *
|
| - * Address input = serialization.read(input);
|
| - *
|
| - * When reading, the serialization instance doing the reading must be configured
|
| - * with compatible rules to the one doing the writing. It's possible for the
|
| - * rules to be different, but they need to be able to read the same
|
| - * representation. For most practical purposes right now they should be the
|
| - * same. The simplest way to achieve this is by having the serialization
|
| - * variable [Serialization.selfDescribing] be true. In that case the rules
|
| - * themselves are also
|
| - * stored along with the serialized data, and can be read back on the receiving
|
| - * end. Note that this may not work for all rules or all formats. The
|
| - * [Serialization.selfDescribing] variable is true by default, but the
|
| - * [SimpleJsonFormat] does not support it, since the point is to provide a
|
| - * representation in a form
|
| - * other services might expect. Using CustomRule or ClosureRule also does not
|
| - * yet work with the [Serialization.selfDescribing] variable.
|
| - *
|
| - * ## Named objects
|
| - *
|
| - * When reading, some object references should not be serialized, but should be
|
| - * connected up to other instances on the receiving side. A notable example of
|
| - * this is when serialization rules have been stored. Instances of BasicRule
|
| - * take a [ClassMirror] in their constructor, and we cannot serialize those. So
|
| - * when we read the rules, we must provide a Map<String, Object> which maps from
|
| - * the simple name of classes we are interested in to a [ClassMirror]. This can
|
| - * be provided either in the [Serialization.namedObjects],
|
| - * or as an additional parameter to the reading and writing methods on the
|
| - * [Reader] or [Writer] respectively.
|
| - *
|
| - * new Serialization()
|
| - * ..addRuleFor(Person, constructorFields: ["name"])
|
| - * ..namedObjects['Person'] = reflect(new Person()).type;
|
| - */
|
| -library serialization;
|
| -
|
| -import 'src/mirrors_helpers.dart';
|
| -import 'src/serialization_helpers.dart';
|
| -import 'dart:collection';
|
| -
|
| -part 'src/reader_writer.dart';
|
| -part 'src/serialization_rule.dart';
|
| -part 'src/basic_rule.dart';
|
| -part 'src/format.dart';
|
| -
|
| -/**
|
| - * This class defines a particular serialization scheme, in terms of
|
| - * [SerializationRule] instances, and supports reading and writing them.
|
| - * See library comment for examples of usage.
|
| - */
|
| -class Serialization {
|
| - final List<SerializationRule> _rules;
|
| -
|
| - /**
|
| - * The serialization is controlled by the list of Serialization rules. These
|
| - * are most commonly added via [addRuleFor].
|
| - */
|
| - final UnmodifiableListView<SerializationRule> rules;
|
| -
|
| - /**
|
| - * When reading, we may need to resolve references to existing objects in
|
| - * the system. The right action may not be to create a new instance of
|
| - * something, but rather to find an existing instance and connect to it.
|
| - * For example, if we have are serializing an Email message and it has a
|
| - * link to the owning account, it may not be appropriate to try and serialize
|
| - * the account. Instead we should just connect the de-serialized message
|
| - * object to the account object that already exists there.
|
| - */
|
| - Map<String, dynamic> namedObjects = {};
|
| -
|
| - /**
|
| - * When we write out data using this serialization, should we also write
|
| - * out a description of the rules. This is on by default unless using
|
| - * CustomRule subclasses, in which case it requires additional setup and
|
| - * is off by default.
|
| - */
|
| - bool _selfDescribing;
|
| -
|
| - /**
|
| - * When we write out data using this serialization, should we also write
|
| - * out a description of the rules. This is on by default unless using
|
| - * CustomRule subclasses, in which case it requires additional setup and
|
| - * is off by default.
|
| - */
|
| - bool get selfDescribing {
|
| - // TODO(alanknight): Should this be moved to the format?
|
| - // TODO(alanknight): Allow self-describing in the presence of CustomRule.
|
| - // TODO(alanknight): Don't do duplicate work creating a writer and
|
| - // serialization here and then re-creating when we actually serialize.
|
| - if (_selfDescribing != null) return _selfDescribing;
|
| - var meta = ruleSerialization();
|
| - var w = meta.newWriter();
|
| - _selfDescribing = !rules.any((rule) =>
|
| - meta.rulesFor(rule, w, create: false).isEmpty);
|
| - return _selfDescribing;
|
| - }
|
| -
|
| - /**
|
| - * When we write out data using this serialization, should we also write
|
| - * out a description of the rules. This is on by default unless using
|
| - * CustomRule subclasses, in which case it requires additional setup and
|
| - * is off by default.
|
| - */
|
| - void set selfDescribing(bool value) {
|
| - _selfDescribing = value;
|
| - }
|
| -
|
| - /**
|
| - * Creates a new serialization with a default set of rules for primitives
|
| - * and lists.
|
| - */
|
| - factory Serialization() =>
|
| - new Serialization.blank()
|
| - ..addDefaultRules();
|
| -
|
| - /**
|
| - * Creates a new serialization with no default rules at all. The most common
|
| - * use for this is if we are reading self-describing serialized data and
|
| - * will populate the rules from that data.
|
| - */
|
| - factory Serialization.blank()
|
| - => new Serialization._(new List<SerializationRule>());
|
| -
|
| - Serialization._(List<SerializationRule> rules) :
|
| - this._rules = rules,
|
| - this.rules = new UnmodifiableListView(rules);
|
| -
|
| - /**
|
| - * Create a [BasicRule] rule for [instanceOrType]. Normally this will be
|
| - * a type, but for backward compatibilty we also allow you to pass an
|
| - * instance (except an instance of Type), and the rule will be created
|
| - * for its runtimeType. Optionally
|
| - * allows specifying a [constructor] name, the list of [constructorFields],
|
| - * and the list of [fields] not used in the constructor. Returns the new
|
| - * rule. Note that [BasicRule] uses reflection, and so will not work with the
|
| - * current state of dartj2s. If you need to run there, consider using
|
| - * [CustomRule] instead.
|
| - *
|
| - * If the optional parameters aren't specified, the default constructor will
|
| - * be used, and the list of fields will be computed. Alternatively, you can
|
| - * omit [fields] and provide [excludeFields], which will then compute the
|
| - * list of fields specifically excluding those listed.
|
| - *
|
| - * The fields can be actual public fields, but can also be getter/setter
|
| - * pairs or getters whose value is provided in the constructor. For the
|
| - * [constructorFields] they can also be arbitrary objects. Anything that is
|
| - * not a String will be treated as a constant value to be used in any
|
| - * construction of these objects.
|
| - *
|
| - * If the list of fields is computed, fields from the superclass will be
|
| - * included. However, each subclass needs its own rule, since the constructors
|
| - * are not inherited, and so may need to be specified separately for each
|
| - * subclass.
|
| - */
|
| - BasicRule addRuleFor(
|
| - instanceOrType,
|
| - {String constructor,
|
| - List constructorFields,
|
| - List<String> fields,
|
| - List<String> excludeFields}) {
|
| -
|
| - var rule = new BasicRule(
|
| - turnInstanceIntoSomethingWeCanUse(
|
| - instanceOrType),
|
| - constructor, constructorFields, fields, excludeFields);
|
| - addRule(rule);
|
| - return rule;
|
| - }
|
| -
|
| - /** Set up the default rules, for lists and primitives. */
|
| - void addDefaultRules() {
|
| - addRule(new PrimitiveRule());
|
| - addRule(new ListRule());
|
| - // Both these rules apply to lists, so unless otherwise indicated,
|
| - // it will always find the first one.
|
| - addRule(new ListRuleEssential());
|
| - addRule(new MapRule());
|
| - addRule(new SymbolRule());
|
| - addRule(new DateTimeRule());
|
| - }
|
| -
|
| - /**
|
| - * Add a new SerializationRule [rule]. The addRuleFor method will probably
|
| - * handle most simple cases, but for adding an arbitrary rule, including
|
| - * a SerializationRule subclass which you have created, you can use this
|
| - * method.
|
| - */
|
| - void addRule(SerializationRule rule) {
|
| - rule.number = _rules.length;
|
| - _rules.add(rule);
|
| - }
|
| -
|
| - /**
|
| - * This writes out an object graph rooted at [object] and returns the result.
|
| - * The [format] parameter determines the form of the result. The default
|
| - * format is [SimpleMapFormat] and returns a Map.
|
| - */
|
| - write(Object object, {Format format}) {
|
| - return newWriter(format).write(object);
|
| - }
|
| -
|
| - /**
|
| - * Return a new [Writer] object for this serialization. This is useful if you
|
| - * want to do something more complex with the writer than just returning
|
| - * the final result.
|
| - */
|
| - Writer newWriter([Format format]) => new Writer(this, format);
|
| -
|
| - /**
|
| - * Read the serialized data from [input] and return the root object
|
| - * from the result. The [input] can be of any type that the [Format]
|
| - * reads/writes, but normally will be a [List], [Map], or a simple type.
|
| - * The [format] parameter determines the form of the result. The default
|
| - * format is [SimpleMapFormat] and expects a Map as input.
|
| - * If there are objects that need to be resolved
|
| - * in the current context, they should be provided in [externals] as a
|
| - * Map from names to values. In particular, in the current implementation
|
| - * any class mirrors needed should be provided in [externals] using the
|
| - * class name as a key. In addition to the [externals] map provided here,
|
| - * values will be looked up in the [namedObjects] map.
|
| - */
|
| - read(input, {Format format, Map externals: const {}}) {
|
| - return newReader(format).read(input, externals);
|
| - }
|
| -
|
| - /**
|
| - * Return a new [Reader] object for this serialization. This is useful if
|
| - * you want to do something more complex with the reader than just returning
|
| - * the final result.
|
| - */
|
| - Reader newReader([Format format]) => new Reader(this, format);
|
| -
|
| - /**
|
| - * Return the list of SerializationRule that apply to [object]. For
|
| - * internal use, but public because it's used in testing.
|
| - */
|
| - Iterable<SerializationRule> rulesFor(object, Writer w, {create : true}) {
|
| - // This has a couple of edge cases.
|
| - // 1) The owning object may have indicated we should use a different
|
| - // rule than the default.
|
| - // 2) We may not have a rule, in which case we lazily create a BasicRule.
|
| - // 3) Rules are allowed to say mustBePrimary, meaning that they can be used
|
| - // iff no other rule was chosen first.
|
| - // TODO(alanknight): Can the mustBePrimary mechanism be removed or changed.
|
| - // It adds an order dependency to the rules, and is messy. Reconsider in the
|
| - // light of a more general mechanism for multiple rules per object.
|
| - // TODO(alanknight): Finding which rules apply seems likely to be a
|
| - // bottleneck, particularly with the current reflective implementation.
|
| - // Consider how to improve it. e.g. cache the list of rules by class. But
|
| - // be careful of issues like rules which have arbitrary predicates. Or
|
| - // consider having the arbitrary predicates be secondary to an initial
|
| - // class-based lookup mechanism.
|
| - var target, candidateRules;
|
| - if (object is DesignatedRuleForObject) {
|
| - target = object.target;
|
| - candidateRules = object.possibleRules(rules);
|
| - } else {
|
| - target = object;
|
| - candidateRules = rules;
|
| - }
|
| - Iterable applicable = candidateRules.where(
|
| - (each) => each.appliesTo(target, w));
|
| -
|
| - if (applicable.isEmpty) {
|
| - return create ? [addRuleFor(target)] : applicable;
|
| - }
|
| -
|
| - if (applicable.length == 1) return applicable;
|
| - var first = applicable.first;
|
| - var finalRules = applicable.where(
|
| - (x) => !x.mustBePrimary || (x == first));
|
| -
|
| - if (finalRules.isEmpty) throw new SerializationException(
|
| - 'No valid rule found for object $object');
|
| - return finalRules;
|
| - }
|
| -
|
| - /**
|
| - * Create a Serialization for serializing SerializationRules. This is used
|
| - * to save the rules in a self-describing format along with the data.
|
| - * If there are new rule classes created, they will need to be described
|
| - * here.
|
| - */
|
| - Serialization ruleSerialization() {
|
| - // TODO(alanknight): There's an extensibility issue here with new rules.
|
| - // TODO(alanknight): How to handle rules with closures? They have to
|
| - // exist on the other side, but we might be able to hook them up by name,
|
| - // or we might just be able to validate that they're correctly set up
|
| - // on the other side.
|
| -
|
| - var meta = new Serialization()
|
| - ..selfDescribing = false
|
| - ..addRuleFor(ListRule)
|
| - ..addRuleFor(MapRule)
|
| - ..addRuleFor(PrimitiveRule)
|
| - ..addRuleFor(ListRuleEssential)
|
| - ..addRuleFor(BasicRule,
|
| - constructorFields: ['type',
|
| - 'constructorName',
|
| - 'constructorFields', 'regularFields', []],
|
| - fields: [])
|
| - ..addRule(new NamedObjectRule())
|
| - ..addRule(new MirrorRule())
|
| - ..addRuleFor(MirrorRule)
|
| - ..addRuleFor(SymbolRule)
|
| - ..addRuleFor(DateTimeRule);
|
| - meta.namedObjects = namedObjects;
|
| - return meta;
|
| - }
|
| -
|
| - /** Return true if our [namedObjects] collection has an entry for [object].*/
|
| - bool _hasNameFor(object) {
|
| - var sentinel = const _Sentinel();
|
| - return _nameFor(object, () => sentinel) != sentinel;
|
| - }
|
| -
|
| - /**
|
| - * Return the name we have for [object] in our [namedObjects] collection or
|
| - * the result of evaluating [ifAbsent] if there is no entry.
|
| - */
|
| - _nameFor(object, [ifAbsent]) {
|
| - for (var key in namedObjects.keys) {
|
| - if (identical(namedObjects[key], object)) return key;
|
| - }
|
| - return ifAbsent == null ? null : ifAbsent();
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * An exception class for errors during serialization.
|
| - */
|
| -class SerializationException implements Exception {
|
| - final String message;
|
| - const SerializationException(this.message);
|
| - String toString() => "SerializationException($message)";
|
| -}
|
|
|