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 }