Source: lan/serializations.js

/**
 *  @fileoverview
 *  Classes and methods to support serialization of ObjectEx.
 *
 *  @author Partridge Jiang
 */

/*
 * requires /lan/classes.js
 * requires /lan/xmlJsons.js
 */

var
/**
 * @class
 * @description Base class of all concrete object serializers.
 * Object serializer provides general procedures to save/load an object. However, classes
 *   inherited from ObjectEx can define their custom save/load. There are two types of custom
 *   methods can be applied.
 * 1. Custom object save / load methods. Those methods should be named as doSaveObj / doLoadObj, each has
 *   the same parameters: (obj, storageNode, serializer) where obj is the object to be saved,
 *   serializer is the serializer instance currently used.
 * 2. Custom property save / load methods. Those methods should be named as doSaveProp / doLoadProp, each has
 *   the same parameters: (obj, prop, storageNode, serializer) where prop is the property info.
 *   Those property save / load methods are especially useful when some certain property in an object
 *   have to be serialize/deserialized specially. If those methods are defined, all properties will be
 *   passed to such methods to save or load. If only a few (not all) properties should be handled specially,
 *   you can just return null in those methods, and then the default save / load process of serializer will
 *   take the action. Otherwise, please return a true value.
 */
ObjSerializer = Class.create(
/** @lends ObjSerializer# */
{
	/** @private */
	CLASS_NAME: 'ObjSerializer',

	/**
	 * Convert simple value to a serializable form. Used for saving object. Descendants can override this.
	 * @param {Variant} value
	 * @returns {Variant}
	 * @private
	 */
	serializeValue: function(value)
	{
		return value;  // do no conversion here
	},

	/**
	 * Convert a serialized value to its origin form. Used for loading object. Descendants can override this.
	 * @param {Variant} value
	 * @param {String} preferedType 'string', 'number', 'boolean' and so on
	 * @returns {Variant}
	 * @private
	 */
	deserializeValue: function(value, preferedType)
	{
		return value;   // do no conversion here
	},
	/**
	 * Convert a property or field name to a suitable storage form. Used for saving object. Descendants can override this.
	 * @param {String} name Property or field name
	 * @returns {String} Converted storage name.
	 * @private
	 */
	propNameToStorageName: function(name)
	{
		return name;  // do no conversion here
	},
	/**
	 * Convert a storage name to its origin form. Used for loading object. Descendants can override this.
	 * @param {String} name Storage name
	 * @returns {String} Original property or field name.
	 * @private
	 */
	storageNameToPropName: function(name)
	{
		return name;  // do no conversion here
	},

	/**
	 * Get custom save method of obj. Usually obj.doSaveObj
	 * @param {Variant} obj Should be a {@link ObjectEx} or a normal object.
	 * @returns {Function} Custom save method or null.
	 * @private
	 */
	getObjCustomSaveMethod: function(obj)
  {
    return (obj.doSaveObj);
  },
	/**
	 * Get custom load method of obj. Usually obj.doLoadObj
	 * @param {Variant} obj Should be a {@link ObjectEx} or a normal object.
	 * @returns {Function} Custom load method or null.
	 * @private
	 */
	getObjCustomLoadMethod: function(obj)
  {
    return (obj.doLoadObj);
  },
	/**
	 * Get custom save method of a special property of obj. Usually obj.doSaveProp
	 * @param {ObjectEx} obj
	 * @returns {Function} Custom save method or null.
	 * @private
	 */
	getObjCustomPropSaveMethod: function(obj)
  {
    return (obj.doSaveProp);
  },
	/**
	 * Get custom load method of a special property of obj. Usually obj.doLoadProp
	 * @param {ObjectEx} obj Should be a {@link ObjectEx} or a normal object.
	 * @returns {Function} Custom load method or null.
	 * @private
	 */
	getObjCustomPropLoadMethod: function(obj)
  {
    return (obj.doLoadProp);
  },
	/**
	 * Check if value is undefined
	 * @param {Variant} value
	 * @returns {Bool}
	 * @private
	 */
	isUndefined: function(value)
	{
		return DataType.isUndefinedValue(value);
	},
	/**
	 * Check if value is null
	 * @param {Variant} value
	 * @returns {Bool}
	 * @private
	 */
	isNull: function(value)
	{
		return (value === null);
	},
	/**
	 * Check if value is NaN
	 * @param {Variant} value
	 * @returns {Bool}
	 * @private
	 */
	isNaN: function(value)
	{
		return value != value;
	},
	/**
	 * Check if value is a function.
	 * @param {Variant} value
	 * @returns {Bool}
	 * @private
	 */
	isFunction: function(value)
	{
		return DataType.isFunctionValue(value);
	},
	/**
	 * Check if an value is an Array
	 * @param {Variant} value
	 * @returns {Bool}
	 * @private
	 */
	isArray: function(value)
	{
		return DataType.isArrayValue(value);
	},
	/**
	 * Check if an value is an instance of Date.
	 * @param {Variant} value
	 * @returns {Bool}
	 * @private
	 */
	isDate: function(value)
	{
		return DataType.isDateValue(value);
	},
	/**
	 * Check if an value is an Object
	 * @param {Variant} value
	 * @returns {Bool}
	 * @private
	 */
	isObject: function(value)
	{
		return DataType.isObjectValue(value);
	},
	/**
	 * Check if an value is an instance of ObjectEx
	 * @param {Variant} value
	 * @returns {Bool}
	 * @private
	 */
	isObjectEx: function(value)
	{
		return DataType.isObjectExValue(value);
	},
	/**
	 * Check if value is Number or String or Boolean, or can be saved in simple form (like Date).
	 * @param {Variant} value
	 * @returns {Bool}
	 * @private
	 */
	isSimpleType: function(value)
	{
		var s = typeof(value);
		return (s == 'string') || (s == 'number') || (s == 'boolean') || (s == 'undefined')
			|| (this.isNull(value));
	},
	/**
	 * Check if value is Object or Array
	 * @param {Variant} value
	 * @returns {Bool}
	 * @private
	 */
	isComplexType: function(value)
	{
		return (!this.isSimpleType(value));
	},

	// Methods about storage node
	/**
	 * Create a sub node of storageNode. Descendants should override this method.
	 * @param {Object} storageNode
	 * @param {String} name
	 * @param {Bool} isForArray Whether this child node is used to store an array object.
	 * @returns {Variant} Sub node created.
	 * @private
	 */
	createChildStorageNode: function(storageNode, name, isForArray)
	{
		// do nothing here
	},
	/**
	 * Create and append a sub node under parentNode to store array item.
	 * @param {Object} parentNode
	 * @param {String} name
	 * @param {Bool} isForArray Whether this child node is used to store an array object.
	 * @returns {Variant} Sub node created.
	 * @private
	 */
	appendArrayItemStorageNode: function(parentNode, name, isForArray)
	{
		// do nothing here
	},
	/** @private */
	getNameForArrayItemStorageNode: function(arrayItemObj)
	{
		/*
		var nodeName = arrayItemObj.getSerializationName? arrayItemObj.getSerializationName(): null;
		if (!nodeName)
			nodeName = this.getDefaultArrayItemStorageName();
		*/
		var nodeName = this.getDefaultArrayItemStorageName();
		return nodeName;
	},
	/**
	 * Get a sub node with nodeName
	 * @param {Object} parentNode
	 * @param {Object} nodeName
	 * @private
	 */
	getChildStorageNode: function(parentNode, nodeName)
	{
		//do nothing here
	},
	/**
	 * Get all child nodes storing items in an array node
	 * @param {Object} arrayNode
	 * @private
	 */
	getAllArrayItemStorageNodes: function(arrayNode)
	{
		// do nothing here
	},
	/**
	 * Returns all storage names stored in this node
	 * @param {Object} storageNode
	 * @returns {Array} Array of stored names.
	 * @private
	 */
	getAllStoredStorageNames: function(storageNode)
	{
		// do nothing here
	},
	/**
	 * Set a special type tag to storageNode.
	 * @param {Object} storageNode
	 * @param {String} explicitType
	 * @private
	 */
	setStorageNodeExplicitType: function(storageNode, explicitType)
	{
		// do nothing here
	},
	/**
	 * Get explicit type information of a storageNode. If not found, returns null
	 * @param {Object} storageNode
	 * @returns {String}
	 * @private
	 */
	getStorageNodeExplicitType: function(storageNode)
	{
		// do nothing here
	},
	/**
	 * Get tag name of storage node. This method is very useful in XmlObjSerializer, but has little use in JsonObjSerializer
	 * @param {Object} storageNode
	 * @returns {String}
	 * @private
	 */
	getStorageNodeName: function(storageNode)
	{
		// do nothing here
	},
	/**
	 * Check if a node stores a complex value
	 * @param {Object} storageNode
	 * @returns {Bool}
	 * @private
	 */
	isComplexStorageNode: function(storageNode)
	{
		// do nothing here
	},
	/**
	 * Check if a node stores a complex array item value
	 * @param {Object} storageNode
	 * @returns {Bool}
	 * @private
	 */
	isComplexArrayItemStorageNode: function(storageNode)
	{
		// do nothing here
	},
	/**
	 * Check if a node stores a simple value
	 * @param {Object} storageNode
	 * @returns {Bool}
	 * @private
	 */
	isSimpleStorageNode: function(storageNode)
	{
		// do nothing here
	},
	/**
	 * Return a string of type of value.
	 * @param {Object} value
	 * @returns {String}
	 * @private
	 */
	getValueExplicitType: function(value)
	{
		/*
		if (this.isObjectEx(value))
			return value.getClassName();
		else if (this.isArray(value))
			return 'Array';
		else if (this.isDate(value))
			return 'Date';
		else
			return typeof(value);
		*/
		if (value instanceof ObjectEx)
			return value.getClassName();
		else if (this.isObject(value))
			return DataType.OBJECT;
		else if (this.isArray(value))
			return DataType.ARRAY;
		else if (this.isDate(value))
			return DataType.DATE;
		else  // simple value?
			return (typeof value);
	},

	/**
	 * Returns a default to store an array item.
	 * @returns {String}
	 * @private
	 */
	getDefaultArrayItemStorageName: function()
	{
		return 'item';
	},
	getDefaultDateStorageName: function()
	{
		return 'date';
	},

	// Methods used for save
	/**
	 * Save an object to destNode.
	 * @param {Object} obj Object to save, can be a normal Object or {@link ObjectEx}.
	 * @param {Object} storageNode Node to save data. Each concrete serializer has its own type of node to store data.
	 * @param {Hash} options Save options. Can have the following fields:
	 *   {
	 *     saveDefaultValues: Bool,
	 *     propFilter: function(prop, obj), return value decides which property should be saved
	 *   }
	 */
	save: function save(obj, storageNode, options)
	{
		if (typeof(obj) == 'object')
		{
			var customSaveMethod = this.getObjCustomSaveMethod(obj);
			if (customSaveMethod)
			{
				return customSaveMethod(obj, storageNode, this);
			}
		}
		var result = this.doSave(obj, storageNode, options || {});
		if (result && this.isFunction(obj.saved))
			obj.saved();
		return result;
	},

	/**
	 * The real save job is done here. Descendants can override this.
	 * @param {Object} obj
	 * @param {Object} storageNode
	 * @param {Hash} options Save options.
	 * @private
	 */
	doSave: function(obj, storageNode, options)
	{
		if (typeof(obj) == 'object')
		{
			if (obj instanceof ObjectEx)  // ObjectEx
				this.doSaveObjectEx(obj, storageNode, options);
			else if (this.isArray(obj))  // Array
				this.doSaveArray(obj, storageNode);
			else if (this.isDate(obj))  // date
				this.doSaveDate(obj, storageNode);
			else  // a normal object
				this.doSaveSimpleObject(obj, storageNode);
		}
		this.setStorageNodeExplicitType(storageNode, this.getValueExplicitType(obj));
		return obj;
		//return storageNode;
	},
	/**
	 * Save a normal JS object to storageNode. Serialize all fields.
	 * @param {Object} obj
	 * @param {Object} storageNode
	 * @private
	 */
	doSaveSimpleObject: function(obj, storageNode)
	{
		for (var fieldName in obj)
		{
			if (!obj.hasOwnProperty(fieldName))
				continue;
			var fieldValue = obj[fieldName];
			var explicitType;
			if (this.isComplexType(fieldValue))
				explicitType = this.getValueExplicitType(fieldValue);
			this.doSaveFieldValue(obj, fieldName, fieldValue, storageNode, explicitType);
		}
	},
	/**
	 * Save an array to storageNode. Serialize all items.
	 * @param {Array} arrayObj
	 * @param {Object} storageNode
	 * @private
	 */
	doSaveArray: function(arrayObj, storageNode)
	{
		for (var i = 0, l = arrayObj.length; i < l; ++i)
		{
			var item = arrayObj[i];
			var nodeName = this.getNameForArrayItemStorageNode(item);
			if (this.isComplexType(item))
			{
				var subNode = this.appendArrayItemStorageNode(storageNode, this.propNameToStorageName(nodeName), this.isArray(item));
				this.setStorageNodeExplicitType(subNode, this.getValueExplicitType(item));
				this.save(item, subNode);
			}
			else // simple type
				this.doAppendArrayItemSimpleValue(this.serializeValue(item), typeof(item), storageNode);
		}
	},
	/**
	 * Save an date to storageNode..
	 * @param {Date} obj
	 * @param {Object} storageNode
	 * @private
	 */
	doSaveDate: function(obj, storageNode)
	{
		var s = obj.toUTCString();
		this.doSaveSimpleValue(obj,
			this.propNameToStorageName(this.getDefaultDateStorageName()),
			this.serializeValue(s), DataType.DATE, storageNode);
	},
	/**
	 * Save a instance of {@link ObjectEx} to storageNode. Serialize all serializable properties.
	 * @param {ObjectEx} obj
	 * @param {Object} storageNode
	 * @param {Hash} options Save options.
	 * @private
	 */
	doSaveObjectEx: function(obj, storageNode, options)
	{
		var props = obj.getAllPropList();
		var propFilter = options.propFilter;
		for (var i = 0, l = props.getLength(); i < l; ++i)
		{
			var prop = props.getPropInfoAt(i);
			var needSave = propFilter? propFilter(prop, obj): true;

			if (!needSave)
				continue;

			var customSaveMethod = this.getObjCustomPropSaveMethod(obj);
			var saved = false;
			if (customSaveMethod)
				saved = customSaveMethod(obj, prop, storageNode, this);
			if (!!saved)
				continue;

			if (prop.serializable)  // a serialzable property, handle
				this.doSaveObjectExProp(obj, prop, storageNode, options);
		}
	},
	/**
	 * Save a property of instance of {@link ObjectEx}.
	 * @param {ObjectEx} obj Object to save.
	 * @param {Object} prop Property to save.
	 * @param {Object} storageNode
	 * @param {Hash} options Save options.
	 * @private
	 */
	doSaveObjectExProp: function(obj, prop, storageNode, options)
	{
		var propName = prop.name;
		var propType = prop.dataType;
		var propValue = /* obj.getPropStoreFieldValue(propName); // */ obj.getPropValue(propName);
		if (options.saveDefaultValues || (propValue !== prop.defaultValue))  // if value is still the default one, no need to save
		{
			var explicitType;
			if (propValue instanceof ObjectEx)
			{
				if (propValue.getClassName() != propType)
				{
					explicitType = propValue.getClassName();
				}
			}
			else
			{
				if (this.isComplexType(propValue))
				{
					explicitType = this.getValueExplicitType(propValue);
					if (explicitType == propType)  // already has type info, no need to set explicit type
						explicitType = undefined;
				}
			}
			this.doSaveFieldValue(obj, propName, propValue, storageNode, explicitType);
		}
	},
	/**
	 * Save a name=value pair to current storageNode
	 * @param {Object} obj
	 * @param {String} fieldName
	 * @param {Variant} fieldValue
	 * @param {Object} storageNode
	 * @param {String} explicitType
	 * @private
	 */
	doSaveFieldValue: function(obj, fieldName, fieldValue, storageNode, explicitType)
	{
		if (typeof(fieldValue) != 'function')  // can not save a function currently
		{
			if (this.isComplexType(fieldValue)) // a complex value, whether an ObjectEx or Object or Array
			{
				subNode = this.createChildStorageNode(storageNode, this.propNameToStorageName(fieldName), this.isArray(fieldValue));
				if (explicitType)
					this.setStorageNodeExplicitType(subNode, explicitType);
				this.save(fieldValue, subNode);
			}
			else // a simple value
			{
				this.doSaveSimpleValue(obj, this.propNameToStorageName(fieldName),
				  this.serializeValue(fieldValue), this.getValueExplicitType(fieldValue), storageNode);
			}
		}
	},
	/**
	 * Save a simple value (non-object, just number, boolean or string) to current storageNode.
	 *   The name and value should already be converted.
	 * @param {Object} obj
	 * @param {String} storageName
	 * @param {Variant} storageValue
	 * @param {String} valueType Type of original value.
	 * @param {Object} storageNode
	 * @private
	 */
	doSaveSimpleValue: function(obj, storageName, storageValue, valueType, storageNode)
	{
		// do nothing here
	},
	/**
	 * Append a simple value to an array storage node.
	 * @param {Variant} storageValue
	 * @param {String} valueType Type of original value.
	 * @param {Object} arrayStorageNode
	 * @private
	 */
	doAppendArrayItemSimpleValue: function(storageValue, valueType, arrayStorageNode)
	{
		// do nothing here
	},

	// Methods to load data
	/**
	 * load an object from storageNode.
	 * @param {Object} obj Object to load, can be a normal Object or {@link ObjectEx}.
	 *   Leave this param to null will force the serializer create a new instance.
	 * @param {Object} storageNode Node to save data. Each concrete serializer has its own type of node to store data.
	 * @returns {Object} Object loaded.
	 */
	load: function(obj, storageNode)
	{
		if (!obj)  // obj is null, create new one
		{
			var objType = this.getStorageNodeExplicitType(storageNode);
			if (objType)
				obj = DataType.createInstance(objType);
			else if (this.doLoadUntypedNode)
				return this.doLoadUntypedNode(storageNode);
			else
				return null;   // can not load
		}
		if (typeof(obj) === 'object')
		{
			var customLoadMethod = this.getObjCustomLoadMethod(obj);
			if (customLoadMethod)
			{
				return customLoadMethod(obj, storageNode, this);
			}
		}
		var result = this.doLoad(obj, storageNode);
		if (result && this.isFunction(obj.loaded))
			obj.loaded();
		return result;
	},
	/**
	 * The real load job is done here. Descendants can override this.
	 * @param {Object} obj
	 * @param {Object} storageNode
	 * @private
	 */
	doLoad: function(obj, storageNode)
	{
		var explicitType = this.getStorageNodeExplicitType(storageNode);
		if (typeof(obj) == 'object')
		{
			if (obj instanceof ObjectEx)  // ObjectEx
				obj = this.doLoadObjectEx(obj, storageNode);
			else if (this.isArray(obj)) // Array
				obj = this.doLoadArray(obj, storageNode);
			else if (this.isDate(obj))  // date
				obj = this.doLoadDate(obj, storageNode);
			else // a normal object
 				obj = this.doLoadSimpleObject(obj, storageNode);
		}
		return obj;
	},
	/**
	 * Load an array from storageNode. Deserialize all items.
	 * @param {Array} arrayObj
	 * @param {Object} storageNode
	 * @private
	 */
	doLoadArray: function(arrayObj, storageNode)
	{
		var itemNodes = this.getAllArrayItemStorageNodes(storageNode);
		for (var i = 0, l = itemNodes.length; i < l; ++i)
		{
			var node = itemNodes[i];
			//if (node)
			{
				if (this.isComplexArrayItemStorageNode(node))
				{
					var obj;
					var valueType = this.getStorageNodeExplicitType(node) || this.getStorageNodeName(node);
					if (valueType) // complex value
					{
						obj = DataType.createInstance(valueType);
						this.load(obj, node);
					}
					else
					{
						// guess it is an object
						obj = {};
						this.load(obj, node);
					}
					arrayObj.push(obj);
				}
				else // simple value
 				{
					var value = this.deserializeValue(this.doGetArrayItemSimpleStorageValue(node));
					arrayObj.push(value);
				}
			}
		}
		return arrayObj;
	},
	/**
	 * Load a date object from storageNode.
	 * @param {Date} obj
	 * @param {Object} storageNode
	 * @private
	 */
	doLoadDate: function(obj, storageNode)
	{
		var storageName = this.propNameToStorageName(this.getDefaultDateStorageName());
		var s = this.doLoadSimpleValue(obj, storageName, DataType.DATE, storageNode);
		if (s)
		{
			var r = new Date(s);
			//obj = r;
			obj.copyFrom(r);
			return obj;
		}
		else
			return undefined;
	},
	/**
	 * Load a normal JS object from storageNode. Deserialize all fields.
	 * @param {Object} obj
	 * @param {Object} storageNode
	 * @private
	 */
	doLoadSimpleObject: function(obj, storageNode)
	{
		// fetch all stored fields
		var storageNames = this.getAllStoredStorageNames(storageNode);
		for (var i = 0, l = storageNames.length; i < l; ++i)
		{
			var storageName = storageNames[i];
			var fieldName = this.storageNameToPropName(storageName);
			// check if it is a complex value
			var subNode = this.getChildStorageNode(storageNode, storageName);
			if (subNode)  // complex value
			{
				var subObj;
				var explicitType = this.getStorageNodeExplicitType(subNode);
				if (explicitType)
				{
					subObj = DataType.createInstance(explicitType);
				}
				else
					subObj = {};  // guess it is a object
				this.load(subObj, subNode);
				obj[fieldName] = subObj;
			}
			else // simple type
			{
				var value = this.doLoadSimpleValue(obj, storageName, null, storageNode);  // value type is unknown
				obj[fieldName] = value;
			}
		}
		return obj;
	},
	/**
	 * Load a instance of {@link ObjectEx} to storageNode. Deserialize all serializable properties.
	 * @param {ObjectEx} obj
	 * @param {Object} storageNode
	 * @private
	 */
	doLoadObjectEx: function(obj, storageNode)
	{
		var props = obj.getAllPropList();
		for (var i = 0, l = props.getLength(); i < l; ++i)
		{
			var prop = props.getPropInfoAt(i);

			if (!prop.serializable)  // not a serialzable property, bypass
				continue;

			var customLoadMethod = this.getObjCustomPropLoadMethod(obj);
			var loaded = false;
			if (customLoadMethod)
			{
				loaded = customLoadMethod(obj, prop, storageNode, this);
			}
			if (!!loaded)
				continue;

			this.doLoadObjectExProp(obj, prop, storageNode);
		}
		return obj;
	},
	/**
	 * Load a property of instance of {@link ObjectEx}.
	 * @param {ObjectEx} obj Object to save.
	 * @param {Object} prop Property to save.
	 * @param {Object} storageNode
	 * @private
	 */
	doLoadObjectExProp: function(obj, prop, storageNode)
	{
		var propName = prop.name;
		var propType = prop.dataType;
		var propValue = this.doLoadFieldValue(obj, propName, storageNode, propType);

		if ((propValue !== null) && (propValue !== undefined))
		{
			obj.setPropValue(propName, propValue, true);  // set value and ignore read only
		}
	},
	/**
	 * Load a value from current storageNode
	 * @param {Object} obj
	 * @param {String} fieldName
	 * @param {Object} storageNode
	 * @param {String} supposedValueType
	 * @returns {Variant} Value loaded
	 * @private
	 */
	doLoadFieldValue: function(obj, fieldName, storageNode, supposedValueType)
	{
		var result;
		var handled = false;
		if (DataType.isComplexType(supposedValueType))  // a complex type, should check sub node
		{
			var subNode = this.getChildStorageNode(storageNode, this.propNameToStorageName(fieldName));
			if (subNode)
			{
				// check subNode explicit type
				var explicitType = this.getStorageNodeExplicitType(subNode);
				if (explicitType && (explicitType != supposedValueType))  // explicitType and supposedValueType not match, follow explicitType
				{
					result = DataType.createInstance(explicitType);
				}
				else
					result = DataType.createInstance(supposedValueType);
				this.load(result, subNode);
				handled = true;
			}
		}
		if (DataType.isSimpleType(supposedValueType) || (!handled))  // simple type, or load complex subnode not found, load directly
		{
			result = this.doLoadSimpleValue(obj, this.propNameToStorageName(fieldName), supposedValueType, storageNode);
		}
		return result;
	},
	/**
	 * Load a simple value (non-object, just number, boolean or string) from current storageNode.
	 * @param {Object} obj
	 * @param {String} storageName
	 * @param {String} valueType Type of original value.
	 * @param {Object} storageNode
	 * @private
	 */
	doLoadSimpleValue: function(obj, storageName, valueType, storageNode)
	{
		var storageValue = this.doLoadSimpleStorageValue(storageName, storageNode);
		return this.deserializeValue(storageValue, valueType);
	},
	/** @private */
	doLoadSimpleStorageValue: function(storageName, storageNode)
	{
		// do nothing here
	},
	/**
	 * Get simple storage value from an array item node
	 * @param {Object} arrayItemStorageNode
	 * @returns {Variant}
	 * @private
	 */
	doGetArrayItemSimpleStorageValue: function(arrayItemStorageNode)
	{
		// do nothing here
	}
});

