summaryrefslogtreecommitdiff
path: root/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverCenter.java
diff options
context:
space:
mode:
Diffstat (limited to 'projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverCenter.java')
-rw-r--r--projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOObserverCenter.java547
1 files changed, 547 insertions, 0 deletions
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. <br><br>
+*
+* 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. <br><br>
+*
+* 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.
+ *
+ *
+ */
+
+