Source: lan/classes.js

/**
 *  @fileoverview
 *  This file simulates the class-based inheritance, most of the code is
 *  borrowed from Prototype lib. My own property system borrowed form WebShow framework
 *  is also added here.
 *
 *  @author Partridge Jiang
 */


"use strict";

(function($jsRoot){

/** @ignore */
function emptyFunction() {};


/** @ignore */
function __$A__(iterable) {
    if (!iterable) return [];
    if (iterable.toArray) return iterable.toArray();
    var length = iterable.length || 0, results = new Array(length);
    while (length--) results[length] = iterable[length];
    return results;
}

/** @class Class */
var Class = {
		/**
		 * @description Create a Prototypejs style class, createCore(baseClass, newClassHash).
		 * @param {Class} baseClass Parent class.
		 * @param {Hash} newClassHash A hash contains methods of current class.
		 * @return {Class} The class created.
		 */
    createCore: function() {
        var parent = null, properties = __$A__(arguments);
        if (!properties[0])
        {
          if (properties.length > 1)
            throw 'Can not create new class, base class not found';
        }
        if (Object.isFunction(properties[0]))
            parent = properties.shift();

        function klass() {
            this.initialize.apply(this, arguments);
        }

        Object.extend(klass, Class.Methods);

        klass.superclass = parent;
        klass.subclasses = [];

        if (parent) {
            var subclass = function() { };
            subclass.prototype = parent.prototype;
            klass.prototype = new subclass;
            parent.subclasses.push(klass);
        }

        for (var i = 0; i < properties.length; i++)
            klass.addMethods(properties[i]);

        if (!klass.prototype.initialize)
            klass.prototype.initialize = emptyFunction; //Prototype.emptyFunction;

        klass.prototype.constructor = klass;

        return klass;
    },
    /**
		 * @description Create a new class, currently same as createCore, create(baseClass, newClassHash).
		 * @see Class#createCore.
		 * @param {Class} baseClass Parent class.
		 * @param {Hash} newClassHash A hash contains methods of current class.
		 * @return {Class} The class created.
		 */
    create: function()
    {
    	var result = Class.createCore.apply(this, arguments);
  		// init properties
  		//if (result.prototype.initProperties)
  		{
  			// init properties field in prototype
  			/*
  			if (!result.prototype.properties)
  				result.prototype.properties = [];
  			*/
  			/*
  			 * Use lasy creation, the property list will be created only the first instance of class is used
  			if (result.prototype.hasOwnProperty('initProperties'))  // prevent call parent initProperties method
  				result.prototype.initProperties.apply(result.prototype);
  			*/
  		}
  		return result;
    },
	/**
	 * Finalize object.
	 * @param {ObjectEx} obj
	 */
	free: function(obj)
	{
		if (obj.finalize)
			obj.finalize();
		obj = null;
	}
};

/** @ignore */
Class.Methods = {
		/* @lends Class */
		/*
		 * @description Add new methods to a existing class.
		 * @param {Hash} source A hash contains methods to add.
		 * @return {Class} resultClass Current class with methods added.
		 */
		/** @ignore */
		// Note this method is modified by Partridge Jiang to support '$origin' param.
    addMethods: function(source) {
    		if (!source)
    			return this;
        var ancestor = this.superclass && this.superclass.prototype;
        var properties = Object.keys(source);

        if (!Object.keys({ toString: true }).length)
            properties.push("toString", "valueOf");

				var doNothing = function() {};


				for (var i = 0, length = properties.length; i < length; i++) {
				    var property = properties[i];
				    var value = source[property];

						var isFunction = Object.isFunction(value);

				    //if (ancestor && isFunction && value.argumentNames().first() == "$super")
					//if (ancestor && isFunction && FunctionUtils.argumentNames(value).first() == "$super")
          if (ancestor && isFunction && FunctionUtils.argumentNames(value)[0] === "$super")
						{
				        var method = value;
				        /** @inner */
				        value = (function(m) {
				            return function() { return (ancestor[m] || doNothing).apply(this, arguments); };
				        })(property).wrap(method);

				        value.valueOf = method.valueOf.bind(method);
				        value.toString = method.toString.bind(method);
				    }
				    this.prototype[property] = value;
				}
        return this;
    }
};

/** @ignore */
Object.extend = function(destination, source, ignoreUnsetValue, ignoreEmptyString)
{
  if (!ignoreUnsetValue && !ignoreEmptyString)  // normal situation, extract out for performance
  {
    for (var property in source)
    {
      destination[property] = source[property];
    }
  }
  else
  {
    for (var property in source)
    {
      var value = source[property];
      if (ignoreUnsetValue && ((value === undefined) || (value === null)))
        continue;
      if (ignoreEmptyString && (value === ''))
        continue;
      destination[property] = value;
    }
  }
  return destination;
};

/** @ignore */
Object.extendEx = function(destination, source, options)
{
  var ops = options || {};
  for (var property in source)
  {
    var value = source[property];
    if (ops.ignoreUnsetValue && ((value === undefined) || (value === null)))
      continue;
    if (ops.ignoreEmptyString && (value === ''))
      continue;

    var oldValue = destination[property];
    if (options.preserveExisted && oldValue)
      continue;
    var oldProto = oldValue && oldValue.constructor && oldValue.constructor.prototype;
    var newProto = value && value.constructor && value.constructor.prototype;
    if (oldValue && typeof(oldValue) === 'object' && oldProto === newProto)
      Object.extendEx(oldValue, value, options);
    else
      destination[property] = value;
  }
  return destination;
};
/** @ignore */
Object._extendSupportMethods = function(destination, methods)
{
  return Object.extendEx(destination, methods, {ignoreUnsetValue: true, preserveExisted: true});
};
/** @ignore */
// e.g., obj.getCascadeFieldValue('level1.level2.name') will return obj.level1.level2.name.
Object.getCascadeFieldValue = function(fieldName, root)
{
  var result;
  var cascadeNames;
  if (fieldName.length && fieldName.splice)  // is an array
    cascadeNames = fieldName;
  else
    cascadeNames = fieldName.split('.');
  if (!root)
    var root = $jsRoot || this;
  for (var i = 0, l = cascadeNames.length; i < l; ++i)
  {
    result = root[cascadeNames[i]];
    if (!result)
      break;
    else
      root = result;
  }
  return result;
};
/** @ignore */
Object.setCascadeFieldValue = function(fieldName, value, root, forceCreateEssentialObjs)
{
  var cascadeNames;
  if (fieldName.length && fieldName.splice)  // is an array
    cascadeNames = fieldName;
  else
    cascadeNames = fieldName.split('.');
  var parent = root;
  for (var i = 0, l = cascadeNames.length; i < l; ++i)
  {
    var name = cascadeNames[i];
    if (i === l - 1)  // success
    {
      parent[name] = value;
      return value; //true;
    }
    else
    {
      var obj = parent[name];
      if (!obj && forceCreateEssentialObjs)  // create new obj
      {
        obj = {};
        parent[name] = obj;
      }
      if (!obj)
        return false;
      else
        parent = obj;
    }
  }
};

/**
 * Create a "heir" object of proto.
 * @ignore
 */
Object._inherit = function(proto)
{
	if (proto === null)
		proto = {};
		//throw TypeError();

	var t = typeof(proto);
	if (!(DataType.isFunctionType(t) || DataType.isObjectType(t)))
		throw TypeError();
	function f() {};
	f.prototype = proto;
	return new f();
};

if (!Object.create)
{
	Object.create = Object._inherit;
}

/** @ignore */
// Add by Partridge Jiang
// copy a set of values from src to dest
Object.copyValues = function(dest, src, propNames)
{
	if (!propNames)
		return Object.extend(dest, source);
	else
	{
		for (var i = 0, l = propNames.length; i < l; ++i)
		{
			var prop = propNames[i];
			var value = src[prop];
			if (value !== undefined)
				dest[prop] = value;
		}
		return dest;
	}
};

// extend class methods
/** @ignore */
Object._extendSupportMethods(Object, {
  keys: function(object) {
      var keys = [];
      for (var property in object)
          keys.push(property);
      return keys;
  },
  isFunction: function(object) {
      return typeof object == "function";
  },
  isUndefined: function(object) {
      return typeof object == "undefined";
  }
});

if (!Object.getOwnPropertyNames)
{
  Object.getOwnPropertyNames = function(obj)
  {
    var result = [];
    for (var k in obj)
    {
      if (obj.hasOwnProperty(k))
        result.push(k);
    }
    return result;
  }
}

var FunctionUtils = {
	argumentNames: function(f) {
		var names = ((f.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/) || [])[1] || '').replace(/\s+/g, '').split(',');
		return names.length == 1 && !names[0] ? [] : names;
	}
	/*
	wrap: function(f, wrapper) {
		var __method = f;
		return function() {
			return wrapper.apply(f, [__method.bind(f)].concat(__$A__(arguments)));
		};
	},
	methodize: function(f) {
		if (f._methodized) return f._methodized;
		var __method = f;
		return f._methodized = function() {
			var a = Array.prototype.slice.call(arguments);
			a.unshift(f);
			return __method.apply(null, a);
		};
	},
	bind: function(f) {
		if (arguments.length < 2 && Object.isUndefined(arguments[0])) return f;
		var __method = f, args = __$A__(arguments), object = args.shift();
		return function() {
			return __method.apply(object, args.concat(__$A__(arguments)));
		};
	},
	delay: function(f) {
		var __method = f, args = __$A__(arguments), timeout = args.shift();
		return window.setTimeout(function() {
			return __method.apply(__method, args);
		}, timeout);
	},
	defer: function() {
		var __method = f, args = __$A__(arguments), timeout = args.shift();
		return window.setTimeout(function() {
			return __method.apply(__method, args);
		}, 10);
	}
	*/
};

/** @ignore */
Object._extendSupportMethods(Function.prototype, {
  argumentNames: function() {
      var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1].replace(/\s+/g, '').split(',');
      return names.length == 1 && !names[0] ? [] : names;
  },
  wrap: function(wrapper) {
      var __method = this;
      return function() {
          return wrapper.apply(this, [__method.bind(this)].concat(__$A__(arguments)));
      };
  },
  methodize: function() {
    if (this._methodized) return this._methodized;
    var __method = this;
    return this._methodized = function() {
      var a = Array.prototype.slice.call(arguments);
      a.unshift(this);
      return __method.apply(null, a);
    };
  },
  bind: function() {
    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
    var __method = this, args = __$A__(arguments), object = args.shift();
    return function() {
      return __method.apply(object, args.concat(__$A__(arguments)));
    };
  },
  delay: function() {
    var __method = this, args = __$A__(arguments), timeout = args.shift();
    return window.setTimeout(function() {
      return __method.apply(__method, args);
    }, timeout);
  },
  defer: function() {
    var __method = this, args = __$A__(arguments), timeout = args.shift();
    return window.setTimeout(function() {
      return __method.apply(__method, args);
    }, 10);
  }
});
/*
Object._extendSupportMethods(Function.prototype, {
	argumentNames: function() {
		return FunctionUtils.argumentNames(this);
	},
	wrap: function(wrapper) {
		return FunctionUtils.wrap(this, wrapper);
	},
	methodize: function() {
		return FunctionUtils.methodize(this);
	},
	bind: function() {
		return FunctionUtils.bind(this);
	},
	delay: function() {
		return FunctionUtils.delay(this);
	},
	defer: function() {
		return FunctionUtils.defer(this);
	}
});
*/