var
/**
 * Use JSON to do serialization job.
 * @class
 * @augments ObjSerializer
 */
JsonObjSerializer = Class.create(ObjSerializer,
/** @lends JsonObjSerializer# */
{
	/** @private */
	CLASS_NAME: 'JsonObjSerializer',
	/** @private */
	TYPE_TAG_NAME: '__type__',
	/** @private */
	createChildStorageNode: function(storageNode, name, isForArray)
	{
		var result = isForArray? []: {};
		storageNode[name] = result;
		return result;
	},
	/** @private */
	appendArrayItemStorageNode: function(parentNode, name, isForArray)
	{
		var result = isForArray? []: {};
		parentNode.push(result);
		return result;
	},
	/** @private */
	getChildStorageNode: function(parentNode, nodeName)
	{
		var result = parentNode[nodeName];
		if (this.isSimpleType(result))  // not a object or array, is a simple value rather than sub node
		{
			result = null;
		}
		return result;
	},
	/** @private */
	getAllArrayItemStorageNodes: function(arrayNode)
	{
		var result = Array.prototype.slice.call(arrayNode);
		return result;
	},
	/** @private */
	getAllStoredStorageNames: function(storageNode)
	{
		var result = [];
		for (var name in storageNode)
		{
			if (storageNode.hasOwnProperty(name)/* && (name != this.TYPE_TAG_NAME)*/)
			{
				if (name != this.TYPE_TAG_NAME)
					result.push(name);
			}
		}
		return result;
	},
	/** @private */
	isComplexStorageNode: function(storageNode)
	{
		return this.isComplexType(storageNode);
	},
	/** @private */
	isComplexArrayItemStorageNode: function(storageNode)
	{
		return this.isComplexStorageNode(storageNode);
	},
	/** @private */
	isSimpleStorageNode: function(storageNode)
	{
		return this.isSimpleType(storageNode);
	},
	/** @private */
	setStorageNodeExplicitType: function(storageNode, explicitType)
	{
		if (!this.isArray(storageNode))  // do not set array tag to a []
			storageNode[this.TYPE_TAG_NAME] = explicitType;
	},
	/** @private */
	getStorageNodeExplicitType: function(storageNode)
	{
		var r = storageNode[this.TYPE_TAG_NAME] || null;
		if ((!r) && this.isArray(storageNode))
			return DataType.ARRAY;
		else
			return r;
	},
	/** @private */
	doSaveSimpleValue: function(obj, storageName, storageValue, valueType, storageNode)
	{
		storageNode[storageName] = storageValue;
	},
	/** @private */
	doAppendArrayItemSimpleValue: function(storageValue, valueType, arrayStorageNode)
	{
		return arrayStorageNode.push(storageValue);
	},
	/** @private */
	doLoadSimpleStorageValue: function(storageName, storageNode)
	{
		return storageNode[storageName];
	},
	/** @private */
	doLoadUntypedNode: function(storageNode)
	{
		return storageNode;
	},
	/** @private */
	doGetArrayItemSimpleStorageValue: function(arrayItemStorageNode)
	{
		// simple values are directly saved in array
		return arrayItemStorageNode;
	}
});

