![JAR search and dependency download from the Maven repository](/logo.png)
com.jgoodies.binding.beans.ExtendedPropertyChangeSupport Maven / Gradle / Ivy
Show all versions of binding Show documentation
/*
* Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. 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 Karsten Lentzsch 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 #equals
or ==
.
* Useful if you want to ensure that a 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 ==
* is required and the #equals
test fails is class
* {@link com.jgoodies.binding.list.SelectionInList}. If the contained
* 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 PropertyChangeSupport
doesn't fire a property change event
* if such a ListModel
is implemented as a {@link java.util.List}.
* This is because instances of 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 #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 #8) Add an option to ensure that update notifications
* are performed in the event dispatch thread. In case a bean
* is changed in a thread other than the event dispatch thread, such
* a feature would help complying with Swing's single thread rule.
*
* 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 Mattias Neuling
* @author Karsten Lentzsch
* @version $Revision: 1.8 $
*
* @see PropertyChangeSupport
* @see PropertyChangeEvent
* @see PropertyChangeListener
* @see Object#equals(Object)
* @see java.util.List#equals(Object)
*/
public final class ExtendedPropertyChangeSupport extends PropertyChangeSupport {
/**
* The object to be provided as the "source" for any generated events.
* @serial
*/
private final Object source;
/**
* The default setting for the identity check.
* Can be overridden by the #firePropertyChange methods
* that accept a checkIdentity
parameter.
*/
private final boolean checkIdentityDefault;
// 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;
}
// Firing Events **********************************************************
/**
* Fires the specified PropertyChangeEvent to any registered listeners.
* Uses the default test (#equals
vs. ==
)
* 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 (#equals
vs. ==
)
* 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 ==
or #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 ==
* false to use #equals
.
*/
public void firePropertyChange(PropertyChangeEvent evt, boolean checkIdentity) {
Object oldValue = evt.getOldValue();
Object newValue = evt.getNewValue();
if (oldValue != null && oldValue == newValue) {
return;
} else if (checkIdentity) {
fireUnchecked(evt);
} else {
super.firePropertyChange(evt);
}
}
/**
* 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 true
an event is fired in all
* other cases. If this parameter is 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 ==
* false to use #equals
.
*/
public void firePropertyChange(
String propertyName,
Object oldValue,
Object newValue,
boolean checkIdentity) {
if (oldValue != null && oldValue == newValue) {
return;
} else 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
* PropertyChangeSupport#getPropertyChangeListeners()
returns
* an array with a mixture of PropertyChangeListeners
* and 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);
}
}
}
}