/**
 * Copyright (c) 2007, Softamis, http://soft-amis.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Author: Alexey Luchkovsky
 * E-mail: jsoner@soft-amis.com
 *
 * Version: 1.024
 * Last modified: 06/06/2007
 */

/**
 * @fileoverview
 *
 * <p>
 * AJAX is a web development technique for creating interactive web applications.
 * AJAX allows increasing the web page's interactivity, speed, and usability.
 * </p>
 * <p>
 * Because AJAX is JavaScript at its core and JSON is a subset of
 * JavaScript they work perfectly together.
 * Combination of ability to represent the general data structures as
 * JSON and fastest way to convert text data into a usable native format
 * are going to catch the attention of a good many developers.
 * </p>
 * <p>
 * JSON is well suited to data-interchange between server and WEB client due
 * JSON notation is built into lot of the programming language and
 * JSON can be mapped easy to object-oriented systems.
 * </p>
 * COMMONS is basis for work with JSON data, which was initially designed
 * for the need of AJAX technology for master-details forms.
 * 
 * <ul>
 *  <span>JSONER include the subset of useful methods, which are the following:</span>
 *  <li> Methods for data lookup. 
 *  <li> Methods for data binding.
 *  <li> Methods of populating data from HTML form to JSON and the contrary.
 *  <li> Methods for creating HTML forms and other components.
 *  <li> Methods for comparison JSONs (quite useful when checking if the input data were changed).
 *  <li> Methods for transformation JSON to XML or HTML string for further processing.
 *  <li> Methods for transformation array of JSONs to a MAP (quite useful when expanding key to value).
 *  <li> Methods to capturing JSON property and the contrary.
 * </ul>
 * JSONER is based on event - event handler paradigm (like SAX).
 * An event-based API reports parsing events (such as the start and end of JSON node)
 * directly to the application through callbacks.
 * In both of those cases, an event-based API provides a simpler, lower-level access to JSON data structure.
 * <ul>
 * <span>JSONER includes two versions for work with event-based API:</span>
 *	<li> Event API for the tree - JSON tree walker.
 *  <li> Event API for linear structure - JSON path evaluator.
 * </ul>
 * The decision has such important advantages as flexibility and universality.
 * Event handlers encapsulate custom business logic and contain only code required to special task.
 *
 * JSONER “lookup functionality” based on Visitor pattern, which on the one hand provides for
 * high speed data search, on the other hand doesn’t set any
 * limitations on data processing, and provides for flexibility and usefulness.
 * <ul>
 *  <span>JSONER include such solutions of “lookup functionality”:</span>
 *  <li> Find first element, which corresponds the given conditions.
 *  <li> Find all the elements, which corresponds the given conditions.
 *  <li> Find the number of elements, which corresponds the given conditions.
 *  <li> Check if there are elements which corresponds the given conditions.
 * </ul>
 *
 * <ul>
 * <span>JSONER include decisions for such tasks with JSON as:</span>
 *  <li> Fast dynamic safe get value method for JSON model.
 *  <li> Dynamic safe populates a value to JSON.
 *  <li> Clones of JSON.
 *  <li> Merges JSON model.
 *  <li> Compares two JSON model on equals.
 *  <li> Converts JSON model to Map.
 * </ul>
 *
 * <ul>
 *  <span>JSONER implements "data binding" functionality:</span>
 *  <li> Collects all node attributes.
 *  <li> Collects all node children.
 *  <li> Gets the first child of the node.
 *  <li> Gets the last child of the node.
 *  <li> Add child to node.
 *  <li> Remove children.
 *  <li> Remove child. 
 * </ul>
 *
 * <p>
 * JSONER include one of the most requested tasks with JSON -
 * transformation JSON to XML for further processing.
 * The one of the advantages of JSONER is dynamic transformation of
 * JSON properties (names and values) to be compatible with further logic.
 * </p>
 * <p>
 * Task, which is not less often used with JSON, is transformation JSON to HTML structure.
 * JSONER include framework to convert JSON model to DOM model based by predefined pattern-layout structures.
 * This way allows separating the presentation of data and task of data layout and the same data.
 * So WEB UI usually has repeated the pattern-layout structures can be used multiple times for different JSON data.
 * It makes interchanging less expensive and data preparing easier.
 * </p>
 * <p>

 * <ul>
 * <span>JSONER include methods to easy the work with HTML forms,
 * such as populating data from HTML form to JSON and the contrary: </span>
 *  <li> Fills the HTML form with corresponding structure from JSON model.
 *  <li> Applies corresponding form data to JSON model.
 * </ul>
 *
 * JSONER can be downloaded free from
 * <a href="http://sourceforge.net/projects/jsontools"> http://sourceforge.net/projects/jsontools </a>.
 * Here anyone can leave his comments and wishes.
 *
 * Please submit bugs to the SourceForge bug tracker, and feature ideas/patches to the email
 * <a href="mailto:jsoner@soft-amis.com">jsoner@soft-amis.com</a>, and we’ll get on it.
 *
 * @author Alexey Luchkovsky.
 *
 */

/**
 * Class identifier.
 * @private
 */
var JSONER =
{
	version: 1.024
};

/**
  * JSONER's constuctor.
  *
  * JSONER is a collection of solutions for most commonly
  * used operations performed with JSON.
  * JSONER allows developers to easily and quickly build rich JSON based web applications
  *
  * JSONER is based on event - event handler paradigm (like SAX).
  * An event-based API reports parsing events (such as the start and end of JSON node)
  * directly to the application through callbacks.
  * In both these cases, an event-based API provides a simpler, lower-level access to JSON data structure.
  *
  * @constructor
  */
function Jsoner()
{
	/**
	  * The map used to precache JSON path - lookup functions to improve performance.
	  * The ones can be used to register custom value providers too.
	  * @type Map
	  * @see Jsoner#registerDataProvider
	  */
  this.fDataProviderCache = new HashMap();
	this.fLogger = new Logger("Jsoner");

	/**
	 * Indicates that the walking node is array.
	 * Protected method.
	 * @return {Boolean} Returns true if it true, otherwise - false.
	 *
	 * @param {Array} The JSON path as array.
	 * @param The JSON node value.
	 * @param The top level JSON object.
	 *
	 * @see #jsonTreeWalker
	 */
	this.isWalkArray = function(aName, aValue, aJson)
	{
		return COMMONS.isArray(aValue);
	};

	/**
	 * Indicates that the walking object represents JSON node.
	 * Protected method.
	 * @return {Boolean} Returns true if it true, otherwise - false.
	 *
	 * @param {Array} The JSON path as array.
	 * @param The JSON node value.
	 * @param The JSON object.
	 *
	 * @see #jsonTreeWalker
	 */
	this.isWalkNode = function(aName, aValue, aJson)
	{
		return COMMONS.isObject(aValue) && !COMMONS.isDate(aValue) && !COMMONS.isRegExp(aValue);
	};
}

/**
  * Predefined JSON event type.
  * It confirms that a branch node starting.
  * @type Number
  * @final
  */
Jsoner.JSON_NODE_START = 1;

/**
  * Predefined JSON event type.
  * It confirms that a branch node closing.
  * @type Number
  * @final
  */
Jsoner.JSON_NODE_END = 2;

/**
  * Predefined JSON event type.
  * It confirms that a leaf node starting.
  * @type Number
  * @final
  */
Jsoner.JSON_NODE_LEAF = 3;

/**
  * Predefined JSON event type.
  * It confirms that a node that represented as attribute starting.
  * @type Number
  * @final
  */
Jsoner.JSON_ATTRIBUTE = 4;

/**
  * Predefined JSON event type.
  * It confirms that a branch node cross linked.
  * @type Number
  * @final
  */
Jsoner.JSON_NODE_CROSS_LINKED = 5;

/**
  * Predefined property name.
  * The property used to break cross linked collisions.
  * @type String
  * @private
  */
Jsoner.MAGIC_HASH_CODE = "$lnk";

/**
  * Predefined cross linked node value prefix.
  * @type String
  * @final
  */
Jsoner.CROSS_LINK_PREFIX = "#link:";

/**
  * Checks if the value matches pattern.
  * @return {Boolean} Returns true if a value is matched pattern.
  *
  * @param The pattern as String or RegExp.
  * @param The value.
  *
  * <ul>
  *  <span>Available types of pattern: </span>
  *  <li> A string with wildcard asterisk (*), that substituted for any characters in a path.
  *  <li> A string without wildcard that matched to equals.
  *  <li> A RegExp pattern.
  * </ul>
  *
  * <ul>
  *  <span>Limitation: only one asterisk supported.</span>
  *  <li> The path started with asterisk (*) represents end part of the path.
  *  <li> The path end of asterisk (*) represents first part of the path.
  *  <li> The path contains asterisk (*) in the middle of text represents first and end paths of the path.
  * </ul>
  */
