1  /*
     2   * Copyright the original author or authors.
     3   * 
     4   * Licensed under the MOZILLA PUBLIC LICENSE, Version 1.1 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   * 
     8   *      http://www.mozilla.org/MPL/MPL-1.1.html
     9   * 
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  import org.as2lib.core.BasicClass;
    18  import org.as2lib.Config;
    19  import org.as2lib.util.ClassUtil;
    20  import org.as2lib.util.ArrayUtil;
    21  import org.as2lib.util.Comparable;
    22  
    23  /**
    24   * {@code ObjectUtil} contains fundamental methods to efficiently and easily work
    25   * with any type of object.
    26   * 
    27   * @author Simon Wacker
    28   * @author Martin Heidegger
    29   */
    30  class org.as2lib.util.ObjectUtil extends BasicClass {
    31  	
    32  	/**
    33  	 * Constant for objects of type string.
    34  	 * 
    35  	 * @see #isTypeOf
    36  	 */
    37  	public static var TYPE_STRING:String = "string";
    38  	
    39  	/**
    40  	 * Constant for objects for type number.
    41  	 * 
    42  	 * @see #isTypeOf
    43  	 */
    44  	public static var TYPE_NUMBER:String = "number";
    45  	
    46  	/**
    47  	 * Constant for objects of type object.
    48  	 * 
    49  	 * @see #isTypeOf
    50  	 */
    51  	public static var TYPE_OBJECT:String = "object";
    52  	
    53  	/**
    54  	 * Constant for objects of type boolean.
    55  	 * 
    56  	 * @see #isTypeOf
    57  	 */
    58  	public static var TYPE_BOOLEAN:String = "boolean";
    59  	
    60  	/**
    61  	 * Constant for objects of type movieclip.
    62  	 * 
    63  	 * @see #isTypeOf
    64  	 */
    65  	public static var TYPE_MOVIECLIP:String = "movieclip";
    66  	
    67  	/**
    68  	 * Constant for objects of type function.
    69  	 * 
    70  	 * @see #isTypeOf
    71  	 */
    72  	public static var TYPE_FUNCTION:String = "function";
    73  	
    74  	/**
    75  	 * Constant for the value undefined.
    76  	 * 
    77  	 * @see #isTypeOf
    78  	 */
    79  	public static var TYPE_UNDEFINED:String = "undefined";
    80  	
    81  	/**
    82  	 * Constant for the value null.
    83  	 * 
    84  	 * @see #isTypeOf
    85  	 */
    86  	public static var TYPE_NULL:String = "null";
    87  	
    88  	/**
    89  	 * Stringifies the passed-in {@code object} using the stringifier returned by the
    90  	 * static {@link Config#getObjectStringifier} method.
    91  	 * 
    92  	 * @param object the object to stringify
    93  	 * @return the string representation of the passed-in {@code object}
    94  	 */
    95  	public static function stringify(object):String {
    96  		return Config.getObjectStringifier().execute(object);
    97  	}
    98  	
    99  	/**
   100  	 * Checks if the type of the passed-in {@code object} matches the passed-in 
   101  	 * {@code type}.
   102  	 * 
   103  	 * <p>Every value (even {@code null} and {@code undefined}) matches type
   104  	 * {@code Object}.
   105  	 *
   106  	 * <p>Instances as well as their primitive correspondent match the types 
   107  	 * {@code String}, {@code Number} or {@code Boolean}.
   108  	 * 
   109  	 * @param object the object whose type to compare with the passed-in {@code type}
   110  	 * @param type the type to use for the comparison
   111  	 * @return {@code true} if the type of the {@code object} matches the passed-in
   112  	 * {@code type} else {@code false}
   113  	 */
   114  	public static function typesMatch(object, type:Function):Boolean {
   115  		if (type === Object) {
   116  			return true;
   117  		}
   118  		if (isPrimitiveType(object)) {
   119  			var t:String = typeof(object);
   120  			// Workaround for former used: typesMatch(type(object), object);
   121  			// Casting is not a good solution, it will break if the Constructor throws a error!
   122  			// This solution is not the fastest but will not break by any exception.
   123  			if ((type === String || ClassUtil.isSubClassOf(type, String)) && t == TYPE_STRING) {
   124  				return true;
   125  			}
   126  			if ((type === Boolean || ClassUtil.isSubClassOf(type, Boolean)) && t == TYPE_BOOLEAN) {
   127  				return true;
   128  			}
   129  			if ((type === Number || ClassUtil.isSubClassOf(type, Number)) && t == TYPE_NUMBER) {
   130  				return true;
   131  			}
   132  			return false;
   133  		} else {
   134  			return (isInstanceOf(object, type));
   135  		}
   136  	}
   137  	
   138  	/**
   139  	 * Compares the results of an execution of the {@code typeof} method applied to
   140  	 * both passed-in objects.
   141  	 * 
   142  	 * @param firstObject the first object of the comparison
   143  	 * @param secondObject the second object of the comparison
   144  	 * @return {@code true} if the execution of the {@code typeof} method returns the 
   145  	 * same else {@code false}
   146  	 */
   147  	public static function compareTypeOf(firstObject, secondObject):Boolean {
   148  		return (typeof(firstObject) == typeof(secondObject));
   149  	}
   150  	
   151  	/**
   152  	 * Checks if the passed-in {@code object} is a primitive type.
   153  	 *
   154  	 * <p>Primitive types are strings, numbers and booleans that are not created via the
   155  	 * new operator. For example {@code "myString"}, {@code 3} and {@code true} are
   156  	 * primitive types, but {@code new String("myString")}, {@code new Number(3)} and
   157  	 * {@code new Boolean(true)} are not.
   158  	 * 
   159  	 * @param object the object to check whether it is a prmitive type
   160  	 * @return {@code true} if {@code object} is a primitive type else {@code false}
   161  	 */
   162  	public static function isPrimitiveType(object):Boolean {
   163  		var t:String = typeof(object);
   164  		return (t == TYPE_STRING || t == TYPE_NUMBER || t == TYPE_BOOLEAN);
   165  	}
   166  	
   167  	/**
   168  	 * Checks if the result of an execution of the {@code typeof} method on the
   169  	 * passed-in {@code object} matches the passed-in {@code type}.
   170  	 * 
   171  	 * <p>All possible types are available as constants.
   172  	 *
   173  	 * @param object the object whose type to check
   174  	 * @param type the string representation of the type
   175  	 * @return {@code true} if the object is of the given {@code type}
   176  	 * @see #TYPE_STRING
   177  	 * @see #TYPE_NUMBER
   178  	 * @see #TYPE_OBJECT
   179  	 * @see #TYPE_BOOLEAN
   180  	 * @see #TYPE_MOVIECLIP
   181  	 * @see #TYPE_NULL
   182  	 * @see #TYPE_UNDEFINED
   183  	 */
   184  	public static function isTypeOf(object, type:String):Boolean {
   185  		return (typeof(object) == type);
   186  	}
   187  	
   188  	/**
   189  	 * Checks if the passed-in {@code object} is an instance of the passed-in
   190  	 * {@code type}.
   191  	 * 
   192  	 * <p>If the passed-in {@code type} is {@code Object}, {@code true} will always be
   193  	 * returned, because every object is an instance of {@code Object}, even {@code null}
   194  	 * and {@code undefined}.
   195  	 * 
   196  	 * @param object the object to check
   197  	 * @param type the type to check whether the {@code object} is an instance of
   198  	 * @return {@code true} if the passed-in {@code object} is an instance of the given
   199  	 * {@code type} else {@code false}
   200  	 */
   201  	public static function isInstanceOf(object, type:Function):Boolean {
   202  		if (type === Object) {
   203  			return true;
   204  		}
   205  		return (object instanceof type);
   206  	}
   207  	
   208  	/**
   209  	 * Checks if the passed-in {@code object} is an explicit instance of the passed-in
   210  	 * {@code clazz}.
   211  	 * 
   212  	 * <p>That means that {@code true} will only be returned if the object was instantiated
   213  	 * directly from the given {@code clazz}.
   214  	 * 
   215  	 * @param object the object to check whether it is an explicit instance of {@code clazz}
   216  	 * @param clazz the class to use as the basis for the check
   217  	 * @return {@code true} if the object is an explicit instance of {@code clazz} else
   218  	 * {@code false}
   219  	 */
   220  	public static function isExplicitInstanceOf(object, clazz:Function):Boolean {
   221  		if (isPrimitiveType(object)) {
   222  			if (clazz == String) {
   223  				return (typeof(object) == TYPE_STRING);
   224  			}
   225  			if (clazz == Number) {
   226  				return (typeof(object) == TYPE_NUMBER);
   227  			}
   228  			if (clazz == Boolean) {
   229  				return (typeof(object) == TYPE_BOOLEAN);
   230  			}
   231  		}
   232  		return (object instanceof clazz	&& !(object.__proto__ instanceof clazz));
   233  	}
   234  	
   235  	/**
   236  	 * Checks if two passed-in parameters are equal.
   237  	 * 
   238  	 * <p>It uses different strategies by the first passed-in {@code obj1}.
   239  	 *   <ul>
   240  	 *     <li>If {@code obj1} is a primitive it compares it with == operator.</li>
   241  	 *     <li>If {@code obj1} implements {@link Comparable} it calls {@code compare()}
   242  	 *         to compare both passed-in parameters</li>
   243  	 *     <li>Any different case compares the structure of both objects.</li>
   244  	 *   </ul>
   245  	 *   
   246  	 * <p>It compares complex objects (that do not implement {@code Comparable})
   247  	 * only if they are instances of the same class. A different class (even
   248  	 * if its only a extended class) will be handled as not equal.
   249  	 * 
   250  	 * <p>It compares complex objects recursivly. It handles back references in 
   251  	 * a proper way.
   252  	 * 
   253  	 * @param obj1 object to be compared
   254  	 * @param obj2 object to compare with passed-in {@code obj1}
   255  	 * @return {@code true} if both parameters are equal
   256  	 */
   257  	public static function compare(obj1, obj2):Boolean {
   258  		return compareRecursive(obj1, obj2, [], []);
   259  	}
   260  	
   261  	/**
   262  	 * Compares recursivly (for objects) if the structure matches.
   263  	 * 
   264  	 * @param obj1 object to compare with a different object.
   265  	 * @param obj2 object to be compared with {@code obj1}.
   266  	 * @param stack1 recursive stack to check endless recursions for the {@code obj1}
   267  	 * @param stack2 recursive stack to check endless recursions for the {@code obj2}
   268  	 * @return {@code true} if both paramerters are equal
   269  	 */
   270  	private static function compareRecursive(obj1, obj2, stack1:Array, stack2:Array):Boolean {
   271  		if (typeof obj1 == "object"
   272  			&& !(   obj1 instanceof String
   273  			     || obj1 instanceof Number
   274  			     || obj1 instanceof Boolean)) {
   275  			if (obj1 === obj2) {
   276  				return true;
   277  			}
   278  			if (obj1 instanceof Comparable) {
   279  				var c:Comparable = obj1;
   280  				return c.compare(obj2);
   281  			}
   282  			if (obj1.__proto__ == obj2.__proto__) {
   283  				var index:Number = ArrayUtil.lastIndexOf(stack1, obj1);
   284  				if (index > -1) {
   285  					if (obj2 == stack2[index]) {
   286  						return true;
   287  					}
   288  				}
   289  				stack1.push(obj1);
   290  				stack2.push(obj2);
   291  				var i:String;
   292  				for (i in obj1) {
   293  					if (!compareRecursive (obj1[i], obj2[i], stack1, stack2)) {
   294  						return false;
   295  					}
   296  				}
   297  				stack1.pop();
   298  				stack2.pop();
   299  				return true;
   300  			} else {
   301  				return false;
   302  			}
   303  		} else {
   304  			return (obj1 == obj2);
   305  		}
   306  	}
   307  	
   308  	/**
   309  	 * Private constructor.
   310  	 */
   311  	private function ObjectUtil(Void) {
   312  	}
   313  	
   314  }