[gxml/jgs: 1/7] serialization:jgs: restore original json-glib style serialization files in own directory
- From: Richard Hans Schwarting <rschwart src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gxml/jgs: 1/7] serialization:jgs: restore original json-glib style serialization files in own directory
- Date: Tue, 15 Apr 2014 02:20:45 +0000 (UTC)
commit 4c2cdad46e0560eba193a5fbabdf6c4c8e24f8eb
Author: Richard Schwarting <aquarichy gmail com>
Date: Mon Apr 14 21:27:57 2014 -0400
serialization:jgs: restore original json-glib style serialization files in own directory
gxml/jgs/Serializable.vala | 363 ++++++++++++++++++++++++++
gxml/jgs/Serialization.vala | 601 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 964 insertions(+), 0 deletions(-)
---
diff --git a/gxml/jgs/Serializable.vala b/gxml/jgs/Serializable.vala
new file mode 100644
index 0000000..2e42961
--- /dev/null
+++ b/gxml/jgs/Serializable.vala
@@ -0,0 +1,363 @@
+/* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Serializable.vala
+ *
+ * Copyright (C) 2011-2013 Richard Schwarting <aquarichy gmail com>
+ * Copyright (C) 2013 Daniel Espinosa <esodan gmail com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Richard Schwarting <aquarichy gmail com>
+ * Daniel Espinosa <esodan gmail com>
+ */
+
+
+/*
+ Version 3: json-glib version
+
+ PLAN:
+ * add support for GObject Introspection to allow us to serialise non-property members
+
+ json-glib
+ * has functions to convert XML structures into Objects and vice versa
+ * can convert simple objects automatically
+ * richer objects need to implement interface
+
+ json_serializable_real_serialize -> json_serialize_pspec
+ * how do these get used with GInterfaces? are these default methods like with superclasses?
+ * TODO: I don't think vala does multiple inheritance, so do we want GXml.Serializable to be an interface
or a superclass?
+
+ json_serializable_default_{de,}serialize_property -> json_serializable_real_{de,}serialize
+
+ json_serializable_{de,}serialize_property -> iface->{de,}serialize_property
+ these all get init'd to -> json_serializable_real_{de,}serialize_property
+ these all call -> json_{de,}serialize_pspec
+
+ json_serializable_{find,list,get,set}_propert{y,ies} -> iface->{find,list,get,set}_propert{y,ies}
+ these all get init'd to -> json_serializable_real_{find,list,get,set}_propert{y,ies}
+ these all call -> g_object_{class,}_{find,list,get,set}_propert{y,ies}
+ */
+
+using GXml;
+
+namespace GXml.Jgs {
+ /**
+ * Interface allowing implementors direct control over serialisation of properties and other data
+ *
+ * A class that implements this interface will still be passed
+ * to { link GXml.Serialization.serialize_object} for
+ * serialization. That function will check whether the object
+ * implements { link GXml.Serializable} and will then prefer
+ * overridden methods instead of standard ones. Most of the
+ * methods for this interface can indicate (via return value)
+ * that, for a given property, the standard serialization
+ * approach should be used instead. Indeed, not all methods
+ * need to be implemented, but some accompany one another and
+ * should be implemented carefully, corresponding to one
+ * another. You can also create virtual properties from
+ * non-public property fields to enable their serialization.
+ *
+ * For an example, look in tests/XmlSerializableTest
+ */
+ public interface Serializable : GLib.Object, GXml.Serializable {
+ /**
+ * Handles serializing potential tasks beyond
+ * serializing individual properties.
+ *
+ * { link doc} is the { link GXml.Document} that will
+ * ultimately contain the serialized object. You can
+ * use it to create the { link GXml.Node}s you want to
+ * add from here to the serialized object, like
+ * { link GXml.Element}s, { link GXml.DocumentFragment}s,
+ * and { link GXml.Text}s. Return your completed XML
+ * structure as a { link GXml.Node} and it will be
+ * added to an <Object> element in the serialized XML.
+ *
+ * Example:
+ *
+ * Say we have a { link GLib.Object}, Cookie, which
+ * looks like this in Vala,
+ *
+ * {{{
+ * class Cookie {
+ * string flavour {}
+ * int mass {}
+ * }
+ * }}}
+ *
+ * The default serialized XML might look like this
+ *
+ * {{{
+ * <Object otype="Cookie" oid="0xC00C1E5">
+ * <Property ptype="gchar*" pname="flavour">Chocolate chip</Property>
+ * <Property ptype="int" pname="mass">28</Property>
+ * </Object>
+ * }}}
+ *
+ * If we want additional information not connected to
+ * any of the properties, we could extend the Cookie
+ * class like this:
+ *
+ * {{{
+ * class Cookie : Serializable {
+ * string flavour {}
+ * int mass {}
+ * public override Node? serialize (Document doc)
+ * throws SerializationError {
+ * return doc.create_comment ("<!-- baked on Nov 16 2013 by Wallace Wells -->");
+ * }
+ * }}}
+ *
+ * This would result in the following serialized XML:
+ *
+ * {{{
+ * <Object otype="Cookie" oid="0xC00C1E5">
+ * <!-- baked on Nov 16 2013 by Wallace Wells -->
+ * <Property ptype="gchar*" pname="flavour">Chocolate chip</Property>
+ * <Property ptype="int" pname="mass">28</Property>
+ * </Object>
+ * }}}
+ *
+ * If you want to completely handle serialization of
+ * your object yourself in { link Serializable.serialize},
+ * you can prevent automatic serialization of properties
+ * by overriding { link Serializable.serialize_property}
+ * and simply returning true.
+ *
+ * @param doc The { link GXml.Document} that contains serialized XML, used to create new {
link GXml.Node}s
+ *
+ * @return A { link GXml.Node} representing serialized content from the implementing object
+ */
+ public virtual GXml.Node?
+ serialize (GXml.Document doc) {
+ return null;
+ }
+
+ /**
+ * Handles serializing individual properties.
+ *
+ * Interface method to handle serialization of an
+ * individual property. The implementing class
+ * receives a description of it, and should create a
+ * { link GXml.Node} that encapsulates the property.
+ * { link GXml.Serialization} will embed the { link GXml.Node} into
+ * a "Property" { link GXml.Element}, so the { link GXml.Node}
+ * returned can often be something as simple as
+ * { link GXml.Text}.
+ *
+ * To let { link GXml.Serialization} attempt to automatically
+ * serialize the property itself, do not implement
+ * this method. If the method returns %NULL,
+ * { link GXml.Serialization} will attempt handle it itself.
+ *
+ * @param property_name String name of a property to serialize
+ * @param spec The { link GLib.ParamSpec} describing the property
+ * @param doc The { link GXml.Document} the returned { link GXml.Node} should belong to
+ *
+ * @return a new { link GXml.Node}, or %NULL
+ */
+ public virtual GXml.Node?
+ serialize_property (string property_name,
+ GLib.ParamSpec spec,
+ GXml.Document doc) {
+ return null;
+ }
+
+ /**
+ * Handle deserialization of an object beyond its
+ * properties.
+ *
+ * This can cover deserialization tasks outside of
+ * just properties, like initialising variables
+ * normally handled by a constructor. (Note that when
+ * deserializing, an object's constructor is not
+ * called.)
+ *
+ * @param serialized_node The XML representation of this object
+ */
+ public virtual void
+ deserialize (GXml.Node serialized_node) {
+ return;
+ }
+
+ /* TODO: consider making the visibility of all of
+ * these interface virtual methods to protected or
+ * internal. In theory, only Serialization should
+ * need to actually see them.
+ */
+
+ /**
+ * Handles deserializing individual properties.
+ *
+ * Interface method to handle deserialization of an
+ * individual property. The implementing class
+ * receives a description of the property and the
+ * { link GXml.Node} that contains the content. The
+ * implementing { link GXml.Serializable} object can extract
+ * the data from the { link GXml.Node} and store it in its
+ * property itself. Note that the { link GXml.Node} may be
+ * as simple as a { link GXml.Text} that stores the data as a
+ * string.
+ *
+ * If the implementation has handled deserialization,
+ * return true. Return false if you want
+ * { link GXml.Serialization} to try to automatically
+ * deserialize it. If { link GXml.Serialization} tries to
+ * handle it, it will want either { link GXml.Serializable}'s
+ * set_property (or at least { link GLib.Object.set_property})
+ * to know about the property.
+ *
+ * @param property_name the name of the property as a string
+ * @param spec the { link GLib.ParamSpec} describing the property
+ * @param property_node the { link GXml.Node} encapsulating data to deserialize
+ * @return `true` if the property was handled, `false` if { link GXml.Serialization} should
handle it
+ */
+ /*
+ * @todo: consider not giving property_name, but
+ * letting them get name from spec
+ * @todo: consider returning { link GLib.Value} as out param
+ */
+ public virtual bool
+ deserialize_property (/* string property_name,
+ GLib.ParamSpec spec,
+ */ GXml.Node property_node) {
+ return false; // default deserialize_property gets used
+ }
+
+
+ /* Correspond to: g_object_class_{find_property,list_properties} */
+
+ /*
+ * Handles finding the { link GLib.ParamSpec} for a given property.
+ *
+ * @param property_name the name of a property to obtain a { link GLib.ParamSpec} for
+ * @return a { link GLib.ParamSpec} describing the named property
+ *
+ * { link GXml.Serialization} uses { link
+ * GLib.ObjectClass.find_property} (as well as { link
+ * GLib.ObjectClass.list_properties}, { link
+ * GLib.Object.get_property}, and { link
+ * GLib.Object.set_property}) to manage serialization
+ * of properties. { link GXml.Serializable} gives the
+ * implementing class an opportunity to override
+ * { link GLib.ObjectClass.find_property} to control
+ * what properties exist for { link GXml.Serialization}'s
+ * purposes.
+ *
+ * For instance, if an object has private data fields
+ * that are not installed public properties, but that
+ * should be serialized, find_property can be defined
+ * to return a { link GLib.ParamSpec} for non-installed
+ * properties. Other { link GXml.Serializable} functions
+ * should be consistent with it.
+ *
+ * An implementing class might wish to maintain such
+ * { link GLib.ParamSpec} s separately, rather than creating new
+ * ones for each call.
+ */
+ public virtual unowned GLib.ParamSpec? find_property (string property_name) {
+ return this.get_class ().find_property (property_name); // default
+ }
+
+ /*
+ * List the known properties for an object's class
+ *
+ * @return an array of { link GLib.ParamSpec} of
+ * "properties" for the object.
+ *
+ * { link GXml.Serialization} uses
+ * { link GLib.ObjectClass.list_properties} (as well as
+ * { link GLib.ObjectClass.find_property},
+ * { link GLib.Object.get_property}, and { link GLib.Object.set_property})
+ * to manage serialization of an object's properties.
+ * { link GXml.Serializable} gives an implementing class an
+ * opportunity to override
+ * { link GLib.ObjectClass.list_properties} to control which
+ * properties exist for { link GXml.Serialization}'s purposes.
+ *
+ * For instance, if an object has private data fields
+ * that are not installed public properties, but that
+ * should be serialized, list_properties can be
+ * defined to return a list of { link GLib.ParamSpec} s covering
+ * all the "properties" to serialize. Other
+ * { link GXml.Serializable} functions should be consistent
+ * with it.
+ *
+ * An implementing class might wish to maintain such
+ * { link GLib.ParamSpec} s separately, rather than creating new
+ * ones for each call.
+ */
+ public virtual unowned GLib.ParamSpec[] list_properties () {
+ return this.get_class ().list_properties ();
+ }
+
+ /*
+ * Get a string version of the specified property
+ *
+ * @param spec The property we're retrieving as a string
+ *
+ * { link GXml.Serialization} uses { link GLib.Object.get_property} (as
+ * well as { link GLib.ObjectClass.find_property},
+ * { link GLib.ObjectClass.list_properties}, and
+ * { link GLib.Object.set_property}) to manage serialization of
+ * an object's properties. { link GXml.Serializable} gives an
+ * implementing class an opportunity to override
+ * { link GLib.Object.get_property} to control what value is
+ * returned for a given parameter.
+ *
+ * For instance, if an object has private data fields
+ * that are not installed public properties, but that
+ * should be serialized,
+ * { link GXml.Serializable.get_property} can be used to
+ * handle this case as a virtual property, supported
+ * by the other { link GXml.Serializable} functions.
+ *
+ * `spec` is usually obtained from list_properties or find_property.
+ *
+ * As indicated by its name, `str_value` is a { link GLib.Value}
+ * that wants to hold a string type.
+ *
+ * @todo: why not just return a string? :D Who cares
+ * how analogous it is to { link GLib.Object.get_property}? :D
+ */
+ public virtual void get_property (GLib.ParamSpec spec, ref GLib.Value str_value) {
+ ((GLib.Object)this).get_property (spec.name, ref str_value);
+ }
+ /*
+ * Set a property's value.
+ *
+ * @param spec Specifies the property whose value will be set
+ * @param value The value to set the property to
+ *
+ * { link GXml.Serialization} uses { link GLib.Object.set_property} (as
+ * well as { link GLib.ObjectClass.find_property},
+ * { link GLib.ObjectClass.list_properties}, and
+ * { link GLib.Object.get_property}) to manage serialization of
+ * an object's properties. { link GXml.Serializable} gives an
+ * implementing class an opportunity to override
+ * { link GLib.Object.set_property} to control how a property's
+ * value is set.
+ *
+ * For instance, if an object has private data fields
+ * that are not installed public properties, but that
+ * should be serialized,
+ * { link GXml.Serializable.set_property} can be used to
+ * handle this case as a virtual property, supported
+ * by the other { link GXml.Serializable} functions.
+ */
+ public virtual void set_property (GLib.ParamSpec spec, GLib.Value value) {
+ ((GLib.Object)this).set_property (spec.name, value);
+ }
+ }
+}
diff --git a/gxml/jgs/Serialization.vala b/gxml/jgs/Serialization.vala
new file mode 100644
index 0000000..4a5fa0a
--- /dev/null
+++ b/gxml/jgs/Serialization.vala
@@ -0,0 +1,601 @@
+/* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* Serialization.vala
+ *
+ * Copyright (C) 2012-2013 Richard Schwarting <aquarichy gmail com>
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Richard Schwarting <aquarichy gmail com>
+ */
+
+/* TODO: so it seems we can get property information from GObjectClass
+ but that's about it. Need to definitely use introspection for anything
+ tastier */
+/* TODO: document memory management for the C side */
+
+using GXml;
+
+namespace GXml {
+ private static void print_object_properties (GLib.Object obj) {
+ ParamSpec[] properties;
+ properties = obj.get_class ().list_properties ();
+ stdout.printf ("object has %d properties\n", properties.length);
+ foreach (ParamSpec prop_spec in properties) {
+ stdout.printf ("---\n");
+ stdout.printf ("name %s\n", prop_spec.name);
+ stdout.printf (" value_type %s\n", prop_spec.value_type.name ());
+ stdout.printf (" owner_type %s\n", prop_spec.owner_type.name ());
+ stdout.printf (" get_name () %s\n", prop_spec.get_name ());
+ stdout.printf (" get_blurb () %s\n", prop_spec.get_blurb ());
+ stdout.printf (" get_nick () %s\n", prop_spec.get_nick ());
+ }
+ }
+
+ /**
+ * Errors from { link Serialization}.
+ */
+ public errordomain SerializationError {
+ /**
+ * An object without a known { link GLib.Type} was encountered.
+ */
+ UNKNOWN_TYPE,
+ /**
+ * A property was described in XML that is not known to the object's type.
+ */
+ UNKNOWN_PROPERTY,
+ /**
+ * An object with a known { link GLib.Type} that we do not support was encountered.
+ */
+ UNSUPPORTED_TYPE
+ }
+
+ /**
+ * Serializes and deserializes { link GLib.Object}s to and from
+ * { link GXml.Node}.
+ *
+ * Serialization can automatically serialize a variety of public
+ * properties. { link GLib.Object}s can also implement the
+ * { link GXml.Serializable} to partially or completely manage
+ * serialization themselves, including non-public properties or
+ * data types not automatically supported by { link GXml.Serialization}.
+ */
+ public class Serialization : GLib.Object {
+ private static void print_debug (GXml.Document doc, GLib.Object object) {
+ stdout.printf ("Object XML\n---\n%s\n", doc.to_string ());
+
+ stdout.printf ("object\n---\n");
+ stdout.printf ("get_type (): %s\n", object.get_type ().name ());
+ stdout.printf ("get_class ().get_type (): %s\n", object.get_class ().get_type ().name
());
+ GXml.print_object_properties (object);
+ }
+
+ /*
+ * This coordinates the automatic serialization of individual
+ * properties. As of 0.2, it supports enums, anything that
+ * { link GLib.Value} can transform into a string, and
+ * operates recursively.
+ */
+ private static GXml.Node serialize_property (GLib.Object object, ParamSpec prop_spec,
GXml.Document doc) throws SerializationError {
+ Type type;
+ Value value;
+ Node value_node;
+ Serializable serializable = null;
+
+ if (object.get_type ().is_a (typeof (Serializable))) {
+ serializable = (Serializable)object;
+ }
+
+ type = prop_spec.value_type;
+
+ if (prop_spec.value_type.is_enum ()) {
+ /* We're going to handle this simply by saving it
+ as an int. If we save a string representation,
+ we can't easily convert it back to the number
+ in a generic fashion unless we can use GEnumClass,
+ but I can't figure out how to get that right now,
+ except from a GParamSpecEnum, but I don't know
+ how to get that, at least in Vala (e.g. is it
+ supposed to be as simple in C as casting the
+ GParamSpec for an enum to GParamSpecEnum (assuming
+ it truly is the latter, but is returned as the
+ former by list_properties) */
+ value = Value (typeof (int));
+ if (serializable != null) {
+ serializable.get_property (prop_spec, ref value);
+ } else {
+ object.get_property (prop_spec.name, ref value);
+ }
+ value_node = doc.create_text_node ("%d".printf (value.get_int ()));
+ /* TODO: in the future, perhaps figure out GEnumClass
+ and save it as the human readable enum value :D */
+ } else if (Value.type_transformable (prop_spec.value_type, typeof (string))) { //
e.g. int, double, string, bool
+ value = Value (typeof (string));
+ if (serializable != null) {
+ serializable.get_property (prop_spec, ref value);
+ } else {
+ object.get_property (prop_spec.name, ref value);
+ }
+ value_node = doc.create_text_node (value.get_string ());
+ } else if (type == typeof (GLib.Type)) {
+ value_node = doc.create_text_node (type.name ());
+/*
+ } else if (type.is_a (typeof (Gee.Collection))) {
+ // We need to be able to figure out
+ // * what generics it has, and
+ // * any parametres for delegates it might have used.
+ GXml.print_object_properties (object);
+ value_node = null;
+ } else if (type == typeof (GLib.HashTable)) {
+
+ } else if (type == typeof (Gee.List)) {
+ // TODO: can we do a catch all for Gee.Collection and have <Collection /> ?
+ } else if (type.is_a (typeof (Gee.TreeSet))) {
+ object.get_property (prop_spec, ref value);
+ doc.create_element ("Collection");
+ foreach (Object member in
+ } else if {
+ g-dup-func gpointer
+ GParamPointer
+ $43 = {g_type_instance = {g_class = 0x67ad30}, name = 0x7ffff7b7d685
"g-dup-func", flags = 234, value_type = 68, owner_type = 14758512, _nick = 0x7ffff7b7d67c "dup func", _blurb =
+ 0x7ffff7b7d67c "dup func", qdata = 0x0, ref_count = 4, param_id = 2}
+*/
+ } else if (type.is_a (typeof (GLib.Object))
+ && ! type.is_a (typeof (Gee.Collection))) {
+ GLib.Object child_object;
+
+ // TODO: this is going to get complicated
+ value = Value (typeof (GLib.Object));
+ if (serializable != null) {
+ serializable.get_property (prop_spec, ref value);
+ } else {
+ object.get_property (prop_spec.name, ref value);
+ /* This can fail; consider case of Gee.TreeSet that isn't special
cased above, gets error
+
(/home/richard/mine/development/gnome/gdom/gxml/test/.libs/gxml_test:10996):
+ GLib-GObject-CRITICAL **: Read-only property 'read-only-view' on
class 'GeeReadOnlyBidirSortedSet' has type
+ 'GeeSortedSet' which is not equal to or more restrictive than the
type 'GeeBidirSortedSet' of the property
+ on the interface 'GeeBidirSortedSet' */
+ }
+ child_object = value.get_object ();
+ Document value_doc = Serialization.serialize_object (child_object); // catch
serialisation errors?
+
+ value_node = doc.copy_node (value_doc.document_element);
+ } else if (type.name () == "gpointer") {
+ GLib.warning ("DEBUG: skipping gpointer with name '%s' of object '%s'",
prop_spec.name, object.get_type ().name ());
+ value_node = doc.create_text_node (prop_spec.name);
+ } else {
+ throw new SerializationError.UNSUPPORTED_TYPE ("Can't currently serialize
type '%s' for property '%s' of object '%s'", type.name (), prop_spec.name, object.get_type ().name ());
+ }
+
+ return value_node;
+ }
+
+ /**
+ * Serializes a { link GLib.Object} into a { link GXml.Document}.
+ *
+ * This takes a { link GLib.Object} and serializes it
+ * into a { link GXml.Document} which can be saved to
+ * disk or transferred over a network. It handles
+ * serialization of primitive properties and some more
+ * complex ones like enums, other { link GLib.Object}s
+ * recursively, and some collections.
+ *
+ * The serialization process can be customised for an object
+ * by having the object implement the { link GXml.Serializable}
+ * interface, which allows direct control over the
+ * conversation of individual properties into { link GXml.Node}s
+ * and the object's list of properties as used by
+ * { link GXml.Serialization}.
+ *
+ * A { link GXml.SerializationError} may be thrown if there is
+ * a problem serializing a property (e.g. the type is unknown,
+ * unsupported, or the property isn't known to the object).
+ *
+ * @param object A { link GLib.Object} to serialize
+ * @return a { link GXml.Document} representing the serialized `object`
+ */
+ public static GXml.Document serialize_object (GLib.Object object) throws SerializationError {
+ Document doc;
+ Element root;
+ ParamSpec[] prop_specs;
+ Element prop;
+ Serializable serializable = null;
+ Node value_prop = null;
+ string oid;
+
+ // If the object has been serialized before, let's not do it again!
+ oid = "%p".printf (object);
+ Serialization.init_caches ();
+
+ try {
+ /* Create an XML Document to return the object
+ in. TODO: consider just returning an
+ <Object> node; but then we'd probably want
+ a separate document for it to already be a
+ part of as its owner_document. */
+ doc = new Document ();
+
+ // first, check if its been serialised already, and if so, just return an
ObjectRef element for it.
+ if (oid != "" && Serialization.serialize_cache.contains (oid)) {
+ // GLib.message ("cache hit on oid %s", oid);
+ root = doc.create_element ("ObjectRef");
+ doc.append_child (root);
+ root.set_attribute ("otype", object.get_type ().name ());
+ root.set_attribute ("oid", oid);
+ return doc;
+ }
+
+ if (object.get_type ().is_a (typeof (Serializable))) {
+ serializable = (Serializable)object;
+ }
+
+ root = doc.create_element ("Object");
+ doc.append_child (root);
+ root.set_attribute ("otype", object.get_type ().name ());
+ root.set_attribute ("oid", oid);
+
+ // Cache this before we start exploring properties in case there's a cycle
+ Serialization.serialize_cache.set (oid, root);
+
+ /* TODO: make sure we don't use an out param for our returned list
+ size in our interface's list_properties (), using
+ [CCode (array_length_type = "guint")] */
+ if (serializable != null) {
+ prop_specs = serializable.list_properties ();
+ } else {
+ prop_specs = object.get_class ().list_properties ();
+ }
+
+ /* Exam the properties of the object and store
+ them with their name, type and value in XML
+ Elements. Use GValue to convert them to
+ strings. (Too bad deserialising isn't that
+ easy w.r.t. string conversion.) */
+ foreach (ParamSpec prop_spec in prop_specs) {
+ prop = doc.create_element ("Property");
+ prop.set_attribute ("ptype", prop_spec.value_type.name ());
+ prop.set_attribute ("pname", prop_spec.name);
+
+ value_prop = null;
+ if (serializable != null) {
+ value_prop = serializable.serialize_property (prop_spec.name,
prop_spec, doc);
+ }
+ if (value_prop == null) {
+ value_prop = Serialization.serialize_property (object,
prop_spec, doc);
+ }
+
+ prop.append_child (value_prop);
+ root.append_child (prop);
+ }
+ } catch (GLib.Error e) {
+ GLib.error ("%s", e.message);
+ // TODO: handle this better
+ }
+
+ /* Debug output */
+ bool debug = false;
+ if (debug) {
+ Serialization.print_debug (doc, object);
+ }
+
+ return doc;
+ }
+
+ /*
+ * This handles deserializing properties individually.
+ * Because { link GLib.Value} doesn't handle transforming
+ * strings back to other types, we use our own function to do
+ * that.
+ */
+ private static void deserialize_property (ParamSpec spec, Element prop_elem, out Value val)
throws SerializationError {
+ Type type;
+
+ type = spec.value_type;
+
+ // if (false || ptype != "") {
+ // // TODO: undisable if we support fields at some point
+ // type = Type.from_name (ptype);
+ // if (type == 0) {
+ // /* This probably shouldn't happen while we're using
+ // ParamSpecs but if we support non-property fields
+ // later, it might be necessary again :D */
+ // throw new SerializationError.UNKNOWN_TYPE ("Deserializing object '%s'
has property '%s' with unknown type '%s'", otype, pname, ptype);
+ // }
+ // }
+
+ // Get value and save this all as a parameter
+ bool transformed = false;
+ val = Value (type);
+ if (GLib.Value.type_transformable (type, typeof (string))) {
+ try {
+ string_to_gvalue (prop_elem.content, ref val);
+ transformed = true;
+ } catch (SerializationError e) {
+ throw new SerializationError.UNSUPPORTED_TYPE ("string_to_gvalue
should transform it but failed");
+ }
+ // } else if (type.is_a (typeof (Gee.Collection))) {
+ } else if (type.is_a (typeof (GLib.Object))) {
+ GXml.Node prop_elem_child;
+ Object property_object;
+
+ prop_elem_child = prop_elem.first_child;
+
+ try {
+ property_object = Serialization.deserialize_object (prop_elem_child);
+ val.set_object (property_object);
+ transformed = true;
+ } catch (GXml.SerializationError e) {
+ // We don't want this one caught by deserialize_object, or we'd have
a cascading error message. Hmm, not so bad if it does, though.
+ e.message += "\nXML [%s]".printf (prop_elem.to_string ());
+ throw e;
+ }
+ }
+
+ if (transformed == false) {
+ throw new SerializationError.UNSUPPORTED_TYPE ("Failed to transform property
from string to type.");
+ }
+ }
+
+ /*
+ * This table is used while deserializing objects to avoid
+ * creating duplicate objects when we encounter multiple
+ * references to a single serialized object.
+ *
+ * TODO: one problem, if you deserialize two XML structures,
+ * some differing objects might have the same OID :( Need to
+ * find make it more unique than just the memory address. */
+ private static HashTable<string,Object> deserialize_cache = null;
+ private static HashTable<string,GXml.Node> serialize_cache = null;
+ // public so that tests can call it
+ public static void clear_cache () { // TODO: rename to clear_caches, just changed back
temporarily to avoid API break for 0.3.2
+ if (Serialization.deserialize_cache != null)
+ Serialization.deserialize_cache.remove_all ();
+ if (Serialization.serialize_cache != null)
+ Serialization.serialize_cache.remove_all ();
+ }
+
+ private static void init_caches () {
+ if (Serialization.deserialize_cache == null) {
+ Serialization.deserialize_cache = new HashTable<string,Object> (str_hash,
str_equal);
+ }
+ if (Serialization.serialize_cache == null) {
+ Serialization.serialize_cache = new HashTable<string,GXml.Node> (str_hash,
str_equal);
+ }
+ }
+
+ /**
+ * Deserialize a { link GXml.Document} back into a { link GLib.Object}.
+ *
+ * This deserializes a { link GXml.Document} back into a
+ * { link GLib.Object}. The { link GXml.Document}
+ * must represent a { link GLib.Object} as serialized
+ * by { link GXml.Serialization}. The types of the
+ * objects that are being deserialized must be known
+ * to the system deserializing them or a
+ * { link GXml.SerializationError} will result.
+ *
+ * @param doc { link GXml.Document} representing a { link GLib.Object}
+ * @return the deserialized { link GLib.Object}
+ */
+ public static GLib.Object deserialize_object (GXml.Node node) throws SerializationError {
+ Element obj_elem;
+ string otype;
+ string oid;
+ Type type;
+ Object obj;
+ unowned ObjectClass obj_class;
+ ParamSpec[] specs;
+ Serializable serializable = null;
+
+ if (node.get_type ().is_a (typeof (GXml.Document))) {
+ obj_elem = (node as GXml.Document).document_element as GXml.Element;
+ } else {
+ obj_elem = node as GXml.Element;
+ }
+
+ // If the object has been deserialised before, get it from cache
+ oid = obj_elem.get_attribute ("oid");
+ Serialization.init_caches ();
+ if (oid != "" && Serialization.deserialize_cache.contains (oid)) {
+ return Serialization.deserialize_cache.get (oid);
+ }
+
+ // Get the object's type
+ // TODO: wish there was a g_object_class_from_name () method
+ otype = obj_elem.get_attribute ("otype");
+ type = Type.from_name (otype);
+ if (type == 0) {
+ throw new SerializationError.UNKNOWN_TYPE ("Deserializing object claims
unknown type '%s'", otype);
+ }
+
+ // Get the list of properties as ParamSpecs
+ obj = Object.newv (type, new Parameter[] {}); // TODO: causes problems with Enums
when 0 isn't a valid enum value (e.g. starts from 2 or something)
+ obj_class = obj.get_class ();
+
+ // Set it as the last possible action, so that invalid objects won't end up getting
stored // Changed our mind, for deserializing ObjectRefs
+ Serialization.deserialize_cache.set (oid, obj);
+
+ if (type.is_a (typeof (Serializable))) {
+ serializable = (Serializable)obj;
+ }
+
+ if (serializable != null) {
+ specs = serializable.list_properties ();
+ } else {
+ specs = obj_class.list_properties ();
+ }
+
+ SerializationError err = null;
+
+ foreach (Node child_node in obj_elem.child_nodes) {
+ if (child_node.node_name == "Property") {
+ Element prop_elem;
+ string pname;
+ Value val;
+ //string ptype;
+
+ prop_elem = (Element)child_node;
+ pname = prop_elem.get_attribute ("pname");
+ //ptype = prop_elem.get_attribute ("ptype"); // optional
+
+ // Check name and type for property
+ ParamSpec? spec = null;
+ if (serializable != null) {
+ spec = serializable.find_property (pname);
+ } else {
+ spec = obj_class.find_property (pname);
+ }
+
+ if (spec == null) {
+ err = new SerializationError.UNKNOWN_PROPERTY ("Deserializing
object of type '%s' claimed unknown property named '%s'\nXML [%s]", otype, pname, obj_elem.to_string ());
+ break;
+ }
+
+ try {
+ bool serialized = false;
+
+ if (serializable != null) {
+ serialized = serializable.deserialize_property
(spec.name, /* out val, */ spec, prop_elem); // TODO: consider rearranging these or the ones in Serializer to
match
+ }
+ if (!serialized) {
+ Serialization.deserialize_property (spec, prop_elem,
out val);
+ if (serializable != null) {
+ serializable.set_property (spec, val);
+ } else {
+ obj.set_property (pname, val);
+ }
+ /* TODO: should we make a note that for implementing
{get,set}_property in
+ the interface, they should specify override (in
Vala)? What about in C?
+ Need to test which one gets called in which
situations (yeah, already read
+ the tutorial) */
+ }
+ } catch (SerializationError.UNSUPPORTED_TYPE e) {
+ err = new SerializationError.UNSUPPORTED_TYPE ("Cannot
deserialize object '%s's property '%s' with type '%s/%s': %s\nXML [%s]", otype, spec.name,
spec.value_type.name (), spec.value_type.to_string (), e.message, obj_elem.to_string ());
+ break;
+ }
+ }
+ }
+
+ // TODO: should make a test to ensure this works
+ if (err != null) {
+ Serialization.deserialize_cache.remove (oid);
+ throw err;
+ }
+
+ return obj;
+ }
+
+ /* TODO:
+ * - can't seem to pass delegates on struct methods to another function :(
+ * - no easy string_to_gvalue method in GValue :(
+ */
+
+ /**
+ * Transforms a string into another type hosted by { link GLib.Value}.
+ *
+ * A utility function that handles converting a string
+ * representation of a value into the type specified by the
+ * supplied #GValue dest. A #GXmlSerializationError will be
+ * set if the string cannot be parsed into the desired type.
+ *
+ * @param str the string to transform into the given #GValue object
+ * @param dest the #GValue out parameter that will contain the parsed value from the string
+ * @return `true` if parsing succeeded, otherwise `false`
+ */
+ /*
+ * @todo: what do functions written in Vala return in C when
+ * they throw an exception? NULL/0/FALSE?
+ */
+ public static bool string_to_gvalue (string str, ref GLib.Value dest) throws
SerializationError {
+ Type t = dest.type ();
+ GLib.Value dest2 = Value (t);
+ bool ret = false;
+
+ if (t == typeof (int64)) {
+ int64 val;
+ if (ret = int64.try_parse (str, out val)) {
+ dest2.set_int64 (val);
+ }
+ } else if (t == typeof (int)) {
+ int64 val;
+ if (ret = int64.try_parse (str, out val)) {
+ dest2.set_int ((int)val);
+ }
+ } else if (t == typeof (long)) {
+ int64 val;
+ if (ret = int64.try_parse (str, out val)) {
+ dest2.set_long ((long)val);
+ }
+ } else if (t == typeof (uint)) {
+ uint64 val;
+ if (ret = uint64.try_parse (str, out val)) {
+ dest2.set_uint ((uint)val);
+ }
+ } else if (t == typeof (ulong)) {
+ uint64 val;
+ if (ret = uint64.try_parse (str, out val)) {
+ dest2.set_ulong ((ulong)val);
+ }
+ } else if ((int)t == 20) { // gboolean
+ bool val = (str == "TRUE");
+ dest2.set_boolean (val); // TODO: huh, investigate why the type is gboolean
and not bool coming out but is going in
+ ret = true;
+ } else if (t == typeof (bool)) {
+ bool val;
+ if (ret = bool.try_parse (str, out val)) {
+ dest2.set_boolean (val);
+ }
+ } else if (t == typeof (float)) {
+ double val;
+ if (ret = double.try_parse (str, out val)) {
+ dest2.set_float ((float)val);
+ }
+ } else if (t == typeof (double)) {
+ double val;
+ if (ret = double.try_parse (str, out val)) {
+ dest2.set_double (val);
+ }
+ } else if (t == typeof (string)) {
+ dest2.set_string (str);
+ ret = true;
+ } else if (t == typeof (char)) {
+ int64 val;
+ if (ret = int64.try_parse (str, out val)) {
+ dest2.set_schar ((int8)val);
+ }
+ } else if (t == typeof (uchar)) {
+ int64 val;
+ if (ret = int64.try_parse (str, out val)) {
+ dest2.set_uchar ((uchar)val);
+ }
+ } else if (t == Type.BOXED) {
+ } else if (t.is_enum ()) {
+ int64 val;
+ if (ret = int64.try_parse (str, out val)) {
+ dest2.set_enum ((int)val);
+ }
+ } else if (t.is_flags ()) {
+ } else if (t.is_object ()) {
+ } else {
+ }
+
+ if (ret == true) {
+ dest = dest2;
+ return true;
+ } else {
+ throw new SerializationError.UNSUPPORTED_TYPE ("%s/%s", t.name (),
t.to_string ());
+ }
+ }
+ }
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]