Jsoner.prototype.isMatch = function(aPattern, aValue)
{
	var result = false;
	if (COMMONS.isDefined(aPattern))
	{
		var string = String(aValue);
		if (COMMONS.isRegExp(aPattern))
		{
			result = aPattern.test(string);
		}
		else
		if (COMMONS.isString(aPattern))
		{
			var index = aPattern.indexOf('*');
			if (index === -1)
			{
				result = (aValue === aPattern);
			}
			else
			if (index === 0)
			{
				if (aPattern.length > 1)
				{
					var tmp = aPattern.substr(1);
					result = string.lastIndexOf(tmp) === (string.length - tmp.length);
				}
			}
			else
			{
				var tokens = aPattern.split('*');
				result = string.indexOf(tokens[0]) === 0;
				if (result && tokens.length > 1)
				{
					var lastToken = tokens[tokens.length - 1];
					result = string.lastIndexOf(lastToken) === (string.length - lastToken.length);
				}
			}
		}
	}
	return result;
};

/**
  * Factory used to create a converter based on argument type.
  * @return {Function} Returns dummy converter by undefined argument,
  * himself argument by funtion argument or proxy by Map argument.
  *
  * @param The converter template.
  */

Jsoner.prototype.converterFactory = function(aConverter)
{
	var result = COMMONS.proxy;
	if ( COMMONS.isFunction(aConverter) )
	{
    result = aConverter;
	}
	else
	if ( COMMONS.isObject(aConverter) )
	{
		result = function(aValue)
		{
			for(var name in aConverter )
			{
				if ( aConverter.hasOwnProperty(name) && this.isMatch(name, aValue) )
				{
					return aConverter[name];
				}
			}
			return aValue;
		};
	}
	return result;
};

/**
  * Populates the value to JSON object using the path based way to compute a place in JSON object.
  * @see #setValue
  *
  * @param {JSON} The JSON object.
  * @param {String} The path defined a way to compute a place in JSON model
  * and includes different type of nodes or attribute.
  * @param The value.
  * @param {Boolean} The flag defines behavior of populator if node in the path is missed.
  * If it is true, missing nodes with corresponding by path type (Object or Array)
  * will be created otherwise the value will be not populated.
  *
  * @return {JSON} The modified JSON object, useful to make chank of changes.
  */
Jsoner.prototype.populate = function(aJson, aPath, aValue, aCreateMissing)
{
  function parseSegment(aSegment)
  {
	  var result = aSegment;
	  if ( aSegment.charAt(aSegment.length - 1) === ']' )
	  {
		  var index = aSegment.indexOf("[");
		  result = {path:aSegment.substring(0, index),
			          index:aSegment.substring(index + 1, aSegment.length-1)};
		}
	  return result;
  }

	var path = aPath.split('.');
	var obj = aJson;
	var tmpObj;
	var segment;
	for (var i = 0; (i < path.length - 1) && (obj !== null); i++)
	{
		segment = parseSegment(path[i]);
		if ( COMMONS.isString(segment) )
		{
			tmpObj = obj[segment];
			if ( aCreateMissing )
			{
				if ( !COMMONS.isDefined(tmpObj) )
				{
					tmpObj = {};
					obj[segment] = tmpObj;
				}
				else
				if ( COMMONS.isArray(tmpObj) && !COMMONS.isDefined(tmpObj[0]) )
				{
					tmpObj[0] = {};
				}
			}
			obj = COMMONS.isArray(tmpObj) ? tmpObj[0] : tmpObj;
		}
		else
		{
			tmpObj = obj[segment.path];
			if ( !COMMONS.isDefined(tmpObj) && aCreateMissing )
			{
				tmpObj = [];
				obj[segment.path] = tmpObj;
			}
			else
			if ( !COMMONS.isArray(tmpObj) )
			{
				tmpObj = [tmpObj];
				obj[segment.path] = tmpObj;
			}
			obj = tmpObj;

			if ( COMMONS.isDefined(obj) )
			{
				tmpObj = obj[segment.index];
				if ( !COMMONS.isDefined(tmpObj) && aCreateMissing )
				{
					tmpObj = {};
					obj[segment.index] = tmpObj;
				}
				obj = tmpObj;
			}
		}
	}

	if ( COMMONS.isDefined(obj) )
	{
		segment = parseSegment(path[path.length - 1]);
		if ( COMMONS.isString(segment) )
		{
			obj[segment] = aValue;
		}
		else
		{
			tmpObj = obj[segment.path];
	    if ( !COMMONS.isDefined(tmpObj) )
			{
				tmpObj = [];
				obj[segment.path] = tmpObj;
			}
			else
			if ( !COMMONS.isArray(tmpObj) )
			{
				tmpObj = [tmpObj];
				obj[segment.path] = tmpObj;
			}
			tmpObj[segment.index] = aValue;
		}
	}
	return aJson;
};

/**
  * Registers data provider method by path in JSON.
  * @param {String} The path defines a way to compute a value in JSON model.
  * @param {Function} The data providing method.
  *
  * @see #getValue
  */
Jsoner.prototype.registerDataProvider = function(aPath, aFunction)
{
	this.fDataProviderCache.put(aPath, aFunction);
};

/**
  * Gets value from JSON model safe.
  * @param {JSON} The JSON object.
  * @param {String} The path defines a way to compute a value in JSON model.
  *
  * @return The value by path in JSON if data model by path is full defined
  * otherwise it returns undefined.
  */
Jsoner.prototype.getValue = function(aJson, aPath)
{
	var result = undefined;
	if ( COMMONS.isObject(aJson) )
	{
		try
		{
			result = aJson[aPath];
			if ( COMMONS.isUndefined(result) )
			{
				var fnc = this.fDataProviderCache.get(aPath);
				if ( !COMMONS.isFunction(fnc) )
				{
					var fncBody = "return aData";
					if ( aPath.length > 0)
					{
					  if ( aPath.charAt(0) === "[")
					  {
						  fncBody += aPath;
					  }
						else
					  {
						  fncBody += "." + aPath;
					  }
					}
					fnc = new Function("aData", fncBody );
					this.registerDataProvider(aPath, fnc);
				}
				result = fnc.call(this, aJson);
			}
		}
		catch(ex)
		{
		}
	}
	return result;
};

/**
  * Safe sets value to JSON model.
  *
  * @param {JSON} The JSON object.
  * @param {String} The path defines a way to compute a place in JSON model.
  * If nodes in the path are missed they will be created with corresponding to path type (Object or Array).
  * @param The Value.
  * @param {Boolean} The flag defines behavior if node in the path is missed.
  * If it is true, missing nodes with corresponding by path type (Object or Array)
  * will be created otherwise the value will be not assigned.
  * It's true by defaults.
  *
  * @return {JSON} The modified JSON object, useful to make chank of changes.
  * @see #populate
  */
Jsoner.prototype.setValue = function(aJson, aPath, aValue, aCreateMissing)
{
	var result = aJson;
	if ( COMMONS.isObject(aJson) )
	{
		var createMissing = (COMMONS.isUndefined(aCreateMissing) || aCreateMissing);
		this.populate(aJson, aPath, aValue, createMissing);
	}	
	return result;
};

/**
  * Indicates that the JSON property should be ignored.
	* @return {Boolean} If so it returns true, otherwise it returns false.
	*
  * @param {String} The property name.
  * @param The property value.
  * @param The parent object.
  *
  * The method can be overloaded to transform JSON event output.
  * @see #jsonToXML.
  */
Jsoner.prototype.isMute = function(aName, aValue, aParent)
{
	var result = aName === Jsoner.MAGIC_HASH_CODE || !aParent.hasOwnProperty(aName) || COMMONS.isFunction(aValue);
	return result;
};

/**
  * Indicates that the JSON property should be represented as an attribute.
	* @return {Boolean} If so it returns true, otherwise it returns false.
  * @param {String} The property name.
  * @param The property value.
  *
  * The method can be overloaded to transform JSON event output.
  * @see #jsonToXML.
  */
Jsoner.prototype.isAttribute = function(aName, aValue)
{
	var result = true;
	if ( aValue === null || COMMONS.isObject(aValue) )
	{
		result = COMMONS.isDate(aValue) || COMMONS.isRegExp(aValue) || aValue === null;
	}
	return result;
};

/**
  * Indicates that the JSON attribute should be represented as a text node.
  * @return {Boolean} If so it returns true, otherwise it returns false.
  * @param {String} The attribute name.
  *
  * @see #isAttribute
  */
Jsoner.prototype.isText = function(anAttributeName)
{
	return (anAttributeName === "text");
};

/**
  * Indicates that the JSON attribute should be represented as CDATA.
  * @return {Boolean} It returns true if so, otherwise it returns false.
  * @param {String} The attribute name.
  *
  * @see #isAttribute
  */
Jsoner.prototype.isCDATA = function(anAttributeName)
{
	return (anAttributeName === "PCDATA");
};

/**
  * Collects the JSON attributes to array as pair name-value
  * <samp>{name:name, value:value}</samp>.
  * @return {Array} An array of attributes.
  *
  * @param The JSON path as array or String.
  * @param The JSON node value.
  * @param The top level JSON object.
  * 
  * @see #isAttribute.
  */
