[ease/serialize: 3/52] Serializer sort of working.



commit cc305b68ab33d8554822db1861d2e1b0cb9aebfb
Author: Nate Stedman <natesm gmail com>
Date:   Fri Nov 26 22:49:10 2010 -0500

    Serializer sort of working.

 ease-core/ease-color.vala        |   12 +-
 ease-core/ease-document.vala     |   31 +++-
 ease-core/ease-element.vala      |   14 +-
 ease-core/ease-serializable.vala |   64 ++++++
 ease-core/ease-serializer.vala   |  428 ++++++++++++++++++++++++++++++++++++--
 ease-core/ease-slide.vala        |   18 ++-
 ease/ease-editor-window.vala     |    3 -
 ease/ease-main.vala              |    3 +
 8 files changed, 539 insertions(+), 34 deletions(-)
---
diff --git a/ease-core/ease-color.vala b/ease-core/ease-color.vala
index fe4fba8..c6b0cf3 100644
--- a/ease-core/ease-color.vala
+++ b/ease-core/ease-color.vala
@@ -18,7 +18,7 @@
 /**
  * Color abstraction, supporting Clutter, GDK, and Cairo colors.
  */
-public class Ease.Color : GLib.Object
+public class Ease.Color : GLib.Object, Serializable
 {
 	/**
 	 * The format string for converting Colors to strings.
@@ -356,6 +356,16 @@ public class Ease.Color : GLib.Object
 	}
 	
 	/**
+	 * Properties that should be excluded from serialization.
+	 */
+	public string[] serialize_exclude()
+	{
+		return { "red8", "green8", "blue8", "alpha8",
+		         "red16", "green16", "blue16", "alpha16",
+		         "clutter", "gdk" };
+	}
+	
+	/**
 	 * Creates an opaque color.
 	 */
 	public Color.rgb(double r, double g, double b)
diff --git a/ease-core/ease-document.vala b/ease-core/ease-document.vala
index 78ec4a3..7a9492f 100644
--- a/ease-core/ease-document.vala
+++ b/ease-core/ease-document.vala
@@ -21,7 +21,7 @@
  * The Ease Document class is generated from JSON and writes back to JSON
  * when saved.
  */
-public class Ease.Document : GLib.Object, UndoSource
+public class Ease.Document : GLib.Object, UndoSource, Serializable
 {
 	private const string MEDIA_PATH = "Media";
 	
@@ -110,10 +110,32 @@ public class Ease.Document : GLib.Object, UndoSource
 	 */
 	public string path { get; set; }
 	
+	/**
+	 * The properties that should be excluded from serialization.
+	 */
 	public string[] serialize_exclude()
 	{
 		return { "path", "filename", "aspect", "length" };
 	}
+	
+	/**
+	 * Serializes the ListStore of slides.
+	 */
+	public bool serialize_custom(string property, Json.Object object)
+	{
+		if (property != "slides") return false;
+		
+		var array = new Json.Array();
+		Slide s;
+		foreach (var iter in slides)
+		{
+			slides.get(iter, COL_SLIDE, out s);
+			array.add_object_element(Serializer.write(s));
+		}
+		object.set_array_member(property, array);
+		
+		return true;
+	}
 
 	/**
 	 * All { link Slide}s in this Document.
@@ -216,7 +238,12 @@ public class Ease.Document : GLib.Object, UndoSource
 		slide.parent = this;
 		append_slide(slide);
 		
-		Serializer.write(this);
+		var gen = new Json.Generator();
+		var root = new Json.Node(Json.NodeType.OBJECT);
+		root.set_object(Serializer.write(this));
+		gen.root = root;
+		gen.pretty = true;
+		gen.to_file("/home/nate/Desktop/test.json");
 	}
 	
 	public void to_json(Gtk.Window? window) throws GLib.Error
diff --git a/ease-core/ease-element.vala b/ease-core/ease-element.vala
index 9777a50..d7e33db 100644
--- a/ease-core/ease-element.vala
+++ b/ease-core/ease-element.vala
@@ -22,7 +22,7 @@
  * abstract, so each type of element is represented by a subclass. The Element
  * base class contains properties common to all types of element.
  */
-public abstract class Ease.Element : GLib.Object, UndoSource
+public abstract class Ease.Element : GLib.Object, UndoSource, Serializable
 {
 	/**
 	 * The default width of { link Theme} master slides.
@@ -64,6 +64,14 @@ public abstract class Ease.Element : GLib.Object, UndoSource
 	internal Document document { get { return parent.parent; } }
 	
 	/**
+	 * Properties to exclude from serialization.
+	 */
+	public string[] serialize_exclude()
+	{
+		return { "parent", "document" };
+	}
+	
+	/**
 	 * Notifies of a position change for this Element. Note that the "x" and "y"
 	 * properties do not emit the "changed" signal - only this signal.
 	 */
@@ -294,10 +302,6 @@ public abstract class Ease.Element : GLib.Object, UndoSource
 	 */
 	public string identifier { get; set; }
 	
-	/**
-	 * The Element's type: currently "text", "image", or "video".
-	 */
-	public string element_type { get; set; }
 	
 	/**
 	 * The X position of this Element.
diff --git a/ease-core/ease-serializable.vala b/ease-core/ease-serializable.vala
index 3354438..7c7a917 100644
--- a/ease-core/ease-serializable.vala
+++ b/ease-core/ease-serializable.vala
@@ -18,4 +18,68 @@
 public interface Ease.Serializable
 {
 	public abstract string[] serialize_exclude();
+	
+	/**
+	 * Simply combines two arrays, for use with serialize_exclude.
+	 */
+	public string[] serialize_combine(string[] one, string[] two)
+	{
+		string[] combined = new string[one.length + two.length];
+		
+		for (int i = 0; i < one.length; i++)
+		{
+			if (i < one.length) combined[i] = one[i];
+			else combined[i] = two[i - one.length];
+		}
+		
+		return combined;
+	}
+	
+	/**
+	 * Triggers the serialize_custom function properly for subclasses.
+	 */
+	internal bool serialize_custom_run(string property, Json.Object object)
+	{
+		return serialize_custom(property, object);
+	}
+	
+	/**
+	 * Allows custom serialization for specific properties that cannot be
+	 * serialized in the typical way.
+	 *
+	 * If the property was serialized in a custom manner, the function should
+	 * return true, otherwise false. By default, this function simply returns
+	 * false.
+	 *
+	 * @param property The property to serialize.
+	 * @param object The JSON object to modify.
+	 */
+	public virtual bool serialize_custom(string property, Json.Object object)
+	{
+		return false;
+	}
+	
+	/**
+	 * Triggers the serialize_custom function properly for subclasses.
+	 */
+	internal bool deserialize_custom_run(string property, Json.Object object)
+	{
+		return deserialize_custom(property, object);
+	}
+	
+	/**
+	 * Allows custom deserialization for specific properties that cannot be
+	 * deserialized in the typical way.
+	 *
+	 * If the property was deserialized in a custom manner, the function should
+	 * return true, otherwise false. By default, this function simply returns
+	 * false.
+	 *
+	 * @param property The property to deserialize.
+	 * @param object The JSON object to read from.
+	 */
+	public virtual bool deserialize_custom(string property, Json.Object object)
+	{
+		return false;
+	}
 }
diff --git a/ease-core/ease-serializer.vala b/ease-core/ease-serializer.vala
index 9c2aaf3..f5f83b5 100644
--- a/ease-core/ease-serializer.vala
+++ b/ease-core/ease-serializer.vala
@@ -17,38 +17,426 @@
 
 public static class Ease.Serializer
 {
-	private const string SCRIPT = """
-	for (i in object.get_class().get_properties())
+	private static Gee.TreeMap<GLib.Type, CollectionBox> collections;
+	
+	private const string TYPE_KEY = "__EASE_SERIALIZER_OBJECT_TYPE_NAME__";
+	
+	public static Json.Object write(GLib.Object object)
 	{
-		print(i);
+		// create the json object
+		var json = new Json.Object();
+		
+		// store the object's type in the reserved key
+		json.set_string_member(TYPE_KEY, object.get_type().name());
+		
+		// check for any excluded properties
+		string[] exclude = {};
+		if (object is Serializable)
+		{
+			exclude = (object as Serializable).serialize_exclude();
+		}
+		
+		// iterate and serialize properties
+		foreach (var pspec in object.get_class().list_properties())
+		{
+			// don't serialize excluded types
+			if (pspec.name in exclude) continue;
+			
+			// if the object implements Serializable, allow custom behavior
+			if (object is Serializable)
+			{
+				if ((object as Serializable).serialize_custom_run(pspec.name,
+				                                                  json))
+					continue;
+			}
+			
+			// get the object/not object as a value
+			GLib.Value val = GLib.Value(pspec.value_type);
+			object.get_property(pspec.name, ref val);
+			
+			// if the object is a registered collection, turn it into an array
+			if (collections != null)
+			{
+				var type = pspec.value_type;
+				bool iterated = false;
+				do
+				{
+					CollectionIterator? iterator = null;
+					if ((iterator = find_iterator(type)) != null)
+					{
+						var array = new Json.Array();
+						iterator((GLib.Object)val, array);
+						json.set_array_member(pspec.name, array);
+						iterated = true;
+						break;
+					}
+				} while (0 != (type = type.parent()));
+				
+				if (iterated) continue;
+			}
+			
+			// serialize GObjects
+			if (Value.type_transformable(pspec.value_type, typeof(Object)))
+			{
+				json.set_object_member(pspec.name, write((Object)val));
+			}
+			
+			// serialize basic types
+			if (pspec.value_type == typeof(string))
+			{
+				json.set_string_member(pspec.name, (string)val); continue;
+			}
+			
+			if (pspec.value_type == typeof(bool))
+			{
+				json.set_boolean_member(pspec.name, (bool)val); continue;
+			}
+			
+			if (pspec.value_type == typeof(char))
+			{
+				json.set_int_member(pspec.name, (char)val); continue;
+			}
+			
+			if (pspec.value_type == typeof(uchar))
+			{
+				json.set_int_member(pspec.name, (uchar)val); continue;
+			}
+			
+			if (pspec.value_type == typeof(int))
+			{
+				json.set_int_member(pspec.name, (int)val); continue;
+			}
+			
+			if (pspec.value_type == typeof(uint))
+			{
+				json.set_int_member(pspec.name, (uint)val); continue;
+			}
+			
+			if (pspec.value_type == typeof(long))
+			{
+				json.set_int_member(pspec.name, (long)val); continue;
+			}
+			
+			if (pspec.value_type == typeof(ulong))
+			{
+				json.set_int_member(pspec.name, (ulong)val); continue;
+			}
+			
+			if (pspec.value_type == typeof(size_t))
+			{
+				json.set_int_member(pspec.name, (size_t)val); continue;
+			}
+			
+			if (pspec.value_type == typeof(ssize_t))
+			{
+				json.set_int_member(pspec.name, (ssize_t)val); continue;
+			}
+			
+			if (pspec.value_type == typeof(int8))
+			{
+				json.set_int_member(pspec.name, (int8)val); continue;
+			}
+			
+			if (pspec.value_type == typeof(uint8))
+			{
+				json.set_int_member(pspec.name, (uint8)val); continue;
+			}
+			
+			/*if (pspec.value_type == typeof(int16))
+			{
+				json.set_int_member(pspec.name, (int16)val); continue;
+			}
+			
+			if (pspec.value_type == typeof(uint16))
+			{
+				json.set_int_member(pspec.name, (uint16)val); continue;
+			}*/
+			
+			if (pspec.value_type == typeof(int32))
+			{
+				json.set_int_member(pspec.name, (int32)val); continue;
+			}
+			
+			if (pspec.value_type == typeof(uint32))
+			{
+				json.set_int_member(pspec.name, (uint32)val); continue;
+			}
+			
+			if (pspec.value_type == typeof(int64))
+			{
+				json.set_int_member(pspec.name, (int64)val); continue;
+			}
+			
+			if (pspec.value_type == typeof(float))
+			{
+				json.set_double_member(pspec.name, (float)val); continue;
+			}
+			
+			if (pspec.value_type == typeof(double))
+			{
+				json.set_double_member(pspec.name, (double)val); continue;
+			}
+			
+			if (pspec.value_type.is_enum())
+			{
+				json.set_int_member(pspec.name, (int)val); continue;
+			}
+			
+			// uint64 won't fit in an int64, so make it a string to preserve
+			// the content (instead of just casting back and forth)
+			if (pspec.value_type == typeof(uint64))
+			{
+				json.set_string_member(pspec.name, "%lld".printf((uint64)val));
+				continue;
+			}
+		}
+		
+		return json;
 	}
-	""";
 	
-	public static void write(GLib.Object object)
+	public static GLib.Object read(Json.Object json)
 	{
-		debug("Seed should happen now:");
-		var context = Seed.Context.create(null, null);
-		Seed.prepare_global_context(context);
+		// create the object with the type that TYPE_KEY specifies
+		var type = GLib.Type.from_name(json.get_string_member(TYPE_KEY));
+		var object = new GLib.Object(type, null);
 		
-		Seed.Object.set_property(context,
-		                         context.get_global_object(),
-		                         "object",
-		                         Seed.Value.from_object(context, object, null));
+		// deserialize all properties
+		json.foreach_member((jobj, property, node) => {
+			// if the object implements Serializable, allow custom behavior
+			if (object is Serializable)
+			{
+				if ((object as Serializable).deserialize_custom_run(property,
+				                                                    jobj))
+					return;
+			}
+			
+			// get a paramspec for the property to deserialize into
+			var pspec = object.get_class().find_property(property);
+			
+			// create a GValue to serialize into
+			GLib.Value val = GLib.Value(pspec.value_type);
+			
+			// deserialize GObjects
+			if (Value.type_transformable(pspec.value_type, typeof(Object)))
+			{
+				val = read(json.get_object_member(pspec.name));
+			}
+			
+			// serialize basic types
+			else if (pspec.value_type == typeof(string))
+			{
+				val = json.get_string_member(property);
+			}
+			
+			else if (pspec.value_type == typeof(bool))
+			{
+				val = json.get_boolean_member(property);
+			}
+			
+			else if (pspec.value_type == typeof(char))
+			{
+				val = json.get_int_member(property);
+			}
+			
+			else if (pspec.value_type == typeof(uchar))
+			{
+				val = json.get_int_member(property);
+			}
+			
+			else if (pspec.value_type == typeof(int))
+			{
+				val = json.get_int_member(property);
+			}
+			
+			else if (pspec.value_type == typeof(uint))
+			{
+				val = json.get_int_member(property);
+			}
+			
+			else if (pspec.value_type == typeof(long))
+			{
+				val = json.get_int_member(property);
+			}
+			
+			else if (pspec.value_type == typeof(ulong))
+			{
+				val = json.get_int_member(property);
+			}
+			
+			else if (pspec.value_type == typeof(size_t))
+			{
+				val = json.get_int_member(property);
+			}
+			
+			else if (pspec.value_type == typeof(ssize_t))
+			{
+				val = json.get_int_member(property);
+			}
+			
+			else if (pspec.value_type == typeof(int8))
+			{
+				val = json.get_int_member(property);
+			}
+			
+			else if (pspec.value_type == typeof(uint8))
+			{
+				val = json.get_int_member(property);
+			}
+			
+			/*if (pspec.value_type == typeof(int16))
+			{
+				val = json.get_int_member(property);
+			}
+			
+			if (pspec.value_type == typeof(uint16))
+			{
+				val = json.get_int_member(property);
+			}*/
+			
+			else if (pspec.value_type == typeof(int32))
+			{
+				val = json.get_int_member(property);
+			}
+			
+			else if (pspec.value_type == typeof(uint32))
+			{
+				val = json.get_int_member(property);
+			}
+			
+			else if (pspec.value_type == typeof(int64))
+			{
+				val = json.get_int_member(property);
+			}
+			
+			else if (pspec.value_type == typeof(float))
+			{
+				val = json.get_double_member(property);
+			}
+			
+			else if (pspec.value_type == typeof(double))
+			{
+				val = json.get_double_member(property);
+			}
+			
+			else if (pspec.value_type.is_enum())
+			{
+				val = json.get_int_member(property);
+			}
+			
+			object.set_property(property, val);
+		});
 		
-		unowned Seed.Script script = Seed.make_script(context, SCRIPT, null, 0);
+		return object;
+	}
+	
+	/**
+	 * Registers an iteration function for a collection type.
+	 *
+	 * When attempting to find a collection iterator, the serializer will search
+	 * the main class first, then its parent class, etc. Interfaces implemented
+	 * by each level will be searched first.
+	 *
+	 * @param type The type.
+	 * @param callback The function to iterate over the type.
+	 */
+	public static void register(GLib.Type type,
+	                            CollectionIterator iterator,
+	                            CollectionBuilder builder)
+	{
+		if (collections == null)
+		{
+			collections = new Gee.TreeMap<GLib.Type, CollectionBox>();
+		}
 		
-		Seed.Exception? e = null;
-		if ((e = script.exception()) != null)
+		collections.set(type, new CollectionBox(iterator, builder));
+	}
+	
+	/**
+	 * Registers the builtin iterations functions.
+	 */
+	public static void register_builtins()
+	{
+		// GList
+		register(typeof(GLib.List), (object, array) => {
+			foreach (var item in (GLib.List<GLib.Object>)object)
+			{
+				array.add_object_element(write(item));
+			}
+		}, (object, array) => {
+			unowned GLib.List<Object> list = (GLib.List<Object>)object;
+			array.foreach_element((a, index, node) => {
+				list.append(read(node.get_object()));
+			});
+		});
+		
+		// Gee.List
+		register(typeof(Gee.List), (object, array) => {
+			foreach (var item in object as Gee.List<GLib.Object>)
+			{
+				array.add_object_element(write(item));
+			}
+		}, (object, array) => {
+			var list = object as Gee.List<GLib.Object>;
+			array.foreach_element((a, index, node) => {
+				list.insert(list.size, read(node.get_object()));
+			});
+		});
+	}
+	
+	private static CollectionIterator find_iterator(Type type)
+	{
+		if (collections.has_key(type))
 		{
-			critical("%s", Seed.Exception.to_string(context, e));
+			// vala bug
+			var box = collections.get(type);
+			return box.iterator;
 		}
 		
-		unowned Seed.Value val = Seed.evaluate(context, script, null);
+		foreach (var t in type.interfaces())
+		{
+			if (collections.has_key(t))
+			{
+				// vala bug again
+				var box = collections.get(t);
+				return box.iterator;
+			}
+		}
+	
+		return null;
+	}
+	
+	/**
+	 * Allows collections of arbitrary types to be serialized.
+	 *
+	 * @param object The collection object to be serialized into a JSON array.
+	 * @param array The JSON array to add elements to.
+	 */
+	public delegate void CollectionIterator(GLib.Object object,
+	                                        Json.Array array);
+	
+	/**
+	 * Allows collections of arbitrary types to be deserialized.
+	 *
+	 * @param object The collection object to be deserialized into.
+	 * @param array The JSON array to deserialize.
+	 */
+	public delegate void CollectionBuilder(GLib.Object object,
+	                                       Json.Array array);
+	
+	/**
+	 * Delegates cannot be used as generic type arguments in Vala, so we have to
+	 * box them.
+	 */
+	private class CollectionBox
+	{
+		public CollectionIterator iterator;
+		public CollectionBuilder builder;
 		
-		e = null;
-		if ((e = script.exception()) != null)
+		public CollectionBox(CollectionIterator iter, CollectionBuilder builder)
 		{
-			critical("%s", Seed.Exception.to_string(context, e));
+			this.iterator = iter;
+			this.builder = builder;
 		}
 	}
 }
+
diff --git a/ease-core/ease-slide.vala b/ease-core/ease-slide.vala
index eb55230..a370914 100644
--- a/ease-core/ease-slide.vala
+++ b/ease-core/ease-slide.vala
@@ -22,7 +22,7 @@
  * children. The currently selected Slide is often acted upon by an
  * EditorWindow (from main Ease, not core).
  */
-public class Ease.Slide : GLib.Object, UndoSource
+public class Ease.Slide : GLib.Object, UndoSource, Serializable
 {
 	public const string IMAGE_TYPE = "EaseImageElement";
 	public const string SHAPE_TYPE = "EaseShapeElement";
@@ -32,7 +32,11 @@ public class Ease.Slide : GLib.Object, UndoSource
 	/**
 	 * The { link Element}s contained by this Slide
 	 */
-	internal Gee.LinkedList<Element> elements = new Gee.LinkedList<Element>();
+	internal Gee.LinkedList<Element> elements
+	{
+		get; private set;
+		default = new Gee.LinkedList<Element>();
+	}
 	
 	/**
 	 * The Slide's transition
@@ -162,6 +166,15 @@ public class Ease.Slide : GLib.Object, UndoSource
 	}
 	
 	/**
+	 * The properties that should be excluded from serialization.
+	 */
+	public string[] serialize_exclude()
+	{
+		return { "next", "previous", "count", "width", "height", "aspect",
+		         "background_abs", "parent" };
+	}
+	
+	/**
 	 * Emitted when an { link Element} or property of this Slide is changed.
 	 */
 	public signal void changed(Slide self);
@@ -296,7 +309,6 @@ public class Ease.Slide : GLib.Object, UndoSource
 					break;
 			}
 			
-			e.element_type = type;
 			append(e);
 		}
 	}
