Coverage Report - net.wotonomy.foundation.NSNotificationCenter
 
Classes in this File Line Coverage Branch Coverage Complexity
NSNotificationCenter
0% 
0% 
2.955
 
 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  0
 public class NSNotificationCenter
 55  
 {
 56  
     /** 
 57  
     * Null marker class simplifies equals() logic
 58  
     * for CompoundKey class below.  
 59  
     */
 60  0
     public static final Object NullMarker = new Object();    
 61  
   
 62  0
     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  0
     public NSNotificationCenter()
 74  0
     {
 75  0
         observers = new Hashtable();
 76  0
     }
 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  0
         if ( defaultCenter == null )
 85  
         {
 86  0
             defaultCenter = new NSNotificationCenter();   
 87  
         }
 88  0
         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  0
         processKeyQueue();
 112  
         
 113  0
         Object name = notificationName;
 114  0
         if ( name == null )
 115  
         {
 116  0
             name = NullMarker;
 117  
         }
 118  0
         if ( anObject == null )
 119  
         {
 120  0
             anObject = NullMarker;
 121  
         }
 122  0
         Object key = new CompoundKey( name, anObject );
 123  0
         Object value = new CompoundValue( anObserver, aSelector );
 124  0
         List list = (List) observers.get( key );
 125  0
         if ( list == null )
 126  
         {
 127  
             // create new list with value and put it in map
 128  0
             list = new Vector(); // thread-safe
 129  0
             list.add( value );
 130  0
             observers.put( new CompoundKey( 
 131  0
                 name, anObject, keyQueue ), list );
 132  0
         }
 133  
         else
 134  
         {
 135  
             // add only if not already in list
 136  0
             if ( ! list.contains( value ) )
 137  
             {
 138  0
                 list.add( value );   
 139  
             }
 140  
         }
 141  0
     }
 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  0
         List mergedList = new LinkedList();
 154  
         Object key, observerList;
 155  
         
 156  0
         Object name = aNotification.name();
 157  0
         Object object = aNotification.object();
 158  
         
 159  0
         if ( name != null ) 
 160  
         {
 161  0
             if ( object != null ) 
 162  
             { // both are specified
 163  0
                 observerList = observers.get( new CompoundKey( name, object ) );
 164  0
                 if ( observerList != null )
 165  
                 {
 166  0
                     mergedList.addAll( (List) observerList );
 167  
                 }
 168  0
                 observerList = observers.get( new CompoundKey( name, NullMarker ) );
 169  0
                 if ( observerList != null )
 170  
                 {
 171  0
                     mergedList.addAll( (List) observerList );
 172  
                 }
 173  0
                 observerList = observers.get( new CompoundKey( NullMarker, object ) );
 174  0
                 if ( observerList != null )
 175  
                 {
 176  0
                     mergedList.addAll( (List) observerList );
 177  0
                 }
 178  
             }
 179  
             else 
 180  
             { // object is null
 181  0
                 observerList = observers.get( new CompoundKey( name, NullMarker ) );
 182  0
                 if ( observerList != null )
 183  
                 {
 184  0
                     mergedList.addAll( (List) observerList );
 185  0
                 }
 186  
             }
 187  
         }
 188  
         else
 189  0
         if ( object != null ) 
 190  
         { // name is null
 191  0
             observerList = observers.get( new CompoundKey( NullMarker, object ) );
 192  0
             if ( observerList != null )
 193  
             {
 194  0
                 mergedList.addAll( (List) observerList );
 195  
             }
 196  
         }
 197  
 
 198  0
         key = new CompoundKey(
 199  0
             NullMarker, NullMarker );
 200  0
         observerList = observers.get( key );
 201  0
         if ( observerList != null )
 202  
         {
 203  0
             mergedList.addAll( (List) observerList );
 204  
         }
 205  
 
 206  
         CompoundValue value;
 207  0
         Iterator it = mergedList.iterator();
 208  0
         while ( it.hasNext() )
 209  
         {
 210  0
             value = (CompoundValue) it.next();
 211  0
             if ( value.get() == null )
 212  
             {
 213  0
                 it.remove();
 214  0
             }
 215  
             else
 216  
             {
 217  
                 try
 218  
                 {
 219  0
                     value.selector().invoke( 
 220  0
                         value.get(), 
 221  0
                         new Object[] { aNotification } );
 222  
                 }
 223  0
                 catch ( Exception exc )
 224  
                 {
 225  0
                     WotonomyException w = new WotonomyException(
 226  0
                         "Error notifying object: " + value.get() + " : " + aNotification, exc );
 227  
 //                    throw w;
 228  0
                     w.printStackTrace();
 229  0
 postNotification( "Error notifying object", this, new NSDictionary( "exception", w ) );
 230  0
                 }
 231  
             }
 232  0
         }
 233  
         
 234  0
     }
 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  0
         postNotification( new NSNotification( 
 248  0
             notificationName, anObject ) );
 249  0
     }
 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  0
         postNotification( new NSNotification( 
 266  0
             notificationName, anObject, userInfo ) );
 267  0
     }
 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  0
         processKeyQueue();
 279  
         
 280  0
         Iterator it = new LinkedList( observers.keySet() ).iterator();
 281  0
         while ( it.hasNext() )
 282  
         {
 283  0
             removeObserver( anObserver, it.next() );
 284  0
         }
 285  0
     }
 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  0
         processKeyQueue();
 307  
         
 308  
         // get key matches
 309  0
         List keys = matchingKeys( notificationName, anObject );
 310  
         
 311  
         // remove specified observer from each matching key
 312  0
         Iterator it = keys.iterator();
 313  0
         while ( it.hasNext() )
 314  
         {
 315  0
             removeObserver( anObserver, it.next() );               
 316  0
         }
 317  0
     }
 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  0
         List result = new LinkedList();
 327  
 
 328  
         boolean willAdd;
 329  
         CompoundKey key;
 330  0
         Iterator it = observers.keySet().iterator();
 331  0
         while ( it.hasNext() )
 332  
         {
 333  0
             key = (CompoundKey) it.next();
 334  0
             willAdd = false;
 335  0
             if ( ( name == null ) || ( name == key.name() ) )
 336  
             {
 337  0
                 if ( ( object == null ) || ( object == key.get() ) )
 338  
                 {
 339  0
                     willAdd = true;   
 340  
                 }
 341  
             }
 342  0
             if ( willAdd )
 343  
             {
 344  0
                 result.add( key );   
 345  0
             }
 346  
         }
 347  0
         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  0
         if ( anObserver == null )
 359  
         {
 360  0
             observers.remove( key );
 361  0
             return;
 362  
         }
 363  
         
 364  0
         List list = (List) observers.get( key );
 365  0
         if ( list == null ) return;
 366  
 
 367  
         // remove specified observer from list
 368  
         Object observer;
 369  0
         Iterator it = list.iterator();
 370  0
         while ( it.hasNext() )
 371  
         {
 372  0
             observer = ((CompoundValue)it.next()).get();
 373  0
             if ( ( observer == null ) || ( anObserver == observer ) )
 374  
             {
 375  
                 // remove if match or freed object
 376  0
                 it.remove();   
 377  
 
 378  
                 // do not return; process entire list
 379  0
             }
 380  
         }
 381  0
         if ( list.size() == 0 )
 382  
         {
 383  0
             observers.remove( key );       
 384  
         }
 385  0
     }
 386  
 
 387  
     /* Reference queues for cleared WeakKeys */
 388  0
     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  0
         while ((ck = (CompoundKey)keyQueue.poll()) != null) 
 398  
         {
 399  
             //System.out.println( "EOObserverCenter.processQueue: removing object" );
 400  0
             observers.remove(ck);
 401  0
         }
 402  0
     }
 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  0
             super( anObject );
 425  0
             name = aName;
 426  0
             hashCode = aName.hashCode() + anObject.hashCode();
 427  0
         }
 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  0
             super( anObject, aQueue );
 439  0
             name = aName;
 440  0
             hashCode = aName.hashCode() + anObject.hashCode();
 441  0
         }
 442  
         
 443  
         public Object name()
 444  
         {
 445  0
             return name;
 446  
         }
 447  
         
 448  
         public int hashCode()
 449  
         {
 450  0
             return hashCode;
 451  
         }
 452  
         
 453  
         public boolean equals( Object anObject )
 454  
         {
 455  0
             if ( this == anObject ) return true;
 456  
             // assumes only used with other compound keys
 457  0
             CompoundKey key = (CompoundKey) anObject;
 458  0
             if ( name == key.name || ( name != null && name.equals( key.name ) ) )
 459  
             {
 460  0
                 Object object = get();
 461  0
                 if ( object != null )
 462  
                 {
 463  
                     // compares by reference
 464  0
                     if ( object == ( key.get() ) )
 465  
                     {
 466  0
                         return true;   
 467  
                     }
 468  
                 }
 469  
             }
 470  0
             return false;
 471  
         }
 472  
         
 473  
         public String toString()
 474  
         {
 475  0
             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  0
             super( anObject );
 492  0
             hashCode = anObject.hashCode();
 493  0
             selector = aSelector;
 494  0
         }
 495  
         
 496  
         public NSSelector selector()
 497  
         {
 498  0
             return selector;
 499  
         }
 500  
         
 501  
         public int hashCode()
 502  
         {
 503  0
             return hashCode;
 504  
         }
 505  
         
 506  
         public boolean equals( Object anObject )
 507  
         {
 508  0
             if ( this == anObject ) return true;
 509  
             // assumes only used with other compound values
 510  0
             CompoundValue value = (CompoundValue) anObject;
 511  0
             if ( selector == value.selector || 
 512  0
                ( selector != null && selector.equals( value.selector ) ) )
 513  
             {
 514  0
                 Object object = get();
 515  0
                 if ( object != null )
 516  
                 {
 517  0
                     if ( object == value.get() )
 518  
                     {
 519  0
                         return true;   
 520  
                     }
 521  
                 }
 522  
             }
 523  0
             return false;
 524  
         }
 525  
 
 526  
         public String toString()
 527  
         {
 528  0
             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