Jsoner.prototype.collectAttributes = function(aPath, aValue, aJson)
{
	var result = [];
	var value;
	
	if ( COMMONS.isObject(aValue) )
	{
		for (var name in aValue)
		{
			try
			{
				value = aValue[name];
				if ( !this.isMute(name, value, aValue) && this.isAttribute(name, value) )
				{
					result.push( {name:name, value:value} );
				}
			}
			catch(ex)
			{
				this.fLogger.warning( "collectAttributes, unable to collect attribute:" + name, ex);
			}
		}
	}	
	return result;
};

/**
  * Collects the JSON children to array as pair name-value
  * <samp>{name:name, value:value}</samp>.
  * @return {Array} An array of node children.
  *
  * @param The JSON path as array or String.
  * @param The JSON node value.
  * @param The top level JSON object.
  *
  * @see #collectAttributes.
  */
Jsoner.prototype.collectChildren = function(aPath, aValue, aJson)
{
	var result = [];
	var value;
	for (var name in aValue)
	{
		try
		{
			value = aValue[name];
			if ( !this.isMute(name, value, aValue) && !this.isAttribute(name, value) )
			{
				result.push( {name:name, value:value} );
			}
		}
		catch(ex)
		{
			this.fLogger.warning( "collectChildren, unable to collect child:" + name, ex);
		}
	}
	return result;
};

/**
 * Indicates that cross link collisions resolver is turn on.
 *
 * @return {Boolean} Returns true if so, otherwise - false.
 * @param {JSON} The JSON object.
 */
Jsoner.prototype.isResolveCrossLinks = function(anObject)
{
	return true;
};

/**
  * Adds special property which used to break cross linked collisions.
  * @private
  *
  * @param {JSON} The JSON object.
  * @param The property value.
  */
Jsoner.prototype.addHashCode = function(anObject, aValue)
{
	if ( COMMONS.isObject(anObject) )
	{
    anObject[Jsoner.MAGIC_HASH_CODE] = aValue;
	}
};

/**
  * Returns value of property which uses to break cross linked collisions.
  * @private
  *
  * @param {JSON} The JSON object.
  * @return Returns property value.
  *
  */
Jsoner.prototype.getHashCode = function(anObject)
{
	var result = undefined;
	if ( COMMONS.isObject(anObject) )
	{
		result = anObject[Jsoner.MAGIC_HASH_CODE];
  }
	return result;
};

/**
  * Removes special property which uses to break cross linked collisions.
  * @private
  *
  * @param {JSON} The JSON object.
  */
Jsoner.prototype.removeHashCode = function(anObject)
{
	if ( COMMONS.isObject(anObject) && anObject[Jsoner.MAGIC_HASH_CODE] !== undefined )
	{
		anObject[Jsoner.MAGIC_HASH_CODE] = undefined;
		delete anObject[Jsoner.MAGIC_HASH_CODE];
	}
};

/**
  * Traverses the JSON object.
  * <ul>
  * <span> Types of events: </span>
  *  <li> JSON_NODE_START - a branch node starting.
  *  <li> JSON_NODE_END   - a branch node closing.
  *  <li> JSON_NODE_LEAF  - a leaf node starting.
  *  <li> JSON_ATTRIBUTE  - a node, that represented as an attribute starting.
  *  <li> JSON_CROSS_LINKED - a cross linked node starting.
  * </ul>
  * </p>
  *
  * @param {JSON} The JSON object.
  * @param {Function} The callBack function, called to process when the branch of JSON object starting
  * or closing happens.
  * <ul>
  * <span>List of arguments passed to the callBack function:</span>
  *  <li> The path to traversed node as array of string.
  *  <li> The JSON node value.
  *  <li> The collection of attributes as pair name-value <samp>{"name":name, "value":value}</samp>.
  *  <li> The event type.
  *  <li> The JSON node level.
  *  <li> The JSON node index within a parent node.</li>
  * <span> The callBack function should return true to continue tree traversing,
  * otherwise traversing is stopped. </span>
  * </ul>
  */
Jsoner.prototype.jsonTreeWalker = function(aJson, aCallBack)
{
	var level = -1;
	var path = [];

	var result = true;
	var map = new HashMap();

	function walkNode(aName, aValue, aLevel, aIndex)
	{
		var attributes, index, value;
		if (result)
		{
			if (this.isWalkArray(path, aValue, aJson))
			{
				for (var i = 0; i < aValue.length && result; i++)
				{
					walkNode.call(this, aName, aValue[i], aLevel, i);
				}
			}
			else
			{
				var currentPath = COMMONS.isDefined(aIndex) ? aName + "[" + aIndex + "]" : aName;
				if (aLevel > level)
				{
					path.push(currentPath);
					level = aLevel;
				}
				else
				{
					path[aLevel] = currentPath;
				}
				if ( this.isWalkNode(path, aValue, aJson) )
				{
					try
					{
						attributes = this.collectAttributes(path, aValue, aJson);
						index = this.getHashCode(aValue);
						if (COMMONS.isUndefined(index))
						{
							if ( this.isResolveCrossLinks(aValue) )
							{
								index = map.getSize();
								this.addHashCode(aValue, index);
								map.put(index, aName);
							}
							try
							{
								var children = this.collectChildren(path, aValue, aJson);
								if ( children.length > 0  )
								{
									try
									{
										result = aCallBack.call(this, path, aValue, attributes, Jsoner.JSON_NODE_START, aLevel, aIndex);
									}
									catch(ex)
									{
										this.fLogger.error("jsonTreeWalker, [" + path + "] call back error", ex);
									}
									if (result)
									{
										for (var i = 0; i < children.length && result; i++)
										{
											walkNode.call(this, children[i].name, children[i].value, aLevel + 1, null);
										}
										path.pop();
										level--;
										result = aCallBack.call(this, path, aValue, null, Jsoner.JSON_NODE_END, aLevel, aIndex);
									}
								}
								else
								{
									try
									{
										result = aCallBack.call(this, path, aValue, attributes, Jsoner.JSON_NODE_LEAF, aLevel, aIndex);
									}
									catch(ex)
									{
										this.fLogger.error("jsonTreeWalker, [" + path + "] call back error", ex);
									}
								}
							}
							finally
							{
								this.removeHashCode(aValue);
							}
						}
						else
						{
							value = map.get(index);
							try
							{
								result = aCallBack.call(this, path, value, attributes, Jsoner.JSON_NODE_CROSS_LINKED, aLevel, aIndex);
							}
							catch(ex)
							{
								this.fLogger.error("jsonTreeWalker, [" + path + "] call back error", ex);
							}
						}
					}
					catch(exception)
					{
						this.fLogger.error("jsonTreeWalker, traverse node " + aName + " failed", exception);
					}
				}
				else
				{
					result = aCallBack.call(this, path, aValue, null, Jsoner.JSON_ATTRIBUTE, aLevel, aIndex);
				}
			}
		}
	}

	if (COMMONS.isFunction(aCallBack))
	{
		if ( this.isWalkArray([], aJson, aJson))
		{
			walkNode.call(this, "", aJson, 0);
		}
		else
		{
			var value;
			for (var name in aJson)
			{
				try
				{
					value = aJson[name];
					if ( !this.isMute(name, value, aJson) )
					{
						walkNode.call(this, name, value, 0);
					}
				}
				catch(ex)
				{
					this.fLogger.info("jsonTreeWalker, unable to walk object property: " + name, ex );
				}
			}
		}
		map.clear();
	}
	else
	{
		this.fLogger.error("jsonTreeWalker, callback function undefined");
	}
};

/**
 * Returns last property name by JSON path that represented as an array.
 * @param {JSON} The JSON path as array.
 *
 * @return {String} Returns last token by JSON path.
 */
Jsoner.prototype.getLastProperty = function(aPath)
{
	var result = undefined;
	if ( COMMONS.isArray(aPath) )
	{
		result = aPath[aPath.length - 1];
		if ( result.charAt(result.length - 1) ===']' )
		{
			var index = result.indexOf("[");
			if (index > 0)
			{
				result = result.substring(0, index);
			}
		}	
	}
	return result;
};

/**
 * Returns a XML representation of the JSON object based on tree traversing.
 * @return {String} Returns a XML representation of the JSON object based on tree traversing
 * @param {JSON} The JSON object.
 * @param {Boolean} The pretty print flag.
 *
 * @param The node name converter as function or Map.
 *  <ul>
 *  <span>List of arguments passed to the converter in case of a function:</span>
 *   <li> The JSON node name.
 *   <li> The JSON path as array.
 *   <li> The event type.</li>
 *  <span> Converter returns presentation of XML node name.</span>
 *  </ul>
 *
 * @param The attribute name converter as function or Map.
 *  <ul>
 *  <span>List of arguments passed to the converter in case of converter is a function:</span>
 *   <li> The attribute name.
 *   <li> The attribute value.
 *   <li> The JSON path as array.</li>
 *  <span>Converter returns presentation of XML node attribute name.</span>
 *  </ul>
 *
 * @param The attribute value converter as function or Map.
 *  <ul>
 *  <span>List of arguments passed to the converter in case of function:</span>
 *   <li> The attribute value.
 *   <li> The attribute name.
 *   <li> The JSON path as array.
 *   <li> The JSON event type.</li>
 *  <span>Converter returns presentation of XML node attribute value.</span>
 *  </ul>
 *
 */
