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.except.IllegalArgumentException;
    19  import org.as2lib.env.event.EventListenerSource;
    20  import org.as2lib.util.ArrayUtil;
    21  
    22  /**
    23   * {@code TypeSafeEventListenerSource} manages listeners in a type-safe manner.
    24   * 
    25   * @author Simon Wacker
    26   */
    27  class org.as2lib.env.event.TypeSafeEventListenerSource extends BasicClass implements EventListenerSource {
    28  	
    29  	/** The expected listener type. */
    30  	private var t:Function;
    31  	
    32  	/** All added listeners. */
    33  	private var l:Array;
    34  	
    35  	/** Determines whether to check for the correct listener type. */
    36  	private var c:Boolean;
    37  	
    38  	/**
    39  	 * Constructs a new {@code TypeSafeEventListenerSource} instance.
    40  	 *
    41  	 * <p>{@code checkListenerType} is by default set to {@code true}.
    42  	 *
    43  	 * @param listenerType the expected type of listeners
    44  	 * @param checkListenerType determines whether to check that passed-in listeners
    45  	 * are of the expected type
    46  	 * @throws IllegalArgumentException if the passed-in {@code listenerType} is
    47  	 * {@code null} or {@code undefined}
    48  	 */
    49  	public function TypeSafeEventListenerSource(listenerType:Function, checkListenerType:Boolean) {
    50  		if (!listenerType) throw new IllegalArgumentException("Argument 'listenerType' [" + listenerType + "] must not be 'null' nor 'undefined'.", this, arguments);
    51  		this.t = listenerType;
    52  		this.c = checkListenerType == null ? true : checkListenerType;
    53  		this.l = new Array();
    54  	}
    55  	
    56  	/**
    57  	 * Returns the expected listener type.
    58  	 *
    59  	 * @return the expected listener type
    60  	 */
    61  	public function getListenerType(Void):Function {
    62  		return this.t;
    63  	}
    64  	
    65  	/**
    66  	 * Returns whether a listener's type is checked up on the expected listener type.
    67  	 *
    68  	 * @return {@code true} a listener's type is checked else {@code false}
    69  	 */
    70  	public function isListenerTypeChecked(Void):Boolean {
    71  		return this.c;
    72  	}
    73  	
    74  	/**
    75  	 * Adds the passed-in {@code listener}.
    76  	 *
    77  	 * <p>The listener will only be added if it is neither {@code null} nor
    78  	 * {@code undefined} and if it is of the expected listener type specified on
    79  	 * construction and if it has not already been added to this listener source.
    80  	 *
    81  	 * <p>Note that the listener type will not be checked if it was turned of on
    82  	 * construction.
    83  	 * 
    84  	 * @param listener the listener to add
    85  	 * @throws IllegalArgumentException if the passed-in {@code listener} is not of the
    86  	 * expected type specified on construction
    87  	 */
    88  	public function addListener(listener):Void {
    89  		if (listener) {
    90  			if (this.c) {
    91  				if (!(listener instanceof this.t)) {
    92  					throw new IllegalArgumentException("Argument 'listener' [" + listener + "] must be an instance of the expected listener type [" + this.t + "].", this, arguments);
    93  				}
    94  			}
    95  			if (!hasListener(listener)) {
    96  				this.l.push(listener);
    97  			}
    98  		}
    99  	}
   100  	
   101  	/**
   102  	 * Adds all listeners contained in the passed-in {@code listeners} array.
   103  	 *
   104  	 * <p>If the passed-in {@code listeners} array is {@code null} or {@code undefined}
   105  	 * it will be ignored.
   106  	 *
   107  	 * <p>The individual listeners must be instances of the type specified on
   108  	 * construction. If an individual listener is {@code null} or {@code undefined} it
   109  	 * will be ignored.
   110  	 *
   111  	 * <p>All listeners that are of the correct type will be added.
   112  	 * 
   113  	 * <p>Note that the listener type will not be checked if it was turned of on
   114  	 * construction.
   115  	 *
   116  	 * <p>Note also that the order of the listeners contained in the passed-in
   117  	 * {@code listeners} array is preserved.
   118  	 *
   119  	 * @param listeners the listeners to add
   120  	 * @throws IllegalArgumentException if at least one listener in the passed-in
   121  	 * {@code listeners} array is not of the expected type specified on construction
   122  	 * @see #addListener
   123  	 */
   124  	public function addAllListeners(listeners:Array):Void {
   125  		if (listeners) {
   126  			if (this.c) {
   127  				var exceptions:Array;
   128  				var h:Number = listeners.length;
   129  				// the original order of the passed-in 'listeners' is preserved
   130  				for (var i:Number = 0; i < h; i++) {
   131  					try {
   132  						addListener(listeners[i]);
   133  					} catch (e:org.as2lib.env.except.IllegalArgumentException) {
   134  						// this case probably hardly ever occurs; the array is thus only instantiated if
   135  						// really necessary
   136  						if (!exceptions) exceptions = new Array();
   137  						exceptions.push(e);
   138  					}
   139  				}
   140  				if (exceptions) {
   141  					// source this out in own exception; maybe IllegalListenerException?
   142  					var message:String = IllegalArgumentException(exceptions[0]).getMessage();
   143  					for (var k:Number = 1; k < exceptions.length; k++) {
   144  						message += IllegalArgumentException(exceptions[k]).getMessage();
   145  					}
   146  					throw new IllegalArgumentException(message, this, arguments);
   147  				}
   148  			} else {
   149  				var h:Number = listeners.length;
   150  				// the original order of the passed-in 'listeners' is preserved
   151  				for (var i:Number = 0; i < h; i++) {
   152  					addListener(listeners[i]);
   153  				}
   154  			}
   155  		}
   156  	}
   157  	
   158  	/**
   159  	 * Removes the passed-in {@code listener}.
   160  	 *
   161  	 * <p>The removal will be ignored if the passed-in {@code listener} is {@code null}
   162  	 * or {@code undefined}.
   163  	 * 
   164  	 * @param listener the listener to remove
   165  	 */
   166  	public function removeListener(listener):Void {
   167  		if (listener) {
   168  			var i:Number = this.l.length;
   169  			while (--i > -1) {
   170  				if (this.l[i] == listener) {
   171  					this.l.splice(i, 1);
   172  					return;
   173  				}
   174  			}
   175  		}
   176  	}
   177  	
   178  	/**
   179  	 * Removes all added listeners.
   180  	 */
   181  	public function removeAllListeners(Void):Void {
   182  		this.l = new Array();
   183  	}
   184  	
   185  	/**
   186  	 * Returns all added listeners that are of the type specified on construction.
   187  	 * 
   188  	 * @return all added listeners
   189  	 */
   190  	public function getAllListeners(Void):Array {
   191  		return this.l.concat();
   192  	}
   193  	
   194  	/**
   195  	 * Returns {@code true} if passed-in {@code listener} has been added.
   196  	 * 
   197  	 * @param listener the listener to check whether it has been added
   198  	 * @return {@code true} if the {@code listener} has been added
   199  	 */
   200  	public function hasListener(listener):Boolean {
   201  		return ArrayUtil.contains(this.l, listener);
   202  	}
   203  	
   204  }