/** @ignore */
/*
Object._extendSupportMethods(Array.prototype, {
  first: function() {
      return this[0];
  },
  last: function() {
    return this[this.length - 1];
  },
  clear: function() {
    this.length = 0;
    return this;
  },
  without: function() {
    var values = __$A__(arguments);
    return this.select(function(value) {
      return !values.include(value);
    });
  },
  removeAt: function(index)
  {
    this.splice(index, 1);
  },
  remove: function(item)
  {
    var index = this.indexOf(item);
    if (index >= 0)
      return this.removeAt(index);
  },
  forEach: function(func, scope)
  {
    var i, len;
    for (i = 0, len = this.length; i < len; ++i) {
      if (i in this) {
        func.call(scope, this[i], i, this);
      }
    }
  }
});
*/
if (!Array.prototype.indexOf)
{
	/** @ignore */
	Array.prototype.indexOf = function(item, i) {
	  i || (i = 0);
	  var length = this.length;
	  if (i < 0) i = length + i;
	  for (; i < length; i++)
	    if (this[i] === item) return i;
	  return -1;
	};
}

if (!Array.prototype.lastIndexOf)
{
	/** @ignore */
	Array.prototype.lastIndexOf = function(item, i) {
	  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
	  var n = this.slice(0, i).reverse().indexOf(item);
	  return (n < 0) ? n : i - n - 1;
	};
}

/** @ignore */
Object._extendSupportMethods(String.prototype, {
	gsub: function gsub(pattern, replacement) {
		var result = '', source = this, match;
		//replacement = this.gsub.prepareReplacement(replacement);

		while (source.length > 0) {
			if (match = source.match(pattern)) {
				result += source.slice(0, match.index);
				result += replacement;
				source  = source.slice(match.index + match[0].length);
			} else {
				result += source, source = '';
			}
		}
		return result;
	},

	sub: function(pattern, replacement, count) {
		//replacement = this.gsub.prepareReplacement(replacement);
		count = Object.isUndefined(count) ? 1 : count;

		return this.gsub(pattern, function(match) {
			if (--count < 0) return match[0];
			return replacement(match);
		});
	},

	scan: function(pattern, iterator) {
		this.gsub(pattern, iterator);
		return String(this);
	},

  truncate: function(length, truncation) {
    length = length || 30;
    truncation = Object.isUndefined(truncation) ? '...' : truncation;
    return this.length > length ?
      this.slice(0, length - truncation.length) + truncation : String(this);
  },

  strip: function() {
    return this.replace(/^\s+/, '').replace(/\s+$/, '');
  },

  stripTags: function() {
    return this.replace(/<\/?[^>]+>/gi, '');
  },

  stripScripts: function() {
    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
  },

  extractScripts: function() {
    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
    return (this.match(matchAll) || []).map(function(scriptTag) {
      return (scriptTag.match(matchOne) || ['', ''])[1];
    });
  },

  evalScripts: function() {
    return this.extractScripts().map(function(script) { return eval(script); });
  },

  escapeHTML: function escapeHTML() {
		/*
    var self = arguments.callee;
    self.text.data = this;
    return self.div.innerHTML;
    */
		return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n/g, '<br />');
  },

  unescapeHTML: function() {
		/*
    var div = new Element('div');
    div.innerHTML = this.stripTags();
    return div.childNodes[0] ? (div.childNodes.length > 1 ?
      __$A__(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue; }) :
      div.childNodes[0].nodeValue) : '';
    */
		return this.replace(/\<br \/\>/g, '\n').replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
  },

  toQueryParams: function(separator) {
    var match = this.strip().match(/([^?#]*)(#.*)?$/);
    if (!match) return { };

    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
      if ((pair = pair.split('='))[0]) {
        var key = decodeURIComponent(pair.shift());
        var value = pair.length > 1 ? pair.join('=') : pair[0];
        if (value != undefined) value = decodeURIComponent(value);

        if (key in hash) {
          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
          hash[key].push(value);
        }
        else hash[key] = value;
      }
      return hash;
    });
  },

  toArray: function() {
    return this.split('');
  },

  succ: function() {
    return this.slice(0, this.length - 1) +
      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
  },

  times: function(count) {
    return count < 1 ? '' : new Array(count + 1).join(this);
  },

  camelize: function() {
    var parts = this.split('-'), len = parts.length;
    if (len == 1) return parts[0];

    var camelized = this.charAt(0) == '-'
      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
      : parts[0];

    for (var i = 1; i < len; i++)
      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);

    return camelized;
  },

  capitalize: function() {
    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
  },
	capitalizeFirst: function() {
    return this.charAt(0).toUpperCase() + this.substring(1);
  },

  underscore: function() {
    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
  },

  dasherize: function() {
    return this.gsub(/_/,'-');
  },

  inspect: function(useDoubleQuotes) {
    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
      var character = String.specialChar[match[0]];
      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
    });
    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
  },

  toJSON: function() {
    return this.inspect(true);
  },

  unfilterJSON: function(filter) {
    return this.sub(filter || Prototype.JSONFilter, '#{1}');
  },

  isJSON: function() {
    var str = this;
    if (str.blank()) return false;
    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
  },

  evalJSON: function(sanitize) {
    var json = this.unfilterJSON();
    try {
      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
    } catch (e) { }
    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
  },

  include: function(pattern) {
    return this.indexOf(pattern) > -1;
  },

  startsWith: function(pattern) {
    return this.indexOf(pattern) === 0;
  },

  endsWith: function(pattern) {
    var d = this.length - pattern.length;
    return d >= 0 && this.lastIndexOf(pattern) === d;
  },

  empty: function() {
    return this == '';
  },

  blank: function() {
    return /^\s*$/.test(this);
  },

  interpolate: function(object, pattern) {
    return new Template(this, pattern).evaluate(object);
  }
});

// added by partridge
/** @ignore */
Object._extendSupportMethods(String.prototype, {
	upperFirst: function()
	{
		return this.charAt(0).toUpperCase() + this.substring(1);
	},
	// 'The {0} is dead. Don\'t code {0}. Code {1} that is open source!'.format('ASP', 'PHP');
	format: function() {
    var formatted = this;
    for (var i = 0; i < arguments.length; i++) {
        var regexp = new RegExp('\\{'+i+'\\}', 'gi');
        formatted = formatted.replace(regexp, arguments[i]);
    }
    return formatted;
	},
  trim: function()
  {
    return this.replace(/^\s*|\s*$/g, "");
  },
	ltrim: function()
	{
		return this.replace(/^\s+/,"");
	},
	rtrim: function()
	{
		return this.replace(/\s+$/,"");
	},
  trimLeft: function()
  {
    return this.ltrim();
  },
  trimRight: function()
  {
    return this.rtrim();
  },
	pad: function(length, padString, rightPad)
	{
		var p = padString || ' ';
		var a = [];
		for (var i = 0, l = length - this.length; i < l; ++i)
			a.push(p);
		var str = this;
		if (rightPad)
			return str + a.join('');
		else
			return a.join('') + str;
	},
	lpad: function(length, padString)
	{
		return this.pad(length, padString, false);
	},
	rpad: function(length, padString)
	{
		return this.pad(length, padString, true);
	},
	reverse: function()
	{
		var length = this.length;
		var a = [];
		for (var i = length - 1; i >= 0; --i)
		{
			a.push(this.charAt(i));
		}
		return a.join('');
	},
	toCharArray: function()
	{
		var a = [];
		for (var i = 0, l = this.length; i < l; ++i)
		{
			a.push(this.charAt(i));
		}
		return a;
	},

	/*
	 * Turn camelized word like thisIsAWord to this-is-a-word.
	 */
	hyphenize: function(hyphen)
	{
		if (!hyphen)
			hyphen = '-';
		var len = this.length;
		var a = [];
		for (var i = 0; i < len; ++i)
		{
			var c = this.charAt(i);
			if ((i !== 0) && (c >= 'A') && (c <= 'Z'))
			{
				a.push(hyphen + c.toLowerCase());
			}
			else
				a.push(c);
		}
		return a.join('');
	}
});



/**
 * Some helper methods about string
 * @class
 */
var StringUtils = {
	/** @private */
	STRUE: '$TRUE',
	/** @private */
	SFALSE: '$FALSE',
	/** @private */
	SUNDEFINED: '$UNDEFINED',
	/** @private */
	SNULL: '$NULL',
	/** @private */
	SNAN: '$NAN',
	/** @private */
	SPOSITIVE: '+',
	/** @private */
	SNEGATIVE: '-',
	/** @private */
	SDATEPREFIX: '@',
	/**
	 * Check if a str is all consisted of digitals
	 * @param {String} str
	 * @returns {Bool}
	 * @private
	 */
	isAllDigitalChar: function(str)
	{
		for (var i = 0, l = str.length; i < l; ++i)
		{
			var c = str.charAt(i);
			var isDigital = (c >= '0') && (c <= '9');
			if ((!isDigital) && (c != '.'))
				return false;
		}
		return true;
	},
	/**
   * Check if str is in number format.
   * @param {String} str
   * @returns {Bool}
   */
  isNumbericStr: function(str)
  {
    var a = Number(str);
    return !isNaN(a);
  },
	/**
	 * Serialize a simple value and try to preserve value type info.
	 * @param {Variant} value
	 * @param {Array} unchangeTypes Name of types that need not to be special marked.
	 * @returns {String}
	 */
	serializeValue: function(value, unchangeTypes)
	{
		var utypes = unchangeTypes || [];
		var vtype = DataType.getType(value);
		if (utypes.indexOf(vtype) >= 0)
			return value.toString();

		if (value === null)
			return StringUtils.SNULL;
		else if (typeof(value) == 'undefined')
			return StringUtils.SUNDEFINED;
		else if (value != value)  // NaN
			return StringUtils.SNAN;
		else
		{
			switch (vtype)
			{
				case 'boolean':
					return value ? StringUtils.STRUE : StringUtils.SFALSE;
					break;
				case 'number': case DataType.INT:case DataType.FLOAT:
					// add '+' or '-' symbol before a number value
					var sign = (value >= 0)? StringUtils.SPOSITIVE: '';  //this.SNEGATIVE;
					return sign + value;
					break;
				case DataType.DATE:
					return StringUtils.SDATEPREFIX + value.toString();
				default:  // string
					return value.toString();
			}
		}
	},
	/**
	 * Deserialize a simple value from string by value type info stored inside.
	 * @param {String} str
	 * @param {String} preferedType If provided, str will be converted to this type.
	 * @returns {Variant}
	 */
	deserializeValue: function(str, preferedType)
	{
		if (typeof(str) !== 'string')
			return str;
		switch(str)
		{
			case StringUtils.STRUE: return true; break;
			case StringUtils.SFALSE: return false; break;
			case StringUtils.SNULL: return null; break;
			case StringUtils.SUNDEFINED: return undefined; break;
			case StringUtils.SNAN: return NaN; break;
			default:
			{
				if (preferedType)
				{
					switch (preferedType)
					{
						case DataType.FLOAT:
							return parseFloat(str); break;
						case DataType.INT:
							return parseInt(str); break;
						case 'number':
							return parseFloat(str); break;
						case 'boolean':
							return !!str; break;
						default:
							return str;
					}
				}
				else // guess
				{
					var firstChar = str.charAt(0);
					switch (firstChar)
					{
						case StringUtils.SPOSITIVE:
						case StringUtils.SNEGATIVE: // may be number
							{
								var s = str.substring(1);
								if (StringUtils.isNumbericStr(s)) // really number or number like 1e20
									return parseFloat(str);
								else
									return str;
								break;
							}
						case StringUtils.SDATEPREFIX:  // may be date
							{
								var s = str.substr(1);
								return new Date(s);
							}
						default:
							return str;
					}
				}
			}
		}
	}
};


// Added by partridge
/** @ignore */
Object.extend(Date.prototype, {
	/** @ignore */
	copyFrom: function(src)
	{
		this.setFullYear(src.getFullYear(), src.getMonth(), src.getDate());
		this.setTime(src.getTime());
	}
});

