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    }