com.jgoodies.binding.beans.ExtendedPropertyChangeSupport Maven / Gradle / Ivy
Show all versions of jgoodies-binding Show documentation
/*
* Copyright (c) 2002-2015 JGoodies Software GmbH. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of JGoodies Software GmbH nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jgoodies.binding.beans;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeListenerProxy;
import java.beans.PropertyChangeSupport;
/**
* Differs from its superclass {@link PropertyChangeSupport} in that it can
* check for changed values using {@code #equals} or {@code ==}.
* Useful if you want to ensure that a {@code PropertyChangeEvent} is fired
* if the old and new value are not the same but if they are equal.
*
* The Java
* Bean Specification recommends to not throw a
* PropertyChangeEvent if the old and new value of a bound
* Bean property are equal (see chapter 7.4.4). This can reduce the number
* of events fired and helps avoid loops. Nevertheless a bound property
* may fire an event if the old and new value are equal.
*
* An example for a condition where the identity check {@code ==}
* is required and the {@code #equals} test fails is class
* {@link com.jgoodies.binding.list.SelectionInList}. If the contained
* {@code ListModel} changes its value, an internal listener is removed
* from the old value and added to the new value. The listener must be
* moved from the old instance to the new instance even if these are equal.
* The {@code PropertyChangeSupport} doesn't fire a property change event
* if such a {@code ListModel} is implemented as a {@link java.util.List}.
* This is because instances of {@code List} are equal if and only if
* all list members are equal and if they are in the same sequence.
*
* This class provides two means to fire an event if the old and new
* value are equal but not the same. First, you enable the identity check
* in constructor {@link #ExtendedPropertyChangeSupport(Object, boolean)}.
* By default all calls to {@code #firePropertyChange} will then
* check the identity, not the equality. Second, you can invoke
* {@link #firePropertyChange(PropertyChangeEvent, boolean)} or
* {@link #firePropertyChange(String, Object, Object, boolean)} and
* enable or disable the identity check for this call only.
*
* TODO: (Issue #5) Use WeakReferences to refer to registered listeners.
*
* TODO: (Issue #6) Add an optional check for valid property name
* when adding a listener for a specific property.
*
* TODO: (Issue #7) Add an optional strict check for existing
* property names when firing a named property change.
*
* TODO: (Issue #11) Consider adding an option that saves update notifications
* if 'checkIdentity' is true but the value types can be compared
* safely via #equals, for example Strings, Booleans and Numbers.
*
* @author Karsten Lentzsch
* @version $Revision: 1.16 $
*
* @see PropertyChangeSupport
* @see PropertyChangeEvent
* @see PropertyChangeListener
* @see Object#equals(Object)
* @see java.util.List#equals(Object)
*/
public final class ExtendedPropertyChangeSupport extends PropertyChangeSupport {
/**
* @serial the object to be provided as the "source" for any generated events
*/
private final Object source;
/**
* The default setting for the identity check.
* Can be overridden by the #firePropertyChange methods
* that accept a {@code checkIdentity} parameter.
*/
private final boolean checkIdentityDefault;
// /**
// * Describes whether this change support ensures that registered
// * PropertyChangeListeners are notified in the event dispatch thread (EDT).
// */
// private final boolean ensureNotificationInEDT;
// Instance Creation ******************************************************
/**
* Constructs an ExtendedPropertyChangeSupport object.
*
* @param sourceBean The bean to be given as the source for any events.
*/
public ExtendedPropertyChangeSupport(Object sourceBean) {
this(sourceBean, false);
}
/**
* Constructs an ExtendedPropertyChangeSupport object
* with the specified default test method for differences between
* the old and new property values.
*
* @param sourceBean The object provided as the source for any generated events.
* @param checkIdentityDefault true enables the identity check by default
*/
public ExtendedPropertyChangeSupport(
Object sourceBean,
boolean checkIdentityDefault) {
super(sourceBean);
this.source = sourceBean;
this.checkIdentityDefault = checkIdentityDefault;
}
// /**
// * Constructs an ExtendedPropertyChangeSupport object
// * with the specified default test method for differences between
// * the old and new property values.
// *
// * @param sourceBean The object provided as the source for any generated events.
// * @param checkIdentityDefault true enables the identity check by default
// * @param ensureNotificationInEDT if true, this class ensures that
// * listeners are notified in the event dispatch thread (EDT)
// */
// public ExtendedPropertyChangeSupport(
// Object sourceBean,
// boolean checkIdentityDefault,
// boolean ensureNotificationInEDT) {
// super(sourceBean);
// this.source = sourceBean;
// this.checkIdentityDefault = checkIdentityDefault;
// this.ensureNotificationInEDT = ensureNotificationInEDT;
// }
// Firing Events **********************************************************
/**
* Fires the specified PropertyChangeEvent to any registered listeners.
* Uses the default test ({@code #equals} vs. {@code ==})
* to determine whether the event's old and new values are different.
* No event is fired if old and new value are the same.
*
* @param evt The PropertyChangeEvent object.
*
* @see PropertyChangeSupport#firePropertyChange(PropertyChangeEvent)
*/
@Override
public void firePropertyChange(PropertyChangeEvent evt) {
firePropertyChange(evt, checkIdentityDefault);
}
/**
* Reports a bound property update to any registered listeners.
* Uses the default test ({@code #equals} vs. {@code ==})
* to determine whether the event's old and new values are different.
* No event is fired if old and new value are the same.
*
* @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.
*
* @see PropertyChangeSupport#firePropertyChange(String, Object, Object)
*/
@Override
public void firePropertyChange(
String propertyName,
Object oldValue,
Object newValue) {
firePropertyChange(propertyName, oldValue, newValue, checkIdentityDefault);
}
/**
* Fires an existing PropertyChangeEvent to any registered listeners.
* The boolean parameter specifies whether differences between the old
* and new value are tested using {@code ==} or {@code #equals}.
* No event is fired if old and new value are the same.
*
* @param evt The PropertyChangeEvent object.
* @param checkIdentity true to check differences using {@code ==}
* false to use {@code #equals}.
*/
public void firePropertyChange(
final PropertyChangeEvent evt,
final boolean checkIdentity) {
Object oldValue = evt.getOldValue();
Object newValue = evt.getNewValue();
if (oldValue != null && oldValue == newValue) {
return;
}
// if (!ensureNotificationInEDT || EventQueue.isDispatchThread()) {
firePropertyChange0(evt, checkIdentity);
// return;
// }
// EventQueue.invokeLater(new Runnable() {
// public void run() {
// ExtendedPropertyChangeSupport.this.firePropertyChange0(
// evt,
// checkIdentity);
// }
// });
}
/**
* Reports a bound property update to any registered listeners.
* No event is fired if the old and new value are the same.
* If checkIdentity is {@code true} an event is fired in all
* other cases. If this parameter is {@code false}, an event is fired
* if old and new values are not equal.
*
* @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.
* @param checkIdentity true to check differences using {@code ==}
* false to use {@code #equals}.
*/
public void firePropertyChange(
final String propertyName,
final Object oldValue,
final Object newValue,
final boolean checkIdentity) {
if (oldValue != null && oldValue == newValue) {
return;
}
// if (!ensureNotificationInEDT || EventQueue.isDispatchThread()) {
firePropertyChange0(propertyName, oldValue, newValue, checkIdentity);
// return;
// }
// EventQueue.invokeLater(new Runnable() {
// public void run() {
// ExtendedPropertyChangeSupport.this.firePropertyChange0(
// propertyName,
// oldValue,
// newValue,
// checkIdentity);
// }
// });
}
// Helper Code ************************************************************
private void firePropertyChange0(PropertyChangeEvent evt, boolean checkIdentity) {
if (checkIdentity) {
fireUnchecked(evt);
} else {
super.firePropertyChange(evt);
}
}
private void firePropertyChange0(String propertyName, Object oldValue, Object newValue, boolean checkIdentity) {
if (checkIdentity) {
fireUnchecked(
new PropertyChangeEvent(
source,
propertyName,
oldValue,
newValue));
} else {
super.firePropertyChange(propertyName, oldValue, newValue);
}
}
/**
* Fires a PropertyChangeEvent to all its listeners without checking via
* equals method if the old value is equal to new value. The instance
* equality check is done by the calling firePropertyChange method
* (to avoid instance creation of the PropertyChangeEvent).
*
* If some listeners have been added with a named property, then
* {@code PropertyChangeSupport#getPropertyChangeListeners()} returns
* an array with a mixture of PropertyChangeListeners
* and {@code PropertyChangeListenerProxy}s. We notify all non-proxies
* and those proxies that have a property name that is equals to the
* event's property name.
*
* @param evt event to fire to the listeners
*
* @see PropertyChangeListenerProxy
* @see PropertyChangeSupport#getPropertyChangeListeners()
*/
private void fireUnchecked(PropertyChangeEvent evt) {
PropertyChangeListener[] listeners;
synchronized (this) {
listeners = getPropertyChangeListeners();
}
String propertyName = evt.getPropertyName();
for (PropertyChangeListener listener : listeners) {
if (listener instanceof PropertyChangeListenerProxy) {
PropertyChangeListenerProxy proxy =
(PropertyChangeListenerProxy) listener;
if (proxy.getPropertyName().equals(propertyName)) {
proxy.propertyChange(evt);
}
} else {
listener.propertyChange(evt);
}
}
}
}