/** @ignore */
if (!Math.sqr)
{
  Math.sqr = function(x)
  {
    return x * x;
  };
}
/** @ignore */
if (!Math.sign)
  Math.sign = function(x) {
    return (x > 0)? 1:
      (x < 0)? -1:
        0;
  };

// Add Node.XXXX support in IE
//if (!window.Node) var Node = { };
if (!$jsRoot.Node) $jsRoot.Node = { };

if (!$jsRoot.Node.ELEMENT_NODE) {
  // DOM level 2 ECMAScript Language Binding
  Object.extend($jsRoot.Node, {
    ELEMENT_NODE: 1,
    ATTRIBUTE_NODE: 2,
    TEXT_NODE: 3,
    CDATA_SECTION_NODE: 4,
    ENTITY_REFERENCE_NODE: 5,
    ENTITY_NODE: 6,
    PROCESSING_INSTRUCTION_NODE: 7,
    COMMENT_NODE: 8,
    DOCUMENT_NODE: 9,
    DOCUMENT_TYPE_NODE: 10,
    DOCUMENT_FRAGMENT_NODE: 11,
    NOTATION_NODE: 12
  });
}

/**
 * Enumeration of property scope.
 * @enum {Int}
 */
Class.PropertyScope = {
	DEFAULT: 3,
	PUBLISHED: 3,
	PUBLIC: 2,
	PRIVATE: 1
};

/** @class Class.PropList
 *  @description Property list, inside each prop is stored as a object:
 *  {
 *  	name: property name,
 *  	storeField: defaultly which field to store value,
 *  	dataType:
 *  	serializable: boolean,
 *  	defaultValue: default value of property
 *    // added in 2014-05-02, for object inspector
 *    title: a string that displays in object inspector.
 *    description: a string that describes the property.
 *    category: string, category of property. Different property may have the same category.
 *    scope: private, public or published. Used to filter output properties in object inspector.
 *    enumSource: object. Some property marked as int or other type may be actually a enum,
 *      if this value is set, all owned fields of enumSource will be treated as possible enum values in object inspector.
 *    elementType: string. For array type property, this field set the type of array items.
 *  }
 *  @ignore
 */
Class.PropList = function()
{
	this.props = [];
};
Class.PropList.prototype = {
	//** @private */
	//PROP_KEY_PREFIX: '__$prop__',
	//** @private */
	/*
	getPropKeyPrefix: function(propName)
	{
		return this.PROP_KEY_PREFIX + propName;
	},
	*/
	/**
	 * Add a property info object to the list.
	 * @param {String} propName
	 * @param {Hash} options A hash that may contains the following fields:
	 *   {
	 *     storeField: field name to store the property in object.
	 *     dataType: data type of property, unused currently.
	 *     serializable: whether this property can be serialized or unserialized.
	 *     defaultValue: default value of property, can only be simple type (string, number) now.
	 *     getter:
	 *     setter:
	 *     // added in 2014-05-02, for object inspector
	 *     title: a string that displays in object inspector.
	 *     description: a string that describes the property.
	 *     category: string, category of property. Different property may have the same category.
	 *     scope: private, public or published. Used to filter output properties in object inspector.
	 *     enumSource: object. Some property marked as int or other type may be actually a enum,
	 *       if this value is set, all owned fields of enumSource will be treated as possible enum values in object inspector.
	 *     elementType: string. For array type property, this field set the type of array items.
	 *   }
	 * @returns {Object} A property info object just added.
	 */
	addProperty: function(propName, options)
	{
		if (!options)
			options = {};

		var propInfo;
		propInfo = this.getPropInfo(propName);
		if (!propInfo)
		{
			propInfo = {};
			this.props.push(propInfo);
		}
		propInfo = Object.extend(propInfo, options);
		if (propInfo.serializable  === undefined)
			propInfo.serializable = true;

		propInfo.name = propName;
		/*
		propInfo.storeField = options.storeField;
		propInfo.dataType = options.dataType;
		propInfo.serializable = options.serializable;
		propInfo.defaultValue = options.defaultValue;
		*/

		/*
		// to accelarate the speed of getting prop info, add a hash key here
		this[this.getPropKeyPrefix(propName)] = propInfo;
		*/

		return propInfo;
	},
	/**
	 * Remove a property info object from list
	 * @param {String} propName property name to be removed.
	 */
	removeProperty: function(propName)
	{
		/*
		var hashKey = this.getPropKeyPrefix(propName);
		if (this[hashKey])
			delete this[hashKey];
		*/

		var index = this.indexOf(propName);
		if (index >= 0)
		{
			this.props.splice(index, 1);
		}
		//this.props.length = this.props.length - 1;
	},
	removePropAt: function(index)
	{
		if (index >= 0)
		{
			var prop = this.props[index];
			if (prop)
			{
				/*
				var hashKey = this.getPropKeyPrefix(prop.name);
				if (this[hashKey])
					delete this[hashKey];
				*/
				this.props.splice(index, 1);
			}
		}
	},
	/**
	 * Returns property count in this list.
	 * @returns {Int}
	 */
	getLength: function()
	{
		return this.props.length;
	},
	/**
	 * Get the index of a property in list.
	 * @param {String} propName Name of property to be found.
	 * @returns {Num} Index of property found. If nothing is found, returns -1.
	 */
	indexOf: function(propName)
	{
		for (var i = 0, l = this.props.length; i < l; ++i)
		{
			if (this.props[i].name === propName)
				return i;
		}
		return -1;
	},
	/**
	 * Get property info object from the list at index.
	 * @param {Int} index index of property to be found.
	 * @returns {Object} Property info object found. If nothing is found, returns null.
	 */
	getPropInfoAt: function(index)
	{
		if (index >= 0)
			return this.props[index];
		else
			return null;
	},
	/**
	 * Get property info object from the list.
	 * @param {String} propName Name of property to be found.
	 * @returns {Object} Property info object found. If nothing is found, returns null.
	 */
	getPropInfo: function(propName)
	{
		var result;
		/*
		var hashKey = this.getPropKeyPrefix(propName);
		if (this[hashKey])
			result = this[hashKey];
		else
		*/
		{
			var index = this.indexOf(propName);
			var result = this.getPropInfoAt(index);

			/*
			if (!result)
				;
			else
			{
				console.dir(this);
				throw('should not be here: ' + propName);
			}
			*/

			//this[hashKey] = result;   // add to hash key to accelerate next time searching
		}
		return result;
	},
	/**
	 * Check whether a property existed in the list.
	 * @param {String} propName Name of property to be checked.
	 * @return {Bool} true or false.
	 */
	hasProperty: function(propName)
	{
		/*
		var hashKey = this.getPropKeyPrefix(propName);
		return (!!this[hashKey]) || (this.indexOf(propName) >= 0);
		*/
		return (this.indexOf(propName) >= 0);
	},
	/**
	 * Clear all property info objects in the list.
	 */
	clear: function()
	{
		/*
		var props = this.props;
		for (var i = props.length; i >= 0; --i)
		{
			this.removePropAt(i);
		}
		*/
		this.props.clear();
	},
	/**
	 * Clone the while propList
	 * @returns {Class.PropList}
	 */
	clone: function()
	{
		var result = new Class.PropList();
		result.props = this.props.slice();
    //result.props = [].concat(this.props);
		return result;
	},
	/**
	 * Append the content of propList to current one.
	 * @param {Class.PropList} propList
	 */
	appendList: function(propList)
	{
		for (var i = 0, l = propList.props.length; i < l; ++i)
		{
			this.props.push(propList.props[i]);
		}
	}
};
Class.PropList.prototype.constructor = Class.PropList;

/**
 *  @class Class.EventHandlerList
 *  @description
 *  Event handler list, support for multi-receiver event system
 *  the list hold a handlerInfo array, each item has two fields:
 *  {
 *  	handler: handler function,
 *  	thisArg: this scope object, if null, use default this
 *  }
 *  @ignore
 */
Class.EventHandlerList = function()
{
	this.handlers = [];
  this._$flag_ = 'KekuleEventList';
};
Class.EventHandlerList.prototype = {
	/**
	 * Add a handler to the list
	 * @param {Function} handler An event handler function.
	 * @param {Object} thisArg The handler should be bind to which scope when be invoked.
	 */
	add: function(handler, thisArg)
	{
		if (!thisArg)
			thisArg = null;
		this.handlers.push({
			'thisArg': thisArg,
			'handler': handler
		});
	},
	/**
	 * Remove an event handler from the list.
	 * @param {Function} handler Handler function to be removed.
	 * @param {Object} thisArg If this param is null, all functions same as handler
	 *   in the list will be removed regardless of whether their thisArg is setted.
	 */
	remove: function(handler, thisArg)
	{
		var indexes = this.indexesOf(handler, thisArg);
		if (indexes.length > 0)
		{
			for (var i = indexes.length - 1; i >= 0; --i)
			{
				this.removeAt(indexes[i]);
			}
		}
	},
	/**
	 * Remove an event handler at a specified index.
	 * @param {Num} index
	 */
	removeAt: function(index)
	{
		for (var i = index, l = this.handlers.length; i < l; ++i)
			this.handlers[i] = this.handlers[i + 1];
		this.handlers.length = this.handlers.length - 1;
	},
	/**
	 * Clear all handlers in the list.
	 */
	clear: function()
	{
		this.handlers = [];
	},
	/**
	 * Get handler info object from the list.
	 * @param {Num} index
	 * @return {Object} Handler info object on index.
	 */
	getHandlerInfo: function(index)
	{
		return this.handlers[index];
	},
	/**
	 * Get the index of a handler specified with thisArg.
	 * @param {Function} handler
	 * @param {Object} thisArg
	 * @return {Num} Index of the handler. If nothing is found, returns -1.
	 */
	indexOf: function(handler, thisArg)
	{
		for (var i = 0, l = this.handlers.length; i < l; ++i)
		{
			if (this.handlers[i].handler == handler)
			{
				if ((thisArg !== undefined) && (this.handlers[i].thisArg === thisArg))
					return i;
				else if (thisArg === undefined)
					return i;
			}
		}
		return -1;
	},
	/**
	 * Seek out all indexes that match handler and thisArg.
	 * @param {Function} handler
	 * @param {Object} thisArg
	 * @return {Array} All found indexes. If nothing is found, an empty array will be returned.
	 */
	indexesOf: function(handler, thisArg)
	{
		var result = [];
		for (var i = 0, l = this.handlers.length; i < l; ++i)
		{
			if (this.handlers[i].handler == handler)
			{
				if ((thisArg !== undefined) && (this.handlers[i].thisArg === thisArg))
					result.push(i);
				else if (thisArg === undefined)
					result.push(i);
			}
		}
		return result;
	},
	/**
	 * Get total count of registered handlers.
	 * @return {Num} Number of handlers.
	 */
	getLength: function()
	{
		return this.handlers.length;
	}
};
Class.EventHandlerList.constructor = Class.EventHandlerList;

/**
 * Includes constants and mthods about data types.
 * @class
 */
