1////////////////////////////////////////////////////////////////////////////////
2//
3//  ADOBE SYSTEMS INCORPORATED
4//  Copyright 2005-2007 Adobe Systems Incorporated
5//  All Rights Reserved.
6//
7//  NOTICE: Adobe permits you to use, modify, and distribute this file
8//  in accordance with the terms of the license agreement accompanying it.
9//
10////////////////////////////////////////////////////////////////////////////////
11
12package mx.utils
13{
14
15import flash.events.Event;
16import flash.events.EventDispatcher;
17import flash.utils.getQualifiedClassName;
18import flash.utils.IDataInput;
19import flash.utils.IDataOutput;
20import flash.utils.IExternalizable;
21import flash.utils.Proxy;
22import flash.utils.flash_proxy;
23import mx.core.IPropertyChangeNotifier;
24import mx.events.PropertyChangeEvent;
25import mx.events.PropertyChangeEventKind;
26
27use namespace flash_proxy;
28use namespace object_proxy;
29
30[Bindable("propertyChange")]
31[RemoteClass(alias="flex.messaging.io.ObjectProxy")]
32
33/**
34 *  This class provides the ability to track changes to an item
35 *  managed by this proxy.
36 *  Any number of objects can "listen" for changes on this
37 *  object, by using the <code>addEventListener()</code> method.
38 *
39 *  @example
40 *  <pre>
41 *  import mx.events.PropertyChangeEvent;
42 *  import mx.utils.ObjectUtil;
43 *  import mx.utils.ObjectProxy;
44 *  import mx.utils.StringUtil;
45 *
46 *  var a:Object = { name: "Tyler", age: 5, ssnum: "555-55-5555" };
47 *  var p:ObjectProxy = new ObjectProxy(a);
48 *  p.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, updateHandler);
49 *  p.name = "Jacey";
50 *  p.age = 2;
51 *  delete p.ssnum;
52 *
53 *  // handler function
54 *  function updateHandler(event:ChangeEvent):void
55 *  {
56 *      trace(StringUtil.substitute("updateHandler('{0}', {1}, {2}, {3}, '{4}')",
57 *                                     event.kind,
58 *                                     event.property,
59 *                                     event.oldValue,
60 *                                     event.newValue,
61 *                                     event.target.object_proxy::UUID));
62 *  }
63 *
64 *  // The trace output appears as:
65 *  // updateHandler('opUpdate', name, Jacey, '698AF8CB-B3D9-21A3-1AFFDGHT89075CD2')
66 *  // updateHandler('opUpdate', age, 2, '698AF8CB-B3D9-21A3-1AFFDGHT89075CD2')
67 *  // updateHandler('opDelete', ssnum, null, '698AF8CB-B3D9-21A3-1AFFDGHT89075CD2')
68 *  </pre>
69 */
70public dynamic class ObjectProxy extends Proxy
71                                 implements IExternalizable,
72                                 IPropertyChangeNotifier
73{
74    //--------------------------------------------------------------------------
75    //
76    //  Constructor
77    //
78    //--------------------------------------------------------------------------
79
80    /**
81     *  Initializes this proxy with the specified object, id and proxy depth.
82     *
83     *  @param item Object to proxy.
84     *  If no item is specified, an anonymous object will be constructed
85     *  and assigned.
86     *
87     *  @param uid String containing the unique id
88     *  for this object instance.
89     *  Required for IPropertyChangeNotifier compliance as every object must
90     *  provide a unique way of identifying it.
91     *  If no value is specified, a random id will be assigned.
92     *
93     *  @param proxyDepth An integer indicating how many levels in a complex
94     *  object graph should have a proxy created during property access.
95     *  The default is -1, meaning "proxy to infinite depth".
96     *
97     *  @example
98     *
99     *  <pre>
100     *  import mx.events.PropertyChangeEvent;
101     *  import mx.utils.ObjectUtil;
102     *  import mx.utils.ObjectProxy;
103     *  import mx.utils.StringUtil;
104     *
105     *  var a:Object = { name: "Tyler", age: 5, ssnum: "555-55-5555" };
106     *  var p:ObjectProxy = new ObjectProxy(a);
107     *  p.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, updateHandler);
108     *  p.name = "Jacey";
109     *  p.age = 2;
110     *  delete p.ssnum;
111     *
112     *  // handler function
113     *  function updateHandler(event:PropertyChangeEvent):void
114     *  {
115     *      trace(StringUtil.substitute("updateHandler('{0}', {1}, {2}, {3}, '{4}')",
116     *                                     event.kind,
117     *                                     event.property,
118     *                                     event.oldValue,
119     *                                     event.newValue,
120     *                                     event.target.uid));
121     *  }
122     *
123     *  // trace output
124     *  updateHandler('opUpdate', name, Jacey, '698AF8CB-B3D9-21A3-1AFFDGHT89075CD2')
125     *  updateHandler('opUpdate', age, 2, '698AF8CB-B3D9-21A3-1AFFDGHT89075CD2')
126     *  updateHandler('opDelete', ssnum, null, '698AF8CB-B3D9-21A3-1AFFDGHT89075CD2')
127     *  </pre>
128     */
129    public function ObjectProxy(item:Object = null, uid:String = null,
130                                proxyDepth:int = -1)
131    {
132        super();
133
134        if (!item)
135            item = {};
136        _item = item;
137
138        _proxyLevel = proxyDepth;
139
140        notifiers = {};
141
142        dispatcher = new EventDispatcher(this);
143
144        // If we got an id, use it.  Otherwise the UID is lazily
145        // created in the getter for UID.
146        if (uid)
147            _id = uid;
148    }
149
150    //--------------------------------------------------------------------------
151    //
152    //  Variables
153    //
154    //--------------------------------------------------------------------------
155
156    /**
157     *  A reference to the EventDispatcher for this proxy.
158     */
159    protected var dispatcher:EventDispatcher;
160
161    /**
162     *  A hashmap of property change notifiers that this proxy is
163     *  listening for changes from; the key of the map is the property name.
164     */
165    protected var notifiers:Object;
166
167    /**
168     *  Indicates what kind of proxy to create
169     *  when proxying complex properties.
170     *  Subclasses should assign this value appropriately.
171     */
172    protected var proxyClass:Class = ObjectProxy;
173
174    /**
175     *  Contains a list of all of the property names for the proxied object.
176     *  Descendants need to fill this list by overriding the
177     *  <code>setupPropertyList()</code> method.
178     */
179    protected var propertyList:Array;
180
181    /**
182     *  Indicates how deep proxying should be performed.
183     *  If -1 (default), always proxy;
184     *  if this value is zero, no proxying will be performed.
185     */
186    private var _proxyLevel:int;
187
188    //--------------------------------------------------------------------------
189    //
190    //  Properties
191    //
192    //--------------------------------------------------------------------------
193
194    //----------------------------------
195    //  object
196    //----------------------------------
197
198    /**
199     *  Storage for the object property.
200     */
201    private var _item:Object;
202
203    /**
204     *  The object being proxied.
205     */
206    object_proxy function get object():Object
207    {
208        return _item;
209    }
210
211    //----------------------------------
212    //  type
213    //----------------------------------
214
215    /**
216     *  @private
217     *  Storage for the qualified type name.
218     */
219    private var _type:QName;
220
221    /**
222     *  The qualified type name associated with this object.
223     */
224    object_proxy function get type():QName
225    {
226        return _type;
227    }
228
229    /**
230     *  @private
231     */
232    object_proxy function set type(value:QName):void
233    {
234        _type = value;
235    }
236
237    //----------------------------------
238    //  uid
239    //----------------------------------
240
241    /**
242     *  @private
243     *  Storage for the uid property.
244     */
245    private var _id:String;
246
247    /**
248     *  The unique identifier for this object.
249     */
250    public function get uid():String
251    {
252    	if (_id === null)
253            _id = UIDUtil.createUID();
254
255        return _id;
256    }
257
258    /**
259     *  @private
260     */
261    public function set uid(value:String):void
262    {
263        _id = value;
264    }
265
266    //--------------------------------------------------------------------------
267    //
268    //  Overridden methods
269    //
270    //--------------------------------------------------------------------------
271
272    /**
273     *  Returns the specified property value of the proxied object.
274     *
275     *  @param name Typically a string containing the name of the property,
276     *  or possibly a QName where the property name is found by
277     *  inspecting the <code>localName</code> property.
278     *
279     *  @return The value of the property.
280     *  In some instances this value may be an instance of
281     *  <code>ObjectProxy</code>.
282     */
283    override flash_proxy function getProperty(name:*):*
284    {
285        // if we have a data proxy for this then
286        var result:*;
287
288        if (notifiers[name.toString()])
289            return notifiers[name];
290
291        result = _item[name];
292
293        if (result)
294        {
295            if (_proxyLevel == 0 || ObjectUtil.isSimple(result))
296            {
297                return result;
298            }
299            else
300            {
301                result = object_proxy::getComplexProperty(name, result);
302            } // if we are proxying
303        }
304
305        return result;
306    }
307
308    /**
309     *  Returns the value of the proxied object's method with the specified name.
310     *
311     *  @param name The name of the method being invoked.
312     *
313     *  @param rest An array specifying the arguments to the
314     *  called method.
315     *
316     *  @return The return value of the called method.
317     */
318    override flash_proxy function callProperty(name:*, ... rest):*
319    {
320        return _item[name].apply(_item, rest)
321    }
322
323    /**
324     *  Deletes the specified property on the proxied object and
325     *  sends notification of the delete to the handler.
326     *
327     *  @param name Typically a string containing the name of the property,
328     *  or possibly a QName where the property name is found by
329     *  inspecting the <code>localName</code> property.
330     *
331     *  @return A Boolean indicating if the property was deleted.
332     */
333    override flash_proxy function deleteProperty(name:*):Boolean
334    {
335        var notifier:IPropertyChangeNotifier = IPropertyChangeNotifier(notifiers[name]);
336        if (notifier)
337        {
338            notifier.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,
339                                         propertyChangeHandler);
340            delete notifiers[name];
341        }
342
343        var oldVal:* = _item[name];
344        var deleted:Boolean = delete _item[name];
345
346        if (dispatcher.hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE))
347        {
348            var event:PropertyChangeEvent = new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE);
349            event.kind = PropertyChangeEventKind.DELETE;
350            event.property = name;
351            event.oldValue = oldVal;
352            event.source = this;
353            dispatcher.dispatchEvent(event);
354        }
355
356        return deleted;
357    }
358
359    /**
360     *  @private
361     */
362    override flash_proxy function hasProperty(name:*):Boolean
363    {
364        return(name in _item);
365    }
366
367    /**
368     *  @private
369     */
370    override flash_proxy function nextName(index:int):String
371    {
372        return propertyList[index -1];
373    }
374
375    /**
376     *  @private
377     */
378    override flash_proxy function nextNameIndex(index:int):int
379    {
380        if (index == 0)
381        {
382            setupPropertyList();
383        }
384
385        if (index < propertyList.length)
386        {
387            return index + 1;
388        }
389        else
390        {
391            return 0;
392        }
393    }
394
395    /**
396     *  @private
397     */
398    override flash_proxy function nextValue(index:int):*
399    {
400        return _item[propertyList[index -1]];
401    }
402
403    /**
404     *  Updates the specified property on the proxied object
405     *  and sends notification of the update to the handler.
406     *
407     *  @param name Object containing the name of the property that
408     *  should be updated on the proxied object.
409     *
410     *  @param value Value that should be set on the proxied object.
411     */
412    override flash_proxy function setProperty(name:*, value:*):void
413    {
414        var oldVal:* = _item[name];
415        if (oldVal !== value)
416        {
417            // Update item.
418            _item[name] = value;
419
420            // Stop listening for events on old item if we currently are.
421            var notifier:IPropertyChangeNotifier =
422                IPropertyChangeNotifier(notifiers[name]);
423            if (notifier)
424            {
425                notifier.removeEventListener(
426                    PropertyChangeEvent.PROPERTY_CHANGE,
427                    propertyChangeHandler);
428                delete notifiers[name];
429            }
430
431            // Notify anyone interested.
432            if (dispatcher.hasEventListener(PropertyChangeEvent.PROPERTY_CHANGE))
433            {
434                if (name is QName)
435                    name = QName(name).localName;
436                var event:PropertyChangeEvent =
437                    PropertyChangeEvent.createUpdateEvent(
438                        this, name.toString(), oldVal, value);
439                dispatcher.dispatchEvent(event);
440            }
441        }
442    }
443
444    //--------------------------------------------------------------------------
445    //
446    //  object_proxy methods
447    //
448    //--------------------------------------------------------------------------
449
450    /**
451     *  Provides a place for subclasses to override how a complex property that
452     *  needs to be either proxied or daisy chained for event bubbling is managed.
453     *
454     *  @param name Typically a string containing the name of the property,
455     *  or possibly a QName where the property name is found by
456     *  inspecting the <code>localName</code> property.
457     *
458     *  @param value The property value.
459     *
460     *  @return The property value or an instance of <code>ObjectProxy</code>.
461     */
462    object_proxy function getComplexProperty(name:*, value:*):*
463    {
464        if (value is IPropertyChangeNotifier)
465        {
466            value.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,
467                                   propertyChangeHandler);
468            notifiers[name] = value;
469            return value;
470        }
471
472        if (getQualifiedClassName(value) == "Object")
473        {
474            value = new proxyClass(_item[name], null,
475                _proxyLevel > 0 ? _proxyLevel - 1 : _proxyLevel);
476            value.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,
477                                   propertyChangeHandler);
478            notifiers[name] = value;
479            return value;
480        }
481
482        return value;
483    }
484
485    //--------------------------------------------------------------------------
486    //
487    //  IExternalizable Methods
488    //
489    //--------------------------------------------------------------------------
490
491    /**
492     *  Since Flex only uses ObjectProxy to wrap anonymous objects,
493     *  the server flex.messaging.io.ObjectProxy instance serializes itself
494     *  as a Map that will be returned as a plain ActionScript object.
495     *  You can then set the object_proxy object property to this value.
496     *
497     *  @param input The source object from which the ObjectProxy is
498     *  deserialized.
499     */
500    public function readExternal(input:IDataInput):void
501    {
502        var value:Object = input.readObject();
503        _item = value;
504    }
505
506    /**
507     *  Since Flex only serializes the inner ActionScript object that it wraps,
508     *  the server flex.messaging.io.ObjectProxy populates itself
509     *  with this anonymous object's contents and appears to the user
510     *  as a Map.
511     *
512     *  @param output The source object from which the ObjectProxy is
513     *  deserialized.
514     */
515    public function writeExternal(output:IDataOutput):void
516    {
517        output.writeObject(_item);
518    }
519
520    //--------------------------------------------------------------------------
521    //
522    //  Methods
523    //
524    //--------------------------------------------------------------------------
525
526    /**
527     *  Registers an event listener object
528     *  so that the listener receives notification of an event.
529     *  For more information, including descriptions of the parameters see
530     *  <code>addEventListener()</code> in the
531     *  flash.events.EventDispatcher class.
532     *
533     *  @see flash.events.EventDispatcher#addEventListener()
534     */
535    public function addEventListener(type:String, listener:Function,
536                                     useCapture:Boolean = false,
537                                     priority:int = 0,
538                                     useWeakReference:Boolean = false):void
539    {
540        dispatcher.addEventListener(type, listener, useCapture,
541                                    priority, useWeakReference);
542    }
543
544    /**
545     *  Removes an event listener.
546     *  If there is no matching listener registered with the EventDispatcher object,
547     *  a call to this method has no effect.
548     *  For more information, see
549     *  the flash.events.EventDispatcher class.
550     *
551     *  @param type The type of event.
552     *
553     *  @param listener The listener object to remove.
554     *
555     *  @param useCapture Specifies whether the listener was registered for the capture
556     *  phase or the target and bubbling phases. If the listener was registered for both
557     *  the capture phase and the target and bubbling phases, two calls to
558     *  <code>removeEventListener()</code> are required to remove both, one call with
559     *  <code>useCapture</code>
560     *  set to <code>true</code>, and another call with <code>useCapture</code>
561     *  set to <code>false</code>.
562     *
563     *  @see flash.events.EventDispatcher#removeEventListener()
564     */
565    public function removeEventListener(type:String, listener:Function,
566                                        useCapture:Boolean = false):void
567    {
568        dispatcher.removeEventListener(type, listener, useCapture);
569    }
570
571    /**
572     *  Dispatches an event into the event flow.
573     *  For more information, see
574     *  the flash.events.EventDispatcher class.
575     *
576     *  @param event The Event object that is dispatched into the event flow. If the
577     *  event is being redispatched, a clone of the event is created automatically.
578     *  After an event is dispatched, its target property cannot be changed, so you
579     *  must create a new copy of the event for redispatching to work.
580     *
581     *  @return Returns <code>true</code> if the event was successfully dispatched.
582     *  A value
583     *  of <code>false</code> indicates failure or that <code>preventDefault()</code>
584     *  was called on the event.
585     *
586     *  @see flash.events.EventDispatcher#dispatchEvent()
587     */
588    public function dispatchEvent(event:Event):Boolean
589    {
590        return dispatcher.dispatchEvent(event);
591    }
592
593    /**
594     *  Checks whether there are any event listeners registered
595     *  for a specific type of event.
596     *  This allows you to determine where an object has altered handling
597     *  of an event type in the event flow hierarchy.
598     *  For more information, see
599     *  the flash.events.EventDispatcher class.
600     *
601     *  @param type The type of event
602     *
603     *  @return Returns <code>true</code> if a listener of the specified type is
604     *  registered; <code>false</code> otherwise.
605     *
606     *  @see flash.events.EventDispatcher#hasEventListener()
607     */
608    public function hasEventListener(type:String):Boolean
609    {
610        return dispatcher.hasEventListener(type);
611    }
612
613    /**
614     *  Checks whether an event listener is registered with this object
615     *  or any of its ancestors for the specified event type.
616     *  This method returns <code>true</code> if an event listener is triggered
617     *  during any phase of the event flow when an event of the specified
618     *  type is dispatched to this object or any of its descendants.
619     *  For more information, see the flash.events.EventDispatcher class.
620     *
621     *  @param type The type of event.
622     *
623     *  @return Returns <code>true</code> if a listener of the specified type will
624     *  be triggered; <code>false</code> otherwise.
625     *
626     *  @see flash.events.EventDispatcher#willTrigger()
627     */
628    public function willTrigger(type:String):Boolean
629    {
630        return dispatcher.willTrigger(type);
631    }
632
633    /**
634     *  Called when a complex property is updated.
635     *
636     *  @param event An event object that has changed.
637     */
638    public function propertyChangeHandler(event:PropertyChangeEvent):void
639    {
640        dispatcher.dispatchEvent(event);
641    }
642
643    //--------------------------------------------------------------------------
644    //
645    //  Protected Methods
646    //
647    //--------------------------------------------------------------------------
648
649    /**
650     *  This method creates an array of all of the property names for the
651     *  proxied object.
652     *  Descendants must override this method if they wish to add more
653     *  properties to this list.
654     *  Be sure to call <code>super.setupPropertyList</code> before making any
655     *  changes to the <code>propertyList</code> property.
656     */
657    protected function setupPropertyList():void
658    {
659        if (getQualifiedClassName(_item) == "Object")
660        {
661            propertyList = [];
662            for (var prop:String in _item)
663                propertyList.push(prop);
664        }
665        else
666        {
667            propertyList = ObjectUtil.getClassInfo(_item, null, {includeReadOnly:true, uris:["*"]}).properties;
668        }
669    }
670}
671
672}
673