View Javadoc

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  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  	// suppression count
79  	private static int suppressions = 0;
80  	
81  	// singleton instance - needed for Observer
82  	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  		synchronized ( instance() )
99  		{
100 			// find observer list
101 			List observers = (List) 
102 				observableToObservers.get( new ReferenceKey( anObject ) );
103 				
104 			// if observer list not found, create and add item
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 				// support for java.util.Observable
113 				if ( anObject instanceof Observable )
114 				{
115 					((Observable)anObject).addObserver( instance() );
116 				}
117 			}
118 			else // observer list found - scan for observer
119 			if ( indexOf( observers, anObserver ) < 0 )
120 			{
121 				// observer not found, register it
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             // 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             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 // reference has expired
248                     {
249                         processQueue();
250                         it.remove(); // remove from list
251                         // if last observer, unregister observable
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 		// atomic operation
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 			// remove observer from list
297 			result.remove( index );
298 			
299 			// if last observer, unregister observable
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 				// compare by reference
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     /* Reference queue for cleared WeakKeys */
394     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         synchronized ( instance() )
404         {
405             ReferenceKey rk;
406             while ((rk = (ReferenceKey)queue.poll()) != null) 
407             {
408                 //System.out.println( "EOObserverCenter.processQueue: removing object" );
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                 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             }
491             result += "["+count+"]";
492 		}
493         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