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.util.ArrayUtil;
    18  import org.as2lib.env.reflect.ReflectUtil;
    19  import org.as2lib.data.holder.Map;
    20  import org.as2lib.data.holder.map.HashMap;
    21  import org.as2lib.env.except.IllegalArgumentException;
    22  import org.as2lib.env.event.distributor.CompositeEventDistributorControl;
    23  import org.as2lib.env.event.distributor.EventDistributorControl;
    24  import org.as2lib.env.event.distributor.EventDistributorControlFactory;
    25  
    26  /**
    27   * {@code AbstractCompositeEventDistributorControl} is the default implementation
    28   * of the {@code CompositeEventDistributorControl} interface.
    29   * 
    30   * <p>To use its functionalities, simply extend it and set a factory for the default
    31   * event distributors.
    32   * 
    33   * @author Martin Heidegger
    34   */
    35  class org.as2lib.env.event.distributor.AbstractCompositeEventDistributorControl implements CompositeEventDistributorControl {
    36  	
    37  	/* Factory to create default event distributors for the different types */
    38  	private var eventDistributorControlFactory:EventDistributorControlFactory;
    39  	
    40  	/* All added listeners. */
    41  	private var listeners:Array;
    42  	
    43  	/* All available {@code EventDistributorControl} instances mapped to classes and interfaces. */
    44  	private var distributorMap:Map;
    45  	
    46  	/**
    47  	 * Creates a new {@code AbstractCompositeEventDistributorControl} instance.
    48  	 * 
    49  	 * @param eventDistributorControlFactory the factory to create event distributor
    50  	 * controls for the different listener types
    51  	 */
    52  	public function AbstractCompositeEventDistributorControl(eventDistributorControlFactory:EventDistributorControlFactory) {
    53  		this.eventDistributorControlFactory = eventDistributorControlFactory;
    54  		distributorMap = new HashMap();
    55  		listeners = new Array();
    56  	}
    57  	
    58  	/**
    59  	 * Adds a the given listener to this event distributor control.
    60  	 * 
    61  	 * <p>It validates if the passed-in {@code listener} is of any of the accepted
    62  	 * listener types.
    63  	 * 
    64  	 * <p>The listener will be added to all matching distributors.
    65  	 * 
    66  	 * @param l the Listener to add to the control
    67  	 * @throws IllegalArgumentException if the given listener does not match any of the
    68  	 * accepted types.
    69  	 */
    70  	public function addListener(l):Void {
    71  		if (!hasListener(l)) {
    72  			var acceptedTypes:Array = distributorMap.getKeys();
    73  			var existingDistributors:Array = distributorMap.getValues();
    74  			var added:Boolean = false;
    75  			for (var i:Number = 0; i < acceptedTypes.length; i++) {
    76  				if (l instanceof acceptedTypes[i]) {
    77  					existingDistributors[i].addListener(l);
    78  					added = true;
    79  				}
    80  			}
    81  			if (added) {
    82  				listeners.push(l);
    83  			} else {
    84  				var message:String = "The passed-in listener [" + ReflectUtil.getTypeNameForInstance(l) + "] does not match any of the accepted listener types.";
    85  				var size:Number = distributorMap.size();
    86  				if (size > 0) {
    87  					message += "(" + size + "):";
    88  					for (var i:Number = 0; i < size; i++) {
    89  						message += "\n - " + ReflectUtil.getTypeNameForType(acceptedTypes[i]);
    90  					}
    91  				} else {
    92  					message += "(No types accepted).";
    93  				}
    94  				throw new IllegalArgumentException(message, this, arguments);
    95  			}
    96  		}
    97  	}
    98  	
    99  	/**
   100  	 * Removes the given listener from this distributor control and thus from listening
   101  	 * to events.
   102  	 * 
   103  	 * @param l the listener to remove
   104  	 */
   105  	public function removeListener(l):Void {
   106  		if (hasListener(l)) {
   107  			var acceptedTypes:Array = distributorMap.getKeys();
   108  			var existingDistributors:Array = distributorMap.getValues();
   109  			for (var i:Number = 0; i < acceptedTypes.length; i++) {
   110  				if (l instanceof acceptedTypes[i]) {
   111  					existingDistributors[i].removeListener(l);
   112  				}
   113  			}
   114  			ArrayUtil.removeElement(listeners, l);
   115  		}
   116  	}
   117  	
   118  	/**
   119  	 * Adds a list of listeners to this event distributor control to listen to events.
   120  	 * 
   121  	 * @param listeners the list of listeners to add
   122  	 * @throws IllegalArgumentException if any listener is not accepted (the listeners
   123  	 * before the certain listener will be added) 
   124  	 */
   125  	public function addAllListeners(listeners:Array):Void {
   126  		for (var i:Number = 0; i < listeners.length; i++) {
   127  			addListener(listeners[i]);
   128  		}
   129  	}
   130  	
   131  	/**
   132  	 * Removes all added listeners.
   133  	 */
   134  	public function removeAllListeners(Void):Void {
   135  		var list:Array = getAllListeners();
   136  		for (var i:Number = 0; i < list.length; i++) {
   137  			removeListener(list[i]);
   138  		}
   139  	}
   140  	
   141  	/**
   142  	 * Returns a list that contains all listeners.
   143  	 * 
   144  	 * @return the list that contains all listeners
   145  	 */
   146  	public function getAllListeners(Void):Array {
   147  		return listeners.concat();
   148  	}
   149  	
   150  	/**
   151  	 * Checks if the given listener has already been added.
   152  	 * 
   153  	 * @param l the listener to check whether it has already been added
   154  	 * @return {@code true} if the listener has been added
   155  	 */
   156  	public function hasListener(l):Boolean {
   157  		return ArrayUtil.contains(listeners, l);
   158  	}
   159  	
   160  	/**
   161  	 * Adds acception for the given {@code listenerType}.
   162  	 * 
   163  	 * <p>{@code addListener} does not allow listeners that do not match (instanceof)
   164  	 * at least one of the accepted listener types.
   165  	 * 
   166  	 * @param type the type of listeners that are accepted
   167  	 * @see #registerEventDistributorControl
   168  	 * @see #registerDefaultEventDistributorControl
   169  	 */
   170  	public function acceptListenerType(listenerType:Function):Void {
   171  		if (!distributorMap.containsKey(listenerType)) {
   172  			var distributor:EventDistributorControl = eventDistributorControlFactory.createEventDistributorControl(listenerType);
   173  			for (var i:Number = 0; i < listeners.length; i++) {
   174  				if (listeners[i] instanceof listenerType) {
   175  					distributor.addListener(listeners[i]);
   176  				}
   177  			}
   178  			distributorMap.put(listenerType, distributor);
   179  		}
   180  	}
   181  	
   182  	/**
   183  	 * Returns the distributor that can be used to broadcast an event to all added
   184  	 * listeners that match the distributor's type.
   185  	 * 
   186  	 * <p>If the given {@code type} has not been accepted as listener type yet, it will
   187  	 * be accepted after you invoked this method.
   188  	 * 
   189  	 * <p>Note that the returned distributor will not be updated if you add a new
   190  	 * distributor control for the given {@code type} after you obtained a distributor.
   191  	 * You must get a new distributor if you want an updated one.
   192  	 * 
   193  	 * @return the distributor to distribute events
   194  	 */
   195  	public function getDistributor(type:Function) {
   196  		if (!distributorMap.containsKey(type)) {
   197  			acceptListenerType(type);
   198  		}
   199  		var distributor:EventDistributorControl = distributorMap.get(type);
   200  		return distributor.getDistributor();
   201  	}
   202  	
   203  	/**
   204  	 * Registers the given {@code eventDistributorControl} with its listener and
   205  	 * distributor type returned by its {@link EventDistributorControl#getType} method.
   206  	 * 
   207  	 * <p>The type is then automatically an accepted listener type.
   208  	 * 
   209  	 * <p>If there is already a distributor control registered for the given type, it
   210  	 * will be overwritten.
   211  	 * 
   212  	 * <p>If you hold references to distributors of this type, returned by the
   213  	 * {@link #getDistributor} method, you will have to update them, else events will
   214  	 * not be distributed to newly registered listeners of that type.
   215  	 * 
   216  	 * <p>You use this method if you have a specific event that should be executed with
   217  	 * a special kind of distributor, for example with a consumable one.
   218  	 * 
   219  	 * @param eventDistributorControl the event distributor control to use for event
   220  	 * distribution for the given type
   221  	 * @throws IllegalArgumentException if the given argument {@code eventDistributorControl}
   222  	 * is {@code null} or {@code undefined}
   223  	 * @see #registerDefaultEventDistributorControl
   224  	 * @see #acceptListenerType
   225  	 */
   226  	public function registerEventDistributorControl(eventDistributorControl:EventDistributorControl):Void  {
   227  		if (!eventDistributorControl) throw new IllegalArgumentException("Argument 'eventDistributorControl' [" + eventDistributorControl + "] must neither be 'null' nor 'undefined'.", this, arguments);
   228  		var type:Function = eventDistributorControl.getType();
   229  		eventDistributorControl.removeAllListeners();
   230  		for (var i:Number = 0; i < listeners.length; i++) {
   231  			if (listeners[i] instanceof type) {
   232  				eventDistributorControl.addListener(listeners[i]);
   233  			}
   234  		}	
   235  		distributorMap.put(type, eventDistributorControl);
   236  	}
   237  	
   238  	/**
   239  	 * Registers a default event distributor control with the given listener and
   240  	 * distributor {@code type}.
   241  	 * 
   242  	 * <p>The {@code type} is then automatically an accepted listener type.
   243  	 * 
   244  	 * <p>If there is already a distributor control registered for the given type, it
   245  	 * will be overwritten.
   246  	 * 
   247  	 * @param type the type to register a default distributor control with
   248  	 * @throws IllegalArgumentException if argument {@code type} is {@code null} or
   249  	 * {@code undefined}
   250  	 * @see #acceptListenerType
   251  	 */
   252  	public function registerDefaultEventDistributorControl(type:Function):Void {
   253  		if (!type) throw new IllegalArgumentException("Argument 'type' [" + type + "] must neither be 'null' nor 'undefined'.", this, arguments);
   254  		registerEventDistributorControl(eventDistributorControlFactory.createEventDistributorControl(type));
   255  	}
   256  	
   257  }