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.env.except.IllegalArgumentException; 19 import org.as2lib.env.reflect.ClassNotFoundException; 20 import org.as2lib.env.reflect.Cache; 21 import org.as2lib.env.reflect.PackageInfo; 22 import org.as2lib.env.reflect.ClassInfo; 23 import org.as2lib.env.reflect.ReflectConfig; 24 25 /** 26 * {@code ClassAlgorithm} searches for the class of a specific instance or class 27 * and returns information about that class. 28 * 29 * <p>This class is rather cumbersome to use. It is recommended to use the static 30 * {@link ClassInfo#forObject}, {@link ClassInfo#forInstance}, {@link ClassInfo#forClass} 31 * and {@link ClassInfo#forName} methods instead. They offer more sophisticated 32 * return values and do also store {@code ClassInfo} instances retrieved by classes 33 * or instances and not only those by name like this algorithm does. 34 * 35 * <p>To obtain information corresponding to an instance or a class you can use 36 * this class as follows. 37 * 38 * <code> 39 * var myInstance:MyClass = new MyClass(); 40 * var classAlgorithm:ClassAlgorithm = new ClassAlgorithm(); 41 * var infoByInstance:Object = classAlgorithm.execute(myInstance); 42 * var infoByClass:Object = classAlgorithm.execute(MyClass); 43 * </code> 44 * 45 * <p>It is also possible to retrieve a class info by name. 46 * 47 * <code> 48 * classInfoByName:ClassInfo = classAlgorithm.executeByName("MyClass"); 49 * </code> 50 * 51 * <p>If the class is not contained in the root/default package you must specify 52 * the whole path / its namespace. 53 * 54 * <code> 55 * classInfoByName:ClassInfo = classAlgorithm.executeByName("org.as2lib.MyClass"); 56 * </code> 57 * 58 * <p>Already retrieved class infos are stored in a cache. There thus exists only 59 * one {@code ClassInfo} instance per class. Note that the {@link #execute} method 60 * does not return {@code ClassInfo} instances and does thus not store the found 61 * information. 62 * 63 * @author Simon Wacker 64 */ 65 class org.as2lib.env.reflect.algorithm.ClassAlgorithm extends BasicClass { 66 67 /** The cache to store class and package infos. */ 68 private var c:Cache; 69 70 /** The temporary result. */ 71 private var r; 72 73 /** 74 * Constructs a new {@code ClassAlgorithm} instance. 75 */ 76 public function ClassAlgorithm(Void) { 77 } 78 79 /** 80 * Sets the cache that is used by the {@link #executeByName} method to look whether 81 * the class the shall be found is already stored. 82 * 83 * <p>This cache also determines where the search for a class starts. 84 * 85 * @param cache the new cache 86 */ 87 public function setCache(cache:Cache):Void { 88 c = cache; 89 } 90 91 /** 92 * Returns the cache set via the {@link #setCache} method or the default cache that 93 * is returned by the {@link ReflectConfig#getCache} method. 94 * 95 * @return the currently used cache 96 */ 97 public function getCache(Void):Cache { 98 if (!c) c = ReflectConfig.getCache(); 99 return c; 100 } 101 102 /** 103 * @overload #executeByClass 104 * @overload #executeByInstance 105 */ 106 public function execute(d) { 107 if (typeof(d) == "function") { 108 return executeByClass(d); 109 } 110 return executeByInstance(d); 111 } 112 113 /** 114 * Executes the search for the passed-in class {@code d} and returns information 115 * about this class. 116 * 117 * <p>The returned object has the following properties: 118 * <dl> 119 * <dt>clazz</dt> 120 * <dd>The class as {@code Function} that has been searched for, this is the 121 * passed-in class {@code d}.</dd> 122 * <dt>name</dt> 123 * <dd>The name as {@code String} of the searched for class.</dd> 124 * <dt>package</dt> 125 * <dd>The package represented by a {@link PackageInfo} instance the class is 126 * a member of.</dd> 127 * </dl> 128 * 129 * <p>{@code null} will be returned if: 130 * <ul> 131 * <li>The passed-in class {@code d} is {@code null} or {@code undefined}.</li> 132 * <li>The passed-in class {@code d} could not be found.</li> 133 * </ul> 134 * 135 * <p>The search starts on the package returned by the {@link Cache#getRoot} method 136 * of the set cache. If this method returns a package info whose {@code getPackage} 137 * method returns {@code null} or {@code undefined} {@code _global} is used instead. 138 * 139 * @param d the class to return information about 140 * @return an object that contains information about the passed-in class {@code d} 141 * @see #getCache 142 */ 143 public function executeByClass(d:Function) { 144 if (d === null || d === undefined) return null; 145 return executeByComparator(function(f:Function) { 146 // use f.valueOf() because this allows one to overwrite the original class, 147 // to add a valueOf method that returns the original class and still find 148 // the correct class at run-time (this is for example used by the aop framework) 149 return f.valueOf() == d.valueOf(); 150 }); 151 } 152 153 /** 154 * Executes the search for the class the passed-in object {@code d} is an instance 155 * of and returns information about that class. 156 * 157 * <p>The returned object has the following properties: 158 * <dl> 159 * <dt>clazz</dt> 160 * <dd>The class as {@code Function} that has been searched for.</dd> 161 * <dt>name</dt> 162 * <dd>The name as {@code String} of the searched for class.</dd> 163 * <dt>package</dt> 164 * <dd>The package represented by a {@link PackageInfo} instance the class is 165 * a member of.</dd> 166 * </dl> 167 * 168 * <p>{@code null} will be returned if: 169 * <ul> 170 * <li>The passed-in instance {@code d} is {@code null} or {@code undefined}.</li> 171 * <li>The class of the passed-in instance could not be found.</li> 172 * </ul> 173 * 174 * <p>The search starts on the package returned by the {@link Cache#getRoot} method 175 * of the set cache. If this method returns a package info whose {@code getPackage} 176 * method returns {@code null} or {@code undefined} {@code _global} is used instead. 177 * 178 * @param d the instance of the class to return information about 179 * @return an object that contains information about the class the passed-in object 180 * {@code d} is an instance of 181 * @see #getCache 182 */ 183 public function executeByInstance(d) { 184 // not 'if (!d)' because 'd' could be en empty string or a boolean 185 // 'valueOf' method of 'd' could return 'null' or 'undefined' thus strict eval is used 186 if (d === null || d === undefined) return null; 187 return executeByComparator(function(f:Function) { 188 return f.prototype === d.__proto__; 189 }); 190 } 191 192 /** 193 * Executes the search for a class and returns information about that class. 194 * 195 * <p>The returned object has the following properties: 196 * <dl> 197 * <dt>clazz</dt> 198 * <dd>The class as {@code Function} that has been searched for.</dd> 199 * <dt>name</dt> 200 * <dd>The name as {@code String} of the searched for class.</dd> 201 * <dt>package</dt> 202 * <dd>The package represented by a {@ling PackageInfo} instance the class is 203 * a member of.</dd> 204 * </dl> 205 * 206 * <p>{@code null} will be returned if: 207 * <ul> 208 * <li>The passed-in comparator {@code v} method is {@code null} or {@code undefined}.</li> 209 * <li>The searched for class could not be found.</li> 210 * </ul> 211 * 212 * <p>The search starts on the package returned by the {@link Cache#getRoot} method 213 * of the set cache. If this method returns a package info whose {@code getPackage} 214 * method returns {@code null} or {@code undefined} {@code _global} is used instead. 215 * 216 * <p>The passed-in comparator is invoked for every found class to determine whether 217 * it is the right one or not. The comparator method gets passed the found class and 218 * must return {@code true} or {@code false}. If it returns {@code true} the 219 * algorithm stops and returns the information about this class. 220 * @param v the comparator to determine the correct class 221 * @return an object that contains information about the class 222 * @see #getCache 223 */ 224 public function executeByComparator(v:Function) { 225 if (!v) return null; 226 r = null; 227 var b:PackageInfo = getCache().getRoot(); 228 var a:Object = b.getPackage(); 229 if (!a) a = _global; 230 _global.ASSetPropFlags(a, null, 0, true); 231 _global.ASSetPropFlags(a, ["__proto__", "constructor", "__constructor__", "prototype"], 1, true); 232 findAndStore(b, v); 233 return r; 234 } 235 236 private function findAndStore(a:PackageInfo, v:Function):Boolean { 237 var p = a.getPackage(); 238 var i:String; 239 for (i in p) { 240 var f = p[i]; 241 if (typeof(f) == "function") { 242 if (v(f)) { 243 // flex stores every class in _global and in its actual package 244 // e.g. org.as2lib.core.BasicClass is stored in _global with name org_as2lib_core_BasicClass 245 // this if-clause excludes these extra stored classes 246 if (!eval("_global." + i.split("_").join(".")) || i.indexOf("_") < 0) { 247 r = new Object(); 248 r.clazz = f; 249 r.name = i; 250 r.package = a; 251 return true; 252 } 253 } 254 } else if (typeof(f) == "object") { 255 var e:PackageInfo = c.getPackage(f); 256 if (!e) { 257 e = c.addPackage(new PackageInfo(f, i, a)); 258 } 259 if (!e.isParentPackage(a)) { 260 // todo: replace recursion with loop 261 if (findAndStore(e, v)) { 262 return true; 263 } 264 } 265 } 266 } 267 return false; 268 } 269 270 /** 271 * Returns the class info representing the class corresponding to the passed-in 272 * class name {@code n}. 273 * 274 * <p>The class name must be fully qualified, that means it must consist of the 275 * class's path (namespace) as well as its name. For example 'org.as2lib.core.BasicClass'. 276 * 277 * <p>The search starts on the package returned by the {@link Cache#getRoot} method 278 * of the set cache. If this method returns a package info whose {@code getFullName} 279 * method returns {@code null} or {@code undefined} {@code "_global"} is used instead. 280 * 281 * @param n the fully qualified name of the class 282 * @return the class info representing the class corresponding to the passed-in name 283 * @throws IllegalArgumentException if the passed-in name is {@code null}, {@code undefined} 284 * or an empty string or if the object corresponding to the passed-in name is not of 285 * type function 286 * @throws ClassNotFoundException if a class with the passed-in name could not be found 287 */ 288 public function executeByName(n:String):ClassInfo { 289 if (!n) throw new IllegalArgumentException("The passed-in class name '" + n + "' is not allowed to be null, undefined or an empty string.", this, arguments); 290 var p:PackageInfo = getCache().getRoot(); 291 var x:String = p.getFullName(); 292 if (!x) x = "_global"; 293 var f:Function = eval(x + "." + n); 294 if (!f) throw new ClassNotFoundException("A class with the name '" + n + "' could not be found.", this, arguments); 295 if (typeof(f) != "function") throw new IllegalArgumentException("The object corresponding to the passed-in class name '" + n + "' is not of type function.", this, arguments); 296 var r:ClassInfo = c.getClassByClass(f); 297 if (r) return r; 298 var a:Array = n.split("."); 299 var g:Object = p.getPackage(); 300 for (var i:Number = 0; i < a.length; i++) { 301 if (i == a.length-1) { 302 return c.addClass(new ClassInfo(f, a[i], p)); 303 } else { 304 g = g[a[i]]; 305 p = c.addPackage(new PackageInfo(g, a[i], p)); 306 } 307 } 308 return null; 309 // unreachable!!! 310 } 311 312 }