/** * */ 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); } } }