001 /*
002 * Copyright 2002-2005 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.springframework.binding.support;
017
018 import java.beans.PropertyChangeListener;
019 import java.lang.reflect.InvocationTargetException;
020 import java.lang.reflect.Method;
021
022 import org.springframework.beans.FatalBeanException;
023 import org.springframework.binding.value.PropertyChangePublisher;
024 import org.springframework.util.Assert;
025
026 /**
027 * Consists exclusively of static convenience methods for adding
028 * and removing <code>PropertyChangeListener</code>s for bound
029 * JavaBean properties.<p>
030 *
031 * TODO: Move this code into {@link org.springframework.beans.BeanUtils}
032 *
033 * @author Karsten Lentzsch
034 * @author Oliver Hutchison
035 */
036 public abstract class PropertyChangeSupportUtils {
037
038 /**
039 * Holds the class parameter list that is used to lookup
040 * the adder and remover methods for PropertyChangeListeners.
041 */
042 private static final Class[] NAMED_PCL_PARAMS = new Class[] {String.class, PropertyChangeListener.class};
043
044 /**
045 * Checks and answers whether the given class supports bound properties,
046 * i.e. it provides a pair of bound property event listener registration methods:
047 * <pre>
048 * public void addPropertyChangeListener(String, PropertyChangeListener);
049 * public void removePropertyChangeListener(String, PropertyChangeListener);
050 * </pre>
051 *
052 * @param beanClass the class to test
053 * @return true if the class supports bound properties, false otherwise
054 */
055 public static boolean supportsBoundProperties(Class beanClass) {
056 return PropertyChangePublisher.class.isAssignableFrom(beanClass)
057 || ((getNamedPCLAdder(beanClass) != null) && (getNamedPCLRemover(beanClass) != null));
058 }
059
060 /**
061 * Adds a named property change listener to the given JavaBean. The bean
062 * must provide the optional support for listening on named properties
063 * as described in section 7.4.5 of the
064 * <a href="http://java.sun.com/products/javabeans/docs/spec.html">Java Bean
065 * Specification</a>. The bean class must provide the method:
066 * <pre>
067 * public void addPropertyChangeListener(String, PropertyChangeListener);
068 * </pre>
069 *
070 * @param bean the JavaBean to add a property change handler
071 * @param propertyName the name of the property to be observed
072 * @param listener the listener to add
073
074 * @throws PropertyNotBindableException
075 * if the property change handler cannot be added successfully
076 */
077 public static void addPropertyChangeListener(Object bean, String propertyName, PropertyChangeListener listener) {
078 Assert.notNull(propertyName, "The property name must not be null.");
079 Assert.notNull(listener, "The listener must not be null.");
080 if (bean instanceof PropertyChangePublisher) {
081 ((PropertyChangePublisher)bean).addPropertyChangeListener(propertyName, listener);
082 }
083 else {
084 Class beanClass = bean.getClass();
085 Method namedPCLAdder = getNamedPCLAdder(beanClass);
086 if (namedPCLAdder == null)
087 throw new FatalBeanException("Could not find the bean method"
088 + "/npublic void addPropertyChangeListener(String, PropertyChangeListener);/nin bean '" + bean
089 + "'");
090 try {
091 namedPCLAdder.invoke(bean, new Object[] {propertyName, listener});
092 }
093 catch (InvocationTargetException e) {
094 throw new FatalBeanException("Due to an InvocationTargetException we failed to add "
095 + "a named PropertyChangeListener to bean '" + bean + "'", e);
096 }
097 catch (IllegalAccessException e) {
098 throw new FatalBeanException("Due to an IllegalAccessException we failed to add "
099 + "a named PropertyChangeListener to bean '" + bean + "'", e);
100 }
101 }
102 }
103
104 /**
105 * Removes a named property change listener to the given JavaBean. The bean
106 * must provide the optional support for listening on named properties
107 * as described in section 7.4.5 of the
108 * <a href="http://java.sun.com/products/javabeans/docs/spec.html">Java Bean
109 * Specification</a>. The bean class must provide the method:
110 * <pre>
111 * public void removePropertyChangeHandler(String, PropertyChangeListener);
112 * </pre>
113 *
114 * @param bean the bean to remove the property change listener from
115 * @param propertyName the name of the observed property
116 * @param listener the listener to remove
117 * @throws FatalBeanException
118 * if the property change handler cannot be removed successfully
119 */
120 public static void removePropertyChangeListener(Object bean, String propertyName, PropertyChangeListener listener) {
121 Assert.notNull(propertyName, "The property name must not be null.");
122 Assert.notNull(listener, "The listener must not be null.");
123 if (bean instanceof PropertyChangePublisher) {
124 ((PropertyChangePublisher)bean).removePropertyChangeListener(propertyName, listener);
125 }
126 else {
127 Class beanClass = bean.getClass();
128 Method namedPCLRemover = getNamedPCLRemover(beanClass);
129 if (namedPCLRemover == null)
130 throw new FatalBeanException("Could not find the bean method"
131 + "/npublic void removePropertyChangeListener(String, PropertyChangeListener);/nin bean '"
132 + bean + "'");
133 try {
134 namedPCLRemover.invoke(bean, new Object[] {propertyName, listener});
135 }
136 catch (InvocationTargetException e) {
137 throw new FatalBeanException("Due to an InvocationTargetException we failed to remove "
138 + "a named PropertyChangeListener from bean '" + bean + "'", e);
139 }
140 catch (IllegalAccessException e) {
141 throw new FatalBeanException("Due to an IllegalAccessException we failed to remove "
142 + "a named PropertyChangeListener from bean '" + bean + "'", e);
143 }
144 }
145 }
146
147 /**
148 * Looks up and returns the method that adds a PropertyChangeListener
149 * for a specified property name to instances of the given class.
150 *
151 * @param beanClass the class that provides the adder method
152 * @return the method that adds the PropertyChangeListeners
153 */
154 private static Method getNamedPCLAdder(Class beanClass) {
155 try {
156 return beanClass.getMethod("addPropertyChangeListener", NAMED_PCL_PARAMS);
157 }
158 catch (NoSuchMethodException e) {
159 return null;
160 }
161 }
162
163 /**
164 * Looks up and returns the method that removes a PropertyChangeListener
165 * for a specified property name from instances of the given class.
166 *
167 * @param beanClass the class that provides the remover method
168 * @return the method that removes the PropertyChangeListeners
169 */
170 private static Method getNamedPCLRemover(Class beanClass) {
171 try {
172 return beanClass.getMethod("removePropertyChangeListener", NAMED_PCL_PARAMS);
173 }
174 catch (NoSuchMethodException e) {
175 return null;
176 }
177 }
178 }