Jsoner.prototype.jsonToXML = function(aJson, aPrettyPrint, aNodeConverter,
                                      anAttributeConverter, aValueConverter)
{
	var getPrettyPrintTab = function(aLevel)
	{
		var result = "";
		for (var i = 0; i < aLevel; i++)
		{
			result += " ";
		}
		return result;
	};

	var atttibuteValueConverter = function(aValue, aName, aPath, aType)
	{
		var result = aValue;
		if (aType === Jsoner.JSON_NODE_CROSS_LINKED )
		{
			result = Jsoner.CROSS_LINK_PREFIX + result;
		}
		return result;
	};

	var result = "";
	var nodeConverter = this.converterFactory(aNodeConverter);
	var attributeConverter = this.converterFactory(anAttributeConverter);
	var valueConverter = this.converterFactory(aValueConverter ? aValueConverter : atttibuteValueConverter);

	this.jsonTreeWalker(aJson, function(aPath, aValue, anAttributes, aType)
	{
		var value;
		var name;
		var attribute;

    var nodeName = nodeConverter.call(this, this.getLastProperty(aPath), aPath, aType);
		if ( COMMONS.isDefined(nodeName) )
		{
			if ( aPrettyPrint )
			{
				result += getPrettyPrintTab(aPath.length -1);
			}

			if ( aType === Jsoner.JSON_NODE_START || aType === Jsoner.JSON_NODE_LEAF)
			{
				var CDATA = "";
				var text = "";
				result += "<" + nodeName;
				if ( COMMONS.isArray(anAttributes) )
				{
					for(var i = 0; i < anAttributes.length; i++)
					{
						attribute = anAttributes[i];
						name = attribute.name;

						value = valueConverter.call(this, attribute.value, name, aPath, aType);
						if ( !COMMONS.isUndefined(value) )
						{
							name = attributeConverter.call(this, name, value, aPath);
							if ( COMMONS.isDefined(name) )
							{
								if ( this.isText(name) )
								{
									text += value;
								}
								else
								if (this.isCDATA(name) )
								{
									CDATA += value;
								}
								else
								{
									result += " " + name + "=\"" + value + "\"";
								}
							}
						}
					}
				}

				if ( CDATA.length > 0 || text.length > 0 )
				{
					result += ">";
					if ( aPrettyPrint )
					{
						result += "\n"+ getPrettyPrintTab(aPath.length);
					}
					result += ( CDATA.length > 0 ) ? "<![CDATA[" + CDATA + text + "]]>" : text;
					if (aType === Jsoner.JSON_NODE_LEAF)
					{
						if ( aPrettyPrint )
						{
							result += "\n"+ getPrettyPrintTab(aPath.length -1);
						}
						result += "</" + nodeName + ">";
					}
				}
				else
				{
					result += (aType === Jsoner.JSON_NODE_LEAF) ? "/>" : ">";
				}
			}
			else
			if ( aType === Jsoner.JSON_NODE_END)
			{
				result += "</" + nodeName + ">";
			}
			else
			if ( aType === Jsoner.JSON_ATTRIBUTE || aType === Jsoner.JSON_NODE_CROSS_LINKED )
			{
				value = valueConverter.call(this, aValue, nodeName, aPath, aType);
				if ( COMMONS.isDefined(value) )
				{
					result += "<" + nodeName + ">" + value + "</" + nodeName + ">";
				}
			}

			if (aPrettyPrint)
			{
				result += "\n";
			}
		}
		return true;
	});
	
  return result;
};

/**
  * Converts traverse tree events to path-value events.
  *
  * @param {JSON} The JSON object.
  * @param {Function} The callBack function called to process when the branch of JSON object
  * starting is encountered.
  * <ul>
  * <span> List of arguments passed to the callBack function: </span>
  *  <li> The path in JSON model.
  *  <li> The value of JSON node.
  *  <li> The JSON event type.
  * </ul>
  */
Jsoner.prototype.jsonPathEvaluator = function(aJson, aCallBack)
{
	var result = true;

	this.jsonTreeWalker(aJson, function(aPath, aValue, anAttributes, aType)
	{
		var attribute;
		var value;

		if ( aType === Jsoner.JSON_NODE_START || aType === Jsoner.JSON_NODE_LEAF ||
		     aType === Jsoner.JSON_ATTRIBUTE || aType === Jsoner.JSON_NODE_CROSS_LINKED) 
		{
			var xPath = aPath.join('.');
			if ( aType === Jsoner.JSON_NODE_CROSS_LINKED )
			{
				var last = xPath.lastIndexOf(aValue);
				value = Jsoner.CROSS_LINK_PREFIX + (last > 0 ? xPath.substring(0, last + aValue.length) : aValue);
				result = aCallBack.call(this, xPath, value, aType);
			}
			else
			if ( COMMONS.isArray(anAttributes) )
			{
				for(var i = 0; i < anAttributes.length; i++)
				{
					attribute = anAttributes[i];
					result = aCallBack.call(this, xPath + "." + attribute.name, attribute.value, aType);
					if ( !result )
					{
						break;
					}
				}
			}
			else
			{
				result = aCallBack.call(this, xPath, aValue, aType);
			}
		}
		return result;
	});
};

/**
  * Returns path-value representation of JSON object.
  * @return {String} Returns path-value representation of JSON.
  *
  * @param {JSON}The JSON object.
  */
Jsoner.prototype.jsonToPathValue = function(aJson)
{
	 var result = "";
	 this.jsonPathEvaluator(aJson, function(aPath, aValue, aType)
	 {
			result += aPath + " = " + aValue + "\n";
		  return true;
	 });
	return result;
};

/**
  * Creates new instance of the object.
  *
  * @return Returns new instance of the object.
  * @param The object.
  *
  * @see #clone
  */
Jsoner.prototype.createNewInstance = function(aJson)
{
  var result = aJson;
	if ( COMMONS.isObject(aJson) )
	{
   	var cons = JSINER.getConstructor(aJson);
  	result = new cons();
	}
	return result;
};

/**
  * Creates deep copy of the JSON object.
  * Recursive clones all JSON properties except attributes.
  * Skips ignored properties.
  *
  * @return {JSON} Returns a deep copy of the JSON object.
  * @param {JSON} The JSON object.
  *
  * @see #isAttribute
  * @see #isMute
  */
Jsoner.prototype.clone = function(aJson)
{
	var map = new HashMap();

	var doClone = function(json)
	{
		var result;
	  var value;
		var index = this.getHashCode(json);
		if ( COMMONS.isUndefined(index) )
		{
			result = this.createNewInstance(json);
      if ( this.isResolveCrossLinks(json) )
      {
				index = map.getSize();
				this.addHashCode(json, index);
				map.put(index, result);
	    }
      try
      {
				for (var name in json)
				{
					value = json[name];
					if ( !this.isMute(name, value, json) )
					{
						if ( !this.isAttribute(name, value) )
						{
							index = this.getHashCode(value);
							if ( COMMONS.isDefined(index) )
							{
								value = map.get(index);
							}
							else
							{
								value = doClone.call(this, value);
							}
						}
						result[name] = value;
					}
				}
	    }
			finally
      {
				this.removeHashCode(json);
	    }
		}
		else
		{
			result = map.get(index);
		}
		return result;
	};

	var result = doClone.call(this, aJson, map);
	map.clear();
	return result;
};

/**
  * Creates and returns a merged JSON object
  *
  * @param {JSON} The primary JSON object.
  * @param {JSON} The secondary JSON object.
  * @param {Boolean} The flag defined the missing nodes in result JSON should be created or not.
  * It's true by defaults.
  *
  * @return {JSON} The result of merge.
  */
Jsoner.prototype.merge = function(aTemplate, aJson, aCreateMissing)
{
	var result = this.clone(aTemplate);
	var createMissing = (COMMONS.isUndefined(aCreateMissing) || aCreateMissing);
	var map = new HashMap();

	if ( COMMONS.isObject(aJson) && COMMONS.isObject(aTemplate) )
	{
		this.jsonPathEvaluator(aJson, function(aPath, aValue, aType)
		{
			if ( aType !== Jsoner.JSON_NODE_CROSS_LINKED )
			{
				var populate = COMMONS.isUndefined( this.getValue(result, aPath) );
				if ( !populate )
				{
					var index = aPath.lastIndexOf('.');
					if ( index > 0 )
					{
						var name = aPath.substring(index + 1);
						populate = this.isAttribute(name, aValue);
					}
				}
				if ( populate )
				{
					if ( COMMONS.isString(aValue) && aValue.indexOf(Jsoner.CROSS_LINK_PREFIX) === 0 )
					{
						map.put( aPath, aValue.substring(Jsoner.CROSS_LINK_PREFIX.length));
					}
					else
					{
						this.populate(result, aPath, aValue, createMissing);
					}
				}
			}
			else
			{
				map.put( aPath, aValue.substring(Jsoner.CROSS_LINK_PREFIX.length));
			}
		  return true;
		});
		
		if ( !map.isEmpty() )
		{
			var value;
			for( var name in map.fObject )
			{
				if ( map.fObject.hasOwnProperty(name) )
				{
					value = this.getValue(result, map.get(name) );
				  this.populate(result, name, value, createMissing);
				}
			}
		}
	}

	return result;
};

