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.util.ObjectUtil; 19 import org.as2lib.env.overload.OverloadHandler; 20 import org.as2lib.env.except.IllegalArgumentException; 21 import org.as2lib.env.reflect.ReflectUtil; 22 23 /** 24 * {@code SimpleOverloadHandler} offers basic overloading functionalities. 25 * 26 * <p>Overload handlers are used by the {@code Overload} class to identify the 27 * corresponding method for a specific list of arguments. Whereby the overload handler 28 * holds the method and the expected arguments' types of this method. 29 * 30 * <p>It also offers functionalities to match real arguments against the expected 31 * arguments' types, {@link #matches}, and to determine which overload handler or 32 * rather which arguments' types of two handlers are more explicit, 33 * {@link #isMoreExplicit}. 34 * 35 * <p>It also offers the ability to invoke/execute the target method on a target scope 36 * passing-in a list of real arguments. 37 * 38 * <p>This class is normally not used directly but indirectly via the 39 * {@link Overload#addHandler} method. 40 * 41 * <p>If you nevertheless want to instantiate it by hand and then use it with the 42 * {@code Overload} class you can do this as follows: 43 * 44 * <code> 45 * this.myMethod = function(number:Number, string:String):String { 46 * return (number + ", " + string); 47 * } 48 * var overload:Overload = new Overload(this); 49 * var handler:OverloadHandler = new SimpleOverloadHandler([Number, String], myMethod); 50 * overload.addHandler(handler); 51 * trace(overload.forward([2, "myString"])); 52 * </code> 53 * 54 * <p>Note that the handlers arguments signature (the arguments' types) match exactly 55 * the ones of the method {@code myMethod}. 56 * 57 * @author Simon Wacker 58 */ 59 class org.as2lib.env.overload.SimpleOverloadHandler extends BasicClass implements OverloadHandler { 60 61 /** Contains the arguments types of the method. */ 62 private var argumentsTypes:Array; 63 64 /** The method to execute on the given target. */ 65 private var method:Function; 66 67 /** 68 * Constructs a new {@code SimpleOverloadHandler} instance. 69 * 70 * <p>If the passed-in {@code argumentsTypes} array is {@code null} or 71 * {@code undefined} an empty array is used instead. 72 * 73 * <p>The passed-in {@code argumentsTypes} are the types of arguments this handler 74 * expects the real arguments to have. The arguments' types thus are also the types 75 * of arguments the method, this handler forwards to, expects. The {@link #matches} 76 * and {@link #isMoreExplicit} methods do their job based on the arguments' types. 77 * 78 * <p>An argument-type is represented by a class or interface, that is a 79 * {@code Function} in ActionScript. An argument type can for example be 80 * {@code Number}, {@code String}, {@code org.as2lib.core.BasicClass}, 81 * {@code org.as2lib.core.BasicInterface} or any other class or interface. 82 * 83 * <p>An argument-type of value {@code null} or {@code undefined} is interpreted 84 * as any type allowed and is less explicit then any other type. 85 * 86 * <p>The arguments' types determine what method call is forwarded to this handler 87 * which then invokes the passed-in {@code method}. The forwarding to this handler 88 * normally takes place if it's matching the passed-in real arguments, 89 * {@link #matches}, and if it is the most explicit overload handler, 90 * {@link #isMoreExplicit}. 91 * 92 * @param argumentsTypes the arguments' types of the method 93 * @param method the actual method to execute on the target if the argumetns' types 94 * match 95 * @throws IllegalArgumentException if the passed-in {@code method} is {@code null} 96 * or {@code undefined} 97 */ 98 public function SimpleOverloadHandler(argumentsTypes:Array, method:Function) { 99 if (!method) throw new IllegalArgumentException("Method to be executed by the overload handler must not be null or undefined.", this, arguments); 100 if (!argumentsTypes) argumentsTypes = []; 101 this.argumentsTypes = argumentsTypes; 102 this.method = method; 103 } 104 105 /** 106 * Checks whether the passed-in {@code realArguments} match the arguments' types 107 * of this overload handler. 108 * 109 * <p>If the passed-in {@code realArguments} array is {@code null} or 110 * {@code undefined}, an empty array is used instead. 111 * 112 * <p>If a real argument has the value {@code null} or {@code undefined} it matches 113 * every type. 114 * 115 * <p>If the expected argument-type is {@code null} or {@code undefined} it matches 116 * every real argument. That means {@code null} and {@code undefined} are 117 * interpreted as {@code Object}, which also matches every real argument. 118 * 119 * @param realArguments the real arguments to match against the arguments' types 120 * @return {@code true} if the real arguments match the arguments' types else 121 * {@code false} 122 */ 123 public function matches(realArguments:Array):Boolean { 124 if (!realArguments) realArguments = []; 125 var i:Number = realArguments.length; 126 if (i != argumentsTypes.length) return false; 127 while (--i-(-1)) { 128 // null == undefined 129 if (realArguments[i] != null) { 130 // An expected type of value null or undefined gets interpreted as: whatever. 131 if (argumentsTypes[i] != null) { 132 if (!ObjectUtil.typesMatch(realArguments[i], argumentsTypes[i])) { 133 return false; 134 } 135 } 136 } 137 } 138 return true; 139 } 140 141 /** 142 * Executes the method of this handler on the given {@code target} passing-in the 143 * given {@code args}. 144 * 145 * <p>The {@code this} scope of the method refers to the passed-in {@code target} 146 * on execution. 147 * 148 * @param target the target object to invoke the method on 149 * @param args the arguments to pass-in on method invocation 150 * @return the result of the method invocation 151 */ 152 public function execute(target, args:Array) { 153 return method.apply(target, args); 154 } 155 156 /** 157 * Checks if this overload handler is more explicit than the passed-in 158 * {@code handler}. 159 * 160 * <p>The check is based on the arguments' types of both handlers. They are 161 * compared one by one. 162 * 163 * <p>What means more explicit? The type {@code String} is for example more 164 * explicit than {@code Object}. The type {@code org.as2lib.core.BasicClass} is 165 * also more explicit than {@code Object}. And the type 166 * {@code org.as2lib.env.overload.SimpleOverloadHandler} is more explicit than 167 * {@code org.as2lib.core.BasicClass}. I hope you get the image. As you can see, 168 * the explicitness depends on the inheritance hierarchy. 169 * 170 * <p>Note that classes are supposed to be more explicit than interfaces. 171 * 172 * <ul> 173 * <li>If the passed-in {@code handler} is {@code null} {@code true} will be 174 * returned.</li> 175 * <li>If the passed-in {@code handler}'s {@code getArguments} method returns 176 * {@code null} an empty array will be used instead.</li> 177 * <li>If the arguments' lengths do not match, {@code true} will be returned.</li> 178 * <li>If one argument-type is {@code null} it is less explicit than no matter 179 * what type it is compared with.</li> 180 * </ul> 181 * 182 * @param handler the handler to compare this handler with regarding its 183 * explicitness 184 * @return {@code true} if this handler is more explicit else {@code false} or 185 * {@code null} if the two handlers have the same explicitness 186 */ 187 public function isMoreExplicit(handler:OverloadHandler):Boolean { 188 // explicitness range: null, undefined -> Object -> Number -> ... 189 if (!handler) return true; 190 var s:Number = 0; 191 var t:Array = handler.getArgumentsTypes(); 192 if (!t) t = []; 193 var i:Number = argumentsTypes.length; 194 if (i != t.length) return true; 195 while (--i-(-1)) { 196 if (argumentsTypes[i] != t[i]) { 197 var o = new Object(); 198 o.__proto__ = argumentsTypes[i].prototype; 199 if (!argumentsTypes[i]) { 200 s--; 201 } else if (!t[i]) { 202 s -= -1; 203 } else if (ObjectUtil.isInstanceOf(o, t[i])) { 204 s -= -1; 205 } else { 206 s--; 207 } 208 } 209 } 210 if (s == 0) { 211 return null; 212 } 213 return (s > 0); 214 } 215 216 /** 217 * Returns the arguments' types used to match against the real arguments. 218 * 219 * <p>The arguments' types determine for which types of arguments the method was 220 * declared for. That means which arguments' types the method expects. 221 * 222 * @return the arguments' types the method expects 223 */ 224 public function getArgumentsTypes(Void):Array { 225 return argumentsTypes; 226 } 227 228 /** 229 * Returns the method this overload handler was assigned to. 230 * 231 * <p>This is the method to invoke passing the appropriate arguments when this 232 * handler matches the arguments and is the most explicit one. 233 * 234 * @return the method to invoke when the real arguments match the ones of this 235 * handler and this handler is the most explicit one 236 */ 237 public function getMethod(Void):Function { 238 return method; 239 } 240 241 /** 242 * Returns a detailed string representation of this overload handler. 243 * 244 * <p>The string representation is composed as follows: 245 * <pre>[object SimpleOverloadHandler(firstArgumentType, ..)]</pre> 246 * 247 * @returns the string representation of this overload handler 248 */ 249 public function toString():String { 250 // TODO: Extract into a Stringifier. 251 var result:String = "[object SimpleOverloadHandler"; 252 var l:Number = argumentsTypes.length; 253 if(l > 0) { 254 result += "("; 255 } 256 for(var i:Number = 0; i < l; i++) { 257 if(i != 0) { 258 result += ", "; 259 } 260 result += ReflectUtil.getTypeName(argumentsTypes[i]); 261 } 262 if(l > 0) { 263 result += ")"; 264 } 265 return result + "]"; 266 } 267 268 }