1 // ==========================================================================
  2 // Project:   SproutCore Costello - Property Observing Library
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 // ........................................................................
  9 // ObserverSet
 10 //
 11 
 12 /**
 13   @namespace
 14 
 15   This private class is used to store information about observers on a
 16   particular key.  Note that this object is not observable.  You create new
 17   instances by calling SC.beget(SC.ObserverSet) ;
 18 
 19   @private
 20   @since SproutCore 1.0
 21 */
 22 SC.ObserverSet = {
 23 
 24   /**
 25     Adds the named target/method observer to the set.  The method must be
 26     a function, not a string.
 27   */
 28   add: function(target, method, context) {
 29     var targetGuid = SC.guidFor(target),
 30         methodGuid = SC.guidFor(method),
 31         targets    = this._members,
 32         members    = this.members,
 33         indexes    = targets[targetGuid],       // get the set of methods
 34         index, member;
 35 
 36     if ( !indexes ) indexes = targets[targetGuid] = { _size: 0 };
 37 
 38     index = indexes[methodGuid];
 39     if (index === undefined) {
 40       indexes[methodGuid] = members.length;
 41       // Increase the size of the indexes for this target so that it can be cleaned up.
 42       indexes._size++;
 43       member = [target, method, context];
 44 
 45       //@if(debug)
 46       // If deferred call logging info was specified (i.e., in debug mode when
 47       // such logging is enabled), we need to add it to the enqueued target/
 48       // method.
 49       member[3] = arguments[3];
 50       //@endif
 51 
 52       members.push(member);
 53     }
 54     else {
 55       //@if(debug)
 56       // If deferred call logging info was specified (i.e., in debug mode when
 57       // such logging is enabled), we need to add it to the enqueued target/
 58       // method.
 59       var loggingInfo = arguments[3],
 60           memberLoggingInfo;
 61 
 62       if (loggingInfo) {
 63         member            = members[index];
 64         memberLoggingInfo = member[3];
 65         if (!memberLoggingInfo) {
 66           member[3] = [loggingInfo];
 67         }
 68         else if (!(memberLoggingInfo instanceof Array)) {
 69           member[3] = [memberLoggingInfo, loggingInfo];
 70         }
 71         else {
 72           memberLoggingInfo.push(loggingInfo);
 73         }
 74       }
 75       //@endif
 76     }
 77   },
 78 
 79   /**
 80     removes the named target/method observer from the set.  If this is the
 81     last method for the named target, then the number of targets will also
 82     be reduced.
 83 
 84     returns YES if the items was removed, NO if it was not found.
 85   */
 86   remove: function(target, method) {
 87     var targetGuid = SC.guidFor(target), methodGuid = SC.guidFor(method);
 88     var indexes = this._members[targetGuid], members = this.members;
 89 
 90     if( !indexes ) return false;
 91 
 92     var index = indexes[methodGuid];
 93     if ( index === undefined) return false;
 94 
 95     if (index !== members.length - 1) {
 96       var entry = (members[index] = members[members.length - 1]);
 97       this._members[SC.guidFor(entry[0])][SC.guidFor(entry[1])] = index;
 98     }
 99 
100     // Throw away the last member (it has been moved or is the member we are removing).
101     members.pop();
102 
103     // Remove the method tracked for the target.
104     delete this._members[targetGuid][methodGuid];
105 
106     // If there are no more methods tracked on the target, remove the target.
107     if (--this._members[targetGuid]._size === 0) {
108       delete this._members[targetGuid];
109     }
110 
111     return true;
112   },
113 
114   /**
115     Invokes the target/method pairs in the receiver.  Used by SC.RunLoop
116     Note: does not support context
117   */
118   invokeMethods: function() {
119     var members = this.members, member;
120 
121     //@if(debug)
122     var shouldLog = SC.LOG_DEFERRED_CALLS,
123         target, method, methodName, loggingInfo, loggingInfos,
124         originatingTarget, originatingMethod, originatingMethodName,
125         originatingStack, j, jLen;
126     //@endif
127 
128     for( var i=0, l=members.length; i<l; i++ ) {
129       member = members[i];
130 
131       // Call the method (member[1]) on the target (member[0]) with the context (member[2])
132       member[1].call(member[0], member[2]);
133 
134       //@if(debug)
135       // If we have logging info specified for who scheduled the particular
136       // invocation, and logging is enabled, then output it.
137       if (shouldLog) {
138         target      = member[0];
139         method      = member[1];
140         methodName  = method.displayName || method;
141         loggingInfo = member[3];
142         if (loggingInfo) {
143           // If the logging info is not an array, that means only one place
144           // scheduled the invocation.
145           if (!(loggingInfo instanceof Array)) {
146             // We'll treat single-scheduler cases specially to make the output
147             // better for the user, even if it means some essentially-duplicated
148             // code.
149             originatingTarget     = loggingInfo.originatingTarget;
150             originatingMethod     = loggingInfo.originatingMethod;
151             originatingStack      = loggingInfo.originatingStack;
152             originatingMethodName = (originatingMethod ? originatingMethod.displayName : "(unknown)") || originatingMethod;
153             SC.Logger.log("Invoking runloop-scheduled method %@ on %@.  Originated by target %@,  method %@,  stack: ".fmt(methodName, target, originatingTarget, originatingMethodName), originatingStack);
154           }
155           else {
156             SC.Logger.log("Invoking runloop-scheduled method %@ on %@, which was scheduled by multiple target/method pairs:".fmt(methodName, target));
157             loggingInfos = loggingInfo;
158             for (j = 0, jLen = loggingInfos.length;  j < jLen;  ++j) {
159               loggingInfo           = loggingInfos[j];
160               originatingTarget     = loggingInfo.originatingTarget;
161               originatingMethod     = loggingInfo.originatingMethod;
162               originatingStack      = loggingInfo.originatingStack;
163               originatingMethodName = (originatingMethod ? originatingMethod.displayName : "(unknown)") || originatingMethod;
164               SC.Logger.log("  [%@]  originated by target %@,  method %@,  stack:".fmt(j, originatingTarget, originatingMethodName), originatingStack);
165             }
166           }
167         }
168         else {
169           // If we didn't capture information for this invocation, just report
170           // what we can.
171           SC.Logger.log("Invoking runloop-scheduled method %@ on %@, but we didn’t capture information about who scheduled it…".fmt(methodName, target));
172         }
173       }
174       //@endif
175     }
176   },
177 
178   /**
179     Returns a new instance of the set with the contents cloned.
180   */
181   clone: function() {
182     var newSet = SC.ObserverSet.create(), memberArray = this.members;
183 
184     newSet._members = SC.clone(this._members);
185     var newMembers = newSet.members;
186 
187     for( var i=0, l=memberArray.length; i<l; i++ ) {
188       newMembers[i] = SC.clone(memberArray[i]);
189       newMembers[i].length = 3;
190       //@if(debug)
191       newMembers[i].length = 4;
192       //@endif
193     }
194 
195     return newSet;
196   },
197 
198   /**
199     Creates a new instance of the observer set.
200   */
201   create: function() {
202     return new SC.ObserverSet.constructor();
203   },
204 
205   getMembers: function() {
206     return this.members.slice(0);
207   },
208 
209   constructor: function() {
210     this._members = {};
211     this.members = [];
212   }
213 
214 } ;
215 
216 SC.ObserverSet.constructor.prototype = SC.ObserverSet;
217 SC.ObserverSet.slice = SC.ObserverSet.clone;
218 SC.ObserverSet.copy = SC.ObserverSet.clone;
219 
220