1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.wotonomy.foundation;
20
21 import java.lang.ref.ReferenceQueue;
22 import java.lang.ref.WeakReference;
23 import java.util.Hashtable;
24 import java.util.Iterator;
25 import java.util.LinkedList;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Vector;
29
30 import net.wotonomy.foundation.internal.WotonomyException;
31
32 /***
33 * NSNotificationCenter broadcasts NSNotifications to
34 * registered observers. Observers can register for all
35 * notifications of a specific type, or all notifications
36 * about a specific object, or both. Observers specify
37 * the method that will be called when they are notified.
38 * A global notification center can be accessed with
39 * defaultCenter(), but other centers can be created and
40 * used independently of the default center. <br><br>
41 *
42 * This implementation uses weak references for observers
43 * and observables. The advantage to this approach is
44 * that you do not need to explicitly unregister observers
45 * or observables; they will be unregistered when they are
46 * garbage-collected. Note that you will need to retain
47 * a reference to any objects you register or they may
48 * become unregistered if no other object references them.
49 *
50 * @author michael@mpowers.net
51 * @author $Author: cgruber $
52 * @version $Revision: 893 $
53 */
54 public class NSNotificationCenter
55 {
56 /***
57 * Null marker class simplifies equals() logic
58 * for CompoundKey class below.
59 */
60 public static final Object NullMarker = new Object();
61
62 private static NSNotificationCenter defaultCenter = null;
63
64 /***
65 * A Map of (name,object) pairs to a List
66 * of (observer,selector) pairs.
67 */
68 private Hashtable observers;
69
70 /***
71 * Default constructor creates a new notification center.
72 */
73 public NSNotificationCenter()
74 {
75 observers = new Hashtable();
76 }
77
78 /***
79 * Returns the system default center, creating one
80 * if it has not yet been created.
81 */
82 static public NSNotificationCenter defaultCenter()
83 {
84 if ( defaultCenter == null )
85 {
86 defaultCenter = new NSNotificationCenter();
87 }
88 return defaultCenter;
89 }
90
91 /***
92 * Addes the specified observer to the notification queue for
93 * notifications with the specified name or the specified
94 * object or both.
95 * @param anObserver The observer that wishes to be notified.
96 * @param aSelector The selector that will be invoked.
97 * Must have exactly one argument, to which a notification
98 * will be passed.
99 * @param notificationName The name of the notifications for
100 * which the observer will be notified. If null, will notify
101 * only based on matching anObject.
102 * @param anObject The object of the notifications for which
103 * the observer will be notified. If null, will notify
104 * only based on matching notificationName.
105 */
106 public void addObserver(
107 Object anObserver, NSSelector aSelector,
108 String notificationName, Object anObject )
109 {
110
111 processKeyQueue();
112
113 Object name = notificationName;
114 if ( name == null )
115 {
116 name = NullMarker;
117 }
118 if ( anObject == null )
119 {
120 anObject = NullMarker;
121 }
122 Object key = new CompoundKey( name, anObject );
123 Object value = new CompoundValue( anObserver, aSelector );
124 List list = (List) observers.get( key );
125 if ( list == null )
126 {
127
128 list = new Vector();
129 list.add( value );
130 observers.put( new CompoundKey(
131 name, anObject, keyQueue ), list );
132 }
133 else
134 {
135
136 if ( ! list.contains( value ) )
137 {
138 list.add( value );
139 }
140 }
141 }
142
143 /***
144 * Posts the specified notification. Notifies all registered
145 * observers that match either the notification name or
146 * the notification object, or both.
147 * @param aNotification The notification that will be passed
148 * to the observers selector.
149 */
150 public void postNotification(
151 NSNotification aNotification )
152 {
153 List mergedList = new LinkedList();
154 Object key, observerList;
155
156 Object name = aNotification.name();
157 Object object = aNotification.object();
158
159 if ( name != null )
160 {
161 if ( object != null )
162 {
163 observerList = observers.get( new CompoundKey( name, object ) );
164 if ( observerList != null )
165 {
166 mergedList.addAll( (List) observerList );
167 }
168 observerList = observers.get( new CompoundKey( name, NullMarker ) );
169 if ( observerList != null )
170 {
171 mergedList.addAll( (List) observerList );
172 }
173 observerList = observers.get( new CompoundKey( NullMarker, object ) );
174 if ( observerList != null )
175 {
176 mergedList.addAll( (List) observerList );
177 }
178 }
179 else
180 {
181 observerList = observers.get( new CompoundKey( name, NullMarker ) );
182 if ( observerList != null )
183 {
184 mergedList.addAll( (List) observerList );
185 }
186 }
187 }
188 else
189 if ( object != null )
190 {
191 observerList = observers.get( new CompoundKey( NullMarker, object ) );
192 if ( observerList != null )
193 {
194 mergedList.addAll( (List) observerList );
195 }
196 }
197
198 key = new CompoundKey(
199 NullMarker, NullMarker );
200 observerList = observers.get( key );
201 if ( observerList != null )
202 {
203 mergedList.addAll( (List) observerList );
204 }
205
206 CompoundValue value;
207 Iterator it = mergedList.iterator();
208 while ( it.hasNext() )
209 {
210 value = (CompoundValue) it.next();
211 if ( value.get() == null )
212 {
213 it.remove();
214 }
215 else
216 {
217 try
218 {
219 value.selector().invoke(
220 value.get(),
221 new Object[] { aNotification } );
222 }
223 catch ( Exception exc )
224 {
225 WotonomyException w = new WotonomyException(
226 "Error notifying object: " + value.get() + " : " + aNotification, exc );
227
228 w.printStackTrace();
229 postNotification( "Error notifying object", this, new NSDictionary( "exception", w ) );
230 }
231 }
232 }
233
234 }
235
236 /***
237 * Posts a notification created from the specified name
238 * and object. Calls postNotification( NSNotification ).
239 * @param notificationName a String key to distinguish
240 * this notification.
241 * @param anObject any object, by convention this is
242 * the originator of the notification.
243 */
244 public void postNotification(
245 String notificationName, Object anObject )
246 {
247 postNotification( new NSNotification(
248 notificationName, anObject ) );
249 }
250
251 /***
252 * Posts a notification created from the specified name,
253 * object, and info. Calls postNotification( NSNotification ).
254 * @param notificationName a String key to distinguish
255 * this notification.
256 * @param anObject any object, by convention this is
257 * the originator of the notification.
258 * @param userInfo a Map containing information specific
259 * to the originator of the notification and that may
260 * be of interest to a knowledgable observer.
261 */
262 public void postNotification(
263 String notificationName, Object anObject, Map userInfo )
264 {
265 postNotification( new NSNotification(
266 notificationName, anObject, userInfo ) );
267 }
268
269 /***
270 * Unregisters the specified observer from all notification
271 * queues for which it is registered.
272 * @param anObserver The observer to be unregistered.
273 */
274 public void removeObserver(
275 Object anObserver )
276 {
277
278 processKeyQueue();
279
280 Iterator it = new LinkedList( observers.keySet() ).iterator();
281 while ( it.hasNext() )
282 {
283 removeObserver( anObserver, it.next() );
284 }
285 }
286
287 /***
288 * Unregisters the specified observer from all notifications
289 * queues associated with the specified name or object or both.
290 * @param anObserver The observer to be unregistered, if null
291 * will unregister all observers for the specified notification
292 * name and object.
293 * @param notificationName The name of the notification for which
294 * the observer will be unregistered, if null will unregister
295 * the specified observer for all notifications with the
296 * specified object.
297 * @param anObject The object for the notification for which
298 * the observer will be unregistered, if null will unregister
299 * the specified observer for all objects with the specified
300 * notification.
301 */
302 public void removeObserver(
303 Object anObserver, String notificationName, Object anObject )
304 {
305
306 processKeyQueue();
307
308
309 List keys = matchingKeys( notificationName, anObject );
310
311
312 Iterator it = keys.iterator();
313 while ( it.hasNext() )
314 {
315 removeObserver( anObserver, it.next() );
316 }
317 }
318
319 /***
320 * Returns all keys that match the specified name and object,
321 * but in this case null parameters are considered wildcards.
322 * Pass NullMarkers if you want to explicitly match nulls.
323 */
324 private List matchingKeys( String name, Object object )
325 {
326 List result = new LinkedList();
327
328 boolean willAdd;
329 CompoundKey key;
330 Iterator it = observers.keySet().iterator();
331 while ( it.hasNext() )
332 {
333 key = (CompoundKey) it.next();
334 willAdd = false;
335 if ( ( name == null ) || ( name == key.name() ) )
336 {
337 if ( ( object == null ) || ( object == key.get() ) )
338 {
339 willAdd = true;
340 }
341 }
342 if ( willAdd )
343 {
344 result.add( key );
345 }
346 }
347 return result;
348 }
349
350 /***
351 * Removes the specified observer from the list referenced
352 * by the specified key in the observer map.
353 */
354 private void removeObserver(
355 Object anObserver, Object key )
356 {
357
358 if ( anObserver == null )
359 {
360 observers.remove( key );
361 return;
362 }
363
364 List list = (List) observers.get( key );
365 if ( list == null ) return;
366
367
368 Object observer;
369 Iterator it = list.iterator();
370 while ( it.hasNext() )
371 {
372 observer = ((CompoundValue)it.next()).get();
373 if ( ( observer == null ) || ( anObserver == observer ) )
374 {
375
376 it.remove();
377
378
379 }
380 }
381 if ( list.size() == 0 )
382 {
383 observers.remove( key );
384 }
385 }
386
387
388 private ReferenceQueue keyQueue = new ReferenceQueue();
389
390 /***
391 * Removes any keys whose object has been garbage collected.
392 * (Garbage collected values are removed as they are encountered.)
393 */
394 private void processKeyQueue()
395 {
396 CompoundKey ck;
397 while ((ck = (CompoundKey)keyQueue.poll()) != null)
398 {
399
400 observers.remove(ck);
401 }
402 }
403
404 /***
405 * Key combining a name with an object.
406 * The object is weakly referenced, and keys
407 * are deallocated by reference queue.
408 * equals() compares by reference.
409 */
410 private static class CompoundKey extends WeakReference
411 {
412 private Object name;
413 private int hashCode;
414
415 /***
416 * Creates compound key.
417 * Neither name nor object may be null.
418 * Use NullMarker to represent null
419 * in either name or object.
420 */
421 public CompoundKey (
422 Object aName, Object anObject )
423 {
424 super( anObject );
425 name = aName;
426 hashCode = aName.hashCode() + anObject.hashCode();
427 }
428
429 /***
430 * Creates compound key with queue.
431 * Neither name nor object may be null.
432 * Use NullMarker to represent null
433 * in either name or object.
434 */
435 public CompoundKey (
436 Object aName, Object anObject, ReferenceQueue aQueue )
437 {
438 super( anObject, aQueue );
439 name = aName;
440 hashCode = aName.hashCode() + anObject.hashCode();
441 }
442
443 public Object name()
444 {
445 return name;
446 }
447
448 public int hashCode()
449 {
450 return hashCode;
451 }
452
453 public boolean equals( Object anObject )
454 {
455 if ( this == anObject ) return true;
456
457 CompoundKey key = (CompoundKey) anObject;
458 if ( name == key.name || ( name != null && name.equals( key.name ) ) )
459 {
460 Object object = get();
461 if ( object != null )
462 {
463
464 if ( object == ( key.get() ) )
465 {
466 return true;
467 }
468 }
469 }
470 return false;
471 }
472
473 public String toString()
474 {
475 return "[CompoundKey:"+name()+":"+get()+"]";
476 }
477 }
478
479 /***
480 * Value combining an object with a selector.
481 * The object is weakly referenced, and null
482 * values are not allowed.
483 */
484 private static class CompoundValue extends WeakReference
485 {
486 private NSSelector selector;
487 private int hashCode;
488
489 public CompoundValue( Object anObject, NSSelector aSelector )
490 {
491 super( anObject );
492 hashCode = anObject.hashCode();
493 selector = aSelector;
494 }
495
496 public NSSelector selector()
497 {
498 return selector;
499 }
500
501 public int hashCode()
502 {
503 return hashCode;
504 }
505
506 public boolean equals( Object anObject )
507 {
508 if ( this == anObject ) return true;
509
510 CompoundValue value = (CompoundValue) anObject;
511 if ( selector == value.selector ||
512 ( selector != null && selector.equals( value.selector ) ) )
513 {
514 Object object = get();
515 if ( object != null )
516 {
517 if ( object == value.get() )
518 {
519 return true;
520 }
521 }
522 }
523 return false;
524 }
525
526 public String toString()
527 {
528 return "[CompoundValue:"+get()+":"+selector().name()+"]";
529 }
530 }
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624 }
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671