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 }