[gxml/jgs: 1/7] serialization:jgs: restore original json-glib style serialization files in own directory



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]