Coverage Report - net.wotonomy.control.EOObserverCenter
 
Classes in this File Line Coverage Branch Coverage Complexity
EOObserverCenter
0% 
0% 
2.8
 
 1  
 /*
 2  
 Wotonomy: OpenStep design patterns for pure Java applications.
 3  
 Copyright (C) 2000 Intersect Software Corporation
 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.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  0
 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  0
         private static Map observableToObservers = new Hashtable();
 75  
     
 76  0
         private static List omniscients = new LinkedList();
 77  
         
 78  
         // suppression count
 79  0
         private static int suppressions = 0;
 80  
         
 81  
         // singleton instance - needed for Observer
 82  0
         private static EOObserverCenter instance = null;
 83  
     
 84  
     // optimization: remember last request and result
 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  
                 // atomic operation
 98  0
                 synchronized ( instance() )
 99  
                 {
 100  
                         // find observer list
 101  0
                         List observers = (List) 
 102  0
                                 observableToObservers.get( new ReferenceKey( anObject ) );
 103  
                                 
 104  
                         // if observer list not found, create and add item
 105  0
                         if ( observers == null )
 106  
                         {
 107  0
                                 observers = new ArrayList();
 108  0
                                 observers.add( new WeakReference( anObserver ) );
 109  0
                 processQueue();
 110  0
                                 observableToObservers.put( new ReferenceKey( anObject, queue ), observers );
 111  
 
 112  
                                 // support for java.util.Observable
 113  0
                                 if ( anObject instanceof Observable )
 114  
                                 {
 115  0
                                         ((Observable)anObject).addObserver( instance() );
 116  0
                                 }
 117  
                         }
 118  
                         else // observer list found - scan for observer
 119  0
                         if ( indexOf( observers, anObserver ) < 0 )
 120  
                         {
 121  
                                 // observer not found, register it
 122  0
                                 observers.add( new WeakReference( anObserver ) );
 123  
                         }
 124  
             
 125  0
             lastRequest = null;
 126  0
             lastResult = null;
 127  0
                 }
 128  0
         }
 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  0
                 if ( indexOf( omniscients, anObserver ) < 0 )
 140  
                 {
 141  0
                         omniscients.add( anObserver );
 142  
                 }
 143  0
         }
 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  0
                 if ( observerNotificationSuppressCount() == 0 )
 157  
                 {
 158  0
                         List observers = observersForObject( anObject );
 159  
                         EOObserving o;
 160  0
                         Iterator it = observers.iterator();
 161  0
                         while ( it.hasNext() )
 162  
                         {
 163  0
                                 o = (EOObserving) it.next();
 164  0
                                 o.objectWillChange( anObject );
 165  0
                         }
 166  
                 }
 167  0
         }
 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  0
                 List result = observersForObject( anObject );
 183  0
                 if ( result.size() == 0 ) return null;
 184  
                 
 185  
                 Object o;
 186  0
                 Iterator it = result.iterator();
 187  0
                 while ( it.hasNext() ) 
 188  
                 {
 189  0
                         o = it.next();
 190  0
                         if ( aClass.isAssignableFrom( o.getClass() ) )
 191  
                         {
 192  0
                                 return (EOObserving) o;        
 193  
                         }
 194  
                 }
 195  0
                 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  0
                 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  0
         synchronized ( instance() )
 219  
         {
 220  
             // optimization: this is called very frequently
 221  
             // from the same object calling willChange() a
 222  
             // number of times in a row as it is updating.
 223  0
             if ( lastRequest == anObject )
 224  
             {
 225  0
                 return lastResult;
 226  
             }
 227  
             
 228  
             NSArray result;
 229  
             
 230  0
             List references = observerListForObject( anObject );
 231  0
             if ( references == null ) 
 232  
             {
 233  0
                 result = NSArray.EmptyArray;
 234  0
             }
 235  
             else
 236  
             {
 237  0
                 result = new NSMutableArray();
 238  
                 Object observer;
 239  0
                 Iterator it = references.iterator();
 240  0
                 while ( it.hasNext() )
 241  
                 {
 242  0
                     observer = ((Reference)it.next()).get();
 243  0
                     if ( observer != null )
 244  
                     { 
 245  0
                         result.add( observer );
 246  0
                     }
 247  
                     else // reference has expired
 248  
                     {
 249  0
                         processQueue();
 250  0
                         it.remove(); // remove from list
 251  
                         // if last observer, unregister observable
 252  0
                         if ( references.size() == 0 )
 253  
                         {
 254  0
                             observableToObservers.remove( new ReferenceKey( anObject ) );
 255  0
                         }
 256  
                     }
 257  
                 }
 258  
             }
 259  
             
 260  0
             lastRequest = anObject;
 261  0
             lastResult = result;
 262  
             
 263  0
             return result;
 264  0
         }
 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  0
                 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  
                 // atomic operation
 286  0
                 synchronized ( instance() )
 287  
                 {
 288  0
             lastRequest = null;
 289  0
             lastResult = null;
 290  
             
 291  0
                         List result = observerListForObject( anObject );
 292  0
                         if ( result == null ) return;
 293  0
                         int index = indexOf( result, anObserver );
 294  0
                         if ( index == -1 ) return;
 295  
                         
 296  
                         // remove observer from list
 297  0
                         result.remove( index );
 298  
                         
 299  
                         // if last observer, unregister observable
 300  0
                         if ( result.size() == 0 )
 301  
                         {
 302  0
                 processQueue();
 303  0
                                 observableToObservers.remove( new ReferenceKey( anObject ) );
 304  
                         }
 305  0
                 }
 306  0
         }
 307  
 
 308  
         /**
 309  
         * Unregisters the specified omniscient observer.
 310  
         */
 311  
         public static void removeOmniscientObserver(
 312  
                 EOObserving anObserver )
 313  
         {
 314  0
                 int index = indexOf( omniscients, anObserver );
 315  0
                 if ( index != -1 )
 316  
                 {
 317  0
                         omniscients.remove( index );        
 318  
                 }
 319  0
         }
 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  0
                 if ( suppressions > 0 ) suppressions--;
 333  0
         }
 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  0
                 suppressions++;
 347  0
         }
 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  0
         if ( anObject == null ) return -1;
 357  
         
 358  0
                 synchronized ( aList )
 359  
                 {
 360  0
                         int len = aList.size();
 361  0
                         for ( int i = 0; i < len; i++ )
 362  
                         {
 363  
                                 // compare by reference
 364  0
                                 if ( anObject == ((Reference)aList.get(i)).get() )
 365  
                                 {
 366  0
                                         return i;
 367  
                                 }
 368  
                         }
 369  0
                 }
 370  0
                 return -1;
 371  
         }
 372  
         
 373  
         /**
 374  
         * Private singleton instance, so we can be an observer.
 375  
         */
 376  
         private static EOObserverCenter instance() 
 377  
         {
 378  0
                 if ( instance == null )
 379  
                 {
 380  0
                         instance = new EOObserverCenter();
 381  
                 }
 382  0
                 return instance;
 383  
         }
 384  
         
 385  
         /**
 386  
         * Interface Observer
 387  
         */        
 388  
         public void update( Observable o, Object arg )
 389  
         {
 390  0
                 notifyObserversObjectWillChange( o );
 391  0
         }
 392  
     
 393  
     /* Reference queue for cleared WeakKeys */
 394  0
     private static ReferenceQueue queue = new ReferenceQueue();
 395  
 
 396  
     /* Remove all invalidated entries from the map, that is, remove all entries
 397  
        whose keys have been discarded.  This method should be invoked once by
 398  
        each public mutator in this class.  We don't invoke this method in
 399  
        public accessors because that can lead to surprising
 400  
        ConcurrentModificationExceptions. */
 401  
     private static void processQueue() 
 402  
     {
 403  0
         synchronized ( instance() )
 404  
         {
 405  
             ReferenceKey rk;
 406  0
             while ((rk = (ReferenceKey)queue.poll()) != null) 
 407  
             {
 408  
                 //System.out.println( "EOObserverCenter.processQueue: removing object" );
 409  0
                 observableToObservers.remove(rk);
 410  0
             }
 411  0
         }
 412  0
     }
 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  0
     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  0
             super( anObject );
 430  0
             hashCode = anObject.hashCode();
 431  0
         }
 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  0
             super( anObject, aQueue );
 442  0
             hashCode = anObject.hashCode();
 443  0
         }
 444  
         
 445  
         /**
 446  
         * Passes through to actual key's hash code.
 447  
         */
 448  
         public int hashCode()
 449  
         {
 450  0
             return hashCode;   
 451  
         }
 452  
         
 453  
         /**
 454  
         * Compares by reference.
 455  
         */ 
 456  
         public boolean equals( Object anObject )
 457  
         {
 458  0
             if ( ! ( anObject instanceof ReferenceKey ) ) return false;
 459  0
             Object key = get();
 460  0
             if ( key == null ) return false;
 461  0
             return ( key == ((ReferenceKey)anObject).get() );
 462  
         }
 463  
     }
 464  
     
 465  
     private static String debugString()
 466  
     {
 467  0
         String result = "";
 468  0
         int count = 0;
 469  0
                 synchronized ( instance() )
 470  
                 {
 471  
             Object anObject;
 472  0
             Iterator it = observableToObservers.keySet().iterator();
 473  0
             while ( it.hasNext() )
 474  
             {
 475  0
                             result += ((Reference)it.next()).get() + " : ";    
 476  0
                             count++;
 477  
                 /*
 478  
                 Iterator values = ((List)it.next()).iterator();
 479  
                 while ( values.hasNext() )
 480  
                 {
 481  
                     anObject = ((Reference)values.next()).get();
 482  
                     if ( anObject != null )
 483  
                     {
 484  
 //                        if ( anObject instanceof net.wotonomy.ui.MasterDetailAssociation )
 485  
                             result += anObject.getClass().toString() + " : ";    
 486  
                             count++;
 487  
                     }
 488  
                 }
 489  
                 */
 490  0
             }
 491  0
             result += "["+count+"]";
 492  0
                 }
 493  0
         return result;
 494  
     }
 495  
         
 496  
 }
 497  
 
 498  
 /*
 499  
  * $Log$
 500  
  * Revision 1.2  2006/02/16 16:47:14  cgruber
 501  
  * Move some classes in to "internal" packages and re-work imports, etc.
 502  
  *
 503  
  * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions.
 504  
  *
 505  
  * Revision 1.1  2006/02/16 13:19:57  cgruber
 506  
  * Check in all sources in eclipse-friendly maven-enabled packages.
 507  
  *
 508  
  * Revision 1.9  2002/10/24 18:18:12  mpowers
 509  
  * NSArray's are now considered read-only, so we can return our internal
 510  
  * representation to reduce unnecessary object allocation.
 511  
  *
 512  
  * Revision 1.8  2001/02/21 21:17:32  mpowers
 513  
  * Now retaining a reference to the recent changes observer.
 514  
  * Better documented need to retain reference.
 515  
  * Started implementing notifications.
 516  
  *
 517  
  * Revision 1.7  2001/02/17 16:52:05  mpowers
 518  
  * Changes in imports to support building with jdk1.1 collections.
 519  
  *
 520  
  * Revision 1.6  2001/02/05 18:45:45  mpowers
 521  
  * Reduced access back to private for utility methods.
 522  
  *
 523  
  * Revision 1.5  2001/02/05 18:42:32  mpowers
 524  
  * Updated documentation throughout project.
 525  
  *
 526  
  * Revision 1.4  2001/01/18 16:57:47  mpowers
 527  
  * Added debug facility.
 528  
  *
 529  
  * Revision 1.3  2001/01/10 16:28:53  mpowers
 530  
  * Implemented a compare-by-reference weak hashtable
 531  
  * because WeakHashMap compared by value and we can't
 532  
  * assume that display groups won't contain objects
 533  
  * whose values are equivalent.
 534  
  *
 535  
  * Revision 1.2  2001/01/09 20:10:19  mpowers
 536  
  * Now using weak references to track observables and their observers.
 537  
  *
 538  
  * Revision 1.1.1.1  2000/12/21 15:46:44  mpowers
 539  
  * Contributing wotonomy.
 540  
  *
 541  
  * Revision 1.8  2000/12/20 16:25:35  michael
 542  
  * Added log to all files.
 543  
  *
 544  
  *
 545  
  */
 546  
     
 547