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.overload.OverloadHandler; 19 import org.as2lib.env.overload.SimpleOverloadHandler; 20 import org.as2lib.env.overload.UnknownOverloadHandlerException; 21 import org.as2lib.env.except.IllegalArgumentException; 22 import org.as2lib.env.overload.SameTypeSignatureException; 23 24 /** 25 * {@code Overload} provides methods to overload a method. 26 * 27 * <p>With overloading you have typically two or more methods with the same name. Which 28 * method gets actually invoked depends on its type signature, that means its return 29 * and arguments' types. Here is an example of what overloading may look if it would be 30 * supported by Flash (note that this code does actually not work). 31 * 32 * <p>Example: 33 * <code> 34 * // MyClass.as 35 * class MyClass { 36 * public function myMethod(number:Number, string:String):Void { 37 * trace("myMethod(Number, String):Void"); 38 * } 39 * public function myMethod(number:Number):Void { 40 * trace("myMethod(Number):Void"); 41 * } 42 * public function myMethod(string:String):Number { 43 * trace("myMethod(String):Number"); 44 * return 1; 45 * } 46 * } 47 * </code> 48 * 49 * <p>Usage: 50 * <code> 51 * // test.fla 52 * var myInstance:MyClass = new MyClass(); 53 * myInstance.myMethod(1); 54 * myInstance.myMethod(2, "myString"); 55 * var number:Number = myInstance.myMethod("myString"); 56 * trace(number); 57 * </code> 58 * 59 * <p>Output: 60 * <pre> 61 * myMethod(Number):Void 62 * myMethod(Number, String):Void 63 * myMethod(String):Number 64 * 1 65 * </pre> 66 * 67 * <p>As you can see, depending on what type the passed-in arguments have a different 68 * method is invoked. This is sadly not possible with ActionScript, that is what this 69 * class is for. Using the overload mechanism this class offers the overloading looks 70 * as follows: 71 * 72 * <code> 73 * // MyClass.as 74 * class MyClass { 75 * public function myMethod() { 76 * var o:Overload = new Overload(this); 77 * o.addHandler([Number, String], myMethodByNumberAndString); 78 * o.addHandler([Number], myMethodByNumber); 79 * o.addHandler([String], myMethodByString); 80 * return o.forward(arguments); 81 * } 82 * public function myMethodByNumberAndString(number:Number, string:String):Void { 83 * trace("myMethod(Number, String):Void"); 84 * } 85 * public function myMethodByNumber(number:Number):Void { 86 * trace("myMethod(Number):Void"); 87 * } 88 * public function myMethodByString(string:String):Number { 89 * trace("myMethod(String):Number"); 90 * return 1; 91 * } 92 * } 93 * </code> 94 * 95 * <p>Using the above testing code the output looks the same. 96 * 97 * <p>While this is a good overloading mechanism / overloading alternative it still has 98 * some disadvantages. 99 * <ul> 100 * <li> 101 * If not all methods the overloaded method forwards to returns a value of the 102 * same type, return type type-checking is lost. 103 * </li> 104 * <li> 105 * The type checking of the arguments is also lost at compile time. At run-time the 106 * {@code Overload} class throws an {@code UnknownOverloadHandlerException} if the 107 * real arguments match no added overload handler. 108 * </li> 109 * <li>The overloading slows the method execution a little bit down.</li> 110 * </ul> 111 * 112 * <p>But if you declare the methods to overload to as public, as in the example, you 113 * can still invoke them directly. Doing so, all the above problems do not hold true 114 * anymore. The overloaded methods then acts more as a convenient method that is easy 115 * to use if appropriate. 116 * 117 * @author Simon Wacker 118 */ 119 class org.as2lib.env.overload.Overload extends BasicClass { 120 121 /** All registered handlers. */ 122 private var handlers:Array; 123 124 /** Handler to use if no handler matches. */ 125 private var defaultHandler:OverloadHandler; 126 127 /** The target object to invoke the method on. */ 128 private var target; 129 130 /** 131 * Constructs a new {@code Overload} instance. 132 * 133 * <p>The passed-in {@code target} is normally the object on which the overloading 134 * takes place. This means it is the object that declares all methods that take 135 * part at the overloading. 136 * 137 * @param target the target to invoke the overloaded method on 138 */ 139 public function Overload(target) { 140 this.handlers = new Array(); 141 this.target = target; 142 } 143 144 /** 145 * Sets the default handler. 146 * 147 * <p>This handler will be used if no other handler matches to a list of arguments. 148 * All real arguments used for the overloading are passed as parameters to the 149 * method of this default handler. 150 * 151 * <p>The method is invoked on the same scope as the other handlers. That is the 152 * target passed-in on construction. 153 * 154 * <code> 155 * var overload:Overload = new Overload(this); 156 * overload.addHandler([String], methodWithStringArgument); 157 * overload.addHandler([Number], methodWithNumberArgument); 158 * overload.setDefaultHandler(function() { 159 * trace(arguments.length + " arguments were used."); 160 * }); 161 * return overload.forward(arguments); 162 * </code> 163 * 164 * <p>If the passed-in {@code method} is {@code null}, {@code undefined} or not of 165 * type {@code "function"} the default handler gets removed. 166 * 167 * @param method the method of the handler to invoke if no added handler matches 168 * the overload arguments 169 * @see #removeDefaultHandler 170 */ 171 public function setDefaultHandler(method:Function):Void { 172 if (typeof(method) == "function") { 173 defaultHandler = new SimpleOverloadHandler(null, method); 174 } else { 175 removeDefaultHandler(); 176 } 177 } 178 179 /** 180 * Removes the default handler. 181 * 182 * <p>This handler is used if no other handler matches to a list of arguments. 183 * 184 * @see #setDefaultHandler 185 */ 186 public function removeDefaultHandler(Void):Void { 187 defaultHandler = null; 188 } 189 190 /** 191 * @overload #addHandlerByHandler 192 * @overload #addHandlerByValue 193 */ 194 public function addHandler() { 195 var l:Number = arguments.length; 196 if (l == 1) { 197 var handler:OverloadHandler = arguments[0]; 198 if (handler == null || handler instanceof OverloadHandler) { 199 addHandlerByHandler(handler); 200 return; 201 } 202 } 203 if (l == 2) { 204 var args:Array = arguments[0]; 205 var method:Function = arguments[1]; 206 if ((args == null || args instanceof Array) && (method == null || method instanceof Function)) { 207 return addHandlerByValue(args, method); 208 } 209 } 210 throw new IllegalArgumentException("The types and count of the passed-in arguments [" + arguments + "] must match one of the available choices.", this, arguments); 211 } 212 213 /** 214 * Adds the passed-in {@code handler}. 215 * 216 * <p>Overload handlers are used to determine the method to forward to. This is 217 * done using the methods {@link OverloadHandler#matches} and 218 * {@link OverloadHandler#isMoreExplicit}. If both conditions hold true the method 219 * invocation is forwarded to the method of the handler, that gets returned by the 220 * {@link OverloadHandler#getMethod} method. 221 * 222 * <p>If the passed-in {@code handler} is {@code null} or {@code undefined} no 223 * actions will take place. 224 * 225 * @param handler the new overload handler to add 226 */ 227 public function addHandlerByHandler(handler:OverloadHandler):Void { 228 if (handler) { 229 handlers.push(handler); 230 } 231 } 232 233 /** 234 * Adds a new {@link SimpleOverloadHandler} instance, that gets configured with the 235 * passed-in {@code argumentsTypes} and {@code method}. 236 * 237 * <p>Overload handlers are used to determine the method to forward to. This is 238 * done using the methods {@link OverloadHandler#matches} and 239 * {@link OverloadHandler#isMoreExplicit}. If both conditions hold true the method 240 * invocation is forwarded to the method of the handler, that gets returned by the 241 * {@link OverloadHandler#getMethod} method. 242 * 243 * <p>The passed-in {@code argumentsTypes} are the types of arguments the method 244 * expects from the real arguments to have. The {@code SimpleOverloadHandler} does 245 * its matches and explicity checks upon these arguments' types. 246 * 247 * <p>The passed-in {@code method} is the method to invoke if the added handler 248 * matches the real arguments and if it is the most explicit handler among all 249 * matching ones. 250 * 251 * @param argumentsTypes the arguments' types of the overload handler 252 * @param method the method corresponding to the passed-in {@code argumentsTypes} 253 * @return the newly created overload handler 254 * @see SimpleOverloadHandler#SimpleOverloadHandler 255 */ 256 public function addHandlerByValue(argumentsTypes:Array, method:Function):OverloadHandler { 257 var handler:OverloadHandler = new SimpleOverloadHandler(argumentsTypes, method); 258 handlers.push(handler); 259 return handler; 260 } 261 262 /** 263 * Removes the passed-in {@code handler}. 264 * 265 * <p>All occurrences of the passed-in {@code handler} are removed. 266 * 267 * @param handler the overload handler to remove 268 */ 269 public function removeHandler(handler:OverloadHandler):Void { 270 if (handler) { 271 var i:Number = handlers.length; 272 while (--i-(-1)) { 273 if (handlers[i] == handler) { 274 handlers.splice(i, 1); 275 } 276 } 277 } 278 } 279 280 /** 281 * Forwards to the appropriate overload handler depending on the passed-in 282 * {@code args}. 283 * 284 * <p>This is not done by using the {@link OverloadHandler#execute} method but 285 * manually by using {@code apply} on the method returned by the 286 * {@link OverloadHandler#getMethod} method. Invoking the method this way 287 * increases the number of possible recurions with overlaoded methods. 288 * 289 * <p>If the {@code args} array is {@code null} or {@code undefined} an empty array 290 * is used instead. 291 * 292 * <p>If no overload handler matches, the default overload handler will be used if 293 * it has been set. 294 * 295 * <p>Overload handlers are supposed to have the same type signature if the 296 * {@link OverloadHandler#isMoreExplicit} method returns {@code null}. 297 * 298 * @return the return value of the invoked method 299 * @throws org.as2lib.env.overload.UnknownOverloadHandlerException if no adequate 300 * overload handler could be found 301 * @throws org.as2lib.env.overload.SameTypeSignatureException if there exist at 302 * least two overload handlers with the same type siganture, that means their 303 * arguments' types are the same 304 */ 305 public function forward(args:Array) { 306 return doGetMatchingHandler(arguments.caller, args).getMethod().apply(target, args); 307 } 308 309 /** 310 * Returns the most explicit overload handler from the array of matching handlers. 311 * 312 * <p>If the {@code args} array is {@code null} or {@code undefined} an empty array 313 * is used instead. 314 * 315 * <p>If no handler matches the default handler gets returned if it has been set. 316 * 317 * <p>Overload handlers are supposed to have the same type signature if the 318 * {@link OverloadHandler#isMoreExplicit} method returns {@code null}. 319 * 320 * @param args the arguments that shall match to a specific overload handler 321 * @return the most explicit overload handler 322 * @throws org.as2lib.env.overload.UnknownOverloadHandlerException if no adequate 323 * overload handler could be found 324 * @throws org.as2lib.env.overload.SameTypeSignatureException if there exist at 325 * least two overload handlers with the same type siganture, that means their 326 * arguments' types are the same 327 */ 328 public function getMatchingHandler(args:Array):OverloadHandler { 329 return doGetMatchingHandler(arguments.caller, args); 330 } 331 332 /** 333 * Returns the most explicit overload handler out of the array of matching overload 334 * handlers. 335 * 336 * <p>If the passed-in {@code args} array is {@code null} or {@code undefined} an 337 * empty array is used instead. 338 * 339 * <p>If no handler matches the default handler gets returned if it has been set. 340 * 341 * <p>Overload handlers are supposed to have the same type signature if the 342 * {@link OverloadHandler#isMoreExplicit} method returns {@code null}. 343 * 344 * @param overloadedMethod the overloaded method on the target 345 * @param overloadArguments the arguments for which the overload shall be performed 346 * @return the most explicit overload handler 347 * @throws org.as2lib.env.overload.UnknownOverloadHandlerException if no adequate 348 * overload handler could be found 349 * @throws org.as2lib.env.overload.SameTypeSignatureException if there exist at 350 * least two overload handlers with the same type siganture, that means their 351 * arguments' types are the same 352 */ 353 private function doGetMatchingHandler(overloadedMethod:Function, overloadArguments:Array):OverloadHandler { 354 if (!overloadArguments) overloadArguments = []; 355 var matchingHandlers:Array = getMatchingHandlers(overloadArguments); 356 var i:Number = matchingHandlers.length; 357 if (i == 0) { 358 if (defaultHandler) { 359 return defaultHandler; 360 } 361 throw new UnknownOverloadHandlerException("No appropriate OverloadHandler found.", 362 this, 363 arguments, 364 target, 365 overloadedMethod, 366 overloadArguments, 367 handlers); 368 } 369 var result:OverloadHandler = matchingHandlers[--i]; 370 while (--i-(-1)) { 371 var moreExplicit:Boolean = result.isMoreExplicit(matchingHandlers[i]); 372 if (moreExplicit == null) { 373 throw new SameTypeSignatureException("Two OverloadHandlers have the same type signature.", 374 this, 375 arguments, 376 target, 377 overloadedMethod, 378 overloadArguments, 379 [result, matchingHandlers[i]]); 380 } 381 if (!moreExplicit) result = matchingHandlers[i]; 382 } 383 return result; 384 } 385 386 /** 387 * Returns {@link OverlaodHandler} instances that match the passed-in {@code args}. 388 * 389 * <p>The match is performed using the {@link OverlaodHandler#matches} method. 390 * 391 * @param args the arguments that shall match to overload handlers 392 * @return an array containing the matching {@code OverloadHandler} instances 393 */ 394 private function getMatchingHandlers(args:Array):Array { 395 var result:Array = new Array(); 396 var i:Number = handlers.length; 397 while (--i-(-1)) { 398 var handler:OverloadHandler = handlers[i]; 399 if (handler.matches(args)) result.push(handler); 400 } 401 return result; 402 } 403 404 } 405