diff --git a/ease/ease-editor-window.vala b/ease/ease-editor-window.vala
index fa35b49..2fa6807 100644
--- a/ease/ease-editor-window.vala
+++ b/ease/ease-editor-window.vala
@@ -502,7 +502,6 @@ internal class Ease.EditorWindow : Gtk.Window
 			e.x = slide.width / 2 - width / 2;
 			e.y = slide.height / 2 - height / 2;
 			
-			e.element_type = Slide.IMAGE_TYPE;
 			e.identifier = Theme.CUSTOM_MEDIA;
 			e.filename = document.add_media_file(filename);
 			e.source_filename = filename;
@@ -534,7 +533,6 @@ internal class Ease.EditorWindow : Gtk.Window
 				e.x = slide.width / 2 - e.width / 2;
 				e.y = slide.height / 2 - e.height / 2;
 				
-				e.element_type = Slide.VIDEO_TYPE;
 				e.identifier = Theme.CUSTOM_MEDIA;
 				e.filename = document.add_media_file(filename);
 				e.source_filename = filename;
@@ -587,7 +585,6 @@ internal class Ease.EditorWindow : Gtk.Window
 				e.x = slide.width / 2 - e.width / 2;
 				e.y = slide.height / 2 - e.height / 2;
 				
-				e.element_type = Slide.PDF_TYPE;
 				e.identifier = Theme.CUSTOM_MEDIA;
 				e.filename = document.add_media_file(filename);
 				e.source_filename = filename;
diff --git a/ease/ease-main.vala b/ease/ease-main.vala
index 39ef32e..ddfdeb8 100644
--- a/ease/ease-main.vala
+++ b/ease/ease-main.vala
@@ -61,6 +61,9 @@ internal class Ease.Main : GLib.Object
 		Environment.set_application_name("Ease");
 		Gtk.Window.set_default_icon_name("ease");
 		
+		// use built in serializer iterators
+		Serializer.register_builtins();
+		
 		// parse command line options
 		var context = new OptionContext(_(" - a presentation editor"));
 		



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]