var DataType = {
	/** Unknown data type, same as {@link DataType.VARIANT}. */
	UNKNOWN: null,
	/** Variant data type, same as {@link DataType.UNKNOWN}. */
	VARIANT: null,
	/** Basic data type, including string, number and boolean. */
	PRIMARY: 'primary',
	/** type of JS const undefined */
	UNDEFINED: 'undefined',
	/** Boolean. */
	BOOL: 'boolean',
	/** Boolean. same as {@link DataType.BOOL} */
	BOOLEAN: 'boolean',
	/** Number. */
	NUMBER: 'number',
	/** Explicit integer number. */
	INT: 'int',
	/** Explicit integer number, same as {@link DataType.INT} */
	INTEGER: 'int',
	/** Explicit float number. */
	FLOAT: 'float',
	/** String */
	STRING: 'string',
	/** Array */
	ARRAY: 'array',
	/** Function */
	FUNCTION: 'function',
	/** Hash */
	DATE: 'date',
	HASH: 'object',
	/** A normal JavaScript object. */
	OBJECT: 'object',
	/** Object extended from {@link ObjectEx} */
	OBJECTEX: 'objectex',
	/** A CLASS */
	CLASS: 'class',

	/**
	 * Returns whether a type name is string, number or boolean
	 * @param {String} typeName
	 * @returns {Bool}
	 */
	isSimpleType: function(typeName)
	{
		return (typeName == DataType.STRING) || (typeName == DataType.NUMBER)
			|| (typeName == DataType.INT) || (typeName == DataType.FLOAT)
			|| (typeName == DataType.BOOL) || (typeName == DataType.UNDEFINED)
			|| (typeName == DataType.PRIMARY);
	},
	/**
	 * Returns whether a type name is object, array or objectex
	 * @param {String} typeName
	 * @returns {Bool}
	 */
	isComplexType: function(typeName)
	{
		return !(DataType.isSimpleType(typeName) || DataType.isFunctionType(typeName));
	},
	/**
	 * Returns whether a type name is function.
	 * @param {String} typeName
	 * @returns {Bool}
	 */
	isFunctionType: function(typeName)
	{
		return typeName == DataType.FUNCTION;
	},
	/**
	 * Returns whether a type name is object.
	 * NOTE: this function does not distinguish array.
	 * @param {String} typeName
	 * @returns {Bool}
	 */
	isObjectType: function(typeName)
	{
		return typeName == DataType.OBJECT;
	},
	/**
	 * Returns whether a type name is Date.
	 * @param {String} typeName
	 * @returns {Bool}
	 */
	isDateType: function(typeName)
	{
		return typeName == DataType.DATE;
	},
	/**
	 * Returns whether a type name is ObjectEx.
	 * @param {String} typeName
	 * @returns {Bool}
	 */
	isObjectExType: function(typeName)
	{
		var result = DataType.isComplexType(typeName) && (!DataType.isObjectType(typeName)) && (!DataType.isDateType(typeName));
		if (result)  // check if the class exists
		{
			var classObj = ClassEx.findClass(typeName);
			result = classObj && ClassEx.isOrIsDescendantOf(classObj, ObjectEx);
		}
		return result;
	},
	/**
	 * Get value type and returns a data type string.
	 * @param {Variant} value
	 * @returns {String}
	 */
	getType: function(value)
	{
		var stype = typeof(value);
		switch (stype)
		{
			// TODO: Some native classes such as RegExp are not checked yet
			// basic types
			case 'undefined': return DataType.UNDEFINED;
			case 'function': return DataType.FUNCTION;
			case 'boolean': return DataType.BOOL;
			case 'string': return DataType.STRING;
			case 'number':
				{
					if (Math.floor(value) == value)
						return DataType.INT;
					else
						return DataType.FLOAT;
				}
			case 'object':  // complex
				{
					if (this.isDateValue(value))
						return DataType.DATE;
					else if (DataType.isArrayValue(value))
						return DataType.ARRAY;
					else if (ClassEx.isClass(value))
						return DataType.CLASS;
					else if (DataType.isObjectExValue(value) && value.getClassName)
						return value.getClassName();
					else
						return DataType.OBJECT;
				}
			default:
				return stype;
		}
	},
	/**
	 * Check if value is number, string or bool.
	 * @param {Variant} value
	 * @returns {Bool}
	 * @private
	 */
	isSimpleValue: function(value)
	{
		return DataType.isSimpleType(typeof(value));
	},
	/**
	 * Check if value is undefined
	 * @param {Variant} value
	 * @returns {Bool}
	 * @private
	 */
	isUndefinedValue: function(value)
	{
		return typeof(value) == 'undefined';
	},
	/**
	 * Check if value is null
	 * @param {Variant} value
	 * @returns {Bool}
	 * @private
	 */
	isNullValue: function(value)
	{
		return (value === null);
	},
	/**
	 * Check if value is a function.
	 * @param {Variant} value
	 * @returns {Bool}
	 * @private
	 */
	isFunctionValue: function(value)
	{
		return typeof(value) == 'function';
	},
	/**
	 * Check if an value is an non-array Object.
	 * @param {Variant} value
	 * @returns {Bool}
	 * @private
	 */
	isObjectValue: function(value)
	{
		if (value)  // not null
			return (typeof(value) == 'object') && (!DataType.isArrayValue(value)) && (!DataType.isDateValue(value));
		else
			return false;
	},
	/**
	 * Check if an value is an instance of Date.
	 * @param {Variant} value
	 * @returns {Bool}
	 * @private
	 */
	isDateValue: function(value)
	{
		if (value)
			return ((typeof(value) == 'object') && (value.getFullYear !== undefined));
		return false;
	},
	/**
	 * Check if an value is an Array
	 * @param {Variant} value
	 * @returns {Bool}
	 * @private
	 */
	isArrayValue: function(value)
	{
		if (value)
			return ((typeof(value) == 'object') && (value.length !== undefined));
		else
			return false;
	},
	/**
	 * Check if an value is an instance of ObjectEx
	 * @param {Variant} value
	 * @returns {Bool}
	 * @private
	 */
	isObjectExValue: function(value)
	{
		return (value instanceof ObjectEx);
	},
	/**
	 * Create an instance of typeName
	 * @param {String} typeName
	 * @returns {Variant}
	 */
	createInstance: function(typeName)
	{
		switch (typeName)
		{
			case DataType.UNDEFINED: return undefined;
			case DataType.DATE: return new Date();
			case DataType.ARRAY: return new Array();
			case DataType.OBJECT: return new Object();
			case DataType.FUNCTION: return new Function();
			default: // maybe a ObjectEx descendant
				{
					var classInstance = ClassEx.findClass(typeName.capitalizeFirst()); //eval(typeName.capitalizeFirst());
					return new classInstance();
				}
		}
	}
};

// Wether has full support of Object/defineProperty method (IE8 has partial support and will return false)
var __definePropertyAvailable__ = Object.defineProperty &&
  (function () { try { Object.defineProperty({}, 'x', {}); return true; } catch (e) { return false; } } ())


/**
 * A pack of utility methods to modify a existing class.
 * @class ClassEx
 */
