/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenFlexo 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openflexo.foundation;
import java.beans.PropertyChangeSupport;
import java.lang.ref.WeakReference;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openflexo.foundation.rm.ResourceStatusModification;
import org.openflexo.inspector.InspectableObject;
import org.openflexo.inspector.InspectorObserver;
import org.openflexo.toolbox.HasPropertyChangeSupport;
/**
* This class represents an observable object, or "data" in the model-view paradigm. It can be subclassed to represent an object that the
* application wants to have observed.
* <p>
* An observable object can have one or more observers. An observer may be any object that implements interface <tt>FlexoObserver</tt>.
* After an observable instance changes, an application calling the <code>FlexoObservable</code>'s <code>notifyObservers</code> method
* causes all of its observers to be notified of the change by a call to their <code>update</code> method.
* <p>
* The order in which notifications will be delivered is unspecified. The default implementation provided in the Observerable class will
* notify Observers in the order in which they registered interest, but subclasses may change this order, use no guaranteed order, deliver
* notifications on separate threads, or may guarantee that their subclass follows this order, as they choose.
* <p>
* Note that this notification mechanism is has nothing to do with threads and is completely separate from the <tt>wait</tt> and
* <tt>notify</tt> mechanism of class <tt>Object</tt>.
* <p>
* When an observable object is newly created, its set of observers is empty. Two observers are considered the same if and only if the
* <tt>equals</tt> method returns true for them.
* <p>
* Additionnaly, this class manages other observers than <tt>FlexoObserver</tt> instances with a delegate <tt>Observable</tt> instance: this
* is because FlexoInspector doesn't not use <tt>FlexoObserver</tt> class, as this is a Flexo-external project.
* <p>
* Features allowing to totally enable or disable observing scheme (or some classes) have also been included.
*
* <br>
* NB: this class has been rewrited from {@link java.util.Observable}, because Java doesn't support multiple inheritance.
*
* @author sguerin
* @see FlexoObservable#notifyObservers()
* @see FlexoObservable#notifyObservers(java.lang.Object)
* @see FlexoObserver
* @see FlexoObserver#update(java.util.Observable, java.lang.Object)
*
*
*/
public abstract class FlexoObservable extends FlexoObject implements HasPropertyChangeSupport {
private static final Logger logger = Logger.getLogger(FlexoObservable.class.getPackage().getName());
public static final String DELETED_PROPERTY = "deleted";
private boolean changed = false;
private Vector<WeakReference<FlexoObserver>> _flexoObservers;
private Vector<WeakReference<InspectorObserver>> _inspectorObservers;
private PropertyChangeSupport _pcSupport;
/**
* This hastable stores for all classes encountered as observers for this observable a property coded as a Boolean indicating if
* notifications should be fired.
*/
protected Hashtable<Class, Boolean> observerClasses;
/**
* This flag codes the necessity to fire notifications or not
*/
protected boolean enableObserving = true;
/**
* Construct a FlexoObservable with zero observers.
*/
public FlexoObservable() {
super();
_pcSupport = new PropertyChangeSupport(this);
_flexoObservers = new Vector<WeakReference<FlexoObserver>>();
_inspectorObservers = new Vector<WeakReference<InspectorObserver>>();
observerClasses = new Hashtable<Class, Boolean>();
}
@Override
public PropertyChangeSupport getPropertyChangeSupport() {
return _pcSupport;
}
/**
* Adds an observer to the set of observers for this object, provided that it is not the same as some observer already in the set. The
* order in which notifications will be delivered to multiple observers is not specified. See the class comment.
*
* @param o
* an observer to be added.
* @throws NullPointerException
* if the parameter o is null.
*/
public void addObserver(FlexoObserver o) {
if (o == null) {
throw new NullPointerException();
}
synchronized (_flexoObservers) {
if (!isObservedBy(o)) {
_flexoObservers.add(new WeakReference<FlexoObserver>(o));
if (observerClasses.get(o.getClass()) == null) {
// Add an entry for this kind of observer
observerClasses.put(o.getClass(), new Boolean(true));
}
}
}
}
/**
* Deletes an observer from the set of observers of this object.
*
* @param o
* the observer to be deleted.
*/
public void deleteObserver(FlexoObserver o) {
synchronized (_flexoObservers) {
Iterator<WeakReference<FlexoObserver>> i = _flexoObservers.iterator();
while (i.hasNext()) {
WeakReference<FlexoObserver> reference = i.next();
if (reference.get() == null) {
i.remove();
} else if (reference.get() == o) {
i.remove();
break;
}
}
}
}
/**
* Deletes an observer from the set of observers of this object.
*
* @param o
* the observer to be deleted.
*/
public void deleteInspectorObserver(InspectorObserver obs) {
Iterator<WeakReference<InspectorObserver>> i = _inspectorObservers.iterator();
while (i.hasNext()) {
WeakReference<InspectorObserver> reference = i.next();
if (reference.get() == null) {
i.remove();
} else if (reference.get() == obs) {
i.remove();
break;
}
}
}
/**
* Adds an observer to the set of observers for this object, provided that it is not the same as some observer already in the set. The
* order in which notifications will be delivered to multiple observers is not specified. See the class comment.
*
* @param o
* an observer to be added.
* @throws NullPointerException
* if the parameter o is null.
*/
public void addInspectorObserver(InspectorObserver obs) {
if (obs == null) {
throw new NullPointerException();
}
if (!isObservedBy(obs)) {
_inspectorObservers.add(new WeakReference<InspectorObserver>(obs));
}
}
/**
* If this object has changed, as indicated by the <code>hasChanged</code> method, then notify all of its observers and then call the
* <code>clearChanged</code> method to indicate that this object has no longer changed.
* <p>
* Each observer has its <code>update</code> method called with two arguments: this observable object and <code>null</code>. In other
* words, this method is equivalent to: <blockquote><tt>
* notifyFlexoObservers(null)</tt></blockquote>
*
* @see java.util.Observable#clearChanged()
* @see java.util.Observable#hasChanged()
* @see java.util.FlexoObserver#update(java.util.Observable, java.lang.Object)
*/
public void notifyObservers() {
notifyObservers(null);
}
/**
* If this object has changed, as indicated by the <code>hasChanged</code> method, then notify all of its observers and then call the
* <code>clearChanged</code> method to indicate that this object has no longer changed.
* <p>
* Each observer has its <code>update</code> method called with two arguments: this observable object and the <code>arg</code> argument.
*
* @param arg
* any object.
* @see java.util.Observable#clearChanged()
* @see java.util.Observable#hasChanged()
* @see java.util.FlexoObserver#update(java.util.Observable, java.lang.Object)
*/
public void notifyObservers(DataModification arg) {
if (enableObserving) {
/*
* a temporary array buffer, used as a snapshot of the state of
* current FlexoObservers.
*/
WeakReference<FlexoObserver>[] arrLocal1 = new WeakReference[_flexoObservers.size()];
WeakReference<InspectorObserver>[] arrLocal2 = new WeakReference[_inspectorObservers.size()];
synchronized (this) {
/*
* We don't want the FlexoObserver doing callbacks into
* arbitrary code while holding its own Monitor. The code where
* we extract each Observable from the Vector and store the
* state of the FlexoObserver needs synchronization, but
* notifying observers does not (should not). The worst result
* of any potential race-condition here is that: 1) a
* newly-added FlexoObserver will miss a notification in
* progress 2) a recently unregistered FlexoObserver will be
* wrongly notified when it doesn't care
*/
if (!changed && !(arg instanceof ResourceStatusModification)) {
return;
}
arrLocal1 = _flexoObservers.toArray(arrLocal1);
arrLocal2 = _inspectorObservers.toArray(arrLocal2);
clearChanged();
}
// Notify all Flexo observers of observing for this kind of observer
// is enabled
for (int i = arrLocal1.length - 1; i >= 0; i--) {
WeakReference<FlexoObserver> weakRef = arrLocal1[i];
if (weakRef != null) {
FlexoObserver o = arrLocal1[i].get();
if (o == null) {
_flexoObservers.remove(arrLocal1[i]);
continue;
}
if (observerClasses.get(o.getClass()).booleanValue()) {
o.update(this, arg);
}
}
}
// Notify all Inspector observers
for (int i = arrLocal2.length - 1; i >= 0; i--) {
WeakReference<InspectorObserver> weakRef = arrLocal2[i];
if (weakRef != null) {
InspectorObserver o = arrLocal2[i].get();
if (o == null) {
_inspectorObservers.remove(arrLocal2[i]);
continue;
}
o.update((InspectableObject) this, arg);
}
}
if (arg != null) {
_pcSupport.firePropertyChange(arg.propertyName() != null ? arg.propertyName() : "", arg.oldValue(), arg.newValue());
}
}
}
/**
* Build and return a Vector of all current observers, as a snapshot of the state of current FlexoObservers and Inspector observers. Be
* careful with method such as indexOf, contains, etc... which usually rely on equals() method. They have been overriden to use
* explicitly the == operator.
*/
public Vector getAllObservers() {
Vector returned = new Vector() {
@Override
public synchronized int indexOf(Object o, int index) {
for (int i = index; i < size(); i++) {
if (elementData[i] == o) {
return i;
}
}
return -1;
}
};
Iterator<WeakReference<FlexoObserver>> i = _flexoObservers.iterator();
while (i.hasNext()) {
WeakReference<FlexoObserver> reference = i.next();
if (reference.get() == null) {
i.remove();
} else {
returned.add(reference.get());
}
}
Iterator<WeakReference<InspectorObserver>> i2 = _inspectorObservers.iterator();
while (i2.hasNext()) {
WeakReference<InspectorObserver> reference = i2.next();
if (reference.get() == null) {
i2.remove();
} else {
returned.add(reference.get());
}
}
return returned;
}
/**
* Prints array of all current observers, as a snapshot of the state of current FlexoObservers.
*/
public void printObservers() {
Enumeration e = getAllObservers().elements();
if (logger.isLoggable(Level.INFO)) {
logger.info("Observers of: " + getClass().getName() + " / " + this);
}
int i = 0;
while (e.hasMoreElements()) {
Object object = e.nextElement();
if (object instanceof FlexoObserver) {
FlexoObserver o = (FlexoObserver) object;
if (logger.isLoggable(Level.INFO)) {
logger.info(" * " + i + " hash= " + Integer.toHexString(o.hashCode()) + " FlexoObserver: " + o.getClass().getName()
+ " / " + o);
}
}
if (object instanceof InspectorObserver) {
InspectorObserver o = (InspectorObserver) object;
if (logger.isLoggable(Level.INFO)) {
logger.info(" * " + i + " hash= " + Integer.toHexString(o.hashCode()) + " InspectorObserver: " + o.getClass().getName()
+ " / " + o);
}
}
i++;
}
}
/**
* Clears the observer list so that this object no longer has any observers.
*/
public synchronized void deleteObservers() {
synchronized (_flexoObservers) {
_flexoObservers.clear();
}
_inspectorObservers.clear();
}
/**
* Marks this <tt>Observable</tt> object as having been changed; the <tt>hasChanged</tt> method will now return <tt>true</tt>.
*/
protected synchronized void setChanged() {
changed = true;
}
/**
* Indicates that this object has no longer changed, or that it has already notified all of its observers of its most recent change, so
* that the <tt>hasChanged</tt> method will now return <tt>false</tt>. This method is called automatically by the
* <code>notifyFlexoObservers</code> methods.
*
* @see java.util.Observable#notifyFlexoObservers()
* @see java.util.Observable#notifyFlexoObservers(java.lang.Object)
*/
protected synchronized void clearChanged() {
changed = false;
}
/**
* Tests if this object has changed.
*
* @return <code>true</code> if and only if the <code>setChanged</code> method has been called more recently than the
* <code>clearChanged</code> method on this object; <code>false</code> otherwise.
* @see java.util.Observable#clearChanged()
* @see java.util.Observable#setChanged()
*/
public boolean hasChanged() {
return changed;
}
/**
* Returns the number of observers of this <tt>Observable</tt> object.
*
* @return the number of observers of this object.
*/
public int countObservers() {
return _flexoObservers.size() + _inspectorObservers.size();
}
/**
* Enable observing. Does not affect disabled observing classes
*/
public synchronized void enableObserving() {
enableObserving = true;
}
/**
* Disable observing.
*/
public synchronized void disableObserving() {
enableObserving = false;
}
/**
* Enable observing for all observers of class 'observerClass' and all related subclasses
*/
public synchronized void enableObserving(Class observerClass) {
for (Enumeration en = observerClasses.keys(); en.hasMoreElements();) {
Class temp = (Class) en.nextElement();
if (observerClass.isAssignableFrom(temp)) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Enable observing for " + temp.getName());
}
observerClasses.put(temp, new Boolean(true));
}
}
}
/**
* Disable observing for all observers of class 'observerClass' and all related subclasses
*/
public synchronized void disableObserving(Class observerClass) {
for (Enumeration en = observerClasses.keys(); en.hasMoreElements();) {
Class temp = (Class) en.nextElement();
if (observerClass.isAssignableFrom(temp)) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Disable observing for " + temp.getName());
}
observerClasses.put(temp, new Boolean(false));
}
}
}
public static boolean areSameValue(Object o1, Object o2) {
if (o1 == null) {
return o2 == null;
}
return o1.equals(o2);
}
public boolean isObservedBy(FlexoObserver observer) {
synchronized (_flexoObservers) {
Iterator<WeakReference<FlexoObserver>> i = _flexoObservers.iterator();
while (i.hasNext()) {
WeakReference<FlexoObserver> reference = i.next();
if (reference.get() == null) {
i.remove();
} else if (reference.get() == observer) {
return true;
}
}
return false;
}
}
public synchronized boolean isObservedBy(InspectorObserver observer) {
Iterator<WeakReference<InspectorObserver>> i = _inspectorObservers.iterator();
while (i.hasNext()) {
WeakReference<InspectorObserver> reference = i.next();
if (reference.get() == null) {
i.remove();
} else if (reference.get() == observer) {
return true;
}
}
return false;
}
}