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