1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.wotonomy.control;
20
21 import java.lang.ref.Reference;
22 import java.lang.ref.ReferenceQueue;
23 import java.lang.ref.WeakReference;
24 import java.util.ArrayList;
25 import java.util.Hashtable;
26 import java.util.Iterator;
27 import java.util.LinkedList;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Observable;
31 import java.util.Observer;
32
33 import net.wotonomy.foundation.NSArray;
34 import net.wotonomy.foundation.NSMutableArray;
35
36 /***
37 * EOObserverCenter is a static singleton-like class
38 * that manages EOObservings that want to be notified
39 * of changes to those objects that call
40 * notifyObserversObjectWillChange() before their
41 * properties change. <br><br>
42 *
43 * Implementation note: Because Java implements the
44 * Observer pattern in java.util.Observable, this
45 * class knows how to register itself as an Observer
46 * with Observables as well. However, users should
47 * note that Observables only notify their Observers
48 * of changes after their property has changed.
49 * EODelayedObservers would see no difference because
50 * they always receive their notification after all
51 * changes have taken place. <br><br>
52 *
53 * This implementation uses weak references for observers
54 * and observables. The advantage to this approach is
55 * that you do not need to explicitly unregister observers
56 * or observables; they will be unregistered when they are
57 * garbage-collected. Note that you will need to retain
58 * a reference to any objects you register or they may
59 * become unregistered if no other object references them.
60 *
61 * @author michael@mpowers.net
62 * @author $Author: cgruber $
63 * @version $Revision: 894 $
64 */
65 public class EOObserverCenter implements Observer
66 {
67 /***
68 * Would much rather use a WeakHashMap, but that class
69 * compares by value, and we need to compare by reference.
70 * This means we need to recreate a weak hashmap with
71 * the ReferenceKey class below. Using hashtable for
72 * thread safety.
73 */
74 private static Map observableToObservers = new Hashtable();
75
76 private static List omniscients = new LinkedList();
77
78
79 private static int suppressions = 0;
80
81
82 private static EOObserverCenter instance = null;
83
84
85 private static Object lastRequest;
86 private static NSArray lastResult;
87
88 /***
89 * Registers the specified EOObserving for
90 * notifications from the specified object.
91 * An EOObserving can only be registered
92 * once for a given object.
93 */
94 public static void addObserver(
95 EOObserving anObserver, Object anObject )
96 {
97
98 synchronized ( instance() )
99 {
100
101 List observers = (List)
102 observableToObservers.get( new ReferenceKey( anObject ) );
103
104
105 if ( observers == null )
106 {
107 observers = new ArrayList();
108 observers.add( new WeakReference( anObserver ) );
109 processQueue();
110 observableToObservers.put( new ReferenceKey( anObject, queue ), observers );
111
112
113 if ( anObject instanceof Observable )
114 {
115 ((Observable)anObject).addObserver( instance() );
116 }
117 }
118 else
119 if ( indexOf( observers, anObserver ) < 0 )
120 {
121
122 observers.add( new WeakReference( anObserver ) );
123 }
124
125 lastRequest = null;
126 lastResult = null;
127 }
128 }
129
130 /***
131 * Registers the specified EOObserving for notifications
132 * on all object changes. An EOObserving can be
133 * registered as an omniscient observer at most once.
134 * Use omniscient observers with caution.
135 */
136 public static void addOmniscientObserver(
137 EOObserving anObserver )
138 {
139 if ( indexOf( omniscients, anObserver ) < 0 )
140 {
141 omniscients.add( anObserver );
142 }
143 }
144
145 /***
146 * Notifies all EOObservings registered for
147 * notifications for the specified object.
148 * This method is typically called by objects
149 * that wish to broadcast a notification before
150 * a property change takes place, passing itself
151 * as the argument.
152 */
153 public static void notifyObserversObjectWillChange(
154 Object anObject )
155 {
156 if ( observerNotificationSuppressCount() == 0 )
157 {
158 List observers = observersForObject( anObject );
159 EOObserving o;
160 Iterator it = observers.iterator();
161 while ( it.hasNext() )
162 {
163 o = (EOObserving) it.next();
164 o.objectWillChange( anObject );
165 }
166 }
167 }
168
169 /***
170 * Returns an observer that is an instance of
171 * the specified class and
172 * that is registered for notifications about
173 * the specified object. If more than one
174 * such observer exists, which observer is
175 * returned is undetermined - use
176 * observersForObject instead. If no observer
177 * exists, returns null.
178 */
179 public static EOObserving observerForObject(
180 Object anObject, Class aClass )
181 {
182 List result = observersForObject( anObject );
183 if ( result.size() == 0 ) return null;
184
185 Object o;
186 Iterator it = result.iterator();
187 while ( it.hasNext() )
188 {
189 o = it.next();
190 if ( aClass.isAssignableFrom( o.getClass() ) )
191 {
192 return (EOObserving) o;
193 }
194 }
195 return null;
196 }
197
198 /***
199 * Returns the number of times that notifications
200 * have been suppressed. This is also the number
201 * of times that enableObserverNotification must
202 * be called to allow notifications to take place.
203 */
204 public static int observerNotificationSuppressCount()
205 {
206 return suppressions;
207 }
208
209 /***
210 * Returns a List of observers for the
211 * specified object. Returns an empty List
212 * if no observer has registered for that
213 * object.
214 */
215 public static NSArray observersForObject(
216 Object anObject )
217 {
218 synchronized ( instance() )
219 {
220
221
222
223 if ( lastRequest == anObject )
224 {
225 return lastResult;
226 }
227
228 NSArray result;
229
230 List references = observerListForObject( anObject );
231 if ( references == null )
232 {
233 result = NSArray.EmptyArray;
234 }
235 else
236 {
237 result = new NSMutableArray();
238 Object observer;
239 Iterator it = references.iterator();
240 while ( it.hasNext() )
241 {
242 observer = ((Reference)it.next()).get();
243 if ( observer != null )
244 {
245 result.add( observer );
246 }
247 else
248 {
249 processQueue();
250 it.remove();
251
252 if ( references.size() == 0 )
253 {
254 observableToObservers.remove( new ReferenceKey( anObject ) );
255 }
256 }
257 }
258 }
259
260 lastRequest = anObject;
261 lastResult = result;
262
263 return result;
264 }
265
266 }
267
268 /***
269 * Returns a reference to the actual list of
270 * observers for the given object, or null if it
271 * doesn't exist.
272 */
273 private static List observerListForObject( Object anObject )
274 {
275 return (List) observableToObservers.get( new ReferenceKey( anObject ) );
276 }
277
278 /***
279 * Unregisters the specified observer for
280 * notifications from the specified object.
281 */
282 public static void removeObserver(
283 EOObserving anObserver, Object anObject )
284 {
285
286 synchronized ( instance() )
287 {
288 lastRequest = null;
289 lastResult = null;
290
291 List result = observerListForObject( anObject );
292 if ( result == null ) return;
293 int index = indexOf( result, anObserver );
294 if ( index == -1 ) return;
295
296
297 result.remove( index );
298
299
300 if ( result.size() == 0 )
301 {
302 processQueue();
303 observableToObservers.remove( new ReferenceKey( anObject ) );
304 }
305 }
306 }
307
308 /***
309 * Unregisters the specified omniscient observer.
310 */
311 public static void removeOmniscientObserver(
312 EOObserving anObserver )
313 {
314 int index = indexOf( omniscients, anObserver );
315 if ( index != -1 )
316 {
317 omniscients.remove( index );
318 }
319 }
320
321 /***
322 * Enables notifications after they have been
323 * suppressed by suppressObserverNotification.
324 * If notifications have been suppressed
325 * multiple times, this method must be called
326 * an equal number of times to resume notifications.
327 * If notifications are not currently suppressed,
328 * this method does nothing.
329 */
330 public static void enableObserverNotification()
331 {
332 if ( suppressions > 0 ) suppressions--;
333 }
334
335 /***
336 * Causes notifications to be suppressed until
337 * the next matching call to enableObserverNotification.
338 * If this method is called more than once,
339 * enableObserverNotification must be called an
340 * equal number of times for notifications to resume.
341 * This method always causes notifications to cease
342 * immediately.
343 */
344 public static void suppressObserverNotification()
345 {
346 suppressions++;
347 }
348
349 /***
350 * Because we're comparing by reference, we need to
351 * test for the existence of the object directly.
352 * @return the index or -1 if not found.
353 */
354 private static int indexOf( List aList, Object anObject )
355 {
356 if ( anObject == null ) return -1;
357
358 synchronized ( aList )
359 {
360 int len = aList.size();
361 for ( int i = 0; i < len; i++ )
362 {
363
364 if ( anObject == ((Reference)aList.get(i)).get() )
365 {
366 return i;
367 }
368 }
369 }
370 return -1;
371 }
372
373 /***
374 * Private singleton instance, so we can be an observer.
375 */
376 private static EOObserverCenter instance()
377 {
378 if ( instance == null )
379 {
380 instance = new EOObserverCenter();
381 }
382 return instance;
383 }
384
385 /***
386 * Interface Observer
387 */
388 public void update( Observable o, Object arg )
389 {
390 notifyObserversObjectWillChange( o );
391 }
392
393
394 private static ReferenceQueue queue = new ReferenceQueue();
395
396
397
398
399
400
401 private static void processQueue()
402 {
403 synchronized ( instance() )
404 {
405 ReferenceKey rk;
406 while ((rk = (ReferenceKey)queue.poll()) != null)
407 {
408
409 observableToObservers.remove(rk);
410 }
411 }
412 }
413
414 /***
415 * Private class used to force a hashmap to
416 * perform key comparisons by reference.
417 * Retains a weak reference just like WeakHashMap.
418 */
419 static private class ReferenceKey extends WeakReference
420 {
421 private int hashCode;
422
423 /***
424 * Called to create a disposable reference key,
425 * used for retrieving values from the hashtable.
426 */
427 public ReferenceKey( Object anObject )
428 {
429 super( anObject );
430 hashCode = anObject.hashCode();
431 }
432
433 /***
434 * Called to create a reference key that will be
435 * used as a key in the hashtable, so we need the
436 * reference queue to later remove the key from the
437 * table when referred object is no longer in use.
438 */
439 public ReferenceKey( Object anObject, ReferenceQueue aQueue )
440 {
441 super( anObject, aQueue );
442 hashCode = anObject.hashCode();
443 }
444
445 /***
446 * Passes through to actual key's hash code.
447 */
448 public int hashCode()
449 {
450 return hashCode;
451 }
452
453 /***
454 * Compares by reference.
455 */
456 public boolean equals( Object anObject )
457 {
458 if ( ! ( anObject instanceof ReferenceKey ) ) return false;
459 Object key = get();
460 if ( key == null ) return false;
461 return ( key == ((ReferenceKey)anObject).get() );
462 }
463 }
464
465 private static String debugString()
466 {
467 String result = "";
468 int count = 0;
469 synchronized ( instance() )
470 {
471 Object anObject;
472 Iterator it = observableToObservers.keySet().iterator();
473 while ( it.hasNext() )
474 {
475 result += ((Reference)it.next()).get() + " : ";
476 count++;
477
478
479
480
481
482
483
484
485
486
487
488
489
490 }
491 result += "["+count+"]";
492 }
493 return result;
494 }
495
496 }
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547