/**
  * Indicates that two JSON objects are equal.
  *
  * @return {Boolean} Returns true if the objects are equal, false - otherwise.
  * @param {JSON} The first JSON object.
  * @param {JSON} The second JSON object.
  */
Jsoner.prototype.isEquals = function(aJson1, aJson2)
{
	var isObjectEquals = function(aJson1, aJson2)
	{
		var result = true;
		var crossLinks = new HashMap();
		var pathSet = new KeySet();

		this.jsonPathEvaluator(aJson1, function(aPath, aValue, aType)
		{
			if ( aType !== Jsoner.JSON_NODE_CROSS_LINKED )
			{
				if ( !this.isEquals(aValue, this.getValue(aJson2, aPath)) )
				{
					this.fLogger.info("isEquals, difference found:" + aPath);
					result = false;
				}
				else
				{
					pathSet.add(aPath);
				}
			}
			else
			{
				crossLinks.put(aPath, aValue);
			}
			return result;
		});

		if (result)
		{
			this.jsonPathEvaluator(aJson2, function(aPath, aValue, aType)
			{
				if (!pathSet.isContains(aPath))
				{
					if (aType !== Jsoner.JSON_NODE_CROSS_LINKED)
					{
						if ( !this.isEquals(aValue, this.getValue(aJson1, aPath)) )
						{
							this.fLogger.info("isEquals, difference found:" + aPath);
							result = false;
						}
					}
					else
					if ( !this.isEquals(aValue, crossLinks.get(aPath)) )
					{
						this.fLogger.info("isEquals, cross link not equal:" + aPath);
						result = false;
					}
					else
					{
						crossLinks.remove(aPath);
					}
				}
				return result;
			});
		}
		return result && crossLinks.isEmpty();
	};

	var result = JSINER.getType(aJson1) === JSINER.getType(aJson2);
	if ( result )
	{
		if ( COMMONS.isObject(aJson1) )
		{
			if ( COMMONS.isArray(aJson1) )
			{
				result = (aJson1.length === aJson2.length);
			}
			if ( result )
			{
				result = isObjectEquals.call(this, aJson1, aJson2);
			}
		}
		else
		{
			result = (aJson1 === aJson2);
		}
	}
	return result;
};

/** 
 * Creates an object that represents a difference between
 * two JSON objects.
 * The method getDifference is contrary to getMerge method.
 * The  given JSON object can be restored by getMerge(aJson, difference) operation.
 *
 * @return {JSON} Returns object that represents a difference between two JSON objects.
 * @param {JSON} The first JSON object.
 * @param {JSON} The second JSON object.
 */
Jsoner.prototype.getDifference = function(aTemplate, aJson)
{
	var parsePath = function(aPath)
	{
		var result = aPath;
		var index = aPath.lastIndexOf("]");
		if ( index > 0 && index < aPath.length - 1)
		{
			var bIndex = aPath.lastIndexOf("[");
			result = { path:aPath.substring(0, bIndex),
				         index:COMMONS.toInteger(aPath.substring(bIndex + 1, index)),
				         property:aPath.substring(index + 2) };
		}
		return result;
	};

	var doDifference = function(aTemplate, aJson, aResult)
	{
		var crossLinks = new HashMap();
		var pathSet = new KeySet();

		this.jsonPathEvaluator(aTemplate, function(aPath, aValue, aType)
		{
			if ( aType !== Jsoner.JSON_NODE_CROSS_LINKED )
			{
				pathSet.add(aPath);
				if ( !this.isEquals(aValue,this.getValue(aJson, aPath)) )
				{
					this.setValue(aResult, aPath, aValue);
				}
			}
			else
			{
				crossLinks.put(aPath, aValue);
			}
			return true;
		});

		this.jsonPathEvaluator(aJson, function(aPath, aValue, aType)
		{
			if (!pathSet.isContains(aPath))
			{
				if (aType !== Jsoner.JSON_NODE_CROSS_LINKED)
				{
					var value = this.getValue(aTemplate, aPath);
					if ( !this.isEquals(aValue, value) ) 
					{
						var obj = parsePath(aPath);
						if ( !COMMONS.isString(obj) )
						{
							var path;
							for( var i = 0; i < obj.index; i++ )
							{
								path = obj.path + "[" + i + "]." + obj.property;
								this.setValue(aResult, path, this.getValue(aTemplate, path) );
							}
						}
						this.setValue(aResult, aPath, value);
					}
				}
				else
				if ( this.isEquals(aValue, crossLinks.get(aPath)) )
				{
					crossLinks.remove(aPath);
				}
			}
			return true;
		});

		if ( !crossLinks.isEmpty() )
		{
      for( var name in crossLinks.fObject )
      {
	      if ( crossLinks.fObject.hasOwnProperty(name) )
	      {
	        this.setValue(aResult, name, crossLinks.fObject[name] );
		    }
      }
		}
	};

	var result = aTemplate;
	if ( COMMONS.isObject(aTemplate) && COMMONS.isObject(aJson) )
	{
		result = this.createNewInstance(aTemplate);
	  doDifference.call(this, aTemplate, aJson, result);
	}
	return result;
};

/**
  * Returns the map created by array of JSON objects using path for key and path for value.
  * @return {Map} Returns the map.
  *
  * @param {Array} The array of JSON objects.
  * @param {String} The path defined a way to compute a key with the value associated.
  * @param {String} The path defined a way to compute a value associated with key.
  */
Jsoner.prototype.jsonToMap = function(anArray, aPathToKey, aPathToValue)
{
	var result = {};
	if ( COMMONS.isArray(anArray) )
	{
		var key;
		for(var i = 0; i < anArray.length; i++ )
		{
		  key = this.getValue(anArray[i], aPathToKey);
			if ( COMMONS.isDefined(key) )
			{
				result[key] = this.getValue(anArray[i], aPathToValue);
			}
		}
	}
	else
	{
		this.fLogger.error( "jsonToMap, unsupported data type, array required: " + anArray );
	}
	return result;
};

/**************************************************************************/

/**
  * Visits all accepted JSON nodes until a visitor returns true, otherwise stop processing.
  * @param {JSON} The JSON objects.
  * @param {Function} The acceptor function that indicates that JSON node should be visited.
  * <ul>
  * <span>List of arguments passed to the acceptor:</span>
  *  <li> The path in JSON model.
  *  <li> The value of JSON node.
  *  <li> The collection of node attributes as pair name-value <samp>{"name":name, "value":value}</samp>.
  *  <li> The JSON node level.
  *  <li> The JSON node index within a parent node.</li>
  * <span>Returns true if visit to node accepted.</span>
  * </ul>
  *
  * @param {Function} The visitor.
  * <ul>
  * <span>List of arguments passed to the visitor:</span>
  *  <li> The path in JSON model as array.
  *  <li> The value of JSON node.
  *  <li> The collection of node attributes as pair name-value <samp>{"name":name, "value":value}</samp>.</li>
  * <span>Returns false if traverce of JSON should be aborted, otherwise returns true.</span>
  * </ul>
  */
Jsoner.prototype.visit = function(aJson, anAcceptor, aVisitor)
{
	this.jsonTreeWalker(aJson, function(aPath, aValue, anAttributes, aType)
	{
		var result = true;
		if (aType === Jsoner.JSON_NODE_START || aType === Jsoner.JSON_NODE_LEAF || aType === Jsoner.JSON_ATTRIBUTE)
		{
			if (anAcceptor.call(this, aPath, aValue, anAttributes))
			{
				result = aVisitor.call(this, aPath, aValue, anAttributes);
			}
		}
		return result;
	});
};

/**
  * Default acceptor, suitable for base lookup patterns.
  *
  * @return {Boolean} Returns true if visit node is accepted.
  *
  * @param {Array} The path in JSON model as array.
  * @param The value of JSON node.
  * @param The path pattern.
  *  <ul>
  *   <span>Available types of pattern: </span>
  *   <li> A string with wildcard asterisk (*), that substituted for any characters in a path.
  *   <li> A string without wildcard that matched to equals.
  *   <li> A RegExp pattern.
  *  </ul>
  *
  *  <ul>
  *   <span>Limitation: only one asterisk supported.</span>
  *   <li> The path started with asterisk (*) represents end part of the path.
  *   <li> The path end of asterisk (*) represents first part of the path.
  *   <li> The path contains asterisk (*) in the middle of text represents first and end paths of the path.
  *  </ul>
  *
  * @param {Map} The value pattern to match node attributes and nested nodes attributes.
  *  Pattern represented as a map, contains pairs of nested property or attribute name and
  *  matched value or RegExp pattern.
  *  The value pattern looks like { "relativePath": value or RegExp, "relativePath": value or RegExp }.
  */
