/**
*
*/
package cz.cuni.mff.peckam.java.origamist.utils;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
/**
* A support class for handling observable properties.
*
* @author Martin Pecka
*/
public class ObservablePropertiesSupport
{
/** The map of registered listeners. <code>null</code> holds listeners to be fired on all events. */
protected HashMap<String, List<ObservablePropertyListener<?>>> listeners = new HashMap<String, List<ObservablePropertyListener<?>>>();
/** The object provided as event source. */
protected Object source;
/**
* @param source The source of events generated by this support.
*/
public ObservablePropertiesSupport(Object source)
{
this.source = source;
listeners.put(null, new LinkedList<ObservablePropertyListener<?>>());
}
/**
* Add a ObservablePropertyListener 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 ObservablePropertyListener to be added.
*/
public void addObservablePropertyListener(ObservablePropertyListener<?> listener)
{
if (listener == null)
return;
listeners.get(null).add(listener);
}
/**
* Add a ObservablePropertyListener 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>property</code> or <code>listener</code> is null, no exception is thrown and no action is taken.
*
* @param property The name of the property to listen on.
* @param listener The ObservablePropertyListener to be added.
*/
public void addObservablePropertyListener(String property, ObservablePropertyListener<?> listener)
{
if (property == null || listener == null)
return;
if (listeners.get(property) == null)
listeners.put(property, new LinkedList<ObservablePropertyListener<?>>());
listeners.get(property).add(listener);
}
/**
* Remove a ObservablePropertyListener 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>property</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 property The name of the property that was listened on.
* @param listener The ObservablePropertyListener to be removed.
*/
public void removeObservablePropertyListener(String property, ObservablePropertyListener<?> listener)
{
if (property == null || listener == null)
return;
if (listeners.get(property) == null)
return;
listeners.get(property).remove(listener);
if (listeners.get(property).size() == 0)
listeners.remove(property);
}
/**
* Remove a ObservablePropertyListener from the listener list.
* This removes a ObservablePropertyListener 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 ObservablePropertyListener to be removed.
*/
public void removeObservablePropertyListener(ObservablePropertyListener<?> listener)
{
if (listener == null)
return;
listeners.get(null).remove(listener);
}
/**
* Removes all occurences of the given instance of listener from this support.
*
* @param listener The listener instance to remove.
*
* @return The list of properties this listener was removed from (<code>null</code> signalizes all properties).
*/
public List<String> removeAllObservablePropertyListeners(ObservablePropertyListener<?> listener)
{
List<String> result = new LinkedList<String>();
for (Entry<String, List<ObservablePropertyListener<?>>> entry : listeners.entrySet()) {
if (entry.getValue().remove(listener)) {
result.add(entry.getKey());
}
}
return result;
}
/**
* Add a ObservablePropertyListener for a specific hierarchical 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>property</code> or <code>listener</code> is null, no exception is thrown and no action is taken.
* If <code>property</code> is empty, {@link #addObservablePropertyListener(ObservablePropertyListener)} will be
* called.
*
* @param listener The ObservablePropertyListener to be added.
* @param property The components of the hierarchical name of the property to listen on.
*/
public void addObservablePropertyListener(ObservablePropertyListener<?> listener, String... property)
{
String name = getHierarchicalName(property);
addObservablePropertyListener("".equals(name) ? null : name, listener);
}
/**
* Remove a ObservablePropertyListener for a specific hierarchical 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>listener</code> is null, or was never added for the specified property, no exception is thrown and no
* action is taken.
* If <code>property</code> is null, no exception is thrown and no action is taken.
* If <code>property</code> is empty, {@link #removeObservablePropertyListener(ObservablePropertyListener)} will be
* called.
*
* @param listener The ObservablePropertyListener to be removed.
* @param property The components of the hierarchical name of the property that was listened on.
*/
public void removeObservablePropertyListener(ObservablePropertyListener<?> listener, String... property)
{
String name = getHierarchicalName(property);
removeObservablePropertyListener("".equals(name) ? null : name, listener);
}
/**
* Add a listener that will receive all events with property names beginning with the specified prefix.
* <p>
* Remove this listener by {@link #removePrefixedObservablePropertyListener(ObservablePropertyListener)}.
*
* @param listener The listener to be triggered.
* @param prefix The prefix with which this listener will be triggered. If it composes of more names, a hierarchical
* property name is created out of the given property names.
*/
public <T> void addPrefixedObservablePropertyListener(ObservablePropertyListener<T> listener, String... prefix)
{
addObservablePropertyListener(new PrefixedObservablePropertyListener<T>(getHierarchicalName(prefix), listener));
}
/**
* Remove all occurences of a prefixed listener added by
* {@link #addPrefixedObservablePropertyListener(String, ObservablePropertyListener)}.
*
* @param listener The listener to be removed.
*/
public <T> void removePrefixedObservablePropertyListener(ObservablePropertyListener<T> listener)
{
for (Iterator<ObservablePropertyListener<?>> it = listeners.get(null).iterator(); it.hasNext();) {
ObservablePropertyListener<?> list = it.next();
if (list instanceof PrefixedObservablePropertyListener) {
if (((PrefixedObservablePropertyListener<?>) list).listener == listener)
it.remove();
}
}
}
/**
* Returns a list of all the listeners that were added to the ObservablePropertiesSupport object with
* addObservablePropertyListener().
*
* @return all of the <code>ObservablePropertyListeners</code> added or an empty list if no listeners have been
* added.
*/
public synchronized List<ObservablePropertyListener<?>> getObservablePropertyListeners()
{
List<ObservablePropertyListener<?>> result = new LinkedList<ObservablePropertyListener<?>>();
for (Entry<String, List<ObservablePropertyListener<?>>> entry : listeners.entrySet()) {
result.addAll(entry.getValue());
}
return result;
}
/**
* Returns a list 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>ObservablePropertyListeners</code> associated with the named property. If no such
* listeners have been added, or if <code>propertyName</code> is null, an empty list is returned.
* @since 1.4
*/
public synchronized List<ObservablePropertyListener<?>> getObservablePropertyListeners(String propertyName)
{
if (propertyName == null || listeners.get(propertyName) == null)
return new LinkedList<ObservablePropertyListener<?>>();
return new LinkedList<ObservablePropertyListener<?>>(listeners.get(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 hasObservablePropertyListeners(String propertyName)
{
if (listeners.get(null).size() > 0)
return true;
if (propertyName == null)
return false;
return listeners.get(propertyName) != null && listeners.get(propertyName).size() > 0;
}
/**
* Report an observable property update to any registered listeners.
*
* @param evt The observable change to be fired.
*/
@SuppressWarnings("unchecked")
public <T> void fireObservablePropertyChange(ObservablePropertyEvent<T> evt)
{
for (ObservablePropertyListener<?> l : listeners.get(null)) {
try {
((ObservablePropertyListener<T>) l).changePerformed(evt);
} catch (ClassCastException e) {
System.err.println(e);
}
}
if (listeners.get(evt.getPropertyName()) != null) {
for (ObservablePropertyListener<?> l : listeners.get(evt.getPropertyName())) {
try {
((ObservablePropertyListener<T>) l).changePerformed(evt);
} catch (ClassCastException e) {
System.err.println(e);
}
}
}
}
/**
* Report an observable property update to any registered listeners.
*
* @param source The source of the event.
* @param propertyName The programmatic name of the property that was changed.
* @param evt The change that happened.
*/
public <T> void fireObservablePropertyChange(Object source, String propertyName, ChangeNotification<T> evt)
{
fireObservablePropertyChange(new ObservablePropertyEvent<T>(source, propertyName, evt));
}
/**
* Report an observable property update to any registered listeners.
*
* @param propertyName The programmatic name of the property that was changed.
* @param evt The change that happened.
*/
public <T> void fireObservablePropertyChange(String propertyName, ChangeNotification<T> evt)
{
fireObservablePropertyChange(source, propertyName, evt);
}
/**
* Build the hierarchical property name out of the given list of property names.
*
* @param names The list of property names to build the hierarchical name of.
* @return The hierarchical property name.
*/
protected String getHierarchicalName(String... names)
{
if (names == null || names.length == 0)
return "";
StringBuilder sb = new StringBuilder(names[0]);
for (int i = 1; i < names.length; i++)
sb.append("@").append(names[i]);
return sb.toString();
}
/**
* A listener that is triggered on events with specific prefix.
*
* @author Martin Pecka
*/
protected class PrefixedObservablePropertyListener<T> implements ObservablePropertyListener<T>
{
/** The prefix with which this listener will be triggered. */
protected String prefix;
/** The listener to be triggered. */
protected ObservablePropertyListener<T> listener;
/**
* @param prefix The prefix with which this listener will be triggered.
* @param listener The listener to be triggered.
*/
public PrefixedObservablePropertyListener(String prefix, ObservablePropertyListener<T> listener)
{
this.prefix = prefix;
this.listener = listener;
}
@Override
public void changePerformed(ObservablePropertyEvent<? extends T> evt)
{
if (evt.getPropertyName().startsWith(prefix))
listener.changePerformed(evt);
}
}
}