/** * Copyright (C) 2015 Valkyrie RCP * * 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 org.valkyriercp.binding.value.support; import org.springframework.util.ObjectUtils; import javax.swing.event.EventListenerList; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListenerProxy; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.*; /** * Convenience class that provides propertyChange support. Listeners can be * (un)registered on specific properties while firing of * {@link java.beans.PropertyChangeEvent}s is eased. */ public final class PropertyChangeSupport implements Serializable { /** Lists all the generic listeners. */ transient private EventListenerList listeners; /** Contains SwingPropertyChangeSupports for individual properties. */ private Map children; /** Source of the events. */ private Object source; // Serialization version ID static final long serialVersionUID = 7162625831330845068L; /** * Constructs a SwingPropertyChangeSupport object. * * @param sourceBean The bean to be given as the source for any events. */ public PropertyChangeSupport(Object sourceBean) { if (sourceBean == null) { throw new NullPointerException(); } this.source = sourceBean; } /** * Add a PropertyChangeListener to the listener list. The listener is * registered for all properties. * * @param listener The PropertyChangeListener to be added */ public void addPropertyChangeListener(PropertyChangeListener listener) { if (listener instanceof PropertyChangeListenerProxy) { PropertyChangeListenerProxy proxy = (PropertyChangeListenerProxy) listener; addPropertyChangeListener(proxy.getPropertyName(), (PropertyChangeListener) proxy.getListener()); } else { if (listeners == null) { listeners = new EventListenerList(); } listeners.add(PropertyChangeListener.class, listener); } } /** * Remove a PropertyChangeListener from the listener list. This removes a * PropertyChangeListener that was registered for all properties. * * @param listener The PropertyChangeListener to be removed */ public void removePropertyChangeListener(PropertyChangeListener listener) { if (listener instanceof PropertyChangeListenerProxy) { PropertyChangeListenerProxy proxy = (PropertyChangeListenerProxy) listener; removePropertyChangeListener(proxy.getPropertyName(), (PropertyChangeListener) proxy.getListener()); } else { if (listeners == null) { return; } listeners.remove(PropertyChangeListener.class, listener); } } /** * Returns an array of all the listeners that were added to the * SwingPropertyChangeSupport 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>PropertyChangeListenerProxy</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>PropertyChangeListenerProxy</code> perform the * cast and examine the parameter. * * <pre> * PropertyChangeListener[] listeners = support.getPropertyChangeListeners(); * for (int i = 0; i < listeners.length; i++) { * if (listeners[i] instanceof PropertyChangeListenerProxy) { * PropertyChangeListenerProxy proxy = (PropertyChangeListenerProxy) listeners[i]; * if (proxy.getPropertyName().equals("foo")) { * // proxy is a PropertyChangeListener which was associated * // with the property named "foo" * } * } * } * </pre> * * @see java.beans.PropertyChangeListenerProxy * @see java.beans.PropertyChangeSupport#getPropertyChangeListeners * @return all of the <code>PropertyChangeListener</code> s added or an * empty array if no listeners have been added * @since 1.4 */ public PropertyChangeListener[] getPropertyChangeListeners() { List returnList = new ArrayList(); // Add all the PropertyChangeListeners if (listeners != null) { returnList.addAll(Arrays.asList(listeners.getListeners(PropertyChangeListener.class))); } // Add all the PropertyChangeListenerProxys if (children != null) { Iterator iterator = children.keySet().iterator(); while (iterator.hasNext()) { String key = (String) iterator.next(); PropertyChangeSupport child = (PropertyChangeSupport) children.get(key); PropertyChangeListener[] childListeners = child.getPropertyChangeListeners(); for (int index = childListeners.length - 1; index >= 0; index--) { returnList.add(new PropertyChangeListenerProxy(key, childListeners[index])); } } } return (PropertyChangeListener[]) returnList.toArray(new PropertyChangeListener[returnList.size()]); } /** * Add a PropertyChangeListener for a specific property. The listener will * be invoked only when a call on firePropertyChange names that specific * property. * * @param propertyName The name of the property to listen on. * @param listener The PropertyChangeListener to be added */ public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { if (children == null) { children = new HashMap(); } PropertyChangeSupport child = (PropertyChangeSupport) children.get(propertyName); if (child == null) { child = new PropertyChangeSupport(source); children.put(propertyName, child); } child.addPropertyChangeListener(listener); } /** * Remove a PropertyChangeListener for a specific property. * * @param propertyName The name of the property that was listened on. * @param listener The PropertyChangeListener to be removed */ public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { if (children == null) { return; } PropertyChangeSupport child = (PropertyChangeSupport) children.get(propertyName); if (child == null) { return; } child.removePropertyChangeListener(listener); } /** * Returns an array of all the listeners which have been associated with the * named property. * * @return all of the <code>PropertyChangeListeners</code> associated with * the named property or an empty array if no listeners have been added */ public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) { List returnList = new ArrayList(); if (children != null) { PropertyChangeSupport support = (PropertyChangeSupport) children.get(propertyName); if (support != null) { returnList.addAll(Arrays.asList(support.getPropertyChangeListeners())); } } return (PropertyChangeListener[]) (returnList.toArray(new PropertyChangeListener[0])); } /** * Report a bound property update to any registered listeners. No event is * fired if old and new are equal and non-null. * * @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(String propertyName, Object oldValue, Object newValue) { firePropertyChange(new PropertyChangeEvent(source, propertyName, oldValue, 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(PropertyChangeEvent evt) { Object oldValue = evt.getOldValue(); Object newValue = evt.getNewValue(); if (ObjectUtils.nullSafeEquals(oldValue, newValue)) { return; } String propertyName = evt.getPropertyName(); PropertyChangeSupport child = null; if (children != null) { if (children != null && propertyName != null) { child = (PropertyChangeSupport) children.get(propertyName); } } if (listeners != null) { Object[] listenerList = listeners.getListenerList(); for (int i = 0; i <= listenerList.length - 2; i += 2) { if (listenerList[i] == PropertyChangeListener.class) { ((PropertyChangeListener) listenerList[i + 1]).propertyChange(evt); } } } if (child != null) { child.firePropertyChange(evt); } } /** * Check if there are any listeners for a specific property. * * @param propertyName the property name. * @return true if there are ore or more listeners for the given property */ public boolean hasListeners(String propertyName) { if (listeners != null && listeners.getListenerCount(PropertyChangeListener.class) > 0) { // there is a generic listener return true; } if (children != null) { PropertyChangeSupport child = (PropertyChangeSupport) children.get(propertyName); if (child != null) { // The child will always have a listeners ArrayList. return child.hasListeners(propertyName); } } return false; } private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); if (listeners != null) { Object[] listenerList = listeners.getListenerList(); for (int i = 0; i <= listenerList.length - 2; i += 2) { if (listenerList[i] == PropertyChangeListener.class && (PropertyChangeListener) listenerList[i + 1] instanceof Serializable) { s.writeObject(listenerList[i + 1]); } } } s.writeObject(null); } private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException { s.defaultReadObject(); Object listenerOrNull; while (null != (listenerOrNull = s.readObject())) { addPropertyChangeListener((PropertyChangeListener) listenerOrNull); } } }