/*
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.
*
*
*/