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.PropertyChangeSupport;
036: import java.util.HashMap;
037: import java.util.LinkedList;
038: import java.util.List;
039: import java.util.Map;
040: import java.util.Map.Entry;
041:
042: import com.jgoodies.binding.value.ValueHolder;
043: import com.jgoodies.binding.value.ValueModel;
044:
045: /**
046: * A helper class for observing changes in bound bean properties
047: * where the target bean changes.
048: *
049: * Provides two access styles to the target bean that holds the observed
050: * property: you can specify a bean directly,
051: * or you can use a <em>bean channel</em> to access the bean indirectly.
052: * In the latter case you specify a <code>ValueModel</code>
053: * that holds the bean that in turn holds the observed properties.<p>
054: *
055: * If the target bean is <code>null</code>, it won't report any changes.<p>
056: *
057: * It is recommended to remove all listener by invoking <code>#removeAll</code>
058: * if the observed bean lives much longer than this change support instance.
059: * As an alternative you may use event listener lists that are based
060: * on <code>WeakReference</code>s.<p>
061: *
062: * <strong>Constraints:</strong> All target bean classes must support
063: * bound properties, i. e. must provide the following pair of methods
064: * for registration of multicast property change event listeners:
065: * <pre>
066: * public void addPropertyChangeListener(PropertyChangeListener x);
067: * public void removePropertyChangeListener(PropertyChangeListener x);
068: * </pre>
069: * and the following methods for listening on named properties:
070: * <pre>
071: * public void addPropertyChangeListener(String, PropertyChangeListener x);
072: * public void removePropertyChangeListener(String, PropertyChangeListener x);
073: * </pre>
074: *
075: * @author Karsten Lentzsch
076: * @version $Revision: 1.7 $
077: *
078: * @see PropertyChangeEvent
079: * @see PropertyChangeListener
080: * @see PropertyChangeSupport
081: * @see com.jgoodies.binding.beans.BeanAdapter
082: */
083: public final class IndirectPropertyChangeSupport {
084:
085: /**
086: * Holds a <code>ValueModel</code> that holds the bean, that in turn
087: * holds the adapted property.
088: *
089: * @see #getBean()
090: * @see #setBean(Object)
091: */
092: private final ValueModel beanChannel;
093:
094: /**
095: * Holds the PropertyChangeListeners that are registered for all bound
096: * properties of the target bean. If the target bean changes,
097: * these listeners are removed from the old bean and added to the new bean.
098: *
099: * @see #addPropertyChangeListener(PropertyChangeListener)
100: * @see #removePropertyChangeListener(PropertyChangeListener)
101: * @see #getPropertyChangeListeners()
102: */
103: private final List<PropertyChangeListener> listenerList;
104:
105: /**
106: * Maps property names to the list of PropertyChangeListeners that are
107: * registered for the associated bound property of the target bean.
108: * If the target bean changes, these listeners are removed from
109: * the old bean and added to the new bean.
110: *
111: * @see #addPropertyChangeListener(String, PropertyChangeListener)
112: * @see #removePropertyChangeListener(String, PropertyChangeListener)
113: * @see #getPropertyChangeListeners(String)
114: */
115: private final Map<String, List<PropertyChangeListener>> namedListeners;
116:
117: // Instance creation ****************************************************
118:
119: /**
120: * Constructs an IndirectPropertyChangeSupport that has no bean set.
121: */
122: public IndirectPropertyChangeSupport() {
123: this (new ValueHolder(null, true));
124: }
125:
126: /**
127: * Constructs an IndirectPropertyChangeSupport with the given initial bean.
128: *
129: * @param bean the initial bean
130: */
131: public IndirectPropertyChangeSupport(Object bean) {
132: this (new ValueHolder(bean, true));
133: }
134:
135: /**
136: * Constructs an IndirectPropertyChangeSupport using the given bean channel.
137: *
138: * @param beanChannel the ValueModel that holds the bean
139: */
140: public IndirectPropertyChangeSupport(ValueModel beanChannel) {
141: if (beanChannel == null)
142: throw new NullPointerException(
143: "The bean channel must not be null.");
144:
145: this .beanChannel = beanChannel;
146: listenerList = new LinkedList<PropertyChangeListener>();
147: namedListeners = new HashMap<String, List<PropertyChangeListener>>();
148:
149: beanChannel.addValueChangeListener(new BeanChangeHandler());
150: }
151:
152: // Accessors ************************************************************
153:
154: /**
155: * Returns the Java Bean that holds the observed properties.
156: *
157: * @return the Bean that holds the observed properties
158: *
159: * @see #setBean(Object)
160: */
161: public Object getBean() {
162: return beanChannel.getValue();
163: }
164:
165: /**
166: * Sets a new Java Bean as holder of the observed properties.
167: * Removes all registered listeners from the old bean and
168: * adds them to the new bean.
169: *
170: * @param newBean the new holder of the observed properties
171: *
172: * @see #getBean()
173: */
174: public void setBean(Object newBean) {
175: beanChannel.setValue(newBean);
176: }
177:
178: // Managing Property Change Listeners *************************************
179:
180: /**
181: * Adds a PropertyChangeListener to the list of bean listeners.
182: * The listener is registered for all bound properties of the target bean.
183: * <p>
184: *
185: * If listener is <code>null</code>, no exception is thrown and no action is performed.
186: *
187: * @param listener the PropertyChangeListener to be added
188: *
189: * @see #removePropertyChangeListener(PropertyChangeListener)
190: * @see #removePropertyChangeListener(String, PropertyChangeListener)
191: * @see #addPropertyChangeListener(String, PropertyChangeListener)
192: * @see #getPropertyChangeListeners()
193: */
194: public synchronized void addPropertyChangeListener(
195: PropertyChangeListener listener) {
196: if (listener == null) {
197: return;
198: }
199: listenerList.add(listener);
200: Object bean = getBean();
201: if (bean != null) {
202: BeanUtils.addPropertyChangeListener(bean, listener);
203: }
204: }
205:
206: /**
207: * Removes a PropertyChangeListener from the list of bean listeners.
208: * This method should be used to remove PropertyChangeListeners that
209: * were registered for all bound properties of the target bean.<p>
210: *
211: * If listener is <code>null</code>, no exception is thrown and no action is performed.
212: *
213: * @param listener the PropertyChangeListener to be removed
214: *
215: * @see #addPropertyChangeListener(PropertyChangeListener)
216: * @see #addPropertyChangeListener(String, PropertyChangeListener)
217: * @see #removePropertyChangeListener(String, PropertyChangeListener)
218: * @see #getPropertyChangeListeners()
219: */
220: public synchronized void removePropertyChangeListener(
221: PropertyChangeListener listener) {
222: if (listener == null) {
223: return;
224: }
225: listenerList.remove(listener);
226: Object bean = getBean();
227: if (bean != null) {
228: BeanUtils.removePropertyChangeListener(bean, listener);
229: }
230: }
231:
232: /**
233: * Adds a PropertyChangeListener to the list of bean listeners for a
234: * specific property. The specified property may be user-defined.<p>
235: *
236: * Note that if the bean is inheriting a bound property, then no event
237: * will be fired in response to a change in the inherited property.<p>
238: *
239: * If listener is <code>null</code>, no exception is thrown and no action is performed.
240: *
241: * @param propertyName one of the property names listed above
242: * @param listener the PropertyChangeListener to be added
243: *
244: * @see #removePropertyChangeListener(String, PropertyChangeListener)
245: * @see #addPropertyChangeListener(String, PropertyChangeListener)
246: * @see #getPropertyChangeListeners(String)
247: */
248: public synchronized void addPropertyChangeListener(
249: String propertyName, PropertyChangeListener listener) {
250: if (listener == null) {
251: return;
252: }
253: List<PropertyChangeListener> namedListenerList = namedListeners
254: .get(propertyName);
255: if (namedListenerList == null) {
256: namedListenerList = new LinkedList<PropertyChangeListener>();
257: namedListeners.put(propertyName, namedListenerList);
258: }
259: namedListenerList.add(listener);
260: Object bean = getBean();
261: if (bean != null) {
262: BeanUtils.addPropertyChangeListener(bean, propertyName,
263: listener);
264: }
265: }
266:
267: /**
268: * Removes a PropertyChangeListener from the listener list for a specific
269: * property. This method should be used to remove PropertyChangeListeners
270: * that were registered for a specific bound property.<p>
271: *
272: * If listener is <code>null</code>, no exception is thrown and no action is performed.
273: *
274: * @param propertyName a valid property name
275: * @param listener the PropertyChangeListener to be removed
276: *
277: * @see #addPropertyChangeListener(String, PropertyChangeListener)
278: * @see #removePropertyChangeListener(PropertyChangeListener)
279: * @see #getPropertyChangeListeners(String)
280: */
281: public synchronized void removePropertyChangeListener(
282: String propertyName, PropertyChangeListener listener) {
283: if (listener == null) {
284: return;
285: }
286: List<PropertyChangeListener> namedListenerList = namedListeners
287: .get(propertyName);
288: if (namedListenerList == null)
289: return;
290: namedListenerList.remove(listener);
291:
292: Object bean = getBean();
293: if (bean != null) {
294: BeanUtils.removePropertyChangeListener(bean, propertyName,
295: listener);
296: }
297: }
298:
299: // Requesting Listener Sets ***********************************************
300:
301: /**
302: * Returns an array of all the property change listeners
303: * registered on this component.
304: *
305: * @return all of this component's <code>PropertyChangeListener</code>s
306: * or an empty array if no property change
307: * listeners are currently registered
308: *
309: * @see #addPropertyChangeListener(PropertyChangeListener)
310: * @see #removePropertyChangeListener(PropertyChangeListener)
311: * @see #getPropertyChangeListeners(String)
312: * @see java.beans.PropertyChangeSupport#getPropertyChangeListeners()
313: */
314: public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
315: if (listenerList.isEmpty()) {
316: return new PropertyChangeListener[0];
317: }
318: return listenerList
319: .toArray(new PropertyChangeListener[listenerList.size()]);
320: }
321:
322: /**
323: * Returns an array of all the listeners which have been associated
324: * with the named property.
325: *
326: * @param propertyName the name of the property to lookup listeners
327: * @return all of the <code>PropertyChangeListeners</code> associated with
328: * the named property or an empty array if no listeners have
329: * been added
330: *
331: * @see #addPropertyChangeListener(String, PropertyChangeListener)
332: * @see #removePropertyChangeListener(String, PropertyChangeListener)
333: * @see #getPropertyChangeListeners()
334: */
335: public synchronized PropertyChangeListener[] getPropertyChangeListeners(
336: String propertyName) {
337: List<PropertyChangeListener> namedListenerList = namedListeners
338: .get(propertyName);
339: if (namedListenerList == null || namedListenerList.isEmpty()) {
340: return new PropertyChangeListener[0];
341: }
342: return namedListenerList
343: .toArray(new PropertyChangeListener[namedListenerList
344: .size()]);
345: }
346:
347: // Releasing PropertyChangeListeners **************************************
348:
349: /**
350: * Removes all registered PropertyChangeListeners from
351: * the current target bean - if any.
352: */
353: public void removeAll() {
354: removeAllListenersFrom(getBean());
355: }
356:
357: // Changing the Bean & Adding and Removing the PropertyChangeHandlers *****
358:
359: private void setBean0(Object oldBean, Object newBean) {
360: removeAllListenersFrom(oldBean);
361: addAllListenersTo(newBean);
362: }
363:
364: /**
365: * Adds all registered PropertyChangeListeners from the given bean.
366: * If the bean is null no exception is thrown and no action is taken.
367: *
368: * @param bean the bean to add a the property change listeners to
369: */
370: private void addAllListenersTo(Object bean) {
371: if (bean == null) {
372: return;
373: }
374: for (PropertyChangeListener listener : listenerList) {
375: BeanUtils.addPropertyChangeListener(bean, listener);
376: }
377: for (Entry<String, List<PropertyChangeListener>> entry : namedListeners
378: .entrySet()) {
379: String propertyName = entry.getKey();
380: for (PropertyChangeListener listener : entry.getValue()) {
381: BeanUtils.addPropertyChangeListener(bean, propertyName,
382: listener);
383: }
384: }
385: }
386:
387: /**
388: * Removes all registered PropertyChangeListeners from the given bean.
389: * If the bean is null no exception is thrown and no action is taken.
390: *
391: * @param bean the bean to remove the property change handler from.
392: * @throws PropertyUnboundException
393: * if the bean does not support bound properties
394: * @throws PropertyNotBindableException
395: * if the property change handler cannot be removed successfully
396: */
397: private void removeAllListenersFrom(Object bean) {
398: if (bean == null) {
399: return;
400: }
401: for (PropertyChangeListener listener : listenerList) {
402: BeanUtils.removePropertyChangeListener(bean, listener);
403: }
404: for (Entry<String, List<PropertyChangeListener>> entry : namedListeners
405: .entrySet()) {
406: String propertyName = entry.getKey();
407: for (PropertyChangeListener listener : entry.getValue()) {
408: BeanUtils.removePropertyChangeListener(bean,
409: propertyName, listener);
410: }
411: }
412: }
413:
414: // Helper Classes *********************************************************
415:
416: /**
417: * Listens to changes of the bean.
418: */
419: private final class BeanChangeHandler implements
420: PropertyChangeListener {
421:
422: /**
423: * The bean channel's value has been changed. Set the new bean,
424: * remove all listeners from the old bean and add them to the new bean.
425: *
426: * @param evt the property change event to be handled
427: */
428: public void propertyChange(PropertyChangeEvent evt) {
429: setBean0(evt.getOldValue(), evt.getNewValue());
430: }
431: }
432:
433: }
|