/** * Copyright (c) 2006 JC Wichman | www.Objectpainters.com * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated * documentation files (the "Software"), to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and * to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions * of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ import objectpainters.library.reflect.FunctionFinder; /** * Although there is a MacroMedia Classfinder class it seems awkward and well hidden. * This class implements some kind of reflection functionality for Flash as far as possible, while still * having a readable implementation without all kinds of hidden flash calls to ASNative or ASSetProps * methods. * It allows three main functions: * - retrieving the short class name for an object or class, * - retrieving the full package name for an object or class, * - creating new objects based on a full package name. * * Before you can retrieve class names for objects, you need to register the packages you wish to perform this * functionality on, using for example ClassFinder.registerPackage ("trimm"); * This might need to be automated in the future, for example in the same way Xray performs this feat. Or * we should simply reuse information Xray adds, since its a prerequisite in the application template now * anyway. * See version note #2, a fix for the constructor bug is added to every class registered. * * @author J.C. Wichman, www.objectpainters.com * $LastChangedDate: 2006-10-23 21:10:25 +0200 (ma, 23 okt 2006) $ * $Rev: 168 $ * * #1 / 0.2 / 25-05-2005 / JCW / initial version * #2 / 0.3 / 28-04-2006 / JCW / added constructor bug fix. Please refer to the link: * http://www.quantumwave.com/flash/inheritance.html * The problem that Subclass.prototype.constructor == SuperClass still seems * to persist in Flash 8, and its fixed for each class registered to the * ClassFinder. * #3 / 0.4 / 23-10-2006 / JCW / made the nameField/pathField references less collision sensitive, added a refresh and * auto refresh mechanism, and edit a registerAll function, credits to John Grden (acmewebworks). * Note that in order for the refresh mechanism to REALLY work, an autorefresh on the functionfinder * would be necessary, however this is to tricky/performance intensive, and to much clutter. */ class objectpainters.library.reflect.ClassFinder { private static var nameField:String = "objectpainters.library.reflect.ClassFinder_____emaNssalC";//use a name not likely to cause a name collision private static var pathField:String = "objectpainters.library.reflect.ClassFinder_____htaPssalC";//use a name not likely to cause a name private static var traceHook:Function; //function to call with class info for each registered class private static var classHook:Function; //function to call with class for each registered class private static var autoRefresh:Boolean = false; public static function setAutoRefresh (value:Boolean) { ClassFinder.autoRefresh = value; } /** * Returns the constructor for the given package string. For example you can use: * new (ClassFinder.find("mx.util.Delegate"))(); * It seems new (eval ("mx.util.Delegate")); should work as well, but I havent checked. * * @param a complete classpath, ie mx.util.Delegate * @return a constructor function to be used to create new instances of the given class */ public static function find(package:String) : Function { var packageParts:Array = package.split(".") var obj:Object = _global; for (var i:Number = 0; i < packageParts.length; i++) { obj = obj[packageParts[i]]; } return Function(obj); } /** * Registers a package to be used with getClassName and getPath. * * @param a string describing a top level package to be registered, for example "mx" or "trimm" */ public static function registerPackage (packageName : String) { ClassFinder.traceHook("Registering package:"+packageName); var ref:Object = _global; if (packageName == null || packageName.length == 0) { registerAll(); } else { var lookupKeys:Array = packageName.split("."); for (var i:Number = 0; i < lookupKeys.length; i++) { ref = ref[lookupKeys[i]]; if (ref == null) break; } if (ref != null) { ClassFinder.classPusher (ref, packageName); } } ClassFinder.traceHook("\n\n"); } public static function registerAll() { ClassFinder.traceHook("Registering all classes."); // add the enumerable properties of global for reseting after examination var unprotected:Array = []; for(var items:String in _global) { unprotected.push(items); } // make all properties enumerable _global.ASSetPropFlags(_global, null, 0, 1); // start examination classPusher (_global); // protect global's props again _protect(_global, unprotected); ClassFinder.traceHook("\n\n"); } private static function _protect(package_obj:Object, unprotected:Array):Void { // First, hides all properties, then shows only the props in the unprotected array _global.ASSetPropFlags(package_obj, null, 1, 1); _global.ASSetPropFlags(package_obj, unprotected, 0, 1); // for some reason, on another project, I found that I still had to run this to protect these. _global.ASSetPropFlags(package_obj,["constructor", "__constructor__", "prototype", "__proto__"],1,1); } /** * Returns the short classname (ie without the package info) for an object or a constructor (Class). * * @param obj the obj to get the classname for * @param preventRecursion do not pass any parameters for this field * @return the classname if the object's class was previously correctly registered */ public static function getClassName (obj : Object, preventRecursion:Boolean):String { var toReturn:String = null; if (obj instanceof Function) { //it is a class toReturn = obj[ClassFinder.nameField]; } else { //it is an object, get to class through constructor toReturn = obj.constructor[ClassFinder.nameField]; } if (toReturn == null && ClassFinder.autoRefresh && preventRecursion != false) { ClassFinder.traceHook("AutoRefresh triggered."); ClassFinder.registerAll(); return ClassFinder.getClassName(obj, false); } else { return toReturn; } } /** * Returns the complete classname including the package part, for the given object or constructor (Class). * * @param obj the obj to get the complete package name for * @param preventRecursion do not pass any parameters for this field * @return the complete package name if the object's class was previously correctly registered */ public static function getPath (obj : Object, preventRecursion:Boolean):String { var toReturn:String = null; if (obj instanceof Function) { toReturn = obj[ClassFinder.pathField]; } else { toReturn = obj.constructor[ClassFinder.pathField]; } if (toReturn == null && ClassFinder.autoRefresh && preventRecursion != false) { ClassFinder.traceHook("AutoRefresh triggered."); ClassFinder.registerAll(); return ClassFinder.getPath(obj, false); } else { return toReturn; } } /** * Private helper function to look up all classes in a given package and tell them there own classname and * package name. * * @param node the object that corresponds to the given name for example _global["mx"] * @param name the starting package name, to start the recursion, eg "mx" */ private static function classPusher (node : Object, thePath : String) { // extName that looks to see if there was a name included. If not, we ommit the "." and set the name correctly // If name is not defined, that means the user is searching all of _global var path:String = (thePath == undefined || thePath.length == 0)?"":thePath+"."; for (var childName:String in node) { //if you have found an object that is a function and has a constructor register it (its a class) var childNode:Object = node[childName]; if (childNode instanceof Function && childNode.constructor != null) { //fix constructor property bug childNode.prototype.constructor = childNode; var fullPath:String = path+childName; //if className is not known yet, assign it //trace(childNode[ClassFinder.pathField]+"/"+fullPath); if (childNode[ClassFinder.pathField] != fullPath) { childNode[ClassFinder.pathField] = fullPath; childNode[ClassFinder.nameField] = childName; //hide our newly added properties _global.ASSetPropFlags(childNode,[ClassFinder.pathField, ClassFinder.nameField],1, 1); ClassFinder.traceHook ("Found class::"+fullPath); if (ClassFinder.classHook == null) { FunctionFinder.registerClass (Function(childNode)); } else { ClassFinder.classHook (childNode); } } //else if it isnt a class object, but still an object, recurse it to find more objects. } else if (childNode instanceof Object) { classPusher (childNode, path+childName); } } } public static function installDefaultTraceHook() { ClassFinder.traceHook = function (info:String) { trace (info); } } /** * Install a function which handles info:String about each class visited. * * @param traceHook */ public static function installTraceHook(traceHook:Function) { ClassFinder.traceHook = traceHook; } /** * Install a function to which each class object is passed as it is visited, default this will be the FunctionFinder. * * @param classHook */ public static function installClassHook(classHook:Function) { ClassFinder.classHook = classHook; } /** * Returns a string information object for the object passed. * * @param sender * @return the sender converted to a string, "?" if we cannot identify the sender */ public static function getSenderAsString(sender:Object):String { if (sender == null) { return null; } else if (typeof(sender) == "string") { return String("*"+sender); } else { var className:String = ClassFinder.getClassName (sender); if (className == null) { return "*"+sender.toString(); } else { return className; } } } }