var ClassEx = {
	/**
	 * Checks if a object is a class object.
	 * @param {Object} classObj
	 */
	isClass: function(classObj)
	{
		if (!classObj)
			return false;
		return !!(classObj.superclass || classObj.subclasses);
	},
	/**
	 * Return class object from class name. If this class is not found, null will be returned.
	 * @param {String} className
	 * @returns {Class}
	 */
	findClass: function(className, root)
	{
    /*
		var result;
		var cascadeNames = className.split('.');
    if (!root)
		  var root = $jsRoot;
		for (var i = 0, l = cascadeNames.length; i < l; ++i)
		{
			result = root[cascadeNames[i]];
			if (!result)
				break;
			else
				root = result;
		}
		return result;
		*/
		return Object.getCascadeFieldValue(className, root || $jsRoot);
	},
	/**
	 * Get class name of aClass, usually returns CLASS_NAME field of aClass
	 * @returns {String} Class name.
	 */
	getClassName: function(aClass)
	{
    if (aClass)
		  return aClass.prototype.CLASS_NAME;
    else
      return null;
	},
  /**
   * Get last part of class name of this class.
   * For example, 'Atom' will be returned by class 'Kekule.Atom'.
   * @return {String} Last part of class name of class.
   */
  getClassLocalName: function(aClass)
  {
    var className = ClassEx.getClassName(aClass);
    var pos = className.lastIndexOf('.');
    return (pos >= 0)? className.substring(pos + 1): className;
  },
	/**
	 * Get prototype of aClass.
	 * @returns {Object}
	 */
	getPrototype: function(aClass)
	{
		return aClass.prototype;
	},
	/**
	 * Get super class if aClass.
	 * @returns {Object}
	 */
	getSuperClass: function(aClass)
	{
		return aClass.superclass || aClass.constructor.superclass;
	},
	/**
	 * Get prototype of super class.
	 * @return {Object} If there is no super class, null is returned.
	 */
	getSuperClassPrototype: function(aClass)
	{
		if (aClass.superclass)
			return aClass.superclass.prototype;
		else
			return null;
	},
	/**
	 * Returns the class that is the super class of all input classes.
	 * @param {Object} class1
	 * @param {Object} class2
	 * @returns {Object}
	 * @private
	 */
	_getCommonSuperClass2: function(class1, class2)
	{
		var result = class1;
		while (result && (!ClassEx.isOrIsDescendantOf(class2, result)))
		{
			result = ClassEx.getSuperClass(result);
		}
		return result;
	},
	/**
	 * Returns common super class of all objects.
	 * @param {Array} objects
	 * @returns {Class}
	 * @private
	 */
	getCommonSuperClass: function(objects)
	{
		if (!objects || !objects.length)
			return null;
		var result = objects[0].getClass? objects[0].getClass(): null;
		if (!result)
			return null;
		for (var i = 1, l = objects.length; i < l; ++i)
		{
			var classObj = objects[i].getClass? objects[i].getClass(): null;
			if (!classObj)
				return null;
			result = ClassEx._getCommonSuperClass2(result, classObj);
		}
		return result;
	},
	/**
	 * Check if aClass is a descendant of superClass.
	 * @param {Class} aClass
	 * @param {Class} superClass
	 * @returns {Bool}
	 */
	isDescendantOf: function(aClass, superClass)
	{
		var ancestor = ClassEx.getSuperClass(aClass);
		while (ancestor && (ancestor !== superClass))
		{
			ancestor = ClassEx.getSuperClass(ancestor);
		}
		return !!ancestor;
	},
	/**
	 * Check if aClass is or is a descendant of superClass.
	 * @param {Class} aClass
	 * @param {Class} superClass
	 * @returns {Bool}
	 */
	isOrIsDescendantOf: function(aClass, superClass)
	{
		return (aClass === superClass) || ClassEx.isDescendantOf(aClass, superClass);
	},
	/**
	 * Check if aClass is or is a descendant of one memeber of superClasses.
	 * @param {Class} aClass
	 * @param {Array} superClasses
	 * @returns {Bool}
	 */
	isOrIsDescendantOfClasses: function(aClass, superClasses)
	{
		for (var i = 0, l = superClasses.length; i < l; ++i)
		{
			if (ClassEx.isOrIsDescendantOf(aClass, superClasses[i]))
				return true;
		}
		return false;
	},
	/**
	 * Ensure property system of a class is properly initialized.
	 * @private
	 */
	_ensurePropertySystem: function(aClass)  // used internally for create property list
	{
		var proto = ClassEx.getPrototype(aClass);
		if (!proto)
			return;
		if (!proto.hasOwnProperty('properties'))
		{
			// ensure super class's property list is created
			var parent = ClassEx.getSuperClass(aClass);
			if (parent)
				ClassEx._ensurePropertySystem(parent);
			ClassEx._createPropertyList(aClass);
			if (proto.hasOwnProperty('initProperties'))  // prevent call parent initProperties method
				proto.initProperties.apply(proto);
		}
	},
	/** @private */
	_createPropertyList: function(aClass)  // used internal, create property list
	{
		if (!ClassEx.getPrototype(aClass).hasOwnProperty('properties'))
			ClassEx.getPrototype(aClass).properties = new Class.PropList();
	},
  /** @private */
  _remapPropGetters: function(aClass, availableFieldNames)
  {
    var proto = ClassEx.getPrototype(aClass);
    var fieldNames = availableFieldNames || Object.getOwnPropertyNames(proto);
    // check if has own doGetXXX method that override the getter of super class
    for (var i = 0, l = fieldNames.length; i < l; ++i)
    {
      var fieldName = fieldNames[i];
      if (fieldName.length > 5 && fieldName.startsWith('doGet'))
      {
        var field = proto[fieldName];
        if (typeof(field) === 'function')
        {
          var propName = fieldName.charAt(5).toLowerCase() + fieldName.substr(6);
          if (proto.hasDirectProperty(propName))  // ignore properties defined in this class, only override properties from super classes
            continue;
          var propInfo = ClassEx.getPropInfo(aClass, propName);
          if (propInfo && propInfo.getter)
          {
            //console.log('find prop getter override', i, propName, this.getClassName(), field);
            var getterName = 'get' + propName.capitalizeFirst();
            proto[getterName] = field;

            if (__definePropertyAvailable__)  // redefine property
            {
              var descs = Object.extend({}, propInfo.descriptor);
              descs['get'] = field;
              try
              {
                Object.defineProperty(proto, propName, descs);
              }
              catch (e)
              {
                throw e;
              }
            }
          }
        }
      }
    }
  },
	/**
	 * Get own property list of this aClass, excluding inherited properties.
	 * @returns {Class.PropList}
	 */
	getOwnPropList: function(aClass)
	{
		var proto = ClassEx.getPrototype(aClass);
		if (proto)
		{
			proto._initPropertySystem();
			return proto.properties;
		}
		else
			return null;
	},
	/**
	 * Get property list of this aClass, including inherited properties.
   * @param {Class} aClass
	 * @returns {Class.PropList}
	 */
	getAllPropList: function(aClass)
	{
		var result;
		var s = ClassEx.getSuperClassPrototype(aClass);
		if (s)
		{
			result = s.getAllPropList().clone();
			result.appendList(ClassEx.getOwnPropList(aClass));
		}
		else
			result = ClassEx.getOwnPropList(aClass);
		return result;
	},
  /**
   * Get list of all properties of certain scopes in this class, including ones inherited from parent class.
   * @param {Class} aClass
   * @param {Array} scopes Array item value from {@link Class.PropertyScope}.
   * @returns {Class.PropList}
   */
  getPropListOfScopes: function(aClass, scopes)
  {
    /*
    var list = ClassEx.getAllPropList(aClass);
    if (!scopes || !scopes.length)
      return list;
    for (var i = list.getLength() - 1; i >= 0; --i)
    {
      var propInfo = list.getPropInfoAt(i);
      var propScope = propInfo.scope || Class.PropertyScope.DEFAULT;
      if (scopes.indexOf(propScope) < 0)  // discard
        list.removePropAt(i);
    }
    return list;
    */
    var findOwnList = function(aClass, scopes)
    {
      var list = ClassEx.getOwnPropList(aClass).clone();
      if (list)
      {
        for (var i = list.getLength() - 1; i >= 0; --i)
        {
          var propInfo = list.getPropInfoAt(i);
          var propScope = propInfo.scope || Class.PropertyScope.DEFAULT;
          if (scopes.indexOf(propScope) < 0)  // discard
            list.removePropAt(i);
        }
      }
      return list;
    };

    var result;
    var s = ClassEx.getSuperClass(aClass);
    if (s)
    {
      result = ClassEx.getPropListOfScopes(s, scopes).clone();
      result.appendList(findOwnList(aClass, scopes));
    }
    else
      result = findOwnList(aClass, scopes);
    return result;
  },

	/**
	 *  Define a property in aClass.
	 *  @param {Object} aClass Class object.
	 *  @param {String} propName Name of property, case sensitive.
	 *  @param {Object} options A hash object, may contains the following fields:
	 *  {
	 *  	dataType: type of property data
	 *  	storeField: field in object to store property value,
	 *  	getter: getter function,
	 *  	setter: setter function, if set to null, the property will be read-only,
	 *  	serializable: boolean, whether the property should be save or restore in serialization,
	 *  	defaultValue: default value of property, can only be simple type (number, string, bool...)
	 *  }
	 *  @return {Object} Property info object added to property list.
	 */
  defineProp: function(aClass, propName, options)
  {
		ClassEx._ensurePropertySystem(aClass);
		return ClassEx.getPrototype(aClass).defineProp(propName, options);
  },
  /**
	 *  Define a set of properties in aClass.
	 *  @param {Object} aClass Class object.
	 *  @param {Array} propDefItems An array of property define info. Each item may contains the following fields:
	 *  {
	 * 		name: name of property.
	 *  	dataType: type of property data
	 *  	storeField: field in object to store property value,
	 *  	getter: getter function,
	 *  	setter: setter function, if set to null, the property will be read-only,
	 *  	serializable: boolean, whether the property should be save or restore in serialization,
	 *  	defaultValue: default value of property, can only be simple type (number, string, bool...)
	 *  }
	 *  @return {Object} Property info object added to property list.
	 */
  defineProps: function(aClass, propDefItems)
  {
  	ClassEx._ensurePropertySystem(aClass);
  	var proto = ClassEx.getPrototype(aClass);
  	for (var i = 0, l = propDefItems.length; i < l; ++i)
  	{
  		var item = propDefItems[i];
  		var propName = item.name;
  		var options = item;
  		proto.defineProp(propName, options);
  	}
  },
  /**
   * Get property info object from the property list of aClass.
   * @param {String} propName Name of property.
   * @param {Bool} ownPropertyOnly If true, only property defined in this class will be checked.
   * @return {Object} Property info object found. If there is no such a property, null is returned.
   */
  getPropInfo: function(aClass, propName, ownPropertyOnly)
  {
  	return ClassEx.getPrototype(aClass).getPropInfo(propName, ownPropertyOnly);
  },
  /**
   * Define an event in aClass. Event is actually a special property with type {@link Class.EventHandlerList}
   * @param {String} eventName Name of event.
   * @return {Object} Property info object created.
   */
  defineEvent: function(aClass, eventName)
  {
  	return ClassEx.getPrototype(aClass).defineEvent(eventName);
  },
	/**
	 * Get a used method name in object/class
	 * @private
	 */
	getUnusedMethodName: function(aClass, baseName, fromIndex)
	{
		var start = fromIndex || 0;
		var i = start;
		var p = ClassEx.getPrototype(aClass);
		var name = baseName + Number(i).toString();
		while (p[name])
		{
			++i;
			name = baseName + Number(i).toString();
		}
		return name;
	},
	/**
	 * Extend a class method. New method defined in method param will replace the old one,
	 * while the old one can be called in first input parameter (usually named $origin) inside new method.
	 * However, $super can not be called in new method body.
	 * For example:
	 *   ClassEx.extend(SomeClass, 'setValue', function($origin, value)
	 *     {
	 *       return $origin(value);
	 *     }
	 *   );
	 * @param {Class} aClass
	 * @param {String} methodName
	 * @param {Function} method
	 */
	extendMethod: function(aClass, methodName, method)
	{
		var proto = ClassEx.getPrototype(aClass);
		var originMethod = proto[methodName];
		var newName = ClassEx.getUnusedMethodName(aClass, '__$changed$_' + methodName + '__');
		proto[newName] = originMethod;

		/** @ignore */
		var value = (function(m) {
			return function() {
				return proto[m].apply(this, arguments);
			};
		})(newName).wrap(method);

		proto[methodName] = value;

		value.valueOf = method.valueOf.bind(method);
		value.toString = method.toString.bind(method);
	},
	/**
	 * Extend class with a pack of methods.
	 * If method already in original class, you may use $origin to mark the original one.
	 * For exmaple:
	 *   ClassEx.extend(SomeClass, {
	 *     initialize: function($origin)
	 *   	 {
	 *       $origin();
	 *       // do something more...
	 *     }
	 *   });
	 * @param {Class} aClass
	 * @param {Hash} extension A pack of methods extended.
	 */
	extend: function(aClass, extension)
	{
		var proto = ClassEx.getPrototype(aClass);
		var ancestor = ClassEx.getSuperClassPrototype(aClass);  // proto.superclass && proto.superclass.prototype;
		var properties = Object.keys(extension);

		if (!Object.keys({ toString: true }).length)
			properties.push("toString", "valueOf");

		for (var i = 0, length = properties.length; i < length; i++)
		{
		    var property = properties[i], value = extension[property];
				if (typeof(value) == 'function')
				{
					//var args = value.argumentNames();
					var args = FunctionUtils.argumentNames(value);
					var first = args[0];  //args.first();

					if (first == '$origin')
					{
						var method = value;
						var originMethod = proto[property];
						var newName = ClassEx.getUnusedMethodName(aClass, '__$changed$_' + property + '__');
						proto[newName] = originMethod;

						/** @ignore */
		        value = (function(m) {
		            return function() {
									return proto[m].apply(/*proto*/this, arguments);
								};
		        })(newName).wrap(method);

						value.valueOf = method.valueOf.bind(method);
	        	value.toString = method.toString.bind(method);

						args.shift();
						first = args.first();
					}

					if (first == '$super')
					{
						var method = value;

						/** @ignore */
		        value = (function(m) {
		            return function() { return ancestor[m].apply(/*proto*/this, arguments); };
		        })(property).wrap(method);

		        value.valueOf = method.valueOf.bind(method);
		        value.toString = method.toString.bind(method);
					}
				}

		    proto[property] = value;
		}

    ClassEx._remapPropGetters(aClass, properties);

		return aClass;
	}
};

var
/**
 * @class ObjectEx
 * @description Base class for property support.
 *
 * //@property {Bool} enablePropValueGetEvent Whether propValueGet event should
 * //  be fired when a property value is read.
 * @property {Bool} enableObjectChangeEvent Whether event "change" will be automatically fired when the object is changed.
 * @property {Bool} enablePropValueSetEvent Whether propValueSet event should
 *   be fired when a property value is written.
 * //  Note, if property {@link ObjectEx#enableObjectChangeEvent} is false, this event will never be fired.
 * @property {Bool} bubbleEvent Whether event evoked can be relayed to higher level object.
 * @property {Bool} suppressChildChangeEventInUpdating If this property is true, when object is updating
 *   (calling obj.beginUpdate()), received "change" event will always not be bubbled. Instead, when updating
 *   finished (calling obj.endUpdate()), a "change" event of self (not child object) will be triggered with special
 *   property name '[chilren]'.
 */
/*
 * Invoked when a property value is gotten by its getter.
 *   event param of it has two fields: {propName, propValue}
 *   If property enablePropValueGottenEvent is false, this event will never be fired.
 * @name ObjectEx#propValueGet
 * @event
 * @deprecated
 * @not usable now
 */
/**
 * Invoked when a property value is set by its setter
 *   event param of it has two fields: {propName, propValue}
 *   If property enablePropValueSetEvent is false, this event will never be fired.
 * @name ObjectEx#propValueSet
 * @event
 */
/**
 * Invoked when the object has been modified.
 *   event param of it has one fields: {changedPropNames: Array}
 * @name ObjectEx#change
 * @event
 */
/**
 * Invoked when {@link ObjectEx#finalize} is called and the object is released.
 *   event param of it has one fields: {obj}
 * @name ObjectEx#finalize
 * @event
 */
