diff --git a/gxml/Serializable.vala b/gxml/Serializable.vala index ee700d5b14600a8aded66150f2394061ea7fcd8b..caab9e88086dd75b5be393d4f20acd12918a8e41 100644 --- a/gxml/Serializable.vala +++ b/gxml/Serializable.vala @@ -30,34 +30,210 @@ using GXml; [CCode (gir_namespace = "GXml", gir_version = "0.2")] namespace GXml { + /** + * Provides interface methods that a class can implement to + * directly handle serialisation of various properties or + * treat other data as properties. + * + * 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. + * + * For an example, look in tests/XmlSerializableTest + */ public interface Serializable : GLib.Object { - /** Return true if your implementation will have handled the given property, - and false elsewise (in which case, XmlSerializable will try to deserialize - it). */ - /** OBSOLETENOTE: Return the deserialized value in GLib.Value (even if it's a GLib.Boxed type) because Serializer is going to set the property after calling this, and if you just set it yourself within, it will be overwritten */ + /** + * 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.DomNode} that contains the content. The + * implementing {@link GXml.Serializable} object can extract + * the data from the {@link GXml.DomNode} and store it in its + * property itself. Note that the {@link GXml.DomNode} 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.DomNode} 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, /* out GLib.Value value,*/ GLib.ParamSpec spec, GXml.DomNode property_node) { return false; // default deserialize_property gets used } - // TODO: just added ? to these, do we really want to allow nulls for them? - // TODO: value and property_name are kind of redundant: eliminate? property_name from spec.property_name and value from the object itself :) - /** Serialized properties should have the XML structure ... */ - // TODO: perhaps we should provide that base structure + + /** + * 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.DomNode} that encapsulates the property. + * {@link GXml.Serialization} will embed the {@link GXml.DomNode} into + * a "Property" {@link GXml.Element}, so the {@link GXml.DomNode} + * 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 itsel. + * + * @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.DomNode} should belong to + * @return a new {@link GXml.DomNode}, or `null` + */ + /* + * @todo: consider not giving property_name, let them get name from spec? + */ public virtual GXml.DomNode? serialize_property (string property_name, /*GLib.Value value, */ GLib.ParamSpec spec, GXml.Document doc) { return null; // default serialize_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 (); } - /* Correspond to: g_object_{set,get}_property */ + /* + * 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/Serialization.vala b/gxml/Serialization.vala index ea7e1630dc6c8dbb1ee0d5ae4ea545dfe780f4c3..f8ddf11a7b846da0ef9fa75969466eb2b86ac559 100644 --- a/gxml/Serialization.vala +++ b/gxml/Serialization.vala @@ -1,25 +1,59 @@ +/* + * Copyright (C) 2012 Richard Schwarting + * + * 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 . + * + * Authors: + * Richard Schwarting + */ + +/* 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; [CCode (gir_namespace = "GXml", gir_version = "0.2")] namespace GXml { + /** + * 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 } /** - * SECTION:gxml-serialization - * @short_description: Provides functions for serializing GObjects. - * @x-title:gxml-serialization - * @section_id: - * @see_also: #GXml, #GXmlDocument, #GObject - * @stability: Unstable - * @include: gxml/serialization.h - * @image: library.png + * Serializes and deserializes {@link GLib.Object}s to and from + * {@link GXml.DomNode}. * - * GXmlSerialization provides functions to serialize and - * deserialize GObjects into and from GXmlDomNodes + * 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) { @@ -43,8 +77,12 @@ namespace GXml { } } - // public delegate void GetProperty (GLib.ParamSpec spec, ref GLib.Value value); - + /* + * 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.DomNode serialize_property (GLib.Object object, ParamSpec prop_spec, GXml.Document doc) throws SerializationError, DomError { Type type; Value value; @@ -109,9 +147,29 @@ namespace GXml { return value_node; } - /* TODO: so it seems we can get property information from GObjectClass - but that's about it. Need to definitely use introspection for anything - tastier */ + /** + * Serializes a {@link GLib.Object} into a {@link GXml.DomNode}. + * + * This takes a {@link GLib.Object} and serializes it into a + * {@link GXml.DomNode} 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.DomNode}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.DomNode} representing the serialized `object` + */ public static GXml.DomNode serialize_object (GLib.Object object) throws SerializationError { Document doc; Element root; @@ -179,6 +237,12 @@ namespace GXml { return doc.document_element; // user can get Document through .owner_document } + /* + * 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; @@ -227,6 +291,29 @@ namespace GXml { } } + /* + * 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 cache = null; + + /** + * Deserialize a {@link GXml.DomNode} back into a {@link GLib.Object}. + * + * This deserializes a {@link GXml.DomNode} back into a {@link GLib.Object}. The + * {@link GXml.DomNode} must represented 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 node {@link GXml.DomNode} representing a {@link GLib.Object} + * @return the deserialized {@link GLib.Object} + */ public static GLib.Object deserialize_object (DomNode node) throws SerializationError { Element obj_elem; @@ -314,6 +401,23 @@ namespace GXml { * - 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);