/*
* Copyright (C) 2011 Virginia Tech Department of Computer Science
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sofia.util;
import sofia.internal.events.EventDispatcher;
import java.util.LinkedHashSet;
import java.util.Set;
//--------------------------------------------------------------------------
/**
* A base class for classes that want to send out change notifications using
* the "observer" pattern.
* <p>
* If the variable {@code observable} is a reference to
* an object that extends the {@code Observable} class, then any other object
* can observe changes to it by calling {@link #addObserver(Object)}:
* </p>
* <pre>
* observable.addObserver(x);</pre>
* <p>
* In this case, when a change notification occurs, the system will call, on
* the object {@code x}, a method named {@code changeWasObserved}, whose first
* parameter is the same type (or a superclass) as {@code observable}. See the
* documentation for the {@link #notifyObservers(Object...)} method to see how
* the {@code changeWasObserved} callback can take additional parameters as
* well.
* </p><p>
* To use a different method name than {@code changeWasObserved}, pass its name
* as the second parameter to {@link #addObserver(Object, String)}:
* </p>
* <pre>
* observable.addObserver(x, "methodToCall");</pre>
*
* @author Tony Allevato
*/
public abstract class Observable
{
//~ Fields ................................................................
// The default name of the method that will be called on each observer
// object.
private static final String DEFAULT_METHOD_NAME = "changeWasObserved";
// The set of observers for this object.
private transient LinkedHashSet<Observer> observers;
//~ Constructors ..........................................................
// ----------------------------------------------------------
/**
* Initializes the object with an empty set of observers.
*/
public Observable()
{
observers = new LinkedHashSet<Observer>();
}
//~ Methods ...............................................................
// ----------------------------------------------------------
/**
* <p>
* Adds the specified object {@code observer} to the set of observers for
* this object. When an instance of this observable object calls
* {@link #notifyObservers()}, the {@code changeWasObserved} method will be
* called on the observer.
* </p><p>
* The {@code changeWasObserved} method on the observer must take a single
* argument that is the same type (or a superclass) of this observable
* object. You can also have other overloads of {@code changeWasObserved}
* that take additional parameters after the observable parameter; these
* match the parameters that the observable passes when it calls
* {@link #notifyObservers(Object...)}.
* </p>
*
* @param observer the object that will observe changes to this object
*/
public synchronized void addObserver(Object observer)
{
addObserver(observer, DEFAULT_METHOD_NAME);
}
// ----------------------------------------------------------
/**
* <p>
* Adds the specified object {@code observer} to the set of observers for
* this object, but using a method name different from the default. When
* an instance of this observable object calls {@link #notifyObservers()},
* the method with the specified name will be called on the observer.
* </p><p>
* The method specified here must take a single argument that is the same
* type (or a superclass) of this observable object. You can also have
* other overloads of this method that take additional parameters after the
* observable parameter; these match the parameters that the observable
* passes when it calls {@link #notifyObservers(Object...)}.
* </p>
*
* @param observer the object that will observe changes to this object
* @param method the name of the method that will be called on the observer
*/
public synchronized void addObserver(Object observer, String method)
{
observers.add(new Observer(observer, method));
}
// ----------------------------------------------------------
/**
* Removes the specified object's {@code changeWasObserved} method from the
* set of observers for the receiver. In other words, after this method is
* called, the {@code changeWasObserved} method will no longer be called
* when this object notifies observers of changes.
*
* @param observer the observer whose {@code changeWasObserved} method
* should be removed from the set of observers
*/
public synchronized void removeObserver(Object observer)
{
removeObserver(observer, DEFAULT_METHOD_NAME);
}
// ----------------------------------------------------------
/**
* Removes the specified object's named method from the set of observers
* for the receiver. In other words, after this method is called, the
* method with the name given in the {@code method} argument will no longer
* be called when this object notifies observers of changes.
*
* @param observer the observer whose method should be removed from the set
* of observers
* @param method the method that should be removed from the set of
* observers
*/
public synchronized void removeObserver(Object observer, String method)
{
removeObserver(new Observer(observer, method));
}
// ----------------------------------------------------------
/**
* Removes all observers from the receiver.
*/
public synchronized void clearObservers()
{
observers.clear();
}
// ----------------------------------------------------------
/**
* <p>
* Notifies all observers of the receiver that a change has occurred
* affecting the state of the object. Typically, this method is called
* from inside a setter method or some other method that performs a
* computation or an action on an observable object.
* </p><p>
* This method can take an argument list of your choosing. These arguments
* are passed directly to the {@code changeWasObserved} method, after the
* observable object itself, on the observer object. So, for example, if
* your subclass of {@code Observable} is called {@code MyModel}, and you
* call {@code notifyObservers} from within it like this:
* </p>
* <pre>
* notifyObservers("hello", 5, 9.3);</pre>
* <p>
* Then the observer would be searched for a {@code changeWasObserved}
* method that has its first parameter of type {@code MyModel} (or a
* superclass) followed by parameter types compatible with those in the
* argument list above; for example:
* </p>
* <pre>
* public void changeWasObserved(MyModel model, String str, int x, double y)</pre>
* <p>
* If no such method exists, then an attempt is made to call one that takes
* only the observable object as a parameter.
* </p>
* <pre>
* public void changeWasObserved(MyModel model)</pre>
* <p>
* The order that the observers are called is undefined. User code should
* not be written that depends on some observers being called before or
* after others.
* </p>
*/
@SuppressWarnings("unchecked")
public void notifyObservers(Object... arguments)
{
Set<Observer> clonedObservers;
synchronized (this)
{
clonedObservers = (Set<Observer>) observers.clone();
}
for (Observer observer : clonedObservers)
{
observer.observe(this, arguments);
}
}
// ----------------------------------------------------------
/**
* Removes the internal observer from the set of observers.
*
* @param observer the internal observer
*/
private synchronized void removeObserver(Observer observer)
{
observers.remove(observer);
}
//~ Inner classes .........................................................
// ----------------------------------------------------------
/**
* Encapsulates the information needed to represent an observer -- the
* receiving object, the method to call on it, and a method dispatcher to
* perform the dynamic call.
*/
private static class Observer
{
//~ Fields ............................................................
private Object receiver;
private String method;
private EventDispatcher event;
//~ Constructors ......................................................
// ----------------------------------------------------------
public Observer(Object receiver, String method)
{
this.receiver = receiver;
this.method = method;
event = new EventDispatcher(method);
}
//~ Methods ...........................................................
// ----------------------------------------------------------
public void observe(Object object, Object... arguments)
{
// Create a new argument array that has the observable object
// first, followed by the remaining arguments.
Object[] realArgs = new Object[arguments.length + 1];
realArgs[0] = object;
System.arraycopy(arguments, 0, realArgs, 1, arguments.length);
// Try to dispatch first to the one that takes the actual
// arguments, or to one that just takes the observable object.
@SuppressWarnings("unused")
boolean result =
event.dispatch(receiver, realArgs) ||
event.dispatch(receiver, object);
}
// ----------------------------------------------------------
@Override
public int hashCode()
{
return receiver.getClass().hashCode() ^ method.hashCode();
}
// ----------------------------------------------------------
@Override
public boolean equals(Object other)
{
if (other instanceof Observer)
{
Observer otherObserver = (Observer) other;
return receiver == otherObserver.receiver
&& method.equals(otherObserver.method);
}
else
{
return false;
}
}
}
}