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.data.holder.Map;
    19  import org.as2lib.data.holder.map.PrimitiveTypeMap;
    20  import org.as2lib.env.except.IllegalArgumentException;
    21  import org.as2lib.test.mock.MethodBehavior;
    22  import org.as2lib.test.mock.Behavior;
    23  import org.as2lib.test.mock.MethodBehaviorFactory;
    24  import org.as2lib.test.mock.MethodCall;
    25  import org.as2lib.test.mock.support.DefaultMethodBehavior;
    26  
    27  /**
    28   * {@code DefaultBehavior} stores expected behaviors and exposes them for
    29   * verification.
    30   *
    31   * @author Simon Wacker
    32   */
    33  class org.as2lib.test.mock.support.DefaultBehavior extends BasicClass implements Behavior {
    34  	
    35  	/** The added method call behaviors. */
    36  	private var methodBehaviors:Map;
    37  	
    38  	/** The currently used factory to create method call behaviors. */
    39  	private var methodBehaviorFactory:MethodBehaviorFactory;
    40  	
    41  	/** The name of the lastly added method. */
    42  	private var lastMethodName:String;
    43  	
    44  	/**
    45  	 * Constructs a new {@code DefaultBehavior} instance.
    46  	 */
    47  	public function DefaultBehavior(Void) {
    48  		methodBehaviors = new PrimitiveTypeMap();
    49  	}
    50  	
    51  	/**
    52  	 * Returns either the factory set via {@link #setMethodBehaviorFactory} or the
    53  	 * default one which returns an instance of class {@link DefaultMethodBehavior}.
    54  	 *
    55  	 * @return the currently used factory to obtain method call behaviors
    56  	 */
    57  	public function getMethodBehaviorFactory(Void):MethodBehaviorFactory {
    58  		if (!methodBehaviorFactory) methodBehaviorFactory = getDefaultMethodBehaviorFactory();
    59  		return methodBehaviorFactory;
    60  	}
    61  	
    62  	/**
    63  	 * Returns the default method call behavior factory that returns instances of
    64  	 * class {@link DefaultMethodBehavior}.
    65  	 *
    66  	 * @return the default method call behavior factory
    67  	 */
    68  	private function getDefaultMethodBehaviorFactory(Void):MethodBehaviorFactory {
    69  		var result:MethodBehaviorFactory = getBlankMethodBehaviorFactory();
    70  		result.getMethodBehavior = function(expectedMethodCall:MethodCall):MethodBehavior {
    71  			return new DefaultMethodBehavior(expectedMethodCall);
    72  		};
    73  		return result;
    74  	}
    75  	
    76  	/**
    77  	 * Returns a blank method behavior factory. That is a factory with no implemented
    78  	 * methods.
    79  	 *
    80  	 * @return a blank method behavior factory
    81  	 */
    82  	private function getBlankMethodBehaviorFactory(Void):MethodBehaviorFactory {
    83  		var result = new Object();
    84  		result.__proto__ = MethodBehaviorFactory["prototype"];
    85  		result.__constructor__ = MethodBehaviorFactory;
    86  		return result;
    87  	}
    88  	
    89  	/**
    90  	 * Sets the factory used to obtain method call behaviors to store state.
    91  	 *
    92  	 * <p>If {@code methodBehaviorFactory} is {@code null} the
    93  	 * {@link #getMethodBehaviorFactory} method will return the default factory.
    94  	 *
    95  	 * @param methodBehaviorFactory the new factory
    96  	 */
    97  	public function setMethodBehaviorFactory(methodBehaviorFactory:MethodBehaviorFactory):Void {
    98  		this.methodBehaviorFactory = methodBehaviorFactory;
    99  	}
   100  	
   101  	/**
   102  	 * Adds the given {@code methodBehavior} behavior for the passed-in
   103  	 * {@code methodName}.
   104  	 *
   105  	 * <p>If the passed-in {@code methodName} is {@code null} or an empty string the one
   106  	 * returned by the method behaviour's expected method call will be used. If this
   107  	 * is also {@code null} or an empty string {@code "[unknown]"} will be used.
   108  	 *
   109  	 * @throws IllegalArgumentException if the passed-in {@code methodBehavior} is
   110  	 * {@code null}
   111  	 */
   112  	public function addMethodBehavior(methodName:String, methodBehavior:MethodBehavior):Void {
   113  		if (!methodBehavior) throw new IllegalArgumentException("Method behavior must not be null or undefined.", this, arguments);
   114  		if (methodName == null || methodName == "") methodName = methodBehavior.getExpectedMethodCall().getMethodName();
   115  		if (methodName == null || methodName == "") methodName = "[unknown]";
   116  		lastMethodName = methodName;
   117  		if (!methodBehaviors.containsKey(methodName)) methodBehaviors.put(methodName, new Array());
   118  		var behaviors:Array = methodBehaviors.get(methodName);
   119  		behaviors.push(methodBehavior);
   120  	}
   121  	
   122  	/**
   123  	 * Creates a new method behavior for the passed-in {@code expectedMethodCall}.
   124  	 *
   125  	 * @param expectedMethodCall the method call to create a behavior for
   126  	 * @return the created method behavior
   127  	 * @see #getMethodBehaviorFactory
   128  	 */
   129  	public function createMethodBehavior(expectedMethodCall:MethodCall):MethodBehavior {
   130  		return getMethodBehaviorFactory().getMethodBehavior(expectedMethodCall);
   131  	}
   132  	
   133  	/**
   134  	 * Returns a method behavior that matches the given {@code actualMethodCall}.
   135  	 * 
   136  	 * <p>{@code null} will be returned if:
   137  	 * <ul>
   138  	 *   <li>The passed-in {@code actualMethodCall} is {@code null}.</li>
   139  	 *   <li>There is no matching behavior registered.</li>
   140  	 * </ul>
   141  	 *
   142  	 * @return a matching method behavior
   143  	 */
   144  	public function getMethodBehavior(actualMethodCall:MethodCall):MethodBehavior {
   145  		if (!actualMethodCall) return null;
   146  		var methodName:String = actualMethodCall.getMethodName();
   147  		if (methodName == null || methodName == "") methodName = "[unknown]";
   148  		var behaviors:Array = methodBehaviors.get(methodName);
   149  		var matchingBehaviors:Array = new Array();
   150  		for (var i:Number = 0; i < behaviors.length; i++) {
   151  			var behavior:MethodBehavior = behaviors[i];
   152  			if (behavior.getExpectedMethodCall().matches(actualMethodCall)) {
   153  				matchingBehaviors.push(behavior);
   154  			}
   155  		}
   156  		if (matchingBehaviors.length < 1) return null;
   157  		if (matchingBehaviors.length < 2) return matchingBehaviors[0];
   158  		var result:MethodBehavior = matchingBehaviors[matchingBehaviors.length-1];
   159  		for (var i:Number = behaviors.length-1; i > -1; i--) {
   160  			var behavior:MethodBehavior = behaviors[i];
   161  			if (behavior.expectsAnotherMethodCall()) {
   162  				result = behavior;
   163  			}
   164  		}
   165  		return result;
   166  	}
   167  	
   168  	/**
   169  	 * Returns the lastly added method behavior.
   170  	 *
   171  	 * @return the lastly added method behavior
   172  	 */
   173  	public function getLastMethodBehavior(Void):MethodBehavior {
   174  		var behaviors:Array = methodBehaviors.get(lastMethodName);
   175  		return behaviors[behaviors.length-1];
   176  	}
   177  	
   178  	/**
   179  	 * Removes all added behaviors.
   180  	 */
   181  	public function removeAllBehaviors(Void):Void {
   182  		methodBehaviors.clear();
   183  	}
   184  	
   185  	/**
   186  	 * Verifies all added behaviors.
   187  	 */
   188  	public function verify(Void):Void {
   189  		var behaviors:Array = methodBehaviors.getValues();
   190  		for (var i:Number = 0; i < behaviors.length; i++) {
   191  			for (var k:Number = 0; k < behaviors[i].length; k++) {
   192  				MethodBehavior(behaviors[i][k]).verify();
   193  			}
   194  		}
   195  	}
   196  	
   197  }