view orange/serialization/archives/XMLArchive.d @ 18:3d42ea434d46

Added an error callback. Fixes #3 and #4.
author Jacob Carlborg <doob@me.com>
date Thu, 12 Aug 2010 23:24:51 +0200
parents 27c5b6c5425f
children 8ab9588b92bf 9a575087b961
line wrap: on
line source

/**
 * 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.util.Convert : to;

else
	import std.conv;

import orange.serialization.archives._;
import orange.util._;
import orange.xml.XMLDocument;

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";
		
		XMLDocument!(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 XMLDocument!(U);
	}
	
	public void beginArchiving ()
	{
		if (!hasBegunArchiving)
		{
			doc.header;
			lastElement = doc.tree.element(Tags.archiveTag)
				.attribute(Attributes.typeAttribute, archiveType)
				.attribute(Attributes.versionAttribute, archiveVersion);
			lastElement = lastElement.element(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 (errorCallback)
				{
					if (set.nodes.length == 0)
						errorCallback(new ArchiveException(errorMessage!(ArchiveMode.unarchiving) ~ `The "` ~ to!(string)(Tags.dataTag) ~ `" tag could not be found.`, __FILE__, __LINE__), [Tags.dataTag]);
					
					else
						errorCallback(new ArchiveException(errorMessage!(ArchiveMode.unarchiving) ~ `There were more than one "` ~ to!(string)(Tags.dataTag) ~ `" tag.`, __FILE__, __LINE__), [Tags.dataTag]);
				}	
			}
		}
	}
	
	public DataType data ()
	{
		/*if (!printer)
			printer = new DocPrinter!(U);
		
		return printer.print(doc);*/
		
		return doc.toString();
	}
	
	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(Tags.nullTag)
			.attribute(Attributes.typeAttribute, toDataType(T.stringof))
			.attribute(Attributes.keyAttribute, key);
			callDelegate = false;
		}
		
		else if (auto reference = getArchivedReference(value))
		{
			archiveReference(key, reference);
			callDelegate = false;
		}
		
		else
		{
			DataType id = nextId;
			
			lastElement = lastElement.element(Tags.objectTag)
			.attribute(Attributes.runtimeTypeAttribute, toDataType(value.classinfo.name))
			.attribute(Attributes.typeAttribute, toDataType(T.stringof))
			.attribute(Attributes.keyAttribute, key)
			.attribute(Attributes.idAttribute, id);
			
			addArchivedReference(value, id);
		}
	}

	private void archiveStruct (T) (T value, DataType key)
	{
		lastElement = lastElement.element(Tags.structTag)
		.attribute(Attributes.typeAttribute, toDataType(T.stringof))
		.attribute(Attributes.keyAttribute, key);
	}
	
	private void archiveString (T) (T value, DataType key)
	{
		lastElement.element(Tags.stringTag, toDataType(value))
		.attribute(Attributes.typeAttribute, toDataType(BaseTypeOfArray!(T).stringof))
		.attribute(Attributes.keyAttribute, key);
	}

	private void archiveArray (T) (T value, DataType key)
	{		
		lastElement = lastElement.element(Tags.arrayTag)		
		.attribute(Attributes.typeAttribute, toDataType(BaseTypeOfArray!(T).stringof))
		.attribute(Attributes.lengthAttribute, toDataType(value.length))
		.attribute(Attributes.keyAttribute, key);
	}

	private void archiveAssociativeArray (T) (T value, DataType key)
	{
		lastElement = lastElement.element(Tags.associativeArrayTag)		
		.attribute(Attributes.keyTypeAttribute, toDataType(KeyTypeOfAssociativeArray!(T).stringof))
		.attribute(Attributes.valueTypeAttribute, toDataType(ValueTypeOfAssociativeArray!(T).stringof))
		.attribute(Attributes.lengthAttribute, toDataType(value.length))
		.attribute(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(Tags.pointerTag)
			.attribute(Attributes.keyAttribute, key)
			.attribute(Attributes.idAttribute, id);
			
			addArchivedReference(value, id);
		}
	}
	
	private void archiveEnum (T) (T value, DataType key)
	{
		lastElement.element(Tags.enumTag, toDataType(value))
		.attribute(Attributes.typeAttribute, toDataType(T.stringof))
		.attribute(Attributes.keyAttribute, key);
	}

	private void archivePrimitive (T) (T value, DataType key)
	{
		lastElement.element(toDataType(T.stringof), toDataType(value))
		.attribute(Attributes.keyAttribute, key);
	}
	
	private void archiveTypeDef (T) (T value, DataType key)
	{
		lastElement = lastElement.element(Tags.typedefTag)
		.attribute(Attributes.typeAttribute, toDataType(BaseTypeOfTypeDef!(T).stringof));
		.attribute(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.isValid)
		{
			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 = cast(T) newInstance(name);
		
		addUnarchivedReference(result, id);
		
		return result;
	}

	private T unarchiveStruct (T) (DataType key)
	{
		auto element = getElement(Tags.structTag, key);
		
		if (element.isValid)
			lastElement = element;
		
		return T.init;
	}
	
	private T unarchiveString (T) (DataType key)
	{
		auto element = getElement(Tags.stringTag, key);
		
		if (!element.isValid)
			return T.init;			
			
		return fromDataType!(T)(element.value);
	}

	private T unarchiveArray (T) (DataType key)
	{			
		T value;
		
		auto element = getElement(Tags.arrayTag, key);
		
		if (!element.isValid)
			return T.init;
		
		lastElement = element;
		auto length = getValueOfAttribute(Attributes.lengthAttribute);
		value.length = fromDataType!(size_t)(length);
		
		return value;
	}

	private T unarchiveAssociativeArray (T) (DataType key)
	{		
		auto element = getElement(Tags.associativeArrayTag, key);
		
		if (element.isValid)		
			lastElement = element;
		
		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;
		}
		
		auto element = getElement(Tags.pointerTag, key);
		
		if (!element.isValid)
			return T.init;

		lastElement = element; 
		id = getValueOfAttribute(Attributes.idAttribute);
				
		T result = new BaseTypeOfPointer!(T);
		
		addUnarchivedReference(result, id);
		
		return result;
	}
	
	private T unarchiveEnum (T) (DataType key)
	{
		auto element = getElement(Tags.enumTag, key);
		
		if (!element.isValid)
			return T.init;
		
		return fromDataType!(T)(element.value);
	}

	private T unarchivePrimitive (T) (DataType key)
	{		
		auto element = getElement(toDataType(T.stringof), key);
		
		if (!element.isValid)
			return T.init;
		
		return fromDataType!(T)(element.value);
	}
	
	private T unarchiveTypeDef (T) (DataType key)
	{
		auto element = getElement(Tags.typedefTag, key);
		
		if (element.isValid)
			lastElement = element;
		
		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(Tags.baseTag)
		.attribute(Attributes.typeAttribute, toDataType(T.stringof))
		.attribute(Attributes.keyAttribute, key);
	}
	
	public void unarchiveBaseClass (T : Object) (DataType key)
	{
		auto element = getElement(Tags.baseTag, key);
		
		if (element.isValid)
			lastElement = element;
	}
	
	version (Tango)
	{
		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: ";
		}
	}
	
	else
	{
		mixin(
			`template errorMessage (ArchiveMode mode = ArchiveMode.archiving)
			{
				static if (mode == ArchiveMode.archiving)
					enum errorMessage = "Could not continue archiving due to unrecognized data format: ";
					
				else static if (mode == ArchiveMode.unarchiving)
					enum 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 && errorCallback)
			{
				if (set.nodes.length == 0)					
					errorCallback(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__), [tag, Attributes.keyAttribute, key]);
				
				else
					errorCallback(new ArchiveException(`Could not unarchive the value with the key "` ~ to!(string)(key) ~ `" due to malformed data.`, __FILE__, __LINE__), [tag, Attributes.keyAttribute, key]);
			}

			return doc.Node.invalid;
		}		
	}
	
	private DataType getValueOfAttribute (DataType attribute)
	{
		auto set = lastElement.query.attribute(attribute);
		
		if (set.nodes.length == 1)
			return set.nodes[0].value;
		
		else
		{
			if (errorCallback)
			{
				if (set.nodes.length == 0)
					errorCallback(new ArchiveException(`Could not find the attribute "` ~ to!(string)(attribute) ~ `".`, __FILE__, __LINE__), [attribute]);
				
				else
					errorCallback(new ArchiveException(`Could not unarchive the value of the attribute "` ~ to!(string)(attribute) ~ `" due to malformed data.`, __FILE__, __LINE__), [attribute]);
			}
		}		
	}
	
	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(Tags.referenceTag, id)
		.attribute(Attributes.keyAttribute, key);
	}
	
	private DataType unarchiveReference (DataType key)
	{	
		auto element = getElement(Tags.referenceTag, key, Attributes.keyAttribute, false);
		
		if (element.isValid)
			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;
		}
	}
}