Jsoner.prototype.defaultAcceptor = function(aPath, aJson, aPathPattern, aValuePattern)
{
	var result = true;
	if ( COMMONS.isDefined(aPathPattern) )
	{
		var path = aPath.join('.');
		result = this.isMatch(aPathPattern, path);
	}
	if ( result && COMMONS.isDefined(aValuePattern) )
	{
		var value;
		var pattern;
		for (var name in aValuePattern)
		{
			value = this.getValue(aJson, name);
			pattern = aValuePattern[name];
			if ( COMMONS.isRegExp(pattern) )
			{
		    result = pattern.test( "" + value );
			}
			else
			{
				result = (pattern === value);
			}
			if ( !result )
			{
				break;
			}
		}
	}
	return result;
};

/**
  * Looks up all reasonable nodes by specified criteria.
  * @return {Array} Returns collection of reasonable nodes.
  *
  * @param {JSON} The JSON objects.
  * @param {String} The path pattern used to accept position in JSON object.
  *  <ul>
  *   <span>Available types of pattern: </span>
  *   <li> A string with wildcard asterisk (*), that substitutes any characters in a path.
  *   <li> A string without wildcard that matched to equals.
  *   <li> A RegExp pattern.
  *  </ul>
  *
  * @param {Map} The value pattern used to match node attributes and nested nodes attributes.
  * Pattern, which is represented as a map, contains pairs of nested property or attribute name and
  * matched value or RegExp pattern.
  * The value pattern looks like <samp>{"relativePath":RegExp, "relativePath":pattern}</samp>
  *
  * @see #DEFAULT_ACCEPTOR
  */
Jsoner.prototype.lookupAll = function(aJson, aPathPattern, aValuePattern)
{
	var result = [];
	this.visit(aJson, function(aPath, aValue, anAttributes)
	{
		return this.defaultAcceptor.call(this, aPath, aValue, aPathPattern, aValuePattern);
	}, function(aPath, aValue)
	{
		result.push(aValue);
		return true;
	});
	return result;
};

/**
  * Looks up first reasonable node by specified criteria.
  * @return Returns the first matched node.
  *
  * @param {JSON} The JSON objects.
  * @param {String} The path pattern used to accept position in JSON object.
  *  <ul>
  *   <span>Available types of pattern: </span>
  *   <li> A string with wildcard asterisk (*), that substitutes any characters in a path.
  *   <li> A string without wildcard that matches equals.
  *   <li> A RegExp pattern.
  *  </ul>
  *
  * @param {Map} The value pattern to unite corresponding node attributes and nested nodes attributes.
  * Pattern represented as a map, contains pairs of nested property or attribute name and
  * matched value or RegExp pattern.
  * The value pattern looks like <samp>{"relativePath":RegExp, "relativePath":pattern}</samp>
  *
  * @see #DEFAULT_ACCEPTOR
  */
Jsoner.prototype.lookupFirst = function(aJson, aPathPattern, aValuePattern)
{
	var result = null;
	this.visit(aJson, function(aPath, aValue, anAttributes)
	{
		return this.defaultAcceptor(aPath, aValue, aPathPattern, aValuePattern);
	}, function(aPath, aValue)
	{
		result = aValue;
		return false;
	});
	return result;
};

/**
  * Indicates that reasonable node is found by specified criteria.
  * @return {Boolean} Returns true if at least one node is found, otherwise - false.
  *
  * @param {JSON} The JSON objects.
  * @param {String} The path pattern used to accept position in JSON object.
  *  <ul>
  *   <span>Available types of pattern: </span>
  *   <li> A string with wildcard asterisk (*), that substitute any characters in a path.
  *   <li> A string without wildcard that matches equals.
  *   <li> A RegExp pattern.
  *  </ul>
  *
  * @param {Map} The value pattern used to unite coresponding node attributes and nested nodes attributes.
  * Pattern represented as a map, contains pairs of nested property or attribute name and
  * matched value or RegExp pattern.
  * The value pattern looks like <samp>{"relativePath":RegExp, "relativePath":pattern}</samp>
  *
  * @see #DEFAULT_ACCEPTOR
  */
Jsoner.prototype.isContains = function(aJson, aPathPattern, aValuePattern)
{
	var result = this.lookupFirst(aJson, aPathPattern, aValuePattern);
	return (result !== null);
};

/**
  * Returns amount of reasonable nodes by specified criteria.
  * @return {Number} Returns count of reasonable nodes by specified criteria
  *
  * @param {JSON} The JSON objects.
  * @param {String} The path pattern used to accept position in JSON object.
  *  <ul>
  *   <span>Available types of pattern: </span>
  *   <li> A string with wildcard asterisk (*), that substitutes any characters in a path.
  *   <li> A string without wildcard that matched to equals.
  *   <li> A RegExp pattern.
  *  </ul>
  *
  * @param {Map} The value pattern used to match node attributes and nested nodes attributes.
  * Pattern represented as a map, contains pairs of nested property or attribute name and
  * matches value or RegExp pattern.
  * The value pattern looks like <samp>{"relativePath":RegExp, "relativePath":pattern}</samp>
  *
  * @see #DEFAULT_ACCEPTOR
  */
Jsoner.prototype.getCount = function(aJson, aPathPattern, aValuePattern)
{
	var result = 0;
	this.visit(aJson, function(aPath, aValue, anAttributes)
	{
		return this.defaultAcceptor(aPath, aValue, aPathPattern, aValuePattern);
	}, function(aPath, aValue)
	{
		result++;
		return true;
	});
	return result;
};

/**************************************************************************/

/**
  * Returns an array of the attributes on the JSON node.
  * @return {Array} Returns an array of the attributes on the JSON node
  *
  * @param {JSON} The JSON objects.
  * @param {String} The path defines a way to node in JSON.
  *
  * @see #isAttribute
  */
Jsoner.prototype.getAttributes = function(aJson, aPath)
{
  var value = this.getValue(aJson, aPath);
	var result = this.collectAttributes(aPath, value, aJson);
	return result;
};

/**
  * Returns an array of the child nodes on the JSON node.
  * @return {Array} Returns an array of the child nodes on the JSON node.
  *
  * @param {JSON} The JSON objects.
  * @param {String} The path defines a way to node in JSON.
  *
  * @see #isAttribute
  */
Jsoner.prototype.getChildren = function(aJson, aPath)
{
	var result = [];
  var obj = this.getValue(aJson, aPath);
	if ( COMMONS.isDefined(obj) )
	{
		result = this.collectChildren(aPath, obj, aJson);
	}
	return result;
};

/**
  * Indicates that a node is leaf.
  * @return {Boolean} Returns true if so, otherwise - false.
  *
  * @param {JSON} The JSON objects.
  * @param {String} The path defines a way to node in JSON.
  *
  * @see #getChildren
  */
Jsoner.prototype.isLeaf = function(aJson, aPath)
{
	var children = this.getChildren(aJson, aPath);
	return children.length === 0;
};

/**
  * Returns the first child of the JSON node.
  * @return Returns the first child of the JSON node.
  * If the node is leaf or path is wrong returns undefined.
  *
  * @param {JSON} The JSON objects.
  * @param {String} The path defines a way to node in JSON.
  */
Jsoner.prototype.getFirstChild = function(aJson, aPath)
{
	var children = this.getChildren(aJson, aPath);
	var result = children.length > 0 ? children[0] : undefined
	return result;
};

/**
  * Returns the last child of the JSON node.
  * @return Returns the last child of the JSON node.
  * If the node is leaf or path is wrong returns undefined.
  *
  * @param {JSON} The JSON objects.
  * @param {JSON} The path defines a way to node in JSON.
  */
Jsoner.prototype.getLastChild = function(aJson, aPath)
{
	var children = this.getChildren(aJson, aPath);
	var result = children.length > 0 ? children[children.length - 1] : undefined
	return result;
};

/**
  * Inserts the specified value into the array of nodes on the JSON.
  *
  * @param {JSON} The JSON objects.
  * @param {String} The path defines a way to node in JSON.
  * @param  The value should be inserted.
  * @param {Integer} The position where the value should be inserted.
  *
  * @return {JSON} The modified JSON object, useful for making chank of changes.
  */
 Jsoner.prototype.addChild = function(aJson, aPath, aValue, aPosition)
{
	function parsePath(aPath)
  {
	  var result = aPath;
	  if ( aPath.charAt(aPath.length - 1) === ']' )
	  {
		  var index = aPath.lastIndexOf("[");
		  result = {path:aPath.substring(0, index),
			          index:aPath.substring(index + 1, aPath.length-1)};
		}
	  return result;
  }

	var obj = this.getValue(aJson, aPath);
	if ( COMMONS.isUndefined(aPosition) )
	{
		if ( COMMONS.isUndefined(obj) )
		{
			this.setValue(aJson, aPath, aValue );
		}
		else
		{
			if ( !COMMONS.isArray(obj) )
			{
			  obj = [obj];
				this.setValue(aJson, aPath, obj);
			}
		  obj.push(aValue);
		}
	}
	else
	{
		if ( COMMONS.isUndefined(obj) )
		{
			obj = [];
			this.setValue(aJson, aPath, obj );
		}
		else
		{
			if ( !COMMONS.isArray(obj) )
			{
			  obj = [obj];
				this.setValue(aJson, aPath, obj);
			}
			if ( aPosition < obj.length )
			{
				for (var i = obj.length - 1; i >= aPosition; i--)
				{
					obj[i + 1] = obj[i];
				}
			}
			obj[aPosition] = aValue;
		}
	}
	return aJson;
};