var
/**
 * Use XML to do serialization job.
 * @class
 * @augments ObjSerializer
 */
XmlObjSerializer = Class.create(ObjSerializer,
/** @lends XmlObjSerializer# */
{
	/** @private */
	CLASS_NAME: 'XmlObjSerializer',
	/** @private */
	TYPE_TAG_NAME: 'dataType',
	/** @private */
	//ARRAY_ITEM_VALUE_ATTRIB: 'value',
	/** @private */
	//STRUE: '$TRUE',
	/** @private */
	//SFALSE: '$FALSE',
	/** @private */
	//SUNDEFINED: '$UNDEFINED',
	/** @private */
	//SNULL: '$NULL',
	/** @private */
	//SNAN: '$NAN',
	/** @private */
	//SPOSITIVE: '+',
	/** @private */
	//SNEGATIVE: '-',
	/** @private */
	/*
	SDATEPREFIX: '%',
	*/
	/** @private */
	getLocalName: function(elem)
	{
		return elem.localName || elem.baseName || elem.nodeName;
	},
	/**
	 * Get text content of an element. Only check first level text node.
	 * @param {Object} elem
	 * @returns {String}
	 * @private
	 */
	getElemTextValue: function(elem)
	{
		var result = '';
		for (var i = 0, l = elem.childNodes.length; i < l; ++i)
		{
			if (elem.childNodes[i].nodeType == Node.TEXT_NODE)
				result += elem.childNodes[i].nodeValue;
		}
		return result;
	},

	/**
	 * As XML store all data in String form, value should be transformed properly under save/load process.
	 * @private
	 */
	serializeValue: function(value)
	{
		/*
		if (this.isNull(value))
			return this.SNULL;
		else if (this.isUndefined(value))
			return this.SUNDEFINED;
		else if (this.isNaN(value))
			return this.SNAN;
		else
		{
			var vtype = DataType.getType(value);
			switch (vtype)
			{
				case 'boolean':
					return value ? this.STRUE : this.SFALSE;
					break;
				case 'number':
					// add '+' or '-' symbol before a number value
					var sign = (value >= 0)? this.SPOSITIVE: '';  //this.SNEGATIVE;
					return sign + value;
					break;
				default:  // string
					return value;
			}
		}
		*/
		return DataType.StringUtils.serializeValue(value);
	},
	/** @private */
	deserializeValue: function(value, preferedType)
	{
		return DataType.StringUtils.deserializeValue(value, preferedType);
		/*
		if (typeof(value) != 'string')
			return value;
		switch(value)
		{
			case this.STRUE: return true; break;
			case this.SFALSE: return false; break;
			case this.SNULL: return null; break;
			case this.SUNDEFINED: return undefined; break;
			case this.SNAN: return NaN; break;
			default:
			{
				if (preferedType)
				{
					switch (preferedType)
					{
						case DataType.FLOAT:
							return parseFloat(value); break;
						case DataType.INT:
							return parseInt(value); break;
						case 'number':
							return parseFloat(value); break;
						case 'boolean':
							return !!value; break;
						default:
							return value;
					}
				}
				else // guess
				{
					var firstChar = value.charAt(0);
					switch (firstChar)
					{
						case this.SPOSITIVE:
						case this.SNEGATIVE: // may be number
							{
								var s = value.substring(1);
								if (this.isAllDigitalChar(s)) // really number
									return parseFloat(value);
								else
									return value;
								break;
							}
						default:
							return value;
					}
				}
			}
		}
		*/
	},
	/** @private */
	createChildStorageNode: function(storageNode, name, isForArray)
	{
		var result = storageNode.ownerDocument.createElement(name);
		storageNode.appendChild(result);
		return result;
	},
	/** @private */
	appendArrayItemStorageNode: function(parentNode, name, isForArray)
	{
		return this.createChildStorageNode(parentNode, name, isForArray);
	},
	/** @private */
	getAllArrayItemStorageNodes: function(arrayNode)
	{
		//var result = Array.prototype.slice.call(arrayNode.getElementsByTagName('*'));
		var result = [];
		for (var i = 0, l = arrayNode.childNodes.length; i < l; ++i)
		{
			var node = arrayNode.childNodes[i];
			if (node.nodeType == Node.ELEMENT_NODE)
			{
				result.push(node);
			}
		}
		return result;
	},
	/** @private */
	getAllStoredStorageNames: function(storageNode)
	{
		var result = [];
		for (var i = 0, l = storageNode.childNodes.length; i < l; ++i)
		{
			var node = storageNode.childNodes[i];
			if (node.nodeType == Node.ELEMENT_NODE)
			{
				if (this.getLocalName(node) != this.TYPE_TAG_NAME)
					result.push(this.getLocalName(node));
			}
		}
		for (var i = 0, l = storageNode.attributes.length; i < l; ++i)
		{
			var attrib = storageNode.attributes[i];
			if (this.getLocalName(attrib) != this.TYPE_TAG_NAME)
				result.push(this.getLocalName(attrib));
		}
		return result;
	},
	/** @private */
	getChildStorageNode: function(parentNode, nodeName)
	{
		var result = null;
		for (var i = 0, l = parentNode.childNodes.length; i < l; ++i)
		{
			var node = parentNode.childNodes[i];
			if (node.nodeType == Node.ELEMENT_NODE)
			{
				if (this.getLocalName(node) == nodeName)
					result = node;
			}
		}
		return result;
	},
	/** @private */
	isComplexStorageNode: function(storageNode)
	{
		return storageNode.nodeType == Node.ELEMENT_NODE;
	},
	/** @private */
	isComplexArrayItemStorageNode: function(storageNode)
	{
		return this.isComplexStorageNode(storageNode)
			&& (storageNode.attributes.length)
			&& (!this.getElemTextValue(storageNode).trim());
			//&& (!storageNode.getAttribute(this.ARRAY_ITEM_VALUE_ATTRIB));
	},
	/** @private */
	isSimpleStorageNode: function(storageNode)
	{
		return storageNode.nodeType == Node.ATTRIBUTE_NODE;
	},
	/** @private */
	setStorageNodeExplicitType: function(storageNode, explicitType)
	{
		storageNode.setAttribute(this.TYPE_TAG_NAME, explicitType);
	},
	/** @private */
	getStorageNodeExplicitType: function(storageNode)
	{
		return storageNode.getAttribute(this.TYPE_TAG_NAME) || null;
	},
	/** @private */
	getStorageNodeName: function(storageNode)
	{
		var result = storageNode.tagName;
		if (result == this.getDefaultArrayItemStorageName())
			return null;
		else
			return result;
	},
	/** @private */
	doSaveSimpleObject: function($super, obj, storageNode)
	{
		$super(obj, storageNode);
		//storageNode.setAttribute('type', typeof(obj));
	},
	/** @private */
	doSaveArray: function($super, arrayObj, storageNode)
	{
		$super(arrayObj, storageNode);
		//storageNode.setAttribute('type', 'array');
	},
	/** @private */
	doSaveSimpleValue: function(obj, storageName, storageValue, valueType, storageNode)
	{
		storageNode.setAttribute(storageName, storageValue);
	},
	/** @private */
	doAppendArrayItemSimpleValue: function(storageValue, valueType, arrayStorageNode)
	{
		var node = this.appendArrayItemStorageNode(arrayStorageNode,
			this.propNameToStorageName(this.getDefaultArrayItemStorageName()), false);
		//this.doSaveSimpleValue(null, this.ARRAY_ITEM_VALUE_ATTRIB, storageValue, valueType, node);
		// save value directly as text node
		var textNode = node.ownerDocument.createTextNode(storageValue);
		node.appendChild(textNode);
	},
	/** @private */
	doLoadSimpleStorageValue: function(storageName, storageNode)
	{
		return storageNode.getAttribute(storageName);
	},
	/** @private */
	doGetArrayItemSimpleStorageValue: function(arrayItemStorageNode)
	{
		//return arrayItemStorageNode.getAttribute(this.ARRAY_ITEM_VALUE_ATTRIB);
		return this.getElemTextValue(arrayItemStorageNode);
	}
});