ObjectEx = Class.create(
/** @lends ObjectEx# */
{
	/**
	 * name of class
	 * @private
	 */
	CLASS_NAME: 'ObjectEx',
	//* @private */
	//PROPINFO_HASHKEY_PREFIX: '__$propInfo__',
  /** @private */
  EVENT_HANDLERS_FIELD: '__$__k__eventhandlers__$__',
	/**
	 * @constructs
	 */
	initialize: function()
	{
		this._initPropertySystem();
		this.initPropValues();
		this._updateStatus = 0;  // used internal in begin/endUpdate methods
    this._childChangeEventSuppressed = false;
		this._modifiedProps = [];  // used internal in begin/endUpdate methods
		this._finalized = false;  // used internally, mark if the object has been freed
		this.afterInitialization();
	},
	/**
	 * Do jobs after initialization, desendants can override this method
	 */
	afterInitialization: function()
	{
		// do nothing here
	},
	/**
	 *  Free resources used. Like finalize method in Java.
	 */
	finalize: function()
	{
		if (!this._finalized)  // avoid call finalize multiple times on one object
		{
			this.doFinalize();
			this.invokeEvent('finalize', {'obj': this});
      // free all event objects
      //this.setPropStoreFieldValue('eventHandlers', null);
      this[this.EVENT_HANDLERS_FIELD] = null;
			this._finalized = true;
		}
	},
	/**
	 *  Do actual work of finalize.
	 *  @private
	 */
	doFinalize: function()
	{
		// do nothing here
	},
	/**
	 * Define all essential properties in this method.
	 * You do not need to call $super here in descendant classes. Each class only need to
	 * declare his own properties.
	 */
	initProperties: function()
	{
		// define properties
		this.defineProp('enablePropValueGetEvent', {'dataType': DataType.BOOL, 'serializable': false, 'scope': Class.PropertyScope.PUBLIC});
    this.defineProp('enablePropValueSetEvent', {'dataType': DataType.BOOL, 'serializable': false, 'scope': Class.PropertyScope.PUBLIC});
		this.defineProp('enableObjectChangeEvent', {'dataType': DataType.BOOL, 'serializable': false, 'scope': Class.PropertyScope.PUBLIC});
		this.defineProp('bubbleEvent', {'dataType': DataType.BOOL, 'serializable': false, 'scope': Class.PropertyScope.PUBLIC});
    this.defineProp('suppressChildChangeEventInUpdating', {'dataType': DataType.BOOL, 'serializable': false, 'scope': Class.PropertyScope.PUBLIC});
		// private, event storer
		this.defineProp('eventHandlers', {'dataType': DataType.HASH, 'serializable': false, 'scope': Class.PropertyScope.PRIVATE, 'setter': null,
			'getter': function()
				{
					//var r = this.getPropStoreFieldValue('eventHandlers');
          var r = this[this.EVENT_HANDLERS_FIELD];  // direct access, for speed
					if (!r)
					{
						r = {};
						//this.setPropStoreFieldValue('eventHandlers', r);
            this[this.EVENT_HANDLERS_FIELD] = r;
					}
					return r;
				}
		});
		/*
		// define two events that related with properties
		this.defineEvent('propValueGet');
		this.defineEvent('propValueSet');
		this.defineEvent('change');
		*/
	},
	/**
	 * Set initial value of properties.
   * Desendants can override this method.
	 */
	initPropValues: function()
	{
		// do nothing here
	},

	/**
	 * Get object level above this one.
	 * @private
	 */
	getHigherLevelObj: function()
	{
		return null;
	},

	/**
	 * Called after this object is saved through a serialization system. Descendants may override this.
	 */
	saved: function()
	{
		// do nothing here
	},
	/**
	 * Called after this object is loaded by a serialization system. Descendants may override this.
	 */
	loaded: function()
	{
		// do nothing here
	},

	/** @private */
	_initPropertySystem: function()  // used internally for create property list
	{
		if (!this.getPrototype().hasOwnProperty('properties'))
		{
      //console.log('init prop system', this.getClassName());
			// ensure super class's property list is created
			var parent = this.getSuperClassPrototype();
			if (parent)
				parent._initPropertySystem.apply(parent);

			this._createPropertyList();

      this._remapPropGetters();  // override before self property list created, avoid override method of self

			if (this.getPrototype().hasOwnProperty('initProperties'))  // prevent call parent initProperties method
				this.getPrototype().initProperties.apply(this.getPrototype());
		}
	},
	/** @private */
	_createPropertyList: function()  // used internal, create property list
	{
		if (!this.getPrototype().hasOwnProperty('properties'))
			this.getPrototype().properties = new Class.PropList();
	},
  /** @private */
  _remapPropGetters: function()
  {
    ClassEx._remapPropGetters(this.getClass());
    /*
    var proto = this.getPrototype();
    var fieldNames = Object.getOwnPropertyNames(proto);
    // check if has own doGetXXX method that override the getter of super class
    for (var i = 0, l = fieldNames.length; i < l; ++i)
    {
      var fieldName = fieldNames[i];
      if (fieldName.length > 5 && fieldName.startsWith('doGet'))
      {
        var field = proto[fieldName];
        if (typeof(field) === 'function')
        {
          var propName = fieldName.charAt(5).toLowerCase() + fieldName.substr(6);
          if (this.hasDirectProperty(propName))  // ignore properties defined in this class, only override properties from super classes
            continue;
          var propInfo = this.getPropInfo(propName);
          if (propInfo && propInfo.getter)
          {
            //console.log('find prop getter override', i, propName, this.getClassName(), field);
            var getterName = 'get' + propName.capitalizeFirst();
            proto[getterName] = field;


            if (__definePropertyAvailable__)  // redefine property
            {
              var descs = Object.extend({}, propInfo.descriptor);
              descs['get'] = field;
              try
              {
                Object.defineProperty(proto, propName, descs);
              }
              catch (e)
              {
                throw e;
              }
            }
          }
        }
      }
    }
    */
  },
	/**
	 * Get class of this object.
	 * @return {Object} Class object.
	 */
	getClass: function()
	{
		return this.constructor;
	},
	/**
	 * Get super class of this object.
	 * @return {Object} Class object.
	 */
	getSuperClass: function()
	{
		return this.getClass().superclass;
	},
	/**
	 * Get class name of this object, usually returns CLASS_NAME field.
	 * @return {String} Class name of object.
	 */
	getClassName: function()
	{
		return this.getPrototype().CLASS_NAME;
	},
  /**
   * Get last part of class name of this object.
   * For example, 'Atom' will be returned by instance of class 'Kekule.Atom'.
   * @return {String} Last part of class name of object.
   */
  getClassLocalName: function()
  {
    return ClassEx.getClassLocalName(this.getClass());
  },
	/**
	 * Returns a name to be used in serialization. Descendants can override this.
	 * @return {String}
	 */
	getSerializationName: function()
  {
    return this.getClassName();
  },
	/**
	 * Get prototype of this object.
	 * @return {Object}
	 */
	getPrototype: function()
	{
		if (this.prototype)
		{
			return this.prototype;
		}
		else
		{
			return this.constructor.prototype;
		}
	},
	/**
	 * Get prototype of super class.
	 * @return {Object} If there is no super class, null is returned.
	 */
	getSuperClassPrototype: function()
	{
		if (this.constructor && this.constructor.superclass)
			return this.constructor.superclass.prototype;
		else
			return null;
	},
	/*
	getPropList: function()
	{
		if (!this.getPrototype().properties)
			this.getPrototype().properties = new PropList();
		return this.getPrototype().properties;
	},
	*/
	/**
	 * Get property list of this class. The properties inherited from parent class will not be returned.
	 * @returns {Class.PropList}
	 */
	getOwnPropList: function()
	{
		this._initPropertySystem();
		return this.getPrototype().properties;
	},
	/**
	 * Get list of all properties in this class, including ones inherited from parent class.
	 * @returns {Class.PropList}
	 */
	getAllPropList: function()
	{
		var result;
		var s = this.getSuperClassPrototype();
		if (s)
		{
			result = s.getAllPropList().clone();
			result.appendList(this.getOwnPropList());
		}
		else
			result = this.getOwnPropList().clone();
		return result;
	},
  /**
   * Get list of all properties of certain scopes in this class, including ones inherited from parent class.
   * @param {Array} scopes Array item from {@link Class.PropertyScope}.
   * @returns {Class.PropList}
   */
  getPropListOfScopes: function(scopes)
  {
    return ClassEx.getPropListOfScopes(this.getClass(), scopes);
  },
	/** @private */
	getPropInfoHashKey: function(propName)
	{
		return ObjectEx._PROPINFO_HASHKEY_PREFIX + propName;
	},
	/** @private */
	getDefPropStoreFieldName: function(propName)
	{
		return ObjectEx._PROP_STOREFIELD_PREFIX + propName;
	},
	/**
	 *  Define a property in class.
	 *  @param {String} propName Name of property, case sensitive.
	 *  @param {Object} options A hash object, may contains the following fields:
	 *  {
	 *  	dataType: type of property data, a string constant in {@link DataType} or a class name.
	 *  	//storeField: field in object to store property value,  // not allowed for running speed
	 *  	getter: getter function,
	 *  	setter: setter function, if set to null, the property will be read-only,
	 *  	serializable: boolean, whether the property should be save or restore in serialization. Default is true.
	 *  	defaultValue: default value of property, can only be simple type (number, string, bool...)
	 *  }
	 *  @return {Object} Property info object added to property list.
	 */
  defineProp: function(propName, options)
  {
    var ops;
		if (!options)
			ops = {};
    else
      ops = Object.extend({}, options);
		if (ops.serializable === undefined)
			ops.serializable = true;
		//if (!options.storeField)  // use default store field
		ops.storeField = this.getDefPropStoreFieldName(propName);  // always use default store field name for performance
		//options.storeField = this.getDefPropStoreFieldName(propName);
		var list = this.getOwnPropList();
		var prop = list.addProperty(propName, ops);
    var propGetterInfo, propSetterInfo;
		if (ops.getter !== null && ops.getter !== false)
    {
      propGetterInfo = this.createPropGetter(prop, ops.getter);
      prop.getter = propGetterInfo.doGetterName;
    }
		if (ops.setter !== null && ops.setter !== false)
    {
      propSetterInfo = this.createPropSetter(prop, ops.setter);
      prop.setter = propSetterInfo.doSetterName;
    }

		// to accelerate property access, add a hash key here
		this[this.getPropInfoHashKey(propName)] = prop;

    // Add property to object in supported browsers
    if (__definePropertyAvailable__)
    {
      var descs = {
        'enumerable': ops.enumerable,
        'configurable': false
      };
      if (descs.enumerable === undefined)
        descs.enumerable = true;
      if (propGetterInfo)
        descs.get = this[propGetterInfo.getterName];
      if (propSetterInfo)
        descs.set = this[propSetterInfo.setterName];

      prop.descriptor = descs;
      try
      {
        Object.defineProperty(this, propName, descs);
      }
      catch(e)
      {
        //console.log(this.getClassName(), propName);
        throw e;
      }
    }

		return prop;
  },
  /** @private */
  createPropGetter: function(prop, getter)
  {
  	var propNameBase = prop.name.toString().upperFirst();
  	var getterName = 'get' + propNameBase;
		var doGetterName = 'doGet' + propNameBase;
		var actualGetter = this[doGetterName];
  	if (!actualGetter)
		{
			actualGetter = getter || new Function('return this["' + prop.storeField + '"];');
			this.getPrototype()[doGetterName] = actualGetter; // doGetXXX, descendant can override this method
		}

		/*
  	this.getPrototype()[getterName] = new Function(
			//'var args = Array.prototype.slice(arguments); args.unshift("' + prop.name + '");'
			//+ 'return this.getPropValue.apply(this, args);'
  		'return this.getPropValue("' + prop.name + '");'
  	);
  	*/
		/*
		this.getPrototype()[getterName] = function()
			{
				var args = Array.prototype.slice.call(arguments);
				args.unshift(prop.name);
				return this.getPropValue.apply(this, args);
			};
			*/
		this.getPrototype()[getterName] = actualGetter;
    /*
		this.getPrototype()[getterName] = function()
		{
			//var args = Array.prototype.slice.call(arguments);
			return this[doGetterName].apply(this, arguments);
		};
		*/

    //this.getPrototype()[getterName] = this.getPrototype()[doGetterName];

  	return {
      'getterName': getterName,
      'doGetterName': doGetterName   // actual method to retrieve value
    };
  },
  /** @private */
  createPropSetter: function(prop, setter)
  {
		var propName = prop.name.toString();
  	var propNameBase = propName.upperFirst();
  	var setterName = 'set' + propNameBase;
		var doSetterName = 'doSet' + propNameBase;
  	var actualSetter = this[doSetterName];

		if (!this[doSetterName])
		{
			actualSetter = setter || new Function('value', 'this["' + prop.storeField + '"] = value;');
  		this.getPrototype()[doSetterName] = actualSetter; // doSetXXX, descendant can override this method
		}
  	/*
  	this.getPrototype()[setterName] = new Function('value',
  		'return this.setPropValue("' + prop.name + '", value);'
  	);
  	*/
		this.getPrototype()[setterName] = function()
			{
        /*
				var args = Array.prototype.slice.call(arguments);
				var value = args[0];
				*/
        //var args = arguments;
        var value = arguments[0];

				/*
				args.unshift(prop.name);
				return this.setPropValueX.apply(this, args);
				*/
				this[doSetterName].apply(this, arguments);
				this.notifyPropSet(propName, value);
				/*
				// NOTE: here we call actualSetter directly instead of call setPropValue
				// because the former can pass multiple args inside
				actualSetter.apply(this, args);
				this.notifyPropSet(propName, value);
				*/

				return this;
			};

  	//this.getPrototype()[setterName] = actualSetter;
  	//return doSetterName; // actualSetter; //this[setterName];
    return {
      'setterName': setterName,
      'doSetterName': doSetterName   // actual method to set value
    };
  },
  /**
   * Check if property exists in current class.
   * @param {String} propName Name of property.
   * @return {Boolean}
   */
  hasProperty: function(propName)
  {
  	return (this.getPropInfo(propName) != null);
  },
  /**
   * Check if property is defined in current class (not inherited from super class).
   * @param {String} propName Name of property.
   * @return {Boolean}
   */
  hasDirectProperty: function(propName)
  {
    return (this.getPropInfo(propName, true) != null);
  },
  /**
   * Get property info object from the property list of current class.
   * @param {String} propName Name of property.
   * @param {Bool} ownPropertyOnly If true, only property defined in this class will be checked.
   * @return {Object} Property info object found. If there is no such a property, null is returned.
   */
  getPropInfo: function(propName, ownPropertyOnly)
  {
    var pname = propName || '';
    var result;

    if (!ownPropertyOnly)  // check hashkey for a quich search, but it should be disabled when ownPropertyOnly is true
    {
      var hashKey = this.getPropInfoHashKey(pname) || '';
      result = this[hashKey];
    }

		if (!result)
		{
			result = this.getOwnPropList().getPropInfo(pname);
			if (!result && !ownPropertyOnly)  // check parent
			{
				var parent = this.getSuperClassPrototype();
				if (parent && parent.getPropInfo)
					result = parent.getPropInfo(pname);
				else
					result = null;
			}

			/* need further test
			// to accelerate property access, add a hash key here
			this[this.getPropInfoHashKey(propName)] = result;
			*/
		}
		return result;
  },
	/**
	 * Returns type constants of property.
	 * @param {String} propName
	 * @returns {String} Values from {@link DataType}.
	 */
	getPropertyDataType: function(propName)
	{
		var info = this.getPropInfo(propName);
		return info? info.dataType: null;
	},
	/**
	 * Returns if property is serializable.
	 * @param {String} propName
	 * @returns {Bool}
	 */
	isPropertySerializable: function(propName)
	{
		var info = this.getPropInfo(propName);
		var s = info && info.serializable;
		return (s === undefined) || (!!s);
	},
  /**
   * Get value of a property's store field. Use this method to get property value and avoid
   * the call of property getter.
   * Note: if the property has no store field, this method may returns null or undefined.
   * @param {String} propName Name of property.
   * @return {Variant} Value of property. If property does not exists, null is returned.
   */
  getPropStoreFieldValue: function(propName)
  {
    //var storeFieldName = this.getDefPropStoreFieldName(propName);
    var defFieldName = ObjectEx._PROP_STOREFIELD_PREFIX + propName;
    //if (this.hasOwnProperty(defFieldName))
    return this[defFieldName];
    /*
    else
    {
      var info = this.getPropInfo(propName);
      if (info.storeField)
        return this[info.storeField];
      else
        return undefined;
    }
    */
  },
  /**
   * Get value of a property.
   * @param {String} propName Name of property.
   * @return {Variant} Value of property. If property does not exists, null is returned.
   */
  getPropValue: function(propName)
  {
  	var result;
  	var info = this.getPropInfo(propName);
  	if (info)
  	{
	  	if (info.getter) // getter set
			{
				var args = Array.prototype.slice.call(arguments);
				args.shift();
				//result = info.getter.apply(this);
				result = this[info.getter].apply(this, args);
			}
			else
				result = this[info.storeField];
	  	//this.notifyPropGet(propName, result);
	  	return result;
  	}
  	else
  		return null;
  },
	/**
   * Returns values of a series of properties.
   * @param {Variant} propNames Can be an array of property names, also can be an object while the
   *   direct field names of object will be regarded as property names.
   * @returns {Hash} Stores all property name-value pair.
   */
  getPropValues: function(propNames)
  {
    var result = {};
    var names;
    if (DataType.isArrayValue(propNames))
    {
      for (var i = 0, l = propNames.length; i < l; ++i)
      {
        var pname = propNames[i];
        result[pname] = this.getPropValue(pname);
      }
    }
    else if (DataType.isObjectValue(propNames))
    {
      for (var pname in propNames)
      {
        if (propNames.hasOwnProperty(pname) && typeof(obj[pname]) !== 'function')
          result[pname] = this.getPropValue(pname);
      }
    }
    return result;
  },
  /**
   * Set value of a property's store field. Use this method to set property value and avoid
   * the call of property setter. Readonly property can also be changed in this method.
   * Note: if the property has no store field, this method will has no effect on property.
   * @param {String} propName Name of property.
   * @return {Variant} Value of property. If property does not exists, null is returned.
   */
  setPropStoreFieldValue: function(propName, value)
  {
    var defFieldName = ObjectEx._PROP_STOREFIELD_PREFIX + propName;
    this[defFieldName] = value;
    /*
  	var info = this.getPropInfo(propName);
  	if (info.storeField)
  		this[info.storeField] = value;
  	*/
  },
  /**
   * Set value of a property.
   * @param {String} propName Name of property.
   * @param {Variant} value Value of the property.
   * @param {bool} ignoreReadOnly Try set the value directly through store field
   *   even if the property is a readonly one (without a setter).
   */
  setPropValue: function(propName, value, ignoreReadOnly)  // if ignoreReadOnly, a property without setter will still be set value
  {
  	var info = this.getPropInfo(propName);
  	if (info)
  	{
	  	if (info.setter)  // getter set
	  		//info.setter.apply(this, [value]);
				this[info.setter].apply(this, [value]);
	  	else if (ignoreReadOnly)
	  		this[info.storeField] = value;
			this.notifyPropSet(propName, value);
  	}
		return this;  // return this object for linkage call
  },
	/**
	 * Set value of a property. Similar to {@link ObjectEx.setPropValue} but can pass in multiple params.
	 * The first param is always the property name while the rest will be put into setter method (setXXX).
	 */
	setPropValueX: function()
	{
		var args = Array.prototype.slice.call(arguments);
		var propName = args.shift();
		var info = this.getPropInfo(propName);
		if (info)
		{
			if (info.setter)  // getter set
				this[info.setter].apply(this, args);
			this.notifyPropSet(propName, this.getPropValue(propName));
		}
		return this;  // return this object for linkage call
	},

	/**
	 * Set a series of property.
	 * @param {Hash} hash A hash object, its key and values will be used to set property value.
	 * @param {bool} ignoreReadOnly Try set the value directly through store field
	 *   even if the property is a readonly one (without a setter).
	 */
	setPropValues: function(hash, ignoreReadOnly)
	{
		for (var propName in hash)
		{
			if (hash.hasOwnProperty(propName)/* && (typeof(hash[propName]) != 'function')*/)
			{
				if (this.hasProperty(propName))
				{
					var propValue = hash[propName];
					this.setPropValue(propName, propValue, ignoreReadOnly);
				}
			}
		}
		return this;
	},

  /** @private */
  _isPropGetOrSetEvent: function(propName)
  {
  	return (propName == 'propValueGet') || (propName == 'propValueSet');
  },
  /*
   * Notify that a property value is get
   * @param {String} propName Name of property.
   * @param {Variant} value Value of the property.
	 * // Time consuming and has little use, not appliable now.
   */
	/*
  notifyPropGet: function(propName, value)
  {
  	if (this._isPropGetOrSetEvent(propName) || this.isEventPropName(propName))
  		return;
  	//if (this.getEnablePropValueGetEvent())  // cause recursion
  	if (this.getPropStoreFieldValue('enablePropValueGetEvent'))
  		this.invokeEvent('propValueGet', {'propName': propName, 'propValue': value});
  },
  */
  /**
   * Notify that a property value is set
   * @param {String} propName Name of property.
   * @param {Variant} newValue New value of the property.
   */
  notifyPropSet: function(propName, newValue, doNotEvokeObjChange)
  {
		if (this.isUpdating())  // in update state, just queue modified property names
		{
			//console.log('updating', this.getClassName(), propName);
			if (this._modifiedProps.indexOf(propName) < 0)
				this._modifiedProps.push(propName);
			return;
		}

  	if (this._isPropGetOrSetEvent(propName) || this.isEventPropName(propName))
  		return;

  	/*
  	if (propName == 'enablePropValueSetEvent')
  		return;
  	*/
  	//if (this.getEnablePropValueSetEvent()) // cause recursion

		this.doPropChanged(propName, newValue);
  	if (this.getPropStoreFieldValue('enablePropValueSetEvent'))
  	{
  		this.invokeEvent('propValueSet', {'propName': propName, 'propValue': newValue});
  	}
		if (!doNotEvokeObjChange)
		{
			this.objectChange([propName]);
		}
  },
	/**
	 * Called when object is changed.
	 * @privte
	 */
	objectChange: function(modifiedPropNames)
	{
		this.doObjectChange(modifiedPropNames);
    if (this.getEnableObjectChangeEvent())
		  this.invokeEvent('change', {'changedPropNames': modifiedPropNames});
	},
	/** @private */
	doObjectChange: function(modifiedPropNames)
	{
		// do nothing
	},
	/**
   * Do some job when a property value is changed. Descendants can override this.
   * @param {String} propName Name of property.
   * @param {Variant} newValue New value of the property.
   */
	doPropChanged: function(propName, newValue)
	{
		// do nothing here
	},

  // multi-broadcast support
  /**
   * Define an event in class. Event is actually a special property with type {@link Class.EventHandlerList}
   * @param {String} eventName Name of event.
   * @return {Object} Property info object created.
   * @deprecated
   */
  defineEvent: function(eventName)
  {
  	return;   // do nothing here, as this function is deprecated
  	// event is actually a special property, read only
  	var propName = this.eventNameToPropName(eventName);
  	var result = this.defineProp(propName, {'serializable': false, 'setter': null});
  	/*
  	// set event value as an event handler list
  	var handlers = new EventHandlerList();
  	this.setPropValue(propName, handlers, true);
  	*/
  	// NOTE: should not set value in define stage, otherwise the storeField will be directly inside prototype
  	return result;
  },
  /**
   * @private
   * @deprecated
   */
  eventNameToPropName: function(eventName)
    // turn a event name to a special prop name, to not confused with other properties
  {
  	return '__event_' + eventName;
  },
  /**
   * @private
   * @deprecated
   */
  isEventPropName: function(propName)
  {
  	return ((typeof(propName) == 'string') && (propName.indexOf('__event_') >= 0));
  },
  /*
   * Check if property is a event.
   * @param {String} eventName Name to check.
   * @return {Bool} True or false.
   * @deprecated
   */
  /*
  isEvent: function(eventName)
  {
  	var propName = this.eventNameToPropName(eventName);
  	return this.hasProperty(propName);
  },
  */
  /**
   * Check if a object is {@link Class.EventHandlerList}.
   * @param {Object} value
   * @return {bool} True or false.
   */
  isEventHandlerList: function(value)
  {
  	return (value && (typeof(value) == 'object') && (value instanceof Class.EventHandlerList) /*value.add && value.clear*/);
  },
  /**
   * Get the handler list of a event.
   * @param {String} eventName Name of event.
   * @return {Class.EventHandlerList} Handler list of event. If this event does not exist, null is returned.
   */
  getEventHandlerList: function(eventName)
  {
  	var hs = this.getEventHandlers();
  	var result = hs[eventName];
  	if (!result)
  	{
  		result = new Class.EventHandlerList();
  		hs[eventName] = result;
  	}
  	return result;
  	/*
  	var propName = this.eventNameToPropName(eventName);
  	var result = this.getPropValue(propName);
  	if (!result)  // not set yet
  	{
  		result = new EventHandlerList();
  		this.setPropValue(propName, result, true);
  		//this.setPropFieldValue(propName, result, true);
  	}
  	return result;
  	*/
  },

  /**
   * Add an event handler.
   * @param {String} eventName Name of event.
   * @param {Function} listener Handler function.
   * @param {Object} thisArg The scope object applied when the handler is called.
   * @return {Object} Handler info object on success, null on fail.
   */
  addEventListener: function(eventName, listener, thisArg)
    // add a listener function to list
  {
  	var handlerList = this.getEventHandlerList(eventName);
  	if (this.isEventHandlerList(handlerList))
  	{
      if (eventName === 'change')  // automatically turn on object change monitor
      {
        this.setEnableObjectChangeEvent(true);
      }
  		return handlerList.add(listener, thisArg);
  	}
  	else
  		return null;
  },
	/**
	 * Add an event handler that will only be evoked once.
	 * @param {String} eventName Name of event.
	 * @param {Function} listener Handler function.
	 * @param {Object} thisArg The scope object applied when the handler is called.
	 * @return {Object} Handler info object on success, null on fail.
	 */
	addOnceEventListener: function(eventName, listener, thisArg)
	{
		var self = this;
		var wrapper = function(event)
		{
			self.removeEventListener(eventName, wrapper, thisArg);
			listener(event);
		};
		return this.addEventListener(eventName, wrapper, thisArg);
	},
  /**
   * Remove an event handler.
   * @param {String} eventName Name of event.
   * @param {Function} listener Handler function.
   * @param {Object} thisArg The scope object applied when the handler is called.
   *   If not set, all listenr function in list will be removed.
   */
  removeEventListener: function(eventName, listener, thisArg)
  {
  	var handlerList = this.getEventHandlerList(eventName);
  	if (this.isEventHandlerList(handlerList))
  	{
  		return handlerList.remove(listener, thisArg);
  	}
  },
  /**
   * Add an event handler, shortcut for {@link ObjectEx.addEventListener}.
   * @param {String} eventName Name of event.
   * @param {Function} listener Handler function.
   * @param {Object} thisArg The scope object applied when the handler is called.
   * @return {Object} Handler info object on success, null on fail.
   */
  on: function(eventName, listener, thisArg)
  {
    return this.addEventListener(eventName, listener, thisArg);
  },
  /**
   * Add an event handler that will only be evoked once, shortcut for {@link ObjectEx.addOnceEventListener}.
   * @param {String} eventName Name of event.
   * @param {Function} listener Handler function.
   * @param {Object} thisArg The scope object applied when the handler is called.
   * @return {Object} Handler info object on success, null on fail.
   */
  once: function(eventName, listener, thisArg)
  {
    return this.addOnceEventListener(eventName, listener, thisArg);
  },
  /**
   * Remove an event handler, shortcut for (@link ObjectEx.removeEventListener}.
   * @param {String} eventName Name of event.
   * @param {Function} listener Handler function.
   * @param {Object} thisArg The scope object applied when the handler is called.
   *   If not set, all listenr function in list will be removed.
   */
  off: function(eventName, listener, thisArg)
  {
    return this.removeEventListener(eventName, listener, thisArg);
  },
  /**
   * Invoke an event and call all corresponding handlers (listeners).
   * @param {String} eventName Event to be invoked.
   * @param {Object} event A hash object with information about event.
   *   At least should include the following fields:
   *   {
   *     name: name of event,
   *     target: which object invoke this event, generally this object
   *   }
   *   If this parameter is not set, the default value {eventName, this} will be used.
   */
  invokeEvent: function(eventName, event)
  {
  	if (!event)
    {
      event = {
        'name': eventName, 'target': this
      };
    }
    else
    {
      event.name = eventName;
      event.target = this;
    }
    if (!event.stopPropagation)
      event.stopPropagation = this._eventCancelBubble;  // function() { event.cancelBubble = true; };
  	this.dispatchEvent(eventName, event);
  },
  /** @private */
  _eventCancelBubble: function()
  {
    // called with event.stopPropagation, so this here is the event object
    this.cancelBubble = true;
  },
  /**
   * Relay event from child of this object.
   * @param {String} eventName Event to be invoked.
   * @param {Object} event A hash object with information about event.
   */
  relayEvent: function(eventName, event)
  {
  	event.currentTarget = this;
    if (eventName === 'change' && this.getSuppressChildChangeEventInUpdating() && this.isUpdating())  // suppress child change event
		{
			//console.log('suppress child change event', this.getClassName(), event.target.getClassName());
      this._childChangeEventSuppressed = true;
		}
    else
  	  this.dispatchEvent(eventName, event);
  },
  /** @private */
  dispatchEvent: function(eventName, event)
  {
  	var handlerList = this.getEventHandlerList(eventName);
  	//if (this.isEventHandlerList(handlerList))
  	{
	  	for (var i = 0, l = handlerList.getLength(); i < l; ++i)
	  	{
	  		var handlerInfo = handlerList.getHandlerInfo(i);
        handlerInfo.handler.apply(handlerInfo.thisArg, [event]);
	  	}
  	}
    if (!event.cancelBubble && this.getBubbleEvent())
    {
      var higherObj = this.getHigherLevelObj();
      if (higherObj && higherObj.relayEvent)
      {
        higherObj.relayEvent(eventName, event);
      }
    }
  },
  /**
   * Stop propagation of event, disallow it to bubble to higher level.
   * @param {Object} event
   */
  stopEventPropagation: function(event)
  {
  	event.cancelBubble = true;
  },

  /**
   * Overwrite method of object instance (rather than prototype) with a new one.
   * @param {String} methodName
   * @param {Func} newMethod New function.
   *   The arguments of function should be same as overwritten one plus a extra leading param stores the old method.
   *   e.g. Overwrite getPropValue method: <br />
   *     var obj = new ObjectEx(); <br />
   *     obj.overwriteMethod('getPropValue', function($old, propName) <br />
   *       { <br />
   *         console.log('new method');
   *         return $old(propName);
   *       });
   * @returns {ObjectEx}
   */
  overwriteMethod: function(methodName, newMethod)
  {
    var self = this;
    var oldMethod = this[methodName];
    this[methodName] = function _delegator_()
    {
      var args = Array.prototype.slice.call(arguments);
      args.unshift(oldMethod.bind(self));
      return newMethod.apply(self, args);
    };
    return this;
  },

	// clone and assign services
	/**
	 * Assign data in srcObj to this object.
	 * @param {ObjectEx} srcObj
	 */
	assign: function(srcObj)
	{
		return srcObj.assignTo(this);
	},
	/**
	 * Assign data in this object to targetObj.
	 * @param {ObjectEx} targetObj
	 */
	assignTo: function(targetObj)
	{
		var jsonObj = {};
		this.saveObj(jsonObj, 'json');
		targetObj.loadObj(jsonObj, 'json');
	},
	/**
	 * Returns a cloned object.
	 * @returns {ObjectEx}
	 */
	clone: function()
	{
		var classObj = this.getClass();
		var result = new classObj();
		this.assignTo(result);
		return result;
	},

	/**
	 * Begin to update multiple properties of object. In update state, all changed properties
	 *   will not invoke propertySet event until method endUpdate is called.
	 */
	beginUpdate: function()
	{
		++this._updateStatus;
	},
	/**
	 * Update end and notify all properties changed after calling of beginUpdate.
	 */
	endUpdate: function()
	{
		--this._updateStatus;
		this.checkUpdateStatus();
		if (!this.isUpdating())  // update end, notify changed properties
		{
			var modifiedProps = this._modifiedProps || [];
			this._modifiedProps = [];
      if (this._childChangeEventSuppressed)
      {
        modifiedProps.push('[children]');  // TODO: special propName, indicating children has been changed
        this._childChangeEventSuppressed = false;
      }
      this.doEndUpdate(modifiedProps);
      //this._childChangeEventSuppressed = false;
		}
	},
	/**
	 * Actual work of endUpdate, just invoke all property change events.
	 */
	doEndUpdate: function(modifiedPropNames)
	{
		if (modifiedPropNames.length)
		{
			if (this.getPropStoreFieldValue('enablePropValueSetEvent'))
			{
				for (var i = 0, l = modifiedPropNames.length; i < l; ++i)
				{
					var propName = modifiedPropNames[i];
					var propValue = this.getPropValue(propName);
					this.notifyPropSet(propName, propValue, true);
				}
			}
			/*this.invokeEvent('change after update', modifiedPropNames);*/
      //console.log('class end update', this.getClassName(), modifiedPropNames);
			this.objectChange(modifiedPropNames);
		}
	},
	/**
	 * Check if object is in updating state.
	 */
	isUpdating: function()
	{
		return this._updateStatus > 0;
	},
	/**
	 * Check whether property is changed in begin/endUpdate procedure.
	 * @param {Object} propName
	 */
	isPropUpdated: function(propName)
	{
		return (this._modifiedProps.indexOf(propName) >= 0);
	},
	/** @private */
	checkUpdateStatus: function()
	{
		if (this._updateStatus <= 0)
		{
			this._updateStatus = 0;
		}
	},

	// Copy & clone services
	/**
	 * Copy property values to dest object. Dest must be a desendant of {@link ObjectEx}.
	 * @param {ObjectEx} dest
	 */
	copyPropsTo: function(dest)
	{
		var props = this.getAllPropList();
		for (var i = 0, l = props.getLength(); i < l; ++i)
		{
			var propName = props.getPropInfoAt(i).name;
			// check if propName exists in dest
			if (dest.hasProperty(propName))
			{
				var propValue = this.getPropValue(propName);
				dest.setPropValue(propName, propValue, true);
			}
		}
	}
});
/** @private */
ObjectEx._PROPINFO_HASHKEY_PREFIX = '__$propInfo__';
ObjectEx._PROP_STOREFIELD_PREFIX = '__$__k__p__';

// Export to root name space
$jsRoot.Class = Class;
$jsRoot.ClassEx = ClassEx;
$jsRoot.ObjectEx = ObjectEx;
$jsRoot.DataType = DataType;
DataType.StringUtils = StringUtils;

})(this);