/**
 * Removes all children nodes from the specified by path node.
 *
 * @param {JSON} The JSON objects.
 * @param {String} The path defines a way to node in JSON.
 *
 * @return {JSON} The modified JSON object, useful to make chank of changes.
 */
Jsoner.prototype.removeChildren = function(aJson, aPath)
{
	var obj = this.getValue(aJson, aPath);
	if ( !COMMONS.isUndefined(obj) )
	{
		this.setValue(aJson, aPath, undefined);
		delete aJson[aPath];
	}
	return aJson;
};

/**
 * Removes the child node from the specified by path node.
 *
 * @param {JSON} The JSON objects.
 * @param {String} The path defines a way to node in JSON.
 * @param {JSON} The child node should be removed.
 *
 * @return {JSON} The modified JSON object, useful to make chank of changes.
 */
Jsoner.prototype.removeChild = function(aJson, aPath, aValue)
{
  var obj = this.getValue(aJson, aPath);
	if ( !COMMONS.isUndefined(obj) )
	{
		if ( COMMONS.isArray(obj) )
		{
			var index = -1;
			for( var i = 0; i < obj.length; i++)
			{
				var value = obj[i];
				if ( value === aValue )
				{
					index = i;
					break;
				}
			}

			if ( index !== -1 )
			{
				for (var i = index; i < obj.length - 1; i++)
				{
					obj[i] = obj[i + 1];
				}
				obj.length = obj.length - 1;
			}
		}
		else
		if ( this.isEquals(obj, aValue) )
		{
			this.setValue(aJson, aPath, undefined);
			delete aJson[aPath];
		}
	}
	return aJson;
};

/**************************************************************************/

/**
  * Emulates path-value events for HTML form.
  *
  * @param {Form} The HTML form.
  * @param {Function} The observer, called  to process when the form element received.
  *
  * <ul>
  * <span>List of arguments passed to the callBack function:</span>
  *  <li>The HTML form element name.
  *  <li>The HTML form element.
  * </ul>
  */
Jsoner.prototype.htmlFormEvaluator = function(aForm, aCallBack)
{
	if ( COMMONS.isDefined(aForm) )
	{
		for (var i = 0; i < aForm.elements.length; i++)
		{
			var element = aForm.elements[i];
			if ( element.name )
			{
				aCallBack.call(this, element.name, element);
			}
		}
	}
};

/**
  * Utility method, sets value to HTML form field.
  * Assigns the value to HTML form field "value" property by defaults,
  * sets select index for "select" element and
  * sets checked to "checkbox" or "radio group".
  *
  * @param {Element} The HTML form field.
  * @param The value.
  */
Jsoner.prototype.setFieldValue = function(aElement, aValue)
{
	if ( COMMONS.isObject(aElement) )
	{
		if ( typeof(aElement) === "select" )
		{
			for (var i = 0; i < aElement.options.length; i++)
			{
				var option = aElement.options[i];
				if (option.value === aValue)
				{
					aElement.selectedIndex = i;
					option.selected = true;
				}
				else
				{
					option.removeAttribute("selected");
				}
			}
		}
		else
		if ( aElement.type === "checkbox" || aElement.type === "radio" )
		{
			aElement.checked = (aElement.value === ("" + aValue));
		}
		else
		{
			aElement.value = COMMONS.isDefined(aValue) ? aValue : '';
		}
	}	
};

/**
  * Utility method, returns HTML form field value.
  * Captures the "value" property of the HTML form field by defaults,
  * gets option value for "select" element, gets "checkbox" or "radio group" value if
  * the ones is checked.
  * @return {String} Returns the HTML form field value.
  *
  * @param {Element} The HTML form field.
  */
Jsoner.prototype.getFieldValue = function(aElement)
{
	var result = undefined;
	if ( COMMONS.isObject(aElement) )
	{
		if ( typeof(aElement) === "select" )
		{
			result = aElement.options[aElement.selectedIndex].value;
		}
		else
		if ( aElement.type === "checkbox")
		{
			result = aElement.checked ? aElement.value : false;
		}
		else
		if (aElement.type === "radio")
		{
			if ( aElement.checked )
			{
				result = aElement.value;
			}
		}
		else
		{
			result = aElement.value;
		}
	}
	return result;
};

/**
  * Populates JSON data to HTML form.
  *
  * @param {JSON} The JSON object.
  * @param {Form} The HTML form.
  *
  * @param {Map} Function or Map, which converts the HTML form element name into JSON path.
  * <ul>
  *  <span>List of arguments,which is passed to the converter in case of a function:</span>
  *   <li> The HTML form element name.
  *   <li> The HTML form element.</li>
  *  <span>Converter returns corresponding JSON path.</span>
  * </ul>
  */
Jsoner.prototype.populateJsonToForm = function(aJson, aForm, aConvertor)
{
	if ( COMMONS.isDefined(aForm) && COMMONS.isDefined(aJson) )
	{
		var convertor = this.converterFactory(aConvertor);

		this.htmlFormEvaluator(aForm, function(aElementName, aElement)
		{
			 var path = convertor.call(this, aElementName, aElement);
			 if ( COMMONS.isString(path) )
			 {
		     var value = this.getValue(aJson, path);
				 if ( this.getFieldValue(aElement) !== value )
				 {
					 this.setFieldValue(aElement, value ? value : '');
					 if ( COMMONS.isFunction(aElement.onchange) )
					 {
						 aElement.onchange.call(aElement);
					 }
					 else
					 if ( COMMONS.isFunction(aElement.onclick) )
					 {
						 aElement.onclick.call(aElement);
					 }
				 }
			 }
		});
	}
};

/**
  * By default, converts HTML form element name to JSON path.
  *
  * @param {String} The HTML form element name.
  * @param {Element} The HTML form element.
  *
  * @return {String} Returns element name in case when element is not
  * disabled and is not read only, otherwise returns null.
  *
  */
Jsoner.prototype.defaultHtmlConverter = function(aElementName, aElement)
{
	var result = aElementName;
	if ( COMMONS.toBoolean(aElement.readOnly) || COMMONS.toBoolean(aElement.disabled) )
	{
		result = null;
	}
	return result;
};

/**
  * Populates HTML form data to JSON object.
  *
  * @param {Form} The HTML form.
  * @param {JSON} The JSON object.
  *
  * @param {Map} The HTML form element name to JSON path converter as function or Map.
  * <ul>
  *  <span>List of arguments passed to the converter in case of a function:</span>
  *   <li> The HTML form element name.
  *   <li> The HTML form element.</li>
  *  <span>Converter returns corresponding JSON path.</span>
  * </ul>
  *
  * @return {JSON} The modified JSON object, useful to make chank of changes.
  */
Jsoner.prototype.populateFormToJson = function(aForm, aJson, aConverter)
{
	var result = aJson;
	var pathSet = new KeySet();

	if ( COMMONS.isObject(aForm) )
	{
		var converter = this.converterFactory(aConverter || this.defaultHtmlConverter);

		this.htmlFormEvaluator(aForm, function(aElementName, aElement)
		{
		   var path = converter.call(this, aElementName, aElement);
			 if ( COMMONS.isString(path))
			 {
				 var value = this.getFieldValue(aElement);
				 if ( COMMONS.isDefined(value) )
				 {
					 var oldValue = this.getValue(result, path);
					 if ( COMMONS.isNumber(oldValue) )
					 {
						 value = COMMONS.toFloat(value);
					 }
					 else
					 if ( COMMONS.isBoolean(oldValue) )
					 {
						 value = COMMONS.toBoolean(value);
					 }
					 this.populate(result, path, value, true);
				}
			 }
		});
	}
	return result;
};

/********************************************************/

/**
  * Predefined JSON pattern key for CDATA.
  * @type string
  * @final
  */
Jsoner.prototype.PATTERN_CDATA_KEY = "CDATA";

/**
  * Predefined default JSON pattern.
  * @type string
  * @final
  */
Jsoner.prototype.PATTERN_DEFAULT_KEY = "default";

