blob: 03a8f51a5505a0036dd85e3ee26b2a6ee38d987e [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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.utils
{
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.utils.getQualifiedClassName;
import flash.utils.IDataInput;
import flash.utils.IDataOutput;
import flash.utils.IExternalizable;
import flash.utils.Proxy;
import flash.utils.flash_proxy;
import mx.core.IPropertyChangeNotifier;
import mx.events.PropertyChangeEvent;
import mx.events.PropertyChangeEventKind;
use namespace flash_proxy;
use namespace object_proxy;
[Bindable("propertyChange")]
[RemoteClass(alias="flex.messaging.io.ObjectProxy")]
/**
* This class provides the ability to track changes to an item
* managed by this proxy.
* Any number of objects can "listen" for changes on this
* object, by using the <code>addEventListener()</code> method.
*
* @example
* <pre>
* import mx.events.PropertyChangeEvent;
* import mx.utils.ObjectUtil;
* import mx.utils.ObjectProxy;
* import mx.utils.StringUtil;
*
* var a:Object = { name: "Tyler", age: 5, ssnum: "555-55-5555" };
* var p:ObjectProxy = new ObjectProxy(a);
* p.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, updateHandler);
* p.name = "Jacey";
* p.age = 2;
* delete p.ssnum;
*
* // handler function
* function updateHandler(event:ChangeEvent):void
* {
* trace(StringUtil.substitute("updateHandler('{0}', {1}, {2}, {3}, '{4}')",
* event.kind,
* event.property,
* event.oldValue,
* event.newValue,
* event.target.object_proxy::UUID));
* }
*
* // The trace output appears as:
* // updateHandler('opUpdate', name, Tyler, Jacey, '698AF8CB-B3D9-21A3-1AFFDGHT89075CD2')
* // updateHandler('opUpdate', age, 5, 2, '698AF8CB-B3D9-21A3-1AFFDGHT89075CD2')
* // updateHandler('opDelete', ssnum, 555-55-5555, null, '698AF8CB-B3D9-21A3-1AFFDGHT89075CD2')
* </pre>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public dynamic class ObjectProxy extends Proxy
implements IExternalizable,
IPropertyChangeNotifier
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Initializes this proxy with the specified object, id and proxy depth.
*
* @param item Object to proxy.
* If no item is specified, an anonymous object will be constructed
* and assigned.
*
* @param uid String containing the unique id
* for this object instance.
* Required for IPropertyChangeNotifier compliance as every object must
* provide a unique way of identifying it.
* If no value is specified, a random id will be assigned.
*
* @param proxyDepth An integer indicating how many levels in a complex
* object graph should have a proxy created during property access.
* The default is -1, meaning "proxy to infinite depth".
*
* @example
*
* <pre>
* import mx.events.PropertyChangeEvent;
* import mx.utils.ObjectUtil;
* import mx.utils.ObjectProxy;
* import mx.utils.StringUtil;
*
* var a:Object = { name: "Tyler", age: 5, ssnum: "555-55-5555" };
* var p:ObjectProxy = new ObjectProxy(a);
* p.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, updateHandler);
* p.name = "Jacey";
* p.age = 2;
* delete p.ssnum;
*
* // handler function
* function updateHandler(event:PropertyChangeEvent):void
* {
* trace(StringUtil.substitute("updateHandler('{0}', {1}, {2}, {3}, '{4}')",
* event.kind,
* event.property,
* event.oldValue,
* event.newValue,
* event.target.uid));
* }
*
* // trace output
* updateHandler('opUpdate', name, Jacey, '698AF8CB-B3D9-21A3-1AFFDGHT89075CD2')
* updateHandler('opUpdate', age, 2, '698AF8CB-B3D9-21A3-1AFFDGHT89075CD2')
* updateHandler('opDelete', ssnum, null, '698AF8CB-B3D9-21A3-1AFFDGHT89075CD2')
* </pre>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function ObjectProxy(item:Object = null, uid:String = null,
proxyDepth:int = -1)
{
super();
if (!item)
item = {};
_item = item;
_proxyLevel = proxyDepth;
notifiers = {};
dispatcher = new EventDispatcher(this);
// If we got an id, use it. Otherwise the UID is lazily
// created in the getter for UID.
if (uid)
_id = uid;
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* A reference to the EventDispatcher for this proxy.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var dispatcher:EventDispatcher;
/**
* A hashmap of property change notifiers that this proxy is
* listening for changes from; the key of the map is the property name.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var notifiers:Object;
/**
* Indicates what kind of proxy to create
* when proxying complex properties.
* Subclasses should assign this value appropriately.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var proxyClass:Class = ObjectProxy;
/**
* Contains a list of all of the property names for the proxied object.
* Descendants need to fill this list by overriding the
* <code>setupPropertyList()</code> method.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var propertyList:Array;
/**
* Indicates how deep proxying should be performed.
* If -1 (default), always proxy;
* if this value is zero, no proxying will be performed.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
private var _proxyLevel:int;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// object
//----------------------------------
/**
* Storage for the object property.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
private var _item:Object;
/**
* The object being proxied.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
object_proxy function get object():Object
{
return _item;
}
//----------------------------------
// type
//----------------------------------
/**
* @private
* Storage for the qualified type name.
*/
private var _type:QName;
/**
* The qualified type name associated with this object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
object_proxy function get type():QName
{
return _type;
}
/**
* @private
*/
object_proxy function set type(value:QName):void
{
_type = value;
}
//----------------------------------
// uid
//----------------------------------
/**
* @private
* Storage for the uid property.
*/
private var _id:String;
/**
* The unique identifier for this object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get uid():String
{
if (_id === null)
_id = UIDUtil.createUID();
return _id;
}
/**
* @private
*/
public function set uid(value:String):void
{
_id = value;
}
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
/**
* Returns the specified property value of the proxied object.
*
* @param name Typically a string containing the name of the property,
* or possibly a QName where the property name is found by
* inspecting the <code>localName</code> property.
*
* @return The value of the property.
* In some instances this value may be an instance of
* <code>ObjectProxy</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override flash_proxy function getProperty(name:*):*
{
// if we have a data proxy for this then
var result:*;
if (notifiers[name.toString()])
return notifiers[name];
result = _item[name];
if (result)
{
if (_proxyLevel == 0 || ObjectUtil.isSimple(result))
{
return result;
}
else
{
result = object_proxy::getComplexProperty(name, result);
} // if we are proxying
}
return result;
}
/**
* Returns the value of the proxied object's method with the specified name.
*
* @param name The name of the method being invoked.
*
* @param rest An array specifying the arguments to the
* called method.
*
* @return The return value of the called method.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override flash_proxy function callProperty(name:*, ... rest):*
{
return _item[name].apply(_item, rest)
}
/**
* Deletes the specified property on the proxied object and
* sends notification of the delete to the handler.
*
* @param name Typically a string containing the name of the property,
* or possibly a QName where the property name is found by
* inspecting the <code>localName</code> property.
*
* @return A Boolean indicating if the property was deleted.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override flash_proxy function deleteProperty(name:*):Boolean
{
var notifier:IPropertyChangeNotifier = IPropertyChangeNotifier(notifiers[name]);
if (notifier)
{
notifier.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,
propertyChangeHandler);
delete notifiers[name];
}
var oldVal:* = _item[name];
var deleted:Boolean = delete _item[name];
if (dispatcher.hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE))
{
var event:PropertyChangeEvent = new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE);
event.kind = PropertyChangeEventKind.DELETE;
event.property = name;
event.oldValue = oldVal;
event.source = this;
dispatcher.dispatchEvent(event);
}
return deleted;
}
/**
* @private
*/
override flash_proxy function hasProperty(name:*):Boolean
{
return(name in _item);
}
/**
* @private
*/
override flash_proxy function nextName(index:int):String
{
return propertyList[index -1];
}
/**
* @private
*/
override flash_proxy function nextNameIndex(index:int):int
{
if (index == 0)
{
setupPropertyList();
}
if (index < propertyList.length)
{
return index + 1;
}
else
{
return 0;
}
}
/**
* @private
*/
override flash_proxy function nextValue(index:int):*
{
return _item[propertyList[index -1]];
}
/**
* Updates the specified property on the proxied object
* and sends notification of the update to the handler.
*
* @param name Object containing the name of the property that
* should be updated on the proxied object.
*
* @param value Value that should be set on the proxied object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override flash_proxy function setProperty(name:*, value:*):void
{
var oldVal:* = _item[name];
if (oldVal !== value)
{
// Update item.
_item[name] = value;
// Stop listening for events on old item if we currently are.
var notifier:IPropertyChangeNotifier =
IPropertyChangeNotifier(notifiers[name]);
if (notifier)
{
notifier.removeEventListener(
PropertyChangeEvent.PROPERTY_CHANGE,
propertyChangeHandler);
delete notifiers[name];
}
// Notify anyone interested.
if (dispatcher.hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE))
{
if (name is QName)
name = QName(name).localName;
var event:PropertyChangeEvent =
PropertyChangeEvent.createUpdateEvent(
this, name.toString(), oldVal, value);
dispatcher.dispatchEvent(event);
}
}
}
//--------------------------------------------------------------------------
//
// object_proxy methods
//
//--------------------------------------------------------------------------
/**
* Provides a place for subclasses to override how a complex property that
* needs to be either proxied or daisy chained for event bubbling is managed.
*
* @param name Typically a string containing the name of the property,
* or possibly a QName where the property name is found by
* inspecting the <code>localName</code> property.
*
* @param value The property value.
*
* @return The property value or an instance of <code>ObjectProxy</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
object_proxy function getComplexProperty(name:*, value:*):*
{
if (value is IPropertyChangeNotifier)
{
value.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,
propertyChangeHandler);
notifiers[name] = value;
return value;
}
if (getQualifiedClassName(value) == "Object")
{
value = new proxyClass(_item[name], null,
_proxyLevel > 0 ? _proxyLevel - 1 : _proxyLevel);
value.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,
propertyChangeHandler);
notifiers[name] = value;
return value;
}
return value;
}
//--------------------------------------------------------------------------
//
// IExternalizable Methods
//
//--------------------------------------------------------------------------
/**
* Since Flex only uses ObjectProxy to wrap anonymous objects,
* the server flex.messaging.io.ObjectProxy instance serializes itself
* as a Map that will be returned as a plain ActionScript object.
* You can then set the object_proxy object property to this value.
*
* @param input The source object from which the ObjectProxy is
* deserialized.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function readExternal(input:IDataInput):void
{
var value:Object = input.readObject();
_item = value;
}
/**
* Since Flex only serializes the inner ActionScript object that it wraps,
* the server flex.messaging.io.ObjectProxy populates itself
* with this anonymous object's contents and appears to the user
* as a Map.
*
* @param output The source object from which the ObjectProxy is
* deserialized.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function writeExternal(output:IDataOutput):void
{
output.writeObject(_item);
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* Registers an event listener object
* so that the listener receives notification of an event.
* For more information, including descriptions of the parameters see
* <code>addEventListener()</code> in the
* flash.events.EventDispatcher class.
*
* @param type The type of event.
*
* @param listener The listener function that processes the event. This function must accept
* an Event object as its only parameter and must return nothing.
*
* @param useCapture Determines whether the listener works in the capture phase or the
* target and bubbling phases. If <code>useCapture</code> is set to <code>true</code>,
* the listener processes the event only during the capture phase and not in the
* target or bubbling phase. If <code>useCapture</code> is <code>false</code>, the
* listener processes the event only during the target or bubbling phase. To listen for
* the event in all three phases, call <code>addEventListener</code> twice, once with
* <code>useCapture</code> set to <code>true</code>, then again with
* <code>useCapture</code> set to <code>false</code>.
*
* @param priority The priority level of the event listener.
*
* @param useWeakReference Determines whether the reference to the listener is strong or
* weak. A strong reference (the default) prevents your listener from being garbage-collected.
* A weak reference does not.
*
* @see flash.events.EventDispatcher#addEventListener()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function addEventListener(type:String, listener:Function,
useCapture:Boolean = false,
priority:int = 0,
useWeakReference:Boolean = false):void
{
dispatcher.addEventListener(type, listener, useCapture,
priority, useWeakReference);
}
/**
* Removes an event listener.
* If there is no matching listener registered with the EventDispatcher object,
* a call to this method has no effect.
* For more information, see
* the flash.events.EventDispatcher class.
*
* @param type The type of event.
*
* @param listener The listener object to remove.
*
* @param useCapture Specifies whether the listener was registered for the capture
* phase or the target and bubbling phases. If the listener was registered for both
* the capture phase and the target and bubbling phases, two calls to
* <code>removeEventListener()</code> are required to remove both, one call with
* <code>useCapture</code>
* set to <code>true</code>, and another call with <code>useCapture</code>
* set to <code>false</code>.
*
* @see flash.events.EventDispatcher#removeEventListener()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function removeEventListener(type:String, listener:Function,
useCapture:Boolean = false):void
{
dispatcher.removeEventListener(type, listener, useCapture);
}
/**
* Dispatches an event into the event flow.
* For more information, see
* the flash.events.EventDispatcher class.
*
* @param event The Event object that is dispatched into the event flow. If the
* event is being redispatched, a clone of the event is created automatically.
* After an event is dispatched, its target property cannot be changed, so you
* must create a new copy of the event for redispatching to work.
*
* @return Returns <code>true</code> if the event was successfully dispatched.
* A value
* of <code>false</code> indicates failure or that <code>preventDefault()</code>
* was called on the event.
*
* @see flash.events.EventDispatcher#dispatchEvent()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function dispatchEvent(event:Event):Boolean
{
return dispatcher.dispatchEvent(event);
}
/**
* Checks whether there are any event listeners registered
* for a specific type of event.
* This allows you to determine where an object has altered handling
* of an event type in the event flow hierarchy.
* For more information, see
* the flash.events.EventDispatcher class.
*
* @param type The type of event
*
* @return Returns <code>true</code> if a listener of the specified type is
* registered; <code>false</code> otherwise.
*
* @see flash.events.EventDispatcher#hasEventListener()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function hasEventListener(type:String):Boolean
{
return dispatcher.hasEventListener(type);
}
/**
* Checks whether an event listener is registered with this object
* or any of its ancestors for the specified event type.
* This method returns <code>true</code> if an event listener is triggered
* during any phase of the event flow when an event of the specified
* type is dispatched to this object or any of its descendants.
* For more information, see the flash.events.EventDispatcher class.
*
* @param type The type of event.
*
* @return Returns <code>true</code> if a listener of the specified type will
* be triggered; <code>false</code> otherwise.
*
* @see flash.events.EventDispatcher#willTrigger()
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function willTrigger(type:String):Boolean
{
return dispatcher.willTrigger(type);
}
/**
* Called when a complex property is updated.
*
* @param event An event object that has changed.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function propertyChangeHandler(event:PropertyChangeEvent):void
{
dispatcher.dispatchEvent(event);
}
//--------------------------------------------------------------------------
//
// Protected Methods
//
//--------------------------------------------------------------------------
/**
* This method creates an array of all of the property names for the
* proxied object.
* Descendants must override this method if they wish to add more
* properties to this list.
* Be sure to call <code>super.setupPropertyList</code> before making any
* changes to the <code>propertyList</code> property.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function setupPropertyList():void
{
if (getQualifiedClassName(_item) == "Object")
{
propertyList = [];
for (var prop:String in _item)
propertyList.push(prop);
}
else
{
propertyList = ObjectUtil.getClassInfo(_item, null, {includeReadOnly:true, uris:["*"]}).properties;
}
}
}
}