//////////////////////////////////////////////////////////////////////////////// // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// package mx.collections { import flash.events.EventDispatcher; import flash.events.IEventDispatcher; import flash.utils.IDataInput; import flash.utils.IDataOutput; import flash.utils.IExternalizable; import flash.utils.getQualifiedClassName; import mx.core.IPropertyChangeNotifier; import mx.events.CollectionEvent; import mx.events.CollectionEventKind; import mx.events.PropertyChangeEvent; import mx.events.PropertyChangeEventKind; import mx.resources.IResourceManager; import mx.resources.ResourceManager; import mx.utils.ArrayUtil; import mx.utils.UIDUtil; //-------------------------------------- // Events //-------------------------------------- /** * Dispatched when the IList has been updated in some way. * * @eventType mx.events.CollectionEvent.COLLECTION_CHANGE * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ [Event(name="collectionChange", type="mx.events.CollectionEvent")] //-------------------------------------- // Other metadata //-------------------------------------- [RemoteClass(alias="flex.messaging.io.ArrayList")] [ResourceBundle("collections")] [DefaultProperty("source")] /** * The ArrayList class is a simple implementation of IList * that uses a backing Array as the source of the data. * * Items in the backing Array can be accessed and manipulated * using the methods and properties of the IList * interface. Operations on an ArrayList instance modify the * data source; for example, if you use the removeItemAt() * method on an ArrayList, you remove the item from the underlying * Array. * * This base class will not throw ItemPendingErrors but it * is possible that a subclass might. * *
 *  <mx:ArrayList
 *  Properties
 *  source="null"
 *  />
 *  
* * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public class ArrayList extends EventDispatcher implements IList, IExternalizable, IPropertyChangeNotifier { include "../core/Version.as"; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Construct a new ArrayList using the specified array as its source. * If no source is specified an empty array will be used. * * @param source The Array to use as a source for the ArrayList. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function ArrayList(source:Array = null) { super(); disableEvents(); this.source = source; enableEvents(); _uid = UIDUtil.createUID(); } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * @private * Used for accessing localized Error messages. */ private var resourceManager:IResourceManager = ResourceManager.getInstance(); /** * @private * Indicates if events should be dispatched. * calls to enableEvents() and disableEvents() effect the value when == 0 * events should be dispatched. */ private var _dispatchEvents:int = 0; //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // length //---------------------------------- [Bindable("collectionChange")] /** * Get the number of items in the list. An ArrayList should always * know its length so it shouldn't return -1, though a subclass may * override that behavior. * * @return int representing the length of the source. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get length():int { if (source) return source.length; else return 0; } //---------------------------------- // source //---------------------------------- /** * @private * Storage for the source Array. */ private var _source:Array; /** * The source array for this ArrayList. * Any changes done through the IList interface will be reflected in the * source array. * If no source array was supplied the ArrayList will create one internally. * Changes made directly to the underlying Array (e.g., calling * theList.source.pop() will not cause CollectionEvents * to be dispatched. * * @return An Array that represents the underlying source. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get source():Array { return _source; } public function set source(s:Array):void { var i:int; var len:int; if (_source && _source.length) { len = _source.length; for (i = 0; i < len; i++) { stopTrackUpdates(_source[i]); } } _source = s ? s : []; len = _source.length; for (i = 0; i < len; i++) { startTrackUpdates(_source[i]); } if (_dispatchEvents == 0) { var event:CollectionEvent = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE); event.kind = CollectionEventKind.RESET; dispatchEvent(event); } } //---------------------------------- // uid -- mx.core.IPropertyChangeNotifier //---------------------------------- /** * @private * Storage for the UID String. */ private var _uid:String; /** * Provides access to the unique id for this list. * * @return String representing the internal uid. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get uid():String { return _uid; } public function set uid(value:String):void { _uid = value; } //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /** * Get the item at the specified index. * * @param index the index in the list from which to retrieve the item * @param prefetch int indicating both the direction and amount of items * to fetch during the request should the item not be local. * @return the item at that index, null if there is none * @throws ItemPendingError if the data for that index needs to be * loaded from a remote location * @throws RangeError if the index < 0 or index >= length * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function getItemAt(index:int, prefetch:int = 0):Object { if (index < 0 || index >= length) { var message:String = resourceManager.getString( "collections", "outOfBounds", [ index ]); throw new RangeError(message); } return source[index]; } /** * Place the item at the specified index. * If an item was already at that index the new item will replace it and it * will be returned. * * @param item the new value for the index * @param index the index at which to place the item * @return the item that was replaced, null if none * @throws RangeError if index is less than 0 or greater than or equal to length * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function setItemAt(item:Object, index:int):Object { if (index < 0 || index >= length) { var message:String = resourceManager.getString( "collections", "outOfBounds", [ index ]); throw new RangeError(message); } var oldItem:Object = source[index]; source[index] = item; stopTrackUpdates(oldItem); startTrackUpdates(item); //dispatch the appropriate events if (_dispatchEvents == 0) { var hasCollectionListener:Boolean = hasEventListener(CollectionEvent.COLLECTION_CHANGE); var hasPropertyListener:Boolean = hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE); var updateInfo:PropertyChangeEvent; if (hasCollectionListener || hasPropertyListener) { updateInfo = new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE); updateInfo.kind = PropertyChangeEventKind.UPDATE; updateInfo.oldValue = oldItem; updateInfo.newValue = item; updateInfo.property = index; } if (hasCollectionListener) { var event:CollectionEvent = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE); event.kind = CollectionEventKind.REPLACE; event.location = index; event.items.push(updateInfo); dispatchEvent(event); } if (hasPropertyListener) { dispatchEvent(updateInfo); } } return oldItem; } /** * Add the specified item to the end of the list. * Equivalent to addItemAt(item, length); * * @param item the item to add * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function addItem(item:Object):void { addItemAt(item, length); } /** * Add the item at the specified index. * Any item that was after this index is moved out by one. * * @param item the item to place at the index * @param index the index at which to place the item * @throws RangeError if index is less than 0 or greater than the length * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function addItemAt(item:Object, index:int):void { if (index < 0 || index > length) { var message:String = resourceManager.getString( "collections", "outOfBounds", [ index ]); throw new RangeError(message); } source.splice(index, 0, item); startTrackUpdates(item); internalDispatchEvent(CollectionEventKind.ADD, item, index); } /** * @copy mx.collections.ListCollectionView#addAll() * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function addAll(addList:IList):void { addAllAt(addList, length); } /** * @copy mx.collections.ListCollectionView#addAllAt() * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function addAllAt(addList:IList, index:int):void { var length:int = addList.length; for (var i:int = 0; i < length; i++) { this.addItemAt(addList.getItemAt(i), i+index); } } /** * Return the index of the item if it is in the list such that * getItemAt(index) == item. * Note that in this implementation the search is linear and is therefore * O(n). * * @param item the item to find * @return the index of the item, -1 if the item is not in the list. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function getItemIndex(item:Object):int { return ArrayUtil.getItemIndex(item, source); } /** * Removes the specified item from this list, should it exist. * * @param item Object reference to the item that should be removed. * @return Boolean indicating if the item was removed. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function removeItem(item:Object):Boolean { var index:int = getItemIndex(item); var result:Boolean = index >= 0; if (result) removeItemAt(index); return result; } /** * Remove the item at the specified index and return it. * Any items that were after this index are now one index earlier. * * @param index The index from which to remove the item. * @return The item that was removed. * @throws RangeError if index < 0 or index >= length. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function removeItemAt(index:int):Object { if (index < 0 || index >= length) { var message:String = resourceManager.getString( "collections", "outOfBounds", [ index ]); throw new RangeError(message); } var removed:Object = source.splice(index, 1)[0]; stopTrackUpdates(removed); internalDispatchEvent(CollectionEventKind.REMOVE, removed, index); return removed; } /** * Remove all items from the list. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function removeAll():void { if (length > 0) { var len:int = length; for (var i:int = 0; i < len; i++) { stopTrackUpdates(source[i]); } source.splice(0, length); internalDispatchEvent(CollectionEventKind.RESET); } } /** * Notify the view that an item has been updated. * This is useful if the contents of the view do not implement * IEventDispatcher. * If a property is specified the view may be able to optimize its * notification mechanism. * Otherwise it may choose to simply refresh the whole view. * * @param item The item within the view that was updated. * * @param property A String, QName, or int * specifying the property that was updated. * * @param oldValue The old value of that property. * (If property was null, this can be the old value of the item.) * * @param newValue The new value of that property. * (If property was null, there's no need to specify this * as the item is assumed to be the new value.) * * @see mx.events.CollectionEvent * @see mx.core.IPropertyChangeNotifier * @see mx.events.PropertyChangeEvent * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function itemUpdated(item:Object, property:Object = null, oldValue:Object = null, newValue:Object = null):void { var event:PropertyChangeEvent = new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE); event.kind = PropertyChangeEventKind.UPDATE; event.source = item; event.property = property; event.oldValue = oldValue; event.newValue = newValue; itemUpdateHandler(event); } /** * Return an Array that is populated in the same order as the IList * implementation. * * @return An Array populated in the same order as the IList * implementation. * * @throws ItemPendingError if the data is not yet completely loaded * from a remote location * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function toArray():Array { return source.concat(); } /** * Ensures that only the source property is seralized. * @private */ public function readExternal(input:IDataInput):void { source = input.readObject(); } /** * Ensures that only the source property is serialized. * @private */ public function writeExternal(output:IDataOutput):void { output.writeObject(_source); } /** * Pretty prints the contents of this ArrayList to a string and returns it. * * @return A String containing the contents of the ArrayList. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ override public function toString():String { if (source) return source.toString(); else return getQualifiedClassName(this); } //-------------------------------------------------------------------------- // // Internal Methods // //-------------------------------------------------------------------------- /** * Enables event dispatch for this list. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ private function enableEvents():void { _dispatchEvents++; if (_dispatchEvents > 0) _dispatchEvents = 0; } /** * Disables event dispatch for this list. * To re-enable events call enableEvents(), enableEvents() must be called * a matching number of times as disableEvents(). * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ private function disableEvents():void { _dispatchEvents--; } /** * Dispatches a collection event with the specified information. * * @param kind String indicates what the kind property of the event should be * @param item Object reference to the item that was added or removed * @param location int indicating where in the source the item was added. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ private function internalDispatchEvent(kind:String, item:Object = null, location:int = -1):void { if (_dispatchEvents == 0) { if (hasEventListener(CollectionEvent.COLLECTION_CHANGE)) { var event:CollectionEvent = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE); event.kind = kind; event.items.push(item); event.location = location; dispatchEvent(event); } // now dispatch a complementary PropertyChangeEvent if (hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE) && (kind == CollectionEventKind.ADD || kind == CollectionEventKind.REMOVE)) { var objEvent:PropertyChangeEvent = new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE); objEvent.property = location; if (kind == CollectionEventKind.ADD) objEvent.newValue = item; else objEvent.oldValue = item; dispatchEvent(objEvent); } } } /** * Called when any of the contained items in the list dispatch an * ObjectChange event. * Wraps it in a CollectionEventKind.UPDATE object. * * @param event The event object for the ObjectChange event. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function itemUpdateHandler(event:PropertyChangeEvent):void { internalDispatchEvent(CollectionEventKind.UPDATE, event); // need to dispatch object event now if (_dispatchEvents == 0 && hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE)) { var objEvent:PropertyChangeEvent = PropertyChangeEvent(event.clone()); var index:uint = getItemIndex(event.target); objEvent.property = index.toString() + "." + event.property; dispatchEvent(objEvent); } } /** * If the item is an IEventDispatcher, watch it for updates. * This method is called by the addItemAt() method, * and when the source is initially assigned. * * @param item The item passed to the addItemAt() method. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function startTrackUpdates(item:Object):void { if (item && (item is IEventDispatcher)) { IEventDispatcher(item).addEventListener( PropertyChangeEvent.PROPERTY_CHANGE, itemUpdateHandler, false, 0, true); } } /** * If the item is an IEventDispatcher, stop watching it for updates. * This method is called by the removeItemAt() and * removeAll() methods, and before a new * source is assigned. * * @param item The item passed to the removeItemAt() method. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function stopTrackUpdates(item:Object):void { if (item && item is IEventDispatcher) { IEventDispatcher(item).removeEventListener( PropertyChangeEvent.PROPERTY_CHANGE, itemUpdateHandler); } } } }