From aedc34d55462a75e329bbf342251ff6504cd117e Mon Sep 17 00:00:00 2001 From: Benjamin Culkin Date: Sun, 19 May 2024 17:56:33 -0400 Subject: Initial import from SVN --- .../net/wotonomy/control/EOObserverCenter.java | 547 +++++++++++++++++++++ 1 file changed, 547 insertions(+) create mode 100644 projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverCenter.java (limited to 'projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverCenter.java') diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverCenter.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverCenter.java new file mode 100644 index 0000000..d42130c --- /dev/null +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverCenter.java @@ -0,0 +1,547 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2000 Intersect Software Corporation + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.control; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Observable; +import java.util.Observer; + +import net.wotonomy.foundation.NSArray; +import net.wotonomy.foundation.NSMutableArray; + +/** +* EOObserverCenter is a static singleton-like class +* that manages EOObservings that want to be notified +* of changes to those objects that call +* notifyObserversObjectWillChange() before their +* properties change.

+* +* Implementation note: Because Java implements the +* Observer pattern in java.util.Observable, this +* class knows how to register itself as an Observer +* with Observables as well. However, users should +* note that Observables only notify their Observers +* of changes after their property has changed. +* EODelayedObservers would see no difference because +* they always receive their notification after all +* changes have taken place.

+* +* This implementation uses weak references for observers +* and observables. The advantage to this approach is +* that you do not need to explicitly unregister observers +* or observables; they will be unregistered when they are +* garbage-collected. Note that you will need to retain +* a reference to any objects you register or they may +* become unregistered if no other object references them. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 894 $ +*/ +public class EOObserverCenter implements Observer +{ + /** + * Would much rather use a WeakHashMap, but that class + * compares by value, and we need to compare by reference. + * This means we need to recreate a weak hashmap with + * the ReferenceKey class below. Using hashtable for + * thread safety. + */ + private static Map observableToObservers = new Hashtable(); + + private static List omniscients = new LinkedList(); + + // suppression count + private static int suppressions = 0; + + // singleton instance - needed for Observer + private static EOObserverCenter instance = null; + + // optimization: remember last request and result + private static Object lastRequest; + private static NSArray lastResult; + + /** + * Registers the specified EOObserving for + * notifications from the specified object. + * An EOObserving can only be registered + * once for a given object. + */ + public static void addObserver( + EOObserving anObserver, Object anObject ) + { + // atomic operation + synchronized ( instance() ) + { + // find observer list + List observers = (List) + observableToObservers.get( new ReferenceKey( anObject ) ); + + // if observer list not found, create and add item + if ( observers == null ) + { + observers = new ArrayList(); + observers.add( new WeakReference( anObserver ) ); + processQueue(); + observableToObservers.put( new ReferenceKey( anObject, queue ), observers ); + + // support for java.util.Observable + if ( anObject instanceof Observable ) + { + ((Observable)anObject).addObserver( instance() ); + } + } + else // observer list found - scan for observer + if ( indexOf( observers, anObserver ) < 0 ) + { + // observer not found, register it + observers.add( new WeakReference( anObserver ) ); + } + + lastRequest = null; + lastResult = null; + } + } + + /** + * Registers the specified EOObserving for notifications + * on all object changes. An EOObserving can be + * registered as an omniscient observer at most once. + * Use omniscient observers with caution. + */ + public static void addOmniscientObserver( + EOObserving anObserver ) + { + if ( indexOf( omniscients, anObserver ) < 0 ) + { + omniscients.add( anObserver ); + } + } + + /** + * Notifies all EOObservings registered for + * notifications for the specified object. + * This method is typically called by objects + * that wish to broadcast a notification before + * a property change takes place, passing itself + * as the argument. + */ + public static void notifyObserversObjectWillChange( + Object anObject ) + { + if ( observerNotificationSuppressCount() == 0 ) + { + List observers = observersForObject( anObject ); + EOObserving o; + Iterator it = observers.iterator(); + while ( it.hasNext() ) + { + o = (EOObserving) it.next(); + o.objectWillChange( anObject ); + } + } + } + + /** + * Returns an observer that is an instance of + * the specified class and + * that is registered for notifications about + * the specified object. If more than one + * such observer exists, which observer is + * returned is undetermined - use + * observersForObject instead. If no observer + * exists, returns null. + */ + public static EOObserving observerForObject( + Object anObject, Class aClass ) + { + List result = observersForObject( anObject ); + if ( result.size() == 0 ) return null; + + Object o; + Iterator it = result.iterator(); + while ( it.hasNext() ) + { + o = it.next(); + if ( aClass.isAssignableFrom( o.getClass() ) ) + { + return (EOObserving) o; + } + } + return null; + } + + /** + * Returns the number of times that notifications + * have been suppressed. This is also the number + * of times that enableObserverNotification must + * be called to allow notifications to take place. + */ + public static int observerNotificationSuppressCount() + { + return suppressions; + } + + /** + * Returns a List of observers for the + * specified object. Returns an empty List + * if no observer has registered for that + * object. + */ + public static NSArray observersForObject( + Object anObject ) + { + synchronized ( instance() ) + { + // optimization: this is called very frequently + // from the same object calling willChange() a + // number of times in a row as it is updating. + if ( lastRequest == anObject ) + { + return lastResult; + } + + NSArray result; + + List references = observerListForObject( anObject ); + if ( references == null ) + { + result = NSArray.EmptyArray; + } + else + { + result = new NSMutableArray(); + Object observer; + Iterator it = references.iterator(); + while ( it.hasNext() ) + { + observer = ((Reference)it.next()).get(); + if ( observer != null ) + { + result.add( observer ); + } + else // reference has expired + { + processQueue(); + it.remove(); // remove from list + // if last observer, unregister observable + if ( references.size() == 0 ) + { + observableToObservers.remove( new ReferenceKey( anObject ) ); + } + } + } + } + + lastRequest = anObject; + lastResult = result; + + return result; + } + + } + + /** + * Returns a reference to the actual list of + * observers for the given object, or null if it + * doesn't exist. + */ + private static List observerListForObject( Object anObject ) + { + return (List) observableToObservers.get( new ReferenceKey( anObject ) ); + } + + /** + * Unregisters the specified observer for + * notifications from the specified object. + */ + public static void removeObserver( + EOObserving anObserver, Object anObject ) + { + // atomic operation + synchronized ( instance() ) + { + lastRequest = null; + lastResult = null; + + List result = observerListForObject( anObject ); + if ( result == null ) return; + int index = indexOf( result, anObserver ); + if ( index == -1 ) return; + + // remove observer from list + result.remove( index ); + + // if last observer, unregister observable + if ( result.size() == 0 ) + { + processQueue(); + observableToObservers.remove( new ReferenceKey( anObject ) ); + } + } + } + + /** + * Unregisters the specified omniscient observer. + */ + public static void removeOmniscientObserver( + EOObserving anObserver ) + { + int index = indexOf( omniscients, anObserver ); + if ( index != -1 ) + { + omniscients.remove( index ); + } + } + + /** + * Enables notifications after they have been + * suppressed by suppressObserverNotification. + * If notifications have been suppressed + * multiple times, this method must be called + * an equal number of times to resume notifications. + * If notifications are not currently suppressed, + * this method does nothing. + */ + public static void enableObserverNotification() + { + if ( suppressions > 0 ) suppressions--; + } + + /** + * Causes notifications to be suppressed until + * the next matching call to enableObserverNotification. + * If this method is called more than once, + * enableObserverNotification must be called an + * equal number of times for notifications to resume. + * This method always causes notifications to cease + * immediately. + */ + public static void suppressObserverNotification() + { + suppressions++; + } + + /** + * Because we're comparing by reference, we need to + * test for the existence of the object directly. + * @return the index or -1 if not found. + */ + private static int indexOf( List aList, Object anObject ) + { + if ( anObject == null ) return -1; + + synchronized ( aList ) + { + int len = aList.size(); + for ( int i = 0; i < len; i++ ) + { + // compare by reference + if ( anObject == ((Reference)aList.get(i)).get() ) + { + return i; + } + } + } + return -1; + } + + /** + * Private singleton instance, so we can be an observer. + */ + private static EOObserverCenter instance() + { + if ( instance == null ) + { + instance = new EOObserverCenter(); + } + return instance; + } + + /** + * Interface Observer + */ + public void update( Observable o, Object arg ) + { + notifyObserversObjectWillChange( o ); + } + + /* Reference queue for cleared WeakKeys */ + private static ReferenceQueue queue = new ReferenceQueue(); + + /* Remove all invalidated entries from the map, that is, remove all entries + whose keys have been discarded. This method should be invoked once by + each public mutator in this class. We don't invoke this method in + public accessors because that can lead to surprising + ConcurrentModificationExceptions. */ + private static void processQueue() + { + synchronized ( instance() ) + { + ReferenceKey rk; + while ((rk = (ReferenceKey)queue.poll()) != null) + { + //System.out.println( "EOObserverCenter.processQueue: removing object" ); + observableToObservers.remove(rk); + } + } + } + + /** + * Private class used to force a hashmap to + * perform key comparisons by reference. + * Retains a weak reference just like WeakHashMap. + */ + static private class ReferenceKey extends WeakReference + { + private int hashCode; + + /** + * Called to create a disposable reference key, + * used for retrieving values from the hashtable. + */ + public ReferenceKey( Object anObject ) + { + super( anObject ); + hashCode = anObject.hashCode(); + } + + /** + * Called to create a reference key that will be + * used as a key in the hashtable, so we need the + * reference queue to later remove the key from the + * table when referred object is no longer in use. + */ + public ReferenceKey( Object anObject, ReferenceQueue aQueue ) + { + super( anObject, aQueue ); + hashCode = anObject.hashCode(); + } + + /** + * Passes through to actual key's hash code. + */ + public int hashCode() + { + return hashCode; + } + + /** + * Compares by reference. + */ + public boolean equals( Object anObject ) + { + if ( ! ( anObject instanceof ReferenceKey ) ) return false; + Object key = get(); + if ( key == null ) return false; + return ( key == ((ReferenceKey)anObject).get() ); + } + } + + private static String debugString() + { + String result = ""; + int count = 0; + synchronized ( instance() ) + { + Object anObject; + Iterator it = observableToObservers.keySet().iterator(); + while ( it.hasNext() ) + { + result += ((Reference)it.next()).get() + " : "; + count++; + /* + Iterator values = ((List)it.next()).iterator(); + while ( values.hasNext() ) + { + anObject = ((Reference)values.next()).get(); + if ( anObject != null ) + { +// if ( anObject instanceof net.wotonomy.ui.MasterDetailAssociation ) + result += anObject.getClass().toString() + " : "; + count++; + } + } + */ + } + result += "["+count+"]"; + } + return result; + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 16:47:14 cgruber + * Move some classes in to "internal" packages and re-work imports, etc. + * + * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * + * Revision 1.1 2006/02/16 13:19:57 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.9 2002/10/24 18:18:12 mpowers + * NSArray's are now considered read-only, so we can return our internal + * representation to reduce unnecessary object allocation. + * + * Revision 1.8 2001/02/21 21:17:32 mpowers + * Now retaining a reference to the recent changes observer. + * Better documented need to retain reference. + * Started implementing notifications. + * + * Revision 1.7 2001/02/17 16:52:05 mpowers + * Changes in imports to support building with jdk1.1 collections. + * + * Revision 1.6 2001/02/05 18:45:45 mpowers + * Reduced access back to private for utility methods. + * + * Revision 1.5 2001/02/05 18:42:32 mpowers + * Updated documentation throughout project. + * + * Revision 1.4 2001/01/18 16:57:47 mpowers + * Added debug facility. + * + * Revision 1.3 2001/01/10 16:28:53 mpowers + * Implemented a compare-by-reference weak hashtable + * because WeakHashMap compared by value and we can't + * assume that display groups won't contain objects + * whose values are equivalent. + * + * Revision 1.2 2001/01/09 20:10:19 mpowers + * Now using weak references to track observables and their observers. + * + * Revision 1.1.1.1 2000/12/21 15:46:44 mpowers + * Contributing wotonomy. + * + * Revision 1.8 2000/12/20 16:25:35 michael + * Added log to all files. + * + * + */ + + -- cgit v1.2.3