diff orange/serialization/archives/XMLArchive.d @ 0:f7b078e85f7f

First commit
author Jacob Carlborg <doob@me.com>
date Wed, 26 May 2010 17:19:13 +0200
parents
children ae24aae69a3b
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/orange/serialization/archives/XMLArchive.d	Wed May 26 17:19:13 2010 +0200
@@ -0,0 +1,596 @@
+/**
+ * Copyright: Copyright (c) 2010 Jacob Carlborg.
+ * Authors: Jacob Carlborg
+ * Version: Initial created: Jan 26, 2010
+ * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
+ */
+module orange.serialization.archives.XMLArchive;
+
+version (Tango)
+{
+	import tango.text.xml.DocPrinter;
+	import tango.text.xml.Document;
+	import tango.util.Convert : to;
+}
+
+import orange.serialization.archives._;
+import orange.util._;
+
+private enum ArchiveMode
+{
+	archiving,
+	unarchiving
+}
+
+class XMLArchive (U = char) : Archive!(U)
+{
+	static assert (isChar!(U), format!(`The given type "`, U, `" is not a valid type. Valid types are: "char", "wchar" and "dchar".`));
+		
+	private struct Tags
+	{
+		static const DataType structTag = "struct";	
+		static const DataType dataTag = "data";
+		static const DataType archiveTag = "archive";
+		static const DataType arrayTag = "array";
+		static const DataType objectTag = "object";
+		static const DataType baseTag = "base";
+		static const DataType stringTag = "string";
+		static const DataType referenceTag = "reference";
+		static const DataType pointerTag = "pointer";
+		static const DataType associativeArrayTag = "associativeArray";
+		static const DataType typedefTag = "typedef";
+		static const DataType nullTag = "null";
+		static const DataType enumTag = "enum";		
+	}
+
+	private struct Attributes
+	{
+		static const DataType typeAttribute = "type";
+		static const DataType versionAttribute = "version";
+		static const DataType lengthAttribute = "length";
+		static const DataType keyAttribute = "key";
+		static const DataType runtimeTypeAttribute = "runtimeType";
+		static const DataType idAttribute = "id";
+		static const DataType keyTypeAttribute = "keyType";
+		static const DataType valueTypeAttribute = "valueType";
+	}
+	
+	private
+	{
+		DataType archiveType = "org.dsource.orange.xml";
+		DataType archiveVersion = "0.1";
+		
+		Document!(U) doc;
+		doc.Node lastElement;
+		DocPrinter!(U) printer;
+		doc.Node lastElementSaved;
+		
+		bool hasBegunArchiving;
+		bool hasBegunUnarchiving;
+		
+		DataType[void*] archivedReferences;
+		void*[DataType] unarchivedReferences;
+		
+		size_t idCounter;
+	}
+	
+	this ()
+	{
+		doc = new Document!(U);
+	}
+	
+	public void beginArchiving ()
+	{
+		if (!hasBegunArchiving)
+		{
+			doc.header;
+			lastElement = doc.tree.element(null, Tags.archiveTag)
+				.attribute(null, Attributes.typeAttribute, archiveType)
+				.attribute(null, Attributes.versionAttribute, archiveVersion);
+			lastElement = lastElement.element(null, Tags.dataTag);
+			
+			hasBegunArchiving = true;
+		}		
+	}
+	
+	public void beginUnarchiving (DataType data)
+	{
+		if (!hasBegunUnarchiving)
+		{
+			doc.parse(data);	
+			hasBegunUnarchiving = true;
+			
+			auto set = doc.query[Tags.archiveTag][Tags.dataTag];
+
+			if (set.nodes.length == 1)
+				lastElement = set.nodes[0];
+			
+			else if (set.nodes.length == 0)
+				throw new ArchiveException(errorMessage!(ArchiveMode.unarchiving) ~ `The "` ~ to!(string)(Tags.dataTag) ~ `" tag could not be found.`, __FILE__, __LINE__);
+			
+			else
+				throw new ArchiveException(errorMessage!(ArchiveMode.unarchiving) ~ `There were more than one "` ~ to!(string)(Tags.dataTag) ~ `" tag.`, __FILE__, __LINE__);
+		}
+	}
+	
+	public DataType data ()
+	{
+		if (!printer)
+			printer = new DocPrinter!(U);
+		
+		return printer.print(doc);
+	}
+	
+	public void reset ()
+	{
+		hasBegunArchiving = false;
+		hasBegunUnarchiving = false;
+		idCounter = 0;
+		doc.reset;
+	}
+	
+	private void begin ()
+	{
+		lastElementSaved = lastElement;
+	}
+	
+	private void end ()
+	{
+		lastElement = lastElementSaved;
+	}
+	
+	public void archive (T) (T value, DataType key, void delegate () dg = null)
+	{
+		if (!hasBegunArchiving)
+			beginArchiving();
+		
+		restore(lastElement) in {
+			bool callDelegate = true;
+			
+			static if (isTypeDef!(T))
+				archiveTypeDef(value, key);
+			
+			else static if (isObject!(T))
+				archiveObject(value, key, callDelegate);
+			
+			else static if (isStruct!(T))
+				archiveStruct(value, key);
+			 
+			else static if (isString!(T))
+				archiveString(value, key);
+			
+			else static if (isArray!(T))
+				archiveArray(value, key);
+			
+			else static if (isAssociativeArray!(T))
+				archiveAssociativeArray(value, key);
+			
+			else static if (isPrimitive!(T))
+				archivePrimitive(value, key);
+			
+			else static if (isPointer!(T))
+				archivePointer(value, key, callDelegate);
+			
+			else static if (isEnum!(T))
+				archiveEnum(value, key);
+			
+			else
+				static assert(false, format!(`The type "`, T, `" cannot be archived.`));
+			
+			if (callDelegate && dg)
+				dg();
+		};
+	}
+	
+	private void archiveObject (T) (T value, DataType key, ref bool callDelegate)
+	{		
+		if (!value)
+		{
+			lastElement.element(null, Tags.nullTag)
+			.attribute(null, Attributes.typeAttribute, toDataType(T.stringof))
+			.attribute(null, Attributes.keyAttribute, key);
+			callDelegate = false;
+		}
+		
+		else if (auto reference = getArchivedReference(value))
+		{
+			archiveReference(key, reference);
+			callDelegate = false;
+		}
+		
+		else
+		{
+			DataType id = nextId;
+			
+			lastElement = lastElement.element(null, Tags.objectTag)
+			.attribute(null, Attributes.runtimeTypeAttribute, toDataType(value.classinfo.name))
+			.attribute(null, Attributes.typeAttribute, toDataType(T.stringof))
+			.attribute(null, Attributes.keyAttribute, key)
+			.attribute(null, Attributes.idAttribute, id);
+			
+			addArchivedReference(value, id);
+		}
+	}
+
+	private void archiveStruct (T) (T value, DataType key)
+	{
+		lastElement = lastElement.element(null, Tags.structTag)
+		.attribute(null, Attributes.typeAttribute, toDataType(T.stringof))
+		.attribute(null, Attributes.keyAttribute, key);
+	}
+	
+	private void archiveString (T) (T value, DataType key)
+	{
+		lastElement.element(null, Tags.stringTag, toDataType(value))
+		.attribute(null, Attributes.typeAttribute, toDataType(BaseTypeOfArray!(T).stringof))
+		.attribute(null, Attributes.keyAttribute, key);
+	}
+
+	private void archiveArray (T) (T value, DataType key)
+	{		
+		lastElement = lastElement.element(null, Tags.arrayTag)		
+		.attribute(null, Attributes.typeAttribute, toDataType(BaseTypeOfArray!(T).stringof))
+		.attribute(null, Attributes.lengthAttribute, toDataType(value.length))
+		.attribute(null, Attributes.keyAttribute, key);
+	}
+
+	private void archiveAssociativeArray (T) (T value, DataType key)
+	{
+		lastElement = lastElement.element(null, Tags.associativeArrayTag)		
+		.attribute(null, Attributes.keyTypeAttribute, toDataType(KeyTypeOfAssociativeArray!(T).stringof))
+		.attribute(null, Attributes.valueTypeAttribute, toDataType(ValueTypeOfAssociativeArray!(T).stringof))
+		.attribute(null, Attributes.lengthAttribute, toDataType(value.length))
+		.attribute(null, Attributes.keyAttribute, key);
+	}
+
+	private void archivePointer (T) (T value, DataType key, ref bool callDelegate)
+	{
+		if (auto reference = getArchivedReference(value))
+		{
+			archiveReference(key, reference);
+			callDelegate = false;
+		}
+		
+		else
+		{
+			DataType id = nextId;
+			
+			lastElement = lastElement.element(null, Tags.pointerTag)
+			.attribute(null, Attributes.keyAttribute, key)
+			.attribute(null, Attributes.idAttribute, id);
+			
+			addArchivedReference(value, id);
+		}
+	}
+	
+	private void archiveEnum (T) (T value, DataType key)
+	{
+		lastElement.element(null, Tags.enumTag, toDataType(value))
+		.attribute(null, Attributes.typeAttribute, toDataType(T.stringof))
+		.attribute(null, Attributes.keyAttribute, key);
+	}
+
+	private void archivePrimitive (T) (T value, DataType key)
+	{
+		lastElement.element(null, toDataType(T.stringof), toDataType(value))
+		.attribute(null, Attributes.keyAttribute, key);
+	}
+	
+	private void archiveTypeDef (T) (T value, DataType key)
+	{
+		lastElement = lastElement.element(null, Tags.typedefTag)
+		.attribute(null, Attributes.typeAttribute, toDataType(BaseTypeOfTypeDef!(T).stringof));
+		.attribute(null, Attributes.key, key);
+	}
+	
+	public T unarchive (T) (DataType key, T delegate (T) dg = null)
+	{
+		if (!hasBegunUnarchiving)
+			beginUnarchiving(data);
+		
+		return restore!(T)(lastElement) in {
+			T value;
+			
+			bool callDelegate = true;
+			
+			static if (isTypeDef!(T))
+				value = unarchiveTypeDef!(T)(key);
+			
+			else static if (isObject!(T))
+				value = unarchiveObject!(T)(key, callDelegate);				
+
+			else static if (isStruct!(T))
+				value = unarchiveStruct!(T)(key);
+			
+			else static if (isString!(T))
+				value = unarchiveString!(T)(key);
+			 
+			else static if (isArray!(T))
+				value = unarchiveArray!(T)(key);
+
+			else static if (isAssociativeArray!(T))
+				value = unarchiveAssociativeArray!(T)(key);
+
+			else static if (isPrimitive!(T))
+				value = unarchivePrimitive!(T)(key);
+
+			else static if (isPointer!(T))
+				value = unarchivePointer!(T)(key, callDelegate);
+			
+			else static if (isEnum!(T))
+				value = unarchiveEnum!(T)(key);
+			
+			else
+				static assert(false, format!(`The type "`, T, `" cannot be unarchived.`));
+
+			if (callDelegate && dg)
+				return dg(value);
+			
+			return value;
+		};
+	}
+
+	private T unarchiveObject (T) (DataType key, ref bool callDelegate)
+	{			
+		DataType id = unarchiveReference(key);
+		
+		if (auto reference = getUnarchivedReference!(T)(id))
+		{
+			callDelegate = false;
+			return *reference;
+		}
+		
+		auto tmp = getElement(Tags.objectTag, key, Attributes.keyAttribute, false);
+		
+		if (!tmp)
+		{
+			lastElement = getElement(Tags.nullTag, key);
+			callDelegate = false;
+			return null;
+		}
+		
+		lastElement = tmp;
+		
+		auto runtimeType = getValueOfAttribute(Attributes.runtimeTypeAttribute);
+		auto name = fromDataType!(string)(runtimeType);
+		id = getValueOfAttribute(Attributes.idAttribute);
+				
+		T result;
+		
+		static if (is(typeof(T._ctor)))
+		{
+			ParameterTupleOf!(typeof(T._ctor)) params;			
+			result = factory!(T, typeof(params))(name, params);
+		}
+		
+		else
+			 result = factory!(T)(name);
+		
+		addUnarchivedReference(result, id);
+		
+		return result;
+	}
+
+	private T unarchiveStruct (T) (DataType key)
+	{
+		lastElement = getElement(Tags.structTag, key);
+		
+		return T.init;
+	}
+	
+	private T unarchiveString (T) (DataType key)
+	{
+		return fromDataType!(T)(getElement(Tags.stringTag, key).value);
+	}
+
+	private T unarchiveArray (T) (DataType key)
+	{			
+		T value;
+		
+		lastElement = getElement(Tags.arrayTag, key);
+		auto length = getValueOfAttribute(Attributes.lengthAttribute);
+		value.length = fromDataType!(size_t)(length);
+		
+		return value;
+	}
+
+	private T unarchiveAssociativeArray (T) (DataType key)
+	{		
+		lastElement = getElement(Tags.associativeArrayTag, key);
+		
+		return T.init;
+	}
+
+	private T unarchivePointer (T) (DataType key, ref bool callDelegate)
+	{
+		DataType id = unarchiveReference(key);
+		
+		if (auto reference = getUnarchivedReference!(T)(id))
+		{
+			callDelegate = false;
+			return *reference;
+		}
+
+		lastElement = getElement(Tags.pointerTag, key);
+		id = getValueOfAttribute(Attributes.idAttribute);
+				
+		T result = new BaseTypeOfPointer!(T);
+		
+		addUnarchivedReference(result, id);
+		
+		return result;
+	}
+	
+	private T unarchiveEnum (T) (DataType key)
+	{
+		return fromDataType!(T)(getElement(Tags.enumTag, key).value);
+	}
+
+	private T unarchivePrimitive (T) (DataType key)
+	{		
+		return fromDataType!(T)(getElement(toDataType(T.stringof), key).value);
+	}
+	
+	private T unarchiveTypeDef (T) (DataType key)
+	{
+		lastElement = getElement(Tags.typedefTag, key);
+		
+		return T.init;
+	}
+	
+	public AssociativeArrayVisitor!(KeyTypeOfAssociativeArray!(T), ValueTypeOfAssociativeArray!(T)) unarchiveAssociativeArrayVisitor (T)  ()
+	{
+		return AssociativeArrayVisitor!(KeyTypeOfAssociativeArray!(T), ValueTypeOfAssociativeArray!(T))(this);
+	}
+	
+	public void archiveBaseClass (T : Object) (DataType key)
+	{
+		lastElement = lastElement.element(null, Tags.baseTag)
+		.attribute(null, Attributes.typeAttribute, toDataType(T.stringof))
+		.attribute(null, Attributes.keyAttribute, key);
+	}
+	
+	public void unarchiveBaseClass (T : Object) (DataType key)
+	{
+		lastElement = getElement(Tags.baseTag, key);
+	}
+	
+	template errorMessage (ArchiveMode mode = ArchiveMode.archiving)
+	{
+		static if (mode == ArchiveMode.archiving)
+			const errorMessage = "Could not continue archiving due to unrecognized data format: ";
+			
+		else static if (mode == ArchiveMode.unarchiving)
+			const errorMessage = "Could not continue unarchiving due to unrecognized data format: ";
+	}
+	
+	private doc.Node getElement (DataType tag, DataType key, DataType attribute = Attributes.keyAttribute, bool throwOnError = true)
+	{
+		auto set = lastElement.query[tag].attribute((doc.Node node) {			
+			if (node.name == attribute && node.value == key)
+				return true;
+			
+			return false;
+		});
+		
+		if (set.nodes.length == 1)
+			return set.nodes[0].parent;
+		
+		else
+		{
+			if (throwOnError)
+			{
+				if (set.nodes.length == 0)
+					throw new ArchiveException(`Could not find an element "` ~ to!(string)(tag) ~ `" with the attribute "` ~ to!(string)(Attributes.keyAttribute) ~ `" with the value "` ~ to!(string)(key) ~ `".`, __FILE__, __LINE__);
+				
+				else
+					throw new ArchiveException(`Could not unarchive the value with the key "` ~ to!(string)(key) ~ `" due to malformed data.`, __FILE__, __LINE__);
+			}
+			
+			return null;
+		}		
+	}
+	
+	private DataType getValueOfAttribute (DataType attribute)
+	{
+		auto set = lastElement.query.attribute(attribute);
+		
+		if (set.nodes.length == 1)
+			return set.nodes[0].value;
+		
+		else if (set.nodes.length == 0)
+			throw new ArchiveException(`Could not find the attribute "` ~ to!(string)(attribute) ~ `".`, __FILE__, __LINE__);
+		
+		else
+			throw new ArchiveException(`Could not unarchive the value of the attribute "` ~ to!(string)(attribute) ~ `" due to malformed data.`, __FILE__, __LINE__);
+	}
+	
+	private void addArchivedReference (T) (T value, DataType id)
+	{
+		static assert(isReference!(T), format!(`The given type "`, T, `" is not a reference type, i.e. object or pointer.`));
+		
+		archivedReferences[cast(void*) value] = id;
+	}
+	
+	private void addUnarchivedReference (T) (T value, DataType id)
+	{
+		static assert(isReference!(T), format!(`The given type "`, T, `" is not a reference type, i.e. object or pointer.`));
+		
+		unarchivedReferences[id] = cast(void*) value;
+	}
+	
+	private DataType getArchivedReference (T) (T value)
+	{
+		if (auto tmp = cast(void*) value in archivedReferences)
+			return *tmp;
+		
+		return null;
+	}
+	
+	private T* getUnarchivedReference (T) (DataType id)
+	{
+		if (auto reference = id in unarchivedReferences)
+			return cast(T*) reference;
+		
+		return null;
+	}
+	
+	private DataType nextId ()
+	{
+		return toDataType(idCounter++);
+	}
+	
+	private void archiveReference (DataType key, DataType id)
+	{		
+		lastElement.element(null, Tags.referenceTag, id)
+		.attribute(null, Attributes.keyAttribute, key);
+	}
+	
+	private DataType unarchiveReference (DataType key)
+	{	
+		auto element = getElement(Tags.referenceTag, key, Attributes.keyAttribute, false);
+		
+		if (element)
+			return element.value;
+		
+		return cast(DataType) null;
+	}
+	
+	private struct AssociativeArrayVisitor(Key, Value)
+	{
+		private XMLArchive archive;
+		
+		static AssociativeArrayVisitor opCall (XMLArchive archive)
+		{
+			AssociativeArrayVisitor aai;
+			aai.archive = archive;
+			
+			return aai;
+		}
+		
+		int opApply(int delegate(ref Key, ref Value) dg)
+		{  
+			int result;
+			
+			foreach (node ; archive.lastElement.children)
+			{
+				restore(archive.lastElement) in {
+					archive.lastElement = node;
+					
+					if (node.attributes.exist)
+					{
+						Key key = to!(Key)(archive.getValueOfAttribute(Attributes.keyAttribute));
+						Value value = to!(Value)(node.value);
+						
+						result = dg(key, value);	
+					}		
+				};
+				
+				if (result)
+					break;
+			}
+			
+			return result;
+		}
+	}
+}
\ No newline at end of file