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  
    19  /**
    20   * {@code ReflectUtil} obtains simple information about members.
    21   * 
    22   * <p>It is independent on any module of the As2lib. And thus does not include them
    23   * and does not dramatically increase the file size.
    24   * 
    25   * @author Simon Wacker
    26   */
    27  class org.as2lib.env.reflect.ReflectUtil extends BasicClass {
    28  	
    29  	/** The name to use for constructors. */
    30  	public static var CONSTRUCTOR:String = "new";
    31  	
    32  	/** The name to use for unknown information. */
    33  	public static var UNKNOWN:String = "[unknown]";
    34  	
    35  	/** The prefix for a generic member name. */
    36  	private static var MEMBER_PREFIX:String = "__as2lib__member";
    37  	
    38  	/**
    39  	 * Searches for a member name that is currently not used.
    40  	 * 
    41  	 * <p>Uses {@link #MEMBER_PREFIX} and a number from 1 to 10000 with two variants to
    42  	 * find a member name that is currently not used (20.000 possible variants).
    43  	 * 
    44  	 * @param object the object to find an unused member name in
    45  	 * @return the name of the unused member or {@code null} if all names are already
    46  	 * reserved
    47  	 */
    48  	public static function getUnusedMemberName(object):String {
    49  		var i:Number = 10000;
    50  		var prefA:String = MEMBER_PREFIX + "_";
    51  		var prefB:String = MEMBER_PREFIX + "-";
    52  		while(--i-(-1)) {
    53  			if(object[prefA + i] === undefined) {
    54  				return (prefA + i);
    55  			}
    56  			if(object[prefB + i] === undefined) {
    57  				return (prefB + i);
    58  			}
    59  		}
    60  		return null;
    61  	}
    62  	
    63  	/**
    64  	 * @overload #getTypeAndMethodInfoByType
    65  	 * @overload #getTypeAndMethodInfoByInstance
    66  	 */
    67  	public static function getTypeAndMethodInfo(object, method:Function):Array {
    68  		if (object === null || object === undefined) return null;
    69  		if (typeof(object) == "function") {
    70  			return getTypeAndMethodInfoByType(object, method);
    71  		}
    72  		return getTypeAndMethodInfoByInstance(object, method);
    73  	}
    74  	
    75  	/**
    76  	 * Returns an array that contains the passed-in {@code method}'s scope, the name
    77  	 * of the type that declares the method and the name of the method itself.
    78  	 * 
    79  	 * <p>The type that declares the {@code method} must not be the passed-in {@code type}.
    80  	 * It may also be a super-type of the passed-in {@code type}.
    81  	 * 
    82  	 * <p>{@code null} will be returned if the passed-in {@code type} is {@code null}.
    83  	 * 
    84  	 * @param method the method to return information about
    85  	 * @param type the type to start the search for the method
    86  	 * @return an array containing the passed-in {@code method}'s scope, the name of
    87  	 * the declaring type and the passed-in {@code method}'s name
    88  	 */
    89  	public static function getTypeAndMethodInfoByType(type:Function, method:Function):Array {
    90  		if (type === null || type === undefined) return null;
    91  		if (method.valueOf() == type.valueOf()) {
    92  			return [false, getTypeNameForType(type), CONSTRUCTOR];
    93  		}
    94  		var m:String = getMethodNameByObject(method, type);
    95  		if (m !== null && m !== undefined) {
    96  			return [true, getTypeNameForType(type), m];
    97  		}
    98  		return getTypeAndMethodInfoByPrototype(type.prototype, method);
    99  	}
   100  	
   101  	/**
   102  	 * Returns an array that contains the passed-in {@code method}'s scope, the name
   103  	 * of the type that declares the method and the name of the method itself.
   104  	 * 
   105  	 * <p>The type that declares the {@code method} must not be the direct type of the
   106  	 * passed-in {@code instance}. It may also be a super-type of this type.
   107  	 * 
   108  	 * <p>{@code null} will be returned if the passed-in {@code type} is {@code null}.
   109  	 * 
   110  	 * @param method the method to return information about
   111  	 * @param instance the instance of the type to start the search for the method
   112  	 * @return an array containing the passed-in {@code method}'s scope, the name of
   113  	 * the declaring type and the passed-in {@code method}'s name
   114  	 */
   115  	public static function getTypeAndMethodInfoByInstance(instance, method:Function):Array {
   116  		if (instance === null || instance === undefined) return null;
   117  		// MovieClips on the stage do not have a '__constructor__' but a 'constructor' variable.
   118  		// Note that this causes problems with dynamically created inheritance chains like
   119  		// myMovieClip.__proto__ = MyClass.prototype because the '__constructor__' and 'constructor' 
   120  		// properties do not get changed.
   121  		if (instance.__constructor__) {
   122  			if (instance.__constructor__.prototype == instance.__proto__) {
   123  				return getTypeAndMethodInfoByType(instance.__constructor__, method);
   124  			}
   125  		}
   126  		if (instance.constructor) {
   127  			if (instance.constructor.prototype == instance.__proto__) {
   128  				return getTypeAndMethodInfoByType(instance.constructor, method);
   129  			}
   130  		}
   131  		return getTypeAndMethodInfoByPrototype(instance.__proto__, method);
   132  	}
   133  	
   134  	/**
   135  	 * Returns an array that contains the passed-in method's {@code m} scope, the name
   136  	 * of the type that declares the method and the name of the method itself.
   137  	 * 
   138  	 * <p>The type that declares the method must not be the direct type of the
   139  	 * passed-in prototype {@code p}. It may also be a super-type of this type.
   140  	 * 
   141  	 * <p>{@code null} will be returned if the passed-in prototype is {@code null}.
   142  	 * 
   143  	 * @param p the beginning of the prototype chain to search through
   144  	 * @param m the method to return information about
   145  	 * @return an array containing the passed-in method's scope, the name of the
   146  	 * declaring type and the passed-in method's name
   147  	 */
   148  	public static function getTypeAndMethodInfoByPrototype(p, m:Function):Array {
   149  		if (p === null || p === undefined) return null;
   150  		var o = p;
   151  		_global.ASSetPropFlags(_global, null, 0, true);
   152  		var n:String;
   153  		while (p) {
   154  			if (p.constructor.valueOf() == m.valueOf()) {
   155  				n = CONSTRUCTOR;
   156  			} else {
   157  				n = getMethodNameByObject(m, p);
   158  			}
   159  			if (n != null) {
   160  				var r:Array = new Array();
   161  				r[0] = false;
   162  				r[1] = getTypeNameByPrototype(p, _global, "", [_global]);
   163  				r[2] = n;
   164  				return r;
   165  			}
   166  			p = p.__proto__;
   167  		}
   168  		return [null, getTypeNameByPrototype(o, _global, "", [_global]), null];
   169  	}
   170  	
   171  	/**
   172  	 * @overload #getTypeNameForInstance
   173  	 * @overload #getTypeNameForType
   174  	 */
   175  	public static function getTypeName(object):String {
   176  		if (object === null || object === undefined) return null;
   177  		if (typeof(object) == "function") {
   178  			return getTypeNameForType(object);
   179  		}
   180  		return getTypeNameForInstance(object);
   181  	}
   182  	
   183  	/**
   184  	 * Returns the name of the type, the passed-in object is an instance of.
   185  	 *
   186  	 * <p>{@code null} will be returned if:
   187  	 * <ul>
   188  	 *   <li>The passed-in {@code instance} is {@code null} or {@code undefined}.</li>
   189  	 *   <li>The appropriate type could not be found in {@code _global}.</li>
   190  	 * </ul>
   191  	 *
   192  	 * @param instance the instance of the type to return the name of
   193  	 * @return the name of the type of the instance or {@code null}
   194  	 */
   195  	public static function getTypeNameForInstance(instance):String {
   196  		if (instance === null || instance === undefined) return null;
   197  		_global.ASSetPropFlags(_global, null, 0, true);
   198  		// The '__constructor__' or 'constructor' properties may not be correct with dynamic instances.
   199  		// We thus use the '__proto__' property that referes to the prototype of the type.
   200  		return getTypeNameByPrototype(instance.__proto__, _global, "", [_global]);
   201  	}
   202  	
   203  	/**
   204  	 * Returns the name of the passed-in {@code type}.
   205  	 *
   206  	 * <p>{@code null} will be returned if:
   207  	 * <ul>
   208  	 *   <li>The passed-in {@code type} is {@code null} or {@code undefined}.</li>
   209  	 *   <li>The {@code type} could not be found in {@code _global}.</li>
   210  	 * </ul>
   211  	 *
   212  	 * @param type the type to return the name of
   213  	 * @return the name of the passed-in {@code type} or {@code null}
   214  	 */
   215  	public static function getTypeNameForType(type:Function):String {
   216  		if (type === null || type === undefined) return null;
   217  		_global.ASSetPropFlags(_global, null, 0, true);
   218  		return getTypeNameByPrototype(type.prototype, _global, "", [_global]);
   219  	}
   220  	
   221  	/**
   222  	 * Searches for the passed-in {@code c} (prototype) in the passed-in {code p}
   223  	 * (package) and sub-packages and returns the name of the type that declares the
   224  	 * prototype.
   225  	 * 
   226  	 * <p>{@code null} will be returned if:
   227  	 * <ul>
   228  	 *   <li>The prototype or package is {@code null} or {@code undefined}</li>
   229  	 *   <li>The type defining the prototype could not be found.</li>
   230  	 * </ul>
   231  	 *
   232  	 * @param c the prototype to search for
   233  	 * @param p the package to find the type that defines the prototype in
   234  	 * @param n the name of the preceding path separated by periods
   235  	 * @param a already searched through packages
   236  	 * @return the name of the type defining the prototype of {@code null}
   237  	 */
   238  	private static function getTypeNameByPrototype(c, p, n:String, a:Array):String {
   239  		//if (c == null || p == null) return null; // why is this causing trouble?
   240  		var y:String = c.__as2lib__typeName;
   241  		if (y != null && y != c.__proto__.__as2lib__typeName) {
   242  			return y;
   243  		}
   244  		if (n == null) n = "";
   245  		var s:Function = _global.ASSetPropFlags;
   246  		for (var r:String in p) {
   247  			try {
   248  				// flex stores every class in _global and in its actual package
   249  				// e.g. org.as2lib.core.BasicClass is stored in _global with name org_as2lib_core_BasicClass
   250  				// the first part of the if-clause excludes these extra stored classes
   251  				// p[r].prototype === c because a simple == will result in wrong name when searching for the __proto__ of
   252  				// a number
   253  				if ((!eval("_global." + r.split("_").join(".")) || r.indexOf("_") < 0) && p[r].prototype === c) {
   254  					var x:String = n + r;
   255  					c.__as2lib__typeName = x;
   256  					s(c, "__as2lib__typeName", 1, true);
   257  					return x;
   258  				}
   259  				if (p[r].__constructor__.valueOf() == Object) {
   260  					// prevents recursion on back-reference
   261  					var f:Boolean = false;
   262  					for (var i:Number = 0; i < a.length; i++) {
   263  						if (a[i].valueOf() == p[r].valueOf()) f = true;
   264  					}
   265  					if (!f) {
   266  						a.push(p[r]);
   267  						r = getTypeNameByPrototype(c, p[r], n + r + ".", a);
   268  						if (r) return r;
   269  					}
   270  				} else {
   271  					if (typeof(p[r]) == "function") {
   272  						p[r].prototype.__as2lib__typeName = n + r;
   273  						s(p[r].prototype, "__as2lib__typeName", 1, true);
   274  					}
   275  				}
   276  			} catch (e) {
   277  			}
   278  		}
   279  		return null;
   280  	}
   281  	
   282  	/**
   283  	 * @overload #getMethodNameByInstance
   284  	 * @overload #getMethodNameByType
   285  	 */
   286  	public static function getMethodName(method:Function, object):String {
   287  		if (!method || object === null || object === undefined) return null;
   288  		if (typeof(object) == "function") {
   289  			return getMethodNameByType(method, object);
   290  		}
   291  		return getMethodNameByInstance(method, object);
   292  	}
   293  	
   294  	/**
   295  	 * Returns the name of the {@code method} on the instance's {@code type}.
   296  	 *
   297  	 * <p>{@code null} will be returned if:
   298  	 * <ul>
   299  	 *   <li>The passed-in {@code method} or {@code instance} are {@code null}</li>
   300  	 *   <li>The {@code method} does not exist on the {@code instance}'s type.</li>
   301  	 * </ul>
   302  	 *
   303  	 * @param method the method to get the name of
   304  	 * @param instance the instance whose type implements the {@code method}
   305  	 * @return the name of the {@code method} or {@code null}
   306  	 */
   307  	public static function getMethodNameByInstance(method:Function, instance):String {
   308  		if (!method || instance === null || instance === undefined) return null;
   309  		// MovieClips on the stage do not have a '__constructor__' but a 'constructor' variable.
   310  		// Note that this causes problems with dynamically created inheritance chains like
   311  		// myMovieClip.__proto__ = MyClass.prototype because the '__constructor__' and 'constructor' 
   312  		// properties do not get changed.
   313  		if (instance.__constructor__) {
   314  			if (instance.__constructor__.prototype == instance.__proto__) {
   315  				return getMethodNameByType(method, instance.__constructor__);
   316  			}
   317  		}
   318  		if (instance.constructor) {
   319  			if (instance.constructor.prototype == instance.__proto__) {
   320  				return getMethodNameByType(method, instance.constructor);
   321  			}
   322  		}
   323  		return getMethodNameByPrototype(method, instance.__proto__);
   324  	}
   325  	
   326  	/**
   327  	 * Returns the name of the {@code method} on the {@code type}.
   328  	 *
   329  	 * <p>{@code null} will be returned if:
   330  	 * <ul>
   331  	 *   <li>The passed-in {@code method} or {@code type} are {@code null}</li>
   332  	 *   <li>The {@code method} does not exist on the {@code type}.</li>
   333  	 * </ul>
   334  	 *
   335  	 * @param method the method to get the name of
   336  	 * @param type the type that implements the {@code method}
   337  	 * @return the name of the {@code method} or {@code null}
   338  	 */
   339  	public static function getMethodNameByType(method:Function, type:Function):String {
   340  		if (!method || !type) return null;
   341  		var m:String = getMethodNameByPrototype(method, type.prototype);
   342  		if (m != null) return m;
   343  		return getMethodNameByObject(method, type);
   344  	}
   345  	
   346  	/**
   347  	 * Returns the name of the method {@code m} on the prototype chain starting from
   348  	 * the passed-in prototype {@code p}.
   349  	 * 
   350  	 * <p>{@code null} will be returned if:
   351  	 * <ul>
   352  	 *   <li>The passed-in method or prototype are {@code null}</li>
   353  	 *   <li>The method does not exist on the prototype chain.</li>
   354  	 * </ul>
   355  	 * 
   356  	 * @param m the method to get the name of
   357  	 * @param o the prototype that has the {@code method}
   358  	 * @return the name of the {@code method} or {@code null}
   359  	 */
   360  	private static function getMethodNameByPrototype(m:Function, p):String {
   361  		if (m === null || m === undefined || p === null || p === undefined) return null;
   362  		while (p) {
   363  			var n:String = getMethodNameByObject(m, p);
   364  			if (n != null) return n;
   365  			p = p.__proto__;
   366  		}
   367  		return null;
   368  	}
   369  	
   370  	/**
   371  	 * Returns the name of the method {@code m} on the passed-in object {@code o} or
   372  	 * {@code null}.
   373  	 * 
   374  	 * <p>Only the passed-in object is searched through. Note also that all methods
   375  	 * regardless of their access permissions are enumerated.
   376  	 * 
   377  	 * <p>{@code null} will be returned if:
   378  	 * <ul>
   379  	 *   <li>The passed-in method or object are {@code null}</li>
   380  	 *   <li>The method does not exist on the object.</li>
   381  	 * </ul>
   382  	 * 
   383  	 * @param m the method to find
   384  	 * @param o the object that may contain the method
   385  	 * @return the name of the method or {@code null}
   386  	 */
   387  	private static function getMethodNameByObject(m:Function, o):String {
   388  		var r:String = m.__as2lib__methodName;
   389  		if (r != null) return r;
   390  		var s:Function = _global.ASSetPropFlags;
   391  		s(o, null, 0, true);
   392  		s(o, ["__proto__", "prototype", "__constructor__", "constructor"], 7, true);
   393  		for (var n:String in o) {
   394  			try {
   395  				if (o[n].valueOf() == m.valueOf()) {
   396  					m.__as2lib__methodName = n;
   397  					return n;
   398  				}
   399  				if (typeof(o[n]) == "function") {
   400  					o[n].__as2lib__methodName = n;
   401  				}
   402  			} catch (e) {
   403  			}
   404  		}
   405  		// ASSetPropFlags must be restored because unexpected behaviours get caused otherwise
   406  		s(o, null, 1, true);
   407  		return null;
   408  	}
   409  	
   410  	/**
   411  	 * @overload #isMethodStaticByInstance
   412  	 * @overload #isMethodStaticByType
   413  	 */
   414  	public static function isMethodStatic(methodName:String, object):Boolean {
   415  		if (!methodName || object === null || object === undefined) return false;
   416  		if (typeof(object) == "function") {
   417  			return isMethodStaticByType(methodName, object);
   418  		}
   419  		return isMethodStaticByInstance(methodName, object);
   420  	}
   421  	
   422  	/**
   423  	 * Returns whether the method with the passed-in {@code methodName} is static, that
   424  	 * means a per type method.
   425  	 * 
   426  	 * <p>{@code false} will always be returned if the passed-in {@code methodName} is
   427  	 * {@code null} or an empty string or if the passed-in {@code instance} is {@code null}.
   428  	 *
   429  	 * @param methodName the name of the method to check whether it is static
   430  	 * @param instance the instance of the type that implements the method
   431  	 * @return {@code true} if the method is static else {@code false}
   432  	 */
   433  	public static function isMethodStaticByInstance(methodName:String, instance):Boolean {
   434  		if (!methodName || instance === null || instance === undefined) return false;
   435  		// MovieClips on the stage do not have a '__constructor__' but a 'constructor' variable.
   436  		// Note that this causes problems with dynamically created inheritance chains like
   437  		// myMovieClip.__proto__ = MyClass.prototype because the '__constructor__' and 'constructor' 
   438  		// properties do not get changed.
   439  		return isMethodStaticByType(methodName, instance.__constructor__ ? instance.__constructor : instance.constructor);
   440  	}
   441  	
   442  	/**
   443  	 * Returns whether the method with the passed-in {@code methodName} is static, that
   444  	 * means a per type method.
   445  	 * 
   446  	 * <p>{@code false} will always be returned if the passed-in {@code methodName} is
   447  	 * {@code null} or an empty string or if the passed-in {@code type} is {@code null}.
   448  	 *
   449  	 * @param methodName the name of the method to check whether it is static
   450  	 * @param type the type that implements the method
   451  	 * @return {@code true} if the method is static else {@code false}
   452  	 */
   453  	public static function isMethodStaticByType(methodName:String, type:Function):Boolean {
   454  		if (!methodName || !type) return false;
   455  		try {
   456  			if (type[methodName]) return true;
   457  		} catch (e) {
   458  		}
   459  		return false;
   460  	}
   461  	
   462  	/**
   463  	 * @overload #isConstructorByInstance
   464  	 * @overload #isConstructorByType
   465  	 */
   466  	public static function isConstructor(constructor:Function, object):Boolean {
   467  		if (constructor === null || constructor === undefined || object === null || object === undefined) return false;
   468  		if (typeof(object) == "function") {
   469  			return isConstructorByType(constructor, object);
   470  		}
   471  		return isConstructorByInstance(constructor, object);
   472  	}
   473  	
   474  	/**
   475  	 * Returns whether the passed-in {@code method} is the constructor of the passed-in
   476  	 * {@code instance}.
   477  	 * 
   478  	 * <p>{@code false} will always be returned if the passed-in {@code method} is
   479  	 * {@code null} or if the passed-in {@code instance} is {@code null}.
   480  	 *
   481  	 * @param method the method to check whether it is the constructor of the passed-in
   482  	 * {@code instance}
   483  	 * @param instance the instance that might be instantiated by the passed-in {@code method}
   484  	 * @return {@code true} if {@code method} is the constructor of {@code instance}
   485  	 * else {@code false}
   486  	 */
   487  	public static function isConstructorByInstance(method:Function, instance):Boolean {
   488  		if (!method || instance === null || instance === undefined) return false;
   489  		// MovieClips on the stage do not have a '__constructor__' but a 'constructor' variable.
   490  		// Note that this causes problems with dynamically created inheritance chains like
   491  		// myMovieClip.__proto__ = MyClass.prototype because the '__constructor__' and 'constructor' 
   492  		// properties do not get changed.
   493  		return isConstructorByType(method, instance.__constructor__ ? instance.__constructor__ : instance.constructor);
   494  	}
   495  	
   496  	/**
   497  	 * Returns whether the passed-in {@code method} is the constructor of the passed-in
   498  	 * {@code type}.
   499  	 * 
   500  	 * <p>Note that in Flash the constructor is the same as the type.
   501  	 *
   502  	 * <p>{@code false} will always be returned if the passed-in {@code method} is
   503  	 * {@code null} or if the passed-in {@code type} is {@code null}.
   504  	 *
   505  	 * @param method the method to check whether it is the constructor of the passed-in
   506  	 * {@code type}
   507  	 * @param type the type that might declare the passed-in {@code method} as constructor
   508  	 * @return {@code true} if {@code method} is the constructor of {@code type} else
   509  	 * {@code false}
   510  	 */
   511  	public static function isConstructorByType(method:Function, type:Function):Boolean {
   512  		if (method === null || method === undefined || type === null || type === undefined) return false;
   513  		return (method.valueOf() == type.valueOf());
   514  	}
   515  	
   516  	/**
   517  	 * Returns an array that contains the names of the variables of the passed-in
   518  	 * {@code instance} as {@code String}s.
   519  	 *
   520  	 * <p>The resulting array contains all variables' names even those hidden from
   521  	 * for..in loops. Excluded are only {@code "__proto__"}, {@code "prototype"},
   522  	 * {@code "__constructor__"} and {@code "constructor"} and members that are of
   523  	 * type {@code "function"}.
   524  	 *
   525  	 * <p>Note that it is not possible to get variables that have been declared in the
   526  	 * class but have not been initialized yet. These variables' names are thus not
   527  	 * contained in the resulting array.
   528  	 *
   529  	 * <p>This method will never return {@code null}. If the passed-in {@code instance}
   530  	 * has no variables an empty array will be returned.
   531  	 *
   532  	 * @param instance the instance whose varaibles to return
   533  	 * @return all initialized variables of the passed-in {@code instance}
   534  	 */
   535  	public static function getVariableNames(instance):Array {
   536  		var result:Array = new Array();
   537  		var s:Function = _global.ASSetPropFlags;
   538  		s(instance, null, 0, true);
   539  		s(instance, ["__proto__", "prototype", "__constructor__", "constructor"], 7, true);
   540  		for (var i:String in instance) {
   541  			try {
   542  				if (typeof(instance[i]) != "function") {
   543  					result.push(i);
   544  				}
   545  			} catch (e) {
   546  				// catches exceptions that may be thrown by properties
   547  			}
   548  		}
   549  		s(instance, null, 1, true);
   550  		return result;
   551  	}
   552  	
   553  	/**
   554  	 * Evaluates the concrete type by its path.
   555  	 * 
   556  	 * <p>As different compilers may store the classes in different locations, it is
   557  	 * necessary to use this helper if you want to get a concrete type by its name.
   558  	 * 
   559  	 * @param path the path of the type
   560  	 * @return the type appropriate to the {@code path} or {@code undefined} if there
   561  	 * is no type for the given {@code path}
   562  	 */
   563  	public static function getTypeByName(path:String):Function {
   564  		var result:Function = eval("_global." + path);
   565  		if (!result) {
   566  			result = eval("_global." + path.split(".").join("_"));
   567  		}
   568  		return result;
   569  	}
   570  	
   571  	/**
   572  	 * Private constructor.
   573  	 */
   574  	private function ReflectUtil(Void) {
   575  	}
   576  	
   577  }