View Javadoc

1   /*
2   Wotonomy: OpenStep design patterns for pure Java applications.
3   Copyright (C) 2000 Blacksmith, Inc.
4   
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Lesser General Public
7   License as published by the Free Software Foundation; either
8   version 2.1 of the License, or (at your option) any later version.
9   
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  Lesser General Public License for more details.
14  
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, see http://www.gnu.org
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; // thread-safe
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         // remove freed objects
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             // create new list with value and put it in map
128             list = new Vector(); // thread-safe
129             list.add( value );
130             observers.put( new CompoundKey( 
131                 name, anObject, keyQueue ), list );
132         }
133         else
134         {
135             // add only if not already in list
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             { // both are specified
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             { // object is null
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         { // name is null
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 //                    throw w;
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         // remove freed objects
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         // remove freed objects
306         processKeyQueue();
307         
308         // get key matches
309         List keys = matchingKeys( notificationName, anObject );
310         
311         // remove specified observer from each matching key
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         // if observer null, remove all observers for key
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         // remove specified observer from list
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                 // remove if match or freed object
376                 it.remove();   
377 
378                 // do not return; process entire list
379             }
380         }
381         if ( list.size() == 0 )
382         {
383             observers.remove( key );       
384         }
385     }
386 
387     /* Reference queues for cleared WeakKeys */
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             //System.out.println( "EOObserverCenter.processQueue: removing object" );
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             // assumes only used with other compound keys
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                     // compares by reference
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             // assumes only used with other compound values
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     public static void main( String[] argv )
533     {
534         Object aSource = "aSource";
535         Object bSource = "bSource";
536         
537         Object oneTest = new OneTest();
538         Object twoTest = new TwoTest();
539         NSSelector notifyMeOnce = 
540             new NSSelector( "notifyMeOnce", 
541             new Class[] { NSNotification.class } );
542         NSSelector notifyMeTwice = 
543             new NSSelector( "notifyMeTwice", 
544             new Class[] { NSNotification.class } );
545             
546         NSNotificationCenter.defaultCenter().addObserver(
547             oneTest, notifyMeOnce, "aMessage", null );
548             
549         NSNotificationCenter.defaultCenter().addObserver(
550             oneTest, notifyMeOnce, null, aSource );
551             
552         NSNotificationCenter.defaultCenter().addObserver(
553             twoTest, notifyMeOnce, "aMessage", aSource );
554             
555         NSNotificationCenter.defaultCenter().addObserver(
556             twoTest, notifyMeTwice, null, null );
557             
558         NSNotificationCenter.defaultCenter().postNotification(
559             "aMessage", aSource );
560         System.out.println();
561         NSNotificationCenter.defaultCenter().postNotification(
562             "aMessage", bSource );
563         System.out.println();
564         NSNotificationCenter.defaultCenter().postNotification(
565             "bMessage", aSource );
566         System.out.println();
567         NSNotificationCenter.defaultCenter().postNotification(
568             "bMessage", bSource );
569         System.out.println( "---" );
570         
571         NSNotificationCenter.defaultCenter().removeObserver( 
572             oneTest, null, aSource );
573 
574         NSNotificationCenter.defaultCenter().postNotification(
575             "aMessage", aSource );
576         System.out.println();
577         NSNotificationCenter.defaultCenter().postNotification(
578             "aMessage", bSource );
579         System.out.println();
580         NSNotificationCenter.defaultCenter().postNotification(
581             "bMessage", aSource );
582         System.out.println();
583         NSNotificationCenter.defaultCenter().postNotification(
584             "bMessage", bSource );
585         System.out.println( "---" );
586 
587         NSNotificationCenter.defaultCenter().removeObserver( 
588             null );
589 
590         NSNotificationCenter.defaultCenter().postNotification(
591             "aMessage", aSource );
592         System.out.println();
593         NSNotificationCenter.defaultCenter().postNotification(
594             "aMessage", bSource );
595         System.out.println();
596         NSNotificationCenter.defaultCenter().postNotification(
597             "bMessage", aSource );
598         System.out.println();
599         NSNotificationCenter.defaultCenter().postNotification(
600             "bMessage", bSource );
601         System.out.println( "---" );
602     }
603     
604     static private class OneTest
605     {
606         public void notifyMeOnce( NSNotification aNotification )
607         {
608             System.out.println( "OneTest.notifyMeOnce: " + aNotification );   
609         }
610     }
611 
612     static private class TwoTest
613     {
614         public void notifyMeOnce( NSNotification aNotification )
615         {
616             System.out.println( "TwoTest.notifyMeOnce: " + aNotification );   
617         }
618         public void notifyMeTwice( NSNotification aNotification )
619         {
620             System.out.println( "TwoTest.notifyMeTwice: " + aNotification );   
621         }
622     }
623 */
624 }
625 
626 
627 
628 /*
629  * $Log$
630  * Revision 1.2  2006/02/16 13:15:00  cgruber
631  * Check in all sources in eclipse-friendly maven-enabled packages.
632  *
633  * Revision 1.11  2003/06/03 14:51:15  mpowers
634  * Added commented-out println for debugging.
635  *
636  * Revision 1.10  2003/03/27 21:46:00  mpowers
637  * Better handling for null parameters on subscribe.
638  * Better handling for null parameters on post.
639  *
640  * Revision 1.9  2003/01/28 19:44:38  mpowers
641  * Now comparing strings by value not reference.
642  *
643  * Revision 1.8  2001/06/29 16:14:23  mpowers
644  * Fixed a javac compiler error that jikes allowed: shoe's on the other foot!
645  *
646  * Revision 1.7  2001/06/07 22:09:03  mpowers
647  * Exceptions during a notification are no longer being thrown
648  * so we can assure that all notifications get handled.
649  * Instead, we're printing stack traces...
650  *
651  * Revision 1.6  2001/04/09 21:41:50  mpowers
652  * Better debugging.
653  *
654  * Revision 1.5  2001/03/15 21:09:06  mpowers
655  * Fixed notifications with null objects.
656  *
657  * Revision 1.4  2001/02/21 21:18:34  mpowers
658  * Clarified need to retain references.
659  *
660  * Revision 1.3  2001/02/21 18:31:07  mpowers
661  * Finished and tested implementation of NSNotificationCenter.
662  *
663  * Revision 1.1.1.1  2000/12/21 15:47:39  mpowers
664  * Contributing wotonomy.
665  *
666  * Revision 1.3  2000/12/20 16:25:38  michael
667  * Added log to all files.
668  *
669  *
670  */
671