var
/**
 * Factory to register and create different types of serializer.
 * @class
 */
ObjSerializerFactory = {
	/** @private */
	_serializerClasses: {},
	/** @private */
	_defaultName: null,
	/**
	 * Register a serializer.
	 * @param {String} name Name of serializer.
	 * @param {Class} serializerClass Class of serializer. Null can also be used here to unregister a serializer.
	 * @param {Bool} isDefault Whether this serializer is the default one in factory.
	 */
	registerSerializer: function(name, serializerClass, isDefault)
	{
		var sClass = (typeof(serializerClass) == 'string')?
			ClassEx.findClass(serializerClass): //eval(serializerClass):
			serializerClass;
		ObjSerializerFactory._serializerClasses[name] = sClass;
		if (isDefault)
			ObjSerializerFactory._defaultName = name;
	},
	/**
	 * Create a new serializer by name.
	 * @param {String} name Serializer name. If name not set, the default serializer will be created.
	 * @returns {ObjSerializer} Serializer instance or null (if name not found).
	 */
	getSerializer: function(name)
	{
		var sName = name || ObjSerializerFactory._defaultName;
		if (sName)
		{
			var sClass = ObjSerializerFactory._serializerClasses[sName];
			return sClass? new sClass(): null;
		}
		else
			return null;
	}
};

