/*******************************************************************************
* Breakout Cave Survey Visualizer
*
* Copyright (C) 2014 James Edwards
*
* jedwards8 at fastmail dot fm
*
* This program 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 2 of the License, or (at your option) any later
* version.
*
* This program 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
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*******************************************************************************/
package org.andork.event;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.ConcurrentModificationException;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
/**
* This class is an adaptation of {@link PropertyChangeSupport} for
* {@link BasicPropertyChangeListener}.
*/
public class BasicPropertyChangeSupport implements Serializable {
/**
* Contains delegates for the only methods of
* {@link BasicPropertyChangeSupport} that users of a class that
* incorporates a {@code BasicPropertyChangeSupport} should use.
*
* @author Andy
*/
public class External {
/**
* Add a BasicPropertyChangeListener to the listener list. The listener
* is registered for all properties. The same listener object may be
* added more than once, and will be called as many times as it is
* added. If <code>listener</code> is null, no exception is thrown and
* no action is taken.
*
* @param listener
* The BasicPropertyChangeListener to be added
*/
public synchronized void addPropertyChangeListener(BasicPropertyChangeListener listener) {
BasicPropertyChangeSupport.this.addPropertyChangeListener(listener);
}
/**
* Add a BasicPropertyChangeListener for a specific property. The
* listener will be invoked only when a call on firePropertyChange names
* that specific property. The same listener object may be added more
* than once. For each property, the listener will be invoked the number
* of times it was added for that property. If <code>propertyName</code>
* or <code>listener</code> is null, no exception is thrown and no
* action is taken.
*
* @param propertyName
* The name of the property to listen on.
* @param listener
* The BasicPropertyChangeListener to be added
*/
public synchronized void addPropertyChangeListener(Object propertyName, BasicPropertyChangeListener listener) {
BasicPropertyChangeSupport.this.addPropertyChangeListener(propertyName, listener);
}
/**
* Returns an array of all the listeners that were added to the
* BasicPropertyChangeSupport object with addPropertyChangeListener().
* <p>
* If some listeners have been added with a named property, then the
* returned array will be a mixture of PropertyChangeListeners and
* <code>BasicPropertyChangeListenerProxy</code>s. If the calling method
* is interested in distinguishing the listeners then it must test each
* element to see if it's a
* <code>BasicPropertyChangeListenerProxy</code>, perform the cast, and
* examine the parameter.
*
* <pre>
* BasicPropertyChangeListener[] listeners = bean.getPropertyChangeListeners();
* for (int i = 0; i < listeners.length; i++) {
* if (listeners[i] instanceof BasicPropertyChangeListenerProxy) {
* BasicPropertyChangeListenerProxy proxy = (BasicPropertyChangeListenerProxy) listeners[i];
* if (proxy.getPropertyName().equals("foo")) {
* // proxy is a BasicPropertyChangeListener which was
* // associated
* // with the property named "foo"
* }
* }
* }
* </pre>
*
* @see BasicPropertyChangeListenerProxy
* @return all of the <code>PropertyChangeListeners</code> added or an
* empty array if no listeners have been added
* @since 1.4
*/
public synchronized BasicPropertyChangeListener[] getPropertyChangeListeners() {
return BasicPropertyChangeSupport.this.getPropertyChangeListeners();
}
/**
* Returns an array of all the listeners which have been associated with
* the named property.
*
* @param propertyName
* The name of the property being listened to
* @return all of the <code>PropertyChangeListeners</code> associated
* with the named property. If no such listeners have been
* added, or if <code>propertyName</code> is null, an empty
* array is returned.
*/
public synchronized BasicPropertyChangeListener[] getPropertyChangeListeners(Object propertyName) {
return BasicPropertyChangeSupport.this.getPropertyChangeListeners(propertyName);
}
/**
* Check if there are any listeners for a specific property, including
* those registered on all properties. If <code>propertyName</code> is
* null, only check for listeners registered on all properties.
*
* @param propertyName
* the property name.
* @return true if there are one or more listeners for the given
* property
*/
public synchronized boolean hasListeners(Object propertyName) {
return BasicPropertyChangeSupport.this.hasListeners(propertyName);
}
/**
* Remove a BasicPropertyChangeListener from the listener list. This
* removes a BasicPropertyChangeListener that was registered for all
* properties. If <code>listener</code> was added more than once to the
* same event source, it will be notified one less time after being
* removed. If <code>listener</code> is null, or was never added, no
* exception is thrown and no action is taken.
*
* @param listener
* The BasicPropertyChangeListener to be removed
*/
public synchronized void removePropertyChangeListener(BasicPropertyChangeListener listener) {
BasicPropertyChangeSupport.this.removePropertyChangeListener(listener);
}
/**
* Remove a BasicPropertyChangeListener for a specific property. If
* <code>listener</code> was added more than once to the same event
* source for the specified property, it will be notified one less time
* after being removed. If <code>propertyName</code> is null, no
* exception is thrown and no action is taken. If <code>listener</code>
* is null, or was never added for the specified property, no exception
* is thrown and no action is taken.
*
* @param propertyName
* The name of the property that was listened on.
* @param listener
* The BasicPropertyChangeListener to be removed
*/
public synchronized void removePropertyChangeListener(Object propertyName,
BasicPropertyChangeListener listener) {
BasicPropertyChangeSupport.this.removePropertyChangeListener(propertyName, listener);
}
}
private static final long serialVersionUID = -486778571303724183L;
/**
* The listener list. A copy-on-write array is used instead of an List
* because the latter would run the risk of
* {@link ConcurrentModificationException}s (if a listener removed itself or
* another listener during an event notification).
*/
protected transient BasicPropertyChangeListener[] listeners;
private External external;
/**
* Hashtable for managing listeners for specific properties. Maps property
* names to BasicPropertyChangeSupport objects.
*
* @serial
* @since 1.2
*/
private Hashtable<Object, BasicPropertyChangeSupport> children;
/**
* Internal version number
*
* @serial
* @since
*/
private int basicPropertyChangeSupportSerializedDataVersion = 2;
/**
* Constructs a <code>BasicPropertyChangeSupport</code> object.
*
* @param sourceBean
* The bean to be given as the source for any events.
*/
public BasicPropertyChangeSupport() {
}
/**
* Add a BasicPropertyChangeListener to the listener list. The listener is
* registered for all properties. The same listener object may be added more
* than once, and will be called as many times as it is added. If
* <code>listener</code> is null, no exception is thrown and no action is
* taken.
*
* @param listener
* The BasicPropertyChangeListener to be added
*/
public synchronized void addPropertyChangeListener(BasicPropertyChangeListener listener) {
if (listener == null) {
return;
}
if (listener instanceof BasicPropertyChangeListenerProxy) {
BasicPropertyChangeListenerProxy proxy = (BasicPropertyChangeListenerProxy) listener;
// Call two argument add method.
addPropertyChangeListener(proxy.getPropertyName(), (BasicPropertyChangeListener) proxy.getListener());
} else {
BasicPropertyChangeListener[] newListeners;
if (listeners != null) {
newListeners = Arrays.copyOf(listeners, listeners.length + 1);
} else {
newListeners = new BasicPropertyChangeListener[1];
}
newListeners[newListeners.length - 1] = listener;
listeners = newListeners;
}
}
/**
* Add a BasicPropertyChangeListener for a specific property. The listener
* will be invoked only when a call on firePropertyChange names that
* specific property. The same listener object may be added more than once.
* For each property, the listener will be invoked the number of times it
* was added for that property. If <code>propertyName</code> or
* <code>listener</code> is null, no exception is thrown and no action is
* taken.
*
* @param propertyName
* The name of the property to listen on.
* @param listener
* The BasicPropertyChangeListener to be added
*/
public synchronized void addPropertyChangeListener(Object propertyName, BasicPropertyChangeListener listener) {
if (listener == null || propertyName == null) {
return;
}
if (children == null) {
children = new java.util.Hashtable<Object, BasicPropertyChangeSupport>();
}
BasicPropertyChangeSupport child = children.get(propertyName);
if (child == null) {
child = new BasicPropertyChangeSupport();
children.put(propertyName, child);
}
child.addPropertyChangeListener(listener);
}
/**
* @return the {@link External} for this {@code BasicPropertyChangeSupport}.
*/
public synchronized External external() {
if (external == null) {
external = new External();
}
return external;
}
/**
* Report a <code>boolean</code> bound indexed property update to any
* registered listeners.
* <p>
* No event is fired if old and new values are equal and non-null.
* <p>
* This is merely a convenience wrapper around the more general
* fireIndexedPropertyChange method which takes Object values.
*
* @param propertyName
* The programmatic name of the property that was changed.
* @param index
* index of the property element that was changed.
* @param oldValue
* The old value of the property.
* @param newValue
* The new value of the property.
* @since 1.5
*/
public void fireIndexedPropertyChange(Object source, Object propertyName, int index, boolean oldValue,
boolean newValue) {
if (oldValue == newValue) {
return;
}
fireIndexedPropertyChange(source, propertyName, index, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
}
/**
* Report an <code>int</code> bound indexed property update to any
* registered listeners.
* <p>
* No event is fired if old and new values are equal and non-null.
* <p>
* This is merely a convenience wrapper around the more general
* fireIndexedPropertyChange method which takes Object values.
*
* @param propertyName
* The programmatic name of the property that was changed.
* @param index
* index of the property element that was changed.
* @param oldValue
* The old value of the property.
* @param newValue
* The new value of the property.
* @since 1.5
*/
public void fireIndexedPropertyChange(Object source, Object propertyName, int index, int oldValue, int newValue) {
if (oldValue == newValue) {
return;
}
fireIndexedPropertyChange(source, propertyName, index, new Integer(oldValue), new Integer(newValue));
}
/**
* Report a bound indexed property update to any registered listeners.
* <p>
* No event is fired if old and new values are equal and non-null.
*
* @param propertyName
* The programmatic name of the property that was changed.
* @param index
* index of the property element that was changed.
* @param oldValue
* The old value of the property.
* @param newValue
* The new value of the property.
* @since 1.5
*/
public void fireIndexedPropertyChange(Object source, Object propertyName, int index, Object oldValue,
Object newValue) {
firePropertyChange(source, propertyName, oldValue, newValue, index);
}
/**
* Report a boolean bound property update to any registered listeners. No
* event is fired if old and new are equal and non-null.
* <p>
* This is merely a convenience wrapper around the more general
* firePropertyChange method that takes Object values.
*
* @param propertyName
* The programmatic name of the property that was changed.
* @param oldValue
* The old value of the property.
* @param newValue
* The new value of the property.
*/
public void firePropertyChange(Object source, Object propertyName, boolean oldValue, boolean newValue) {
firePropertyChange(source, propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
}
/**
* Report an int bound property update to any registered listeners. No event
* is fired if old and new are equal and non-null.
* <p>
* This is merely a convenience wrapper around the more general
* firePropertyChange method that takes Object values.
*
* @param propertyName
* The programmatic name of the property that was changed.
* @param oldValue
* The old value of the property.
* @param newValue
* The new value of the property.
*/
public void firePropertyChange(Object source, Object propertyName, int oldValue, int newValue) {
if (oldValue == newValue) {
return;
}
firePropertyChange(source, propertyName, new Integer(oldValue), new Integer(newValue));
}
/**
* Fire an existing PropertyChangeEvent to any registered listeners. No
* event is fired if the given event's old and new values are equal and
* non-null.
*
* @param evt
* The PropertyChangeEvent object.
*/
public void firePropertyChange(Object source, Object propertyName, Object oldValue, Object newValue) {
if (oldValue != null && newValue != null && oldValue.equals(newValue)) {
return;
}
firePropertyChange(source, propertyName, oldValue, newValue, -1);
}
/**
* Fire an existing PropertyChangeEvent to any registered listeners. No
* event is fired if the given event's old and new values are equal and
* non-null.
*
* @param evt
* The PropertyChangeEvent object.
*/
public void firePropertyChange(Object source, Object propertyName, Object oldValue, Object newValue, int index) {
if (oldValue != null && newValue != null && oldValue.equals(newValue)) {
return;
}
if (listeners != null) {
for (BasicPropertyChangeListener target : listeners) {
target.propertyChange(source, propertyName, oldValue, newValue, index);
}
}
if (children != null && propertyName != null) {
BasicPropertyChangeSupport child = null;
child = children.get(propertyName);
if (child != null) {
child.firePropertyChange(source, propertyName, oldValue, newValue, index);
}
}
}
/**
* Returns an array of all the listeners that were added to the
* BasicPropertyChangeSupport object with addPropertyChangeListener().
* <p>
* If some listeners have been added with a named property, then the
* returned array will be a mixture of PropertyChangeListeners and
* <code>BasicPropertyChangeListenerProxy</code>s. If the calling method is
* interested in distinguishing the listeners then it must test each element
* to see if it's a <code>BasicPropertyChangeListenerProxy</code>, perform
* the cast, and examine the parameter.
*
* <pre>
* BasicPropertyChangeListener[] listeners = bean.getPropertyChangeListeners();
* for (int i = 0; i < listeners.length; i++) {
* if (listeners[i] instanceof BasicPropertyChangeListenerProxy) {
* BasicPropertyChangeListenerProxy proxy = (BasicPropertyChangeListenerProxy) listeners[i];
* if (proxy.getPropertyName().equals("foo")) {
* // proxy is a BasicPropertyChangeListener which was
* // associated
* // with the property named "foo"
* }
* }
* }
* </pre>
*
* @see BasicPropertyChangeListenerProxy
* @return all of the <code>PropertyChangeListeners</code> added or an empty
* array if no listeners have been added
* @since 1.4
*/
public synchronized BasicPropertyChangeListener[] getPropertyChangeListeners() {
List returnList = new ArrayList();
// Add all the PropertyChangeListeners
if (listeners != null) {
returnList.addAll(Arrays.asList(listeners));
}
// Add all the PropertyChangeListenerProxys
if (children != null) {
Iterator<Object> iterator = children.keySet().iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
BasicPropertyChangeSupport child = children.get(key);
BasicPropertyChangeListener[] childListeners = child.getPropertyChangeListeners();
for (int index = childListeners.length - 1; index >= 0; index--) {
returnList.add(new BasicPropertyChangeListenerProxy(key, childListeners[index]));
}
}
}
return (BasicPropertyChangeListener[]) returnList.toArray(new BasicPropertyChangeListener[0]);
}
/**
* Returns an array of all the listeners which have been associated with the
* named property.
*
* @param propertyName
* The name of the property being listened to
* @return all of the <code>PropertyChangeListeners</code> associated with
* the named property. If no such listeners have been added, or if
* <code>propertyName</code> is null, an empty array is returned.
*/
public synchronized BasicPropertyChangeListener[] getPropertyChangeListeners(Object propertyName) {
ArrayList returnList = new ArrayList();
if (children != null && propertyName != null) {
BasicPropertyChangeSupport support = children.get(propertyName);
if (support != null) {
returnList.addAll(Arrays.asList(support.getPropertyChangeListeners()));
}
}
return (BasicPropertyChangeListener[]) returnList.toArray(new BasicPropertyChangeListener[0]);
}
/**
* Check if there are any listeners for a specific property, including those
* registered on all properties. If <code>propertyName</code> is null, only
* check for listeners registered on all properties.
*
* @param propertyName
* the property name.
* @return true if there are one or more listeners for the given property
*/
public synchronized boolean hasListeners(Object propertyName) {
if (listeners != null) {
// there is a generic listener
return true;
}
if (children != null && propertyName != null) {
BasicPropertyChangeSupport child = children.get(propertyName);
if (child != null && child.listeners != null) {
return child.listeners != null;
}
}
return false;
}
private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
s.defaultReadObject();
Object listenerOrNull;
while (null != (listenerOrNull = s.readObject())) {
addPropertyChangeListener((BasicPropertyChangeListener) listenerOrNull);
}
}
/**
* Remove a BasicPropertyChangeListener from the listener list. This removes
* a BasicPropertyChangeListener that was registered for all properties. If
* <code>listener</code> was added more than once to the same event source,
* it will be notified one less time after being removed. If
* <code>listener</code> is null, or was never added, no exception is thrown
* and no action is taken.
*
* @param listener
* The BasicPropertyChangeListener to be removed
*/
public synchronized void removePropertyChangeListener(BasicPropertyChangeListener listener) {
if (listener == null) {
return;
}
if (listener instanceof BasicPropertyChangeListenerProxy) {
BasicPropertyChangeListenerProxy proxy = (BasicPropertyChangeListenerProxy) listener;
// Call two argument remove method.
removePropertyChangeListener(proxy.getPropertyName(), (BasicPropertyChangeListener) proxy.getListener());
} else {
if (listeners != null) {
int i;
for (i = 0; i < listeners.length; i++) {
if (listeners[i] == listener) {
break;
}
}
if (i < listeners.length) {
if (listeners.length == 1) {
listeners = null;
} else {
BasicPropertyChangeListener[] newListeners = new BasicPropertyChangeListener[listeners.length
- 1];
System.arraycopy(listeners, 0, newListeners, 0, i);
System.arraycopy(listeners, i + 1, newListeners, i, listeners.length - i - 1);
listeners = newListeners;
}
}
}
}
}
/**
* Remove a BasicPropertyChangeListener for a specific property. If
* <code>listener</code> was added more than once to the same event source
* for the specified property, it will be notified one less time after being
* removed. If <code>propertyName</code> is null, no exception is thrown and
* no action is taken. If <code>listener</code> is null, or was never added
* for the specified property, no exception is thrown and no action is
* taken.
*
* @param propertyName
* The name of the property that was listened on.
* @param listener
* The BasicPropertyChangeListener to be removed
*/
public synchronized void removePropertyChangeListener(Object propertyName, BasicPropertyChangeListener listener) {
if (listener == null || propertyName == null) {
return;
}
if (children == null) {
return;
}
BasicPropertyChangeSupport child = children.get(propertyName);
if (child == null) {
return;
}
child.removePropertyChangeListener(listener);
}
/**
* @serialData Null terminated list of <code>PropertyChangeListeners</code>.
* <p>
* At serialization time we skip non-serializable listeners and
* only serialize the serializable listeners.
*
*/
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
if (listeners != null) {
for (BasicPropertyChangeListener l : listeners) {
if (l instanceof Serializable) {
s.writeObject(l);
}
}
}
s.writeObject(null);
}
}