001: /*
002: * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package com.jgoodies.binding.beans;
032:
033: import java.beans.PropertyChangeEvent;
034: import java.beans.PropertyChangeListener;
035: import java.beans.PropertyChangeListenerProxy;
036: import java.beans.PropertyChangeSupport;
037:
038: /**
039: * Differs from its superclass {@link PropertyChangeSupport} in that it can
040: * check for changed values using <code>#equals</code> or <code>==</code>.
041: * Useful if you want to ensure that a <code>PropertyChangeEvent</code> is fired
042: * if the old and new value are not the same but if they are equal.<p>
043: *
044: * The <a href="http://java.sun.com/products/javabeans/docs/spec.html">Java
045: * Bean Specification</a> <em>recommends</em> to not throw a
046: * PropertyChangeEvent if the old and new value of a bound
047: * Bean property are equal (see chapter 7.4.4). This can reduce the number
048: * of events fired and helps avoid loops. Nevertheless a bound property
049: * <em>may</em> fire an event if the old and new value are equal.<p>
050: *
051: * An example for a condition where the identity check <code>==</code>
052: * is required and the <code>#equals</code> test fails is class
053: * {@link com.jgoodies.binding.list.SelectionInList}. If the contained
054: * <code>ListModel</code> changes its value, an internal listener is removed
055: * from the old value and added to the new value. The listener must be
056: * moved from the old instance to the new instance even if these are equal.
057: * The <code>PropertyChangeSupport</code> doesn't fire a property change event
058: * if such a <code>ListModel</code> is implemented as a {@link java.util.List}.
059: * This is because instances of <code>List</code> are equal if and only if
060: * all list members are equal and if they are in the same sequence.<p>
061: *
062: * This class provides two means to fire an event if the old and new
063: * value are equal but not the same. First, you enable the identity check
064: * in constructor {@link #ExtendedPropertyChangeSupport(Object, boolean)}.
065: * By default all calls to <code>#firePropertyChange</code> will then
066: * check the identity, not the equality. Second, you can invoke
067: * {@link #firePropertyChange(PropertyChangeEvent, boolean)} or
068: * {@link #firePropertyChange(String, Object, Object, boolean)} and
069: * enable or disable the identity check for this call only.<p>
070: *
071: * TODO: (Issue #5) Use WeakReferences to refer to registered listeners.<p>
072: *
073: * TODO: (Issue #6) Add an optional check for valid property name
074: * when adding a listener for a specific property.<p>
075: *
076: * TODO: (Issue #7) Add an optional strict check for existing
077: * property names when firing a named property change.<p>
078: *
079: * TODO: (Issue #8) Add an option to ensure that update notifications
080: * are performed in the event dispatch thread. In case a bean
081: * is changed in a thread other than the event dispatch thread, such
082: * a feature would help complying with Swing's single thread rule.<p>
083: *
084: * TODO: (Issue #11) Consider adding an option that saves update notifications
085: * if 'checkIdentity' is true but the value types can be compared
086: * safely via #equals, for example Strings, Booleans and Numbers.
087: *
088: * @author <a href="mailto:neuling@dakosy.de">Mattias Neuling</a>
089: * @author Karsten Lentzsch
090: * @version $Revision: 1.8 $
091: *
092: * @see PropertyChangeSupport
093: * @see PropertyChangeEvent
094: * @see PropertyChangeListener
095: * @see Object#equals(Object)
096: * @see java.util.List#equals(Object)
097: */
098: public final class ExtendedPropertyChangeSupport extends
099: PropertyChangeSupport {
100:
101: /**
102: * The object to be provided as the "source" for any generated events.
103: * @serial
104: */
105: private final Object source;
106:
107: /**
108: * The default setting for the identity check.
109: * Can be overridden by the #firePropertyChange methods
110: * that accept a <code>checkIdentity</code> parameter.
111: */
112: private final boolean checkIdentityDefault;
113:
114: // Instance Creation ******************************************************
115:
116: /**
117: * Constructs an <code>ExtendedPropertyChangeSupport</code> object.
118: *
119: * @param sourceBean The bean to be given as the source for any events.
120: */
121: public ExtendedPropertyChangeSupport(Object sourceBean) {
122: this (sourceBean, false);
123: }
124:
125: /**
126: * Constructs an <code>ExtendedPropertyChangeSupport</code> object
127: * with the specified default test method for differences between
128: * the old and new property values.
129: *
130: * @param sourceBean The object provided as the source for any generated events.
131: * @param checkIdentityDefault true enables the identity check by default
132: */
133: public ExtendedPropertyChangeSupport(Object sourceBean,
134: boolean checkIdentityDefault) {
135: super (sourceBean);
136: this .source = sourceBean;
137: this .checkIdentityDefault = checkIdentityDefault;
138: }
139:
140: // Firing Events **********************************************************
141:
142: /**
143: * Fires the specified PropertyChangeEvent to any registered listeners.
144: * Uses the default test (<code>#equals</code> vs. <code>==</code>)
145: * to determine whether the event's old and new values are different.
146: * No event is fired if old and new value are the same.
147: *
148: * @param evt The PropertyChangeEvent object.
149: *
150: * @see PropertyChangeSupport#firePropertyChange(PropertyChangeEvent)
151: */
152: @Override
153: public void firePropertyChange(PropertyChangeEvent evt) {
154: firePropertyChange(evt, checkIdentityDefault);
155: }
156:
157: /**
158: * Reports a bound property update to any registered listeners.
159: * Uses the default test (<code>#equals</code> vs. <code>==</code>)
160: * to determine whether the event's old and new values are different.
161: * No event is fired if old and new value are the same.
162: *
163: * @param propertyName The programmatic name of the property
164: * that was changed.
165: * @param oldValue The old value of the property.
166: * @param newValue The new value of the property.
167: *
168: * @see PropertyChangeSupport#firePropertyChange(String, Object, Object)
169: */
170: @Override
171: public void firePropertyChange(String propertyName,
172: Object oldValue, Object newValue) {
173: firePropertyChange(propertyName, oldValue, newValue,
174: checkIdentityDefault);
175: }
176:
177: /**
178: * Fires an existing PropertyChangeEvent to any registered listeners.
179: * The boolean parameter specifies whether differences between the old
180: * and new value are tested using <code>==</code> or <code>#equals</code>.
181: * No event is fired if old and new value are the same.
182: *
183: * @param evt The PropertyChangeEvent object.
184: * @param checkIdentity true to check differences using <code>==</code>
185: * false to use <code>#equals</code>.
186: */
187: public void firePropertyChange(PropertyChangeEvent evt,
188: boolean checkIdentity) {
189: Object oldValue = evt.getOldValue();
190: Object newValue = evt.getNewValue();
191: if (oldValue != null && oldValue == newValue) {
192: return;
193: } else if (checkIdentity) {
194: fireUnchecked(evt);
195: } else {
196: super .firePropertyChange(evt);
197: }
198: }
199:
200: /**
201: * Reports a bound property update to any registered listeners.
202: * No event is fired if the old and new value are the same.
203: * If checkIdentity is <code>true</code> an event is fired in all
204: * other cases. If this parameter is <code>false</code>, an event is fired
205: * if old and new values are not equal.
206: *
207: * @param propertyName The programmatic name of the property
208: * that was changed.
209: * @param oldValue The old value of the property.
210: * @param newValue The new value of the property.
211: * @param checkIdentity true to check differences using <code>==</code>
212: * false to use <code>#equals</code>.
213: */
214: public void firePropertyChange(String propertyName,
215: Object oldValue, Object newValue, boolean checkIdentity) {
216:
217: if (oldValue != null && oldValue == newValue) {
218: return;
219: } else if (checkIdentity) {
220: fireUnchecked(new PropertyChangeEvent(source, propertyName,
221: oldValue, newValue));
222: } else {
223: super .firePropertyChange(propertyName, oldValue, newValue);
224: }
225: }
226:
227: /**
228: * Fires a PropertyChangeEvent to all its listeners without checking via
229: * equals method if the old value is equal to new value. The instance
230: * equality check is done by the calling firePropertyChange method
231: * (to avoid instance creation of the PropertyChangeEvent).<p>
232: *
233: * If some listeners have been added with a named property, then
234: * <code>PropertyChangeSupport#getPropertyChangeListeners()</code> returns
235: * an array with a mixture of PropertyChangeListeners
236: * and <code>PropertyChangeListenerProxy</code>s. We notify all non-proxies
237: * and those proxies that have a property name that is equals to the
238: * event's property name.
239: *
240: * @param evt event to fire to the listeners
241: *
242: * @see PropertyChangeListenerProxy
243: * @see PropertyChangeSupport#getPropertyChangeListeners()
244: */
245: private void fireUnchecked(PropertyChangeEvent evt) {
246: PropertyChangeListener[] listeners;
247: synchronized (this ) {
248: listeners = getPropertyChangeListeners();
249: }
250: String propertyName = evt.getPropertyName();
251: for (PropertyChangeListener listener : listeners) {
252: if (listener instanceof PropertyChangeListenerProxy) {
253: PropertyChangeListenerProxy proxy = (PropertyChangeListenerProxy) listener;
254: if (proxy.getPropertyName().equals(propertyName)) {
255: proxy.propertyChange(evt);
256: }
257: } else {
258: listener.propertyChange(evt);
259: }
260: }
261: }
262:
263: }
|