// Register Json and Xml serializer
(function ()
{
	ObjSerializerFactory.registerSerializer('json', JsonObjSerializer, true);  // JSON is the default one
	ObjSerializerFactory.registerSerializer('xml', XmlObjSerializer);
})();

// extend ObjectEx and add save/load methods
ClassEx.extend(ObjectEx,
/** @lends ObjectEx# */
{
	/**
	 * Save current object to destNode.
	 * @param {Object} destNode Storage node to save object. Different serializer requires different node.
	 * @param {Variant} serializerOrName A {@link ObjSerializer} instance or name registered in {@link ObjSerializerFactory}.
	 *   Can be null to use the default serializer.
	 * @param {Hash} options
	 */
	saveObj: function(destNode, serializerOrName, options)
	{
    if (!serializerOrName)  // use default
      serializer = ObjSerializerFactory.getSerializer();
    else if (typeof(serializerOrName) == 'string')  // is name
      serializer = ObjSerializerFactory.getSerializer(serializerOrName);
		else
			serializer = serializerOrName;
		return serializer.save(this, destNode, options);
	},
	/**
	 * load current object from srcNode.
	 * @param {Object} srcNode Storage node to load object. Different serializer requires different node.
	 * @param {Variant} serializerOrName A {@link ObjSerializer} instance or name registered in {@link ObjSerializerFactory}.
	 *   Can be null to use the default serializer.
	 */
	loadObj: function(srcNode, serializerOrName)
	{
		if (!serializerOrName)  // use default
      serializer = ObjSerializerFactory.getSerializer();
    else if (typeof(serializerOrName) == 'string')  // is name
      serializer = ObjSerializerFactory.getSerializer(serializerOrName);
		else
			serializer = serializerOrName;
		return serializer.load(this, srcNode);
	}
});

