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  }