/**
  * Predefined selective JSON pattern resolver.
  * Pattern - it's a specified data,
  * that define how JSON node will be transformed to HTML DOM node.
  *
  * <ul>
  *  <span>Following rules are used to obtain the pattern: </span>
  *   <li> Searches pattern by path.
  *   <li> Searches pattern by type.
  *   <li> Gets default pattern.</li>
  *  <span>Returns the first corresponding pattern.</span>
  * </ul>
  *
  * @param {Map} The object, contains collection of patterns.
  * @param {Sting} The path in JSON model, used to resolve pattern.
  * @param The value of JSON property, used to resolve pattern.
  *
  * @return {JSON} Returns the JSON representation of pattern used to prepare HTML DOM node.
  *
  * @see #jsonToHTML
  */
Jsoner.prototype.selectPatternResolver = function(aJson, aPath, aValue)
{
	var result = this.getValue(aJson, aPath);
	if ( COMMONS.isUndefined(result) )
	{
		var index = aPath.lastIndexOf('.');
		if ( index > 0 )
		{
			aPath = aPath.substring(index + 1);
		}
		if (this.isText(aPath) || this.isCDATA(aPath) )
		{
			result = this.getValue(aJson, this.PATTERN_CDATA_KEY);
		}
		if ( COMMONS.isUndefined(result) )
		{
			var type = typeof(aValue);
			result = this.getValue(aJson, type);
			if ( COMMONS.isUndefined(result) )
			{
				result = this.getValue(aJson, this.PATTERN_DEFAULT_KEY);
			}
		}
	}
	return this.clone(result);
};

/**
  * Predefined cascade JSON pattern resolver.
  * Pattern - it's a specified data,
  * that defines how JSON node will be transformed into DOM node.
  *
  * <ul>
  *  <span>Following rules are used to obtain the pattern: </span>
  *   <li> Gets default pattern.
  *   <li> Searches pattern by type.
  *   <li> Searches pattern by accumulated segments of path.
  *   <li> Searches pattern by path.</li>
  *  <span>Returns the  corresponding merged pattern.</span>
  * </ul>
  *
  * @param {Map} The object, contains collection of patterns.
  * @param {Sting} The path in JSON model, used to resolve pattern.
  * @param The value of JSON property, used to resolve pattern.
  *
  * @return {JSON} Returns the JSON representation of pattern used to prepare HTML DOM node.
  *
  * @see #jsonToHTML.
  */
Jsoner.prototype.cascadePatternResolver = function(aJson, aPath, aValue)
{
	var result = this.merge(this.getValue(aJson, this.PATTERN_DEFAULT_KEY),
													this.getValue(aJson, typeof(aValue)), true);

	var index = aPath.lastIndexOf('.');
	if ( index > 0 )
	{
		var name = aPath.substring(index + 1);
		if (this.isText(name) || this.isCDATA(name) )
		{
			result = this.merge(result, this.getValue(aJson, this.PATTERN_CDATA_KEY), true);
		}
	}
	var key = [];
	var path = aPath.split('.');
	for( var i = 0; i < path.length; i++ )
	{
		key.push( path[i] );
		result = this.merge(result, this.getValue(aJson, key.join('.')), true);
	}
	return result;
};

/**
  * Creates HTML DOM structure based on JSON object and pattern.
  *
  * @param {JSON} The JSON object.
  * @param The collection of patterns.
  *
  * @param {Function} The pattern resolver, used to obtain corresponding pattern.
  * it is pattern resolver, that encapsulates a logic of obtaining  pattern .
  * <ul>
  *  <span>List of arguments passed to the pattern resolver:</span>
	*   <li> The collection of patterns.
  *   <li> The path in JSON model.
  *   <li> The value of JSON property associated with the path.</li>
  *   <span> Resolver returns corresponding pattern to process JSON node or null to skip ones.</span>
  * </ul>
  *
  * @param {Function} The parent node provider or the HTML node used to add child to them.
  * Parent node provider encapsulate a layout logic.
  * <ul>
  *  <span> List of arguments passed to the provider in case of provider is a function: </span>
  *   <li>The path in JSON model.
  *   <li>The value of JSON property.</li>
  *   <span>Returns HTML node or null to skip add child operation.</span>
  * </ul>
  *
  * @param {Function} The HTML node factory.
  * The factory creates HTML DOM node or array of nodes that should be appended to parent node.
  * <ul>
  *  <span> List of arguments passed to the factory: </span>
	*   <li> The pattern.
	*   <li> The owner document.
  *   <li> The path in JSON model.
  *   <li> The value of JSON property associated with the path.</li>
  *   <span>Returns HTML DOM node or array of nodes to append to parent node or null to skip add child operation.</span>
  * </ul>
  */
Jsoner.prototype.jsonToHTML = function(aJson, aPattern, aPatternResolver, aParentNodeProvider, aNodeFactory)
{
  var patternResolver = aPatternResolver || this.cascadePatternResolver;
	var nodeFactory = aNodeFactory || this.jsonToDOM;
	var parentNodeProvider = COMMONS.isFunction(aParentNodeProvider) ? aParentNodeProvider : function() {return aParentNodeProvider;};

	this.jsonPathEvaluator(aJson, function(aName, aValue)
  {
		var pattern = patternResolver.call(this, aPattern, aName, aValue);
	  if ( COMMONS.isDefined(pattern) )
	  {
		  var parent = parentNodeProvider.call(this, aName, aValue);
			if ( COMMONS.isObject(parent) )
			{
				var node = nodeFactory.call(this, pattern, parent.ownerDocument || document, aName, aValue); 
				if ( COMMONS.isObject(node) )
				{
					if ( COMMONS.isArray(node) )
					{
						for( var i = 0; i < node.length; i++ )
						{
							parent.appendChild(node[i]);
						}
					}
					else
					{
						parent.appendChild(node);
					}
				}
			}
	  }
	  return true;
  });
};

/**
  * Creates HTML node or array of HTML nodes by JSON object
  *
  * @param {JSON} The JSON representation of HTML node.
  *
  * @param {Function} The node name converter as function or Map.
  * <ul>
  *  <span>List of arguments passed to the converter in case of a function:</span>
  *   <li> The JSON node name.
  *   <li> The JSON path as array.
  *   <li> The JSON node value.</li>
  *  <span>Returns string representation of HTML node name to process or null to skip processing.</span>
  * </ul>
  * @param {Document} The owner document.
  *
  * @param {Function} The attribute name converter as function or Map.
  * <ul>
	*  <span>List of arguments passed to the converter in case when converter is a function:</span>
  *   <li> The attribute name.
  *   <li> The attribute value.
  *   <li> The JSON path as array.</li>
  *  <span> Returns string presentation of HTML node attribute name. </span>
  * </ul>
  *
  * @throw Exception
  * The jsonToDOM can raise exception in case when impossible operation happens.
  */
Jsoner.prototype.jsonToDOM = function(aJson, aDocument, aNodeConverter, anAttributeConverter )
{
	var result = null;
	var parent = null;
	var level = 0;

	var nodeConverter = this.converterFactory(aNodeConverter);
	var attributeConverter = this.converterFactory(anAttributeConverter);

	this.jsonTreeWalker(aJson, function(aPath, aValue, anAttributes, aType, aLevel)
	{
		var element = null;
		if (aType === Jsoner.JSON_NODE_START || aType === Jsoner.JSON_NODE_LEAF || aType === Jsoner.JSON_ATTRIBUTE)
		{
			var nodeName = nodeConverter.call(this, this.getLastProperty(aPath), aValue);
			if (COMMONS.isString(nodeName))
			{
				element = aDocument.createElement(nodeName);
				if (aType === Jsoner.JSON_ATTRIBUTE)
				{
					element.appendChild(aDocument.createTextNode(aValue));
				}
				if (COMMONS.isArray(anAttributes))
				{
					for (var i = 0; i < anAttributes.length; i++)
					{
						var attribute = anAttributes[i];
						var name = attributeConverter.call(this, attribute.name, attribute.value, aPath);
						if (COMMONS.isDefined(name))
						{
							var value = attribute.value;
							if (this.isText(name) || this.isCDATA(name))
							{
								element.appendChild(aDocument.createTextNode(value));
							}
							else
							{
								if (name === "class")
								{
									element.className = value;
								}
								else
								if (COMMONS.isIE && (name.indexOf("on") === 0))
								{
									element.attachEvent(name, new Function('', value));
								}
								else
								if (COMMONS.isIE && name === "style")
								{
									element.style.cssText = value;
								}
								else
								{
									element.setAttribute(name, value);
								}
							}
						}
					}
				}

				if (aLevel === 0)
				{
					result = result ? [result] : element;
					if (COMMONS.isArray(result))
					{
						result.push(element);
					}
				}
				else
				{
					while (level >= aLevel)
					{
						parent = parent.parentNode;
						level--;
					}
					if ((aPath === "tr" || aPath === "TR") && parent.nodeName === 'TABLE')
					{
						var tbody = aDocument.createElement("tbody");
						parent.appendChild(tbody);
						parent = tbody;
					}
					parent.appendChild(element);
				}
				parent = element;
				level = aLevel;
			}
		}
		return true;
	});

	return result;
};