// extend to ClassEx, add new save/load method
Object.extend(ClassEx, {
	/**
	 * Save an object to destNode.
	 * @param {Variant} obj
	 * @param {Object} destNode Storage node to save object. Different serializer requires different node.
	 * @param {Variant} serializerOrName A {@link ObjSerializer} instance or name registered in {@link ObjSerializerFactory}.
	 *   Can be null to use the default serializer.
	 * @param {Hash} options
	 */
	saveObj: function(obj, destNode, serializerOrName, options)
	{
		if (!serializerOrName)  // use default
			serializer = ObjSerializerFactory.getSerializer();
		else if (typeof(serializerOrName) == 'string')  // is name
			serializer = ObjSerializerFactory.getSerializer(serializerOrName);
		else
			serializer = serializerOrName;
		return serializer.save(obj, destNode, options);
	},
	/**
	 * load object from srcNode.
	 * @param {Object} srcNode Storage node to load object. Different serializer requires different node.
	 * @param {Variant} serializerOrName A {@link ObjSerializer} instance or name registered in {@link ObjSerializerFactory}.
	 *   Can be null to use the default serializer.
	 */
	loadObj: function(obj, srcNode, serializerOrName)
	{
		if (!serializerOrName)  // use default
			serializer = ObjSerializerFactory.getSerializer();
		else if (typeof(serializerOrName) == 'string')  // is name
			serializer = ObjSerializerFactory.getSerializer(serializerOrName);
		else
			serializer = serializerOrName;
		return serializer.load(obj, srcNode);
	}
});

Object.extend(DataType, {
	/**
	 * Convert a value to JSON string. If value is a complex type (Object, Array, ObjectEx),
	 * JSON serializer will be used.
	 * @param {Variant} value
	 * @param {Hash} options JSON serializer options
	 * @returns {String}
	 */
	valueToJson: function(value, options)
	{
		if (DataType.isSimpleValue(value))
		{
			return JSON.stringify(value);
		}
		else  // complex type, use serializer
		{
			var result = DataType.isArrayValue(value)? []: {};
			ClassEx.saveObj(value, result, 'json', options);
			return JSON.stringify(result);
		}
	},
	/**
	 * Convert a JSON string to value.
	 * JSON serializer may be used if the value is a complex one (Object, Array, ObjectEx, etc.).
	 * @param {String} jsonStr
	 * @returns {Variant}
	 */
	jsonToValue: function(jsonStr)
	{
		var jsonObj = JSON.parse(jsonStr);
		if (!jsonObj || DataType.isSimpleValue(jsonObj))
			return jsonObj;
		else
		{
			return ClassEx.loadObj(null, jsonObj, 'json');
		}
	}
});