diff --git a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java index a06edf17c1757faac9665760568ecd8529e5a974..c0da75ffccd60e533c0d0471133ead85982aea0d 100644 --- a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java +++ b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java @@ -248,8 +248,8 @@ public class CachedIntrospectionResults { } } if (beanInfo == null) { - // If none of the factories supported the class, use the default - beanInfo = new ExtendedBeanInfo(Introspector.getBeanInfo(beanClass)); + // If none of the factories supported the class, fall back to the default + beanInfo = Introspector.getBeanInfo(beanClass); } this.beanInfo = beanInfo; diff --git a/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfoFactory.java b/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfoFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..c06d53d0ea8aefbd897db2d95203d32b96e557e0 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfoFactory.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.beans; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import org.springframework.core.Ordered; + +/** + * {@link BeanInfoFactory} implementation that evaluates whether bean classes have + * "non-standard" JavaBeans setter methods and are thus candidates for introspection by + * Spring's {@link ExtendedBeanInfo}. + * + *

Ordered at {@link Ordered#LOWEST_PRECEDENCE} to allow other user-defined + * {@link BeanInfoFactory} types to take precedence. + * + * @author Chris Beams + * @since 3.2 + * @see BeanInfoFactory + */ +class ExtendedBeanInfoFactory implements Ordered, BeanInfoFactory { + + /** + * Return whether the given bean class declares or inherits any non-void returning + * JavaBeans setter methods. + */ + public boolean supports(Class beanClass) { + for (Method method : beanClass.getMethods()) { + String methodName = method.getName(); + Class[] parameterTypes = method.getParameterTypes(); + if (Modifier.isPublic(method.getModifiers()) + && methodName.length() > 3 + && methodName.startsWith("set") + && (parameterTypes.length == 1 + || (parameterTypes.length == 2 && parameterTypes[0].equals(int.class))) + && !void.class.isAssignableFrom(method.getReturnType())) { + return true; + } + } + return false; + } + + /** + * Return a new {@link ExtendedBeanInfo} for the given bean class. + */ + public BeanInfo getBeanInfo(Class beanClass) throws IntrospectionException { + return new ExtendedBeanInfo(Introspector.getBeanInfo(beanClass)); + } + + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + +} diff --git a/spring-beans/src/main/resources/META-INF/spring.beanInfoFactories b/spring-beans/src/main/resources/META-INF/spring.beanInfoFactories new file mode 100644 index 0000000000000000000000000000000000000000..7a104edec22dbf2785cc90ebc26e141d7dbe8437 --- /dev/null +++ b/spring-beans/src/main/resources/META-INF/spring.beanInfoFactories @@ -0,0 +1 @@ +org.springframework.beans.ExtendedBeanInfoFactory diff --git a/spring-beans/src/test/java/org/springframework/beans/CachedIntrospectionResultsTests.java b/spring-beans/src/test/java/org/springframework/beans/CachedIntrospectionResultsTests.java index e5a6a0f82c6b88471c067650d8ece06e9ff8744f..4675d22dbfff683702030588d0052eb8ddc36849 100644 --- a/spring-beans/src/test/java/org/springframework/beans/CachedIntrospectionResultsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/CachedIntrospectionResultsTests.java @@ -17,6 +17,7 @@ package org.springframework.beans; import java.beans.BeanInfo; +import java.beans.PropertyDescriptor; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -25,6 +26,9 @@ import test.beans.TestBean; import org.springframework.core.OverridingClassLoader; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + /** * @author Juergen Hoeller * @author Chris Beams @@ -54,11 +58,32 @@ public final class CachedIntrospectionResultsTests { } @Test - public void customBeanInfoFactory() throws Exception { - CachedIntrospectionResults results = CachedIntrospectionResults.forClass(CachedIntrospectionResultsTests.class); - BeanInfo beanInfo = results.getBeanInfo(); + public void shouldUseExtendedBeanInfoWhenApplicable() throws NoSuchMethodException, SecurityException { + // given a class with a non-void returning setter method + @SuppressWarnings("unused") + class C { + public Object setFoo(String s) { return this; } + public String getFoo() { return null; } + } + + // CachedIntrospectionResults should delegate to ExtendedBeanInfo + CachedIntrospectionResults results = CachedIntrospectionResults.forClass(C.class); + BeanInfo info = results.getBeanInfo(); + PropertyDescriptor pd = null; + for (PropertyDescriptor candidate : info.getPropertyDescriptors()) { + if (candidate.getName().equals("foo")) { + pd = candidate; + } + } - assertTrue("Invalid BeanInfo instance", beanInfo instanceof DummyBeanInfoFactory.DummyBeanInfo); + // resulting in a property descriptor including the non-standard setFoo method + assertThat(pd, notNullValue()); + assertThat(pd.getReadMethod(), equalTo(C.class.getMethod("getFoo"))); + assertThat( + "No write method found for non-void returning 'setFoo' method. " + + "Check to see if CachedIntrospectionResults is delegating to " + + "ExtendedBeanInfo as expected", + pd.getWriteMethod(), equalTo(C.class.getMethod("setFoo", String.class))); } } diff --git a/spring-beans/src/test/java/org/springframework/beans/DummyBeanInfoFactory.java b/spring-beans/src/test/java/org/springframework/beans/DummyBeanInfoFactory.java deleted file mode 100644 index ef52db2771242d2cca0648ef67c442937d307a27..0000000000000000000000000000000000000000 --- a/spring-beans/src/test/java/org/springframework/beans/DummyBeanInfoFactory.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.beans; - -import java.beans.BeanInfo; -import java.beans.PropertyDescriptor; -import java.beans.SimpleBeanInfo; - -public class DummyBeanInfoFactory implements BeanInfoFactory { - - public boolean supports(Class beanClass) { - return CachedIntrospectionResultsTests.class.equals(beanClass); - } - - public BeanInfo getBeanInfo(Class beanClass) { - return new DummyBeanInfo(); - } - - public static class DummyBeanInfo extends SimpleBeanInfo { - - @Override - public PropertyDescriptor[] getPropertyDescriptors() { - return new PropertyDescriptor[0]; - } - } - -} diff --git a/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoFactoryTests.java new file mode 100644 index 0000000000000000000000000000000000000000..d56b672e5bcdb2f1a02945f2b1ae7001ae25828a --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoFactoryTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.beans; + +import java.beans.IntrospectionException; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +/** + * Unit tests for {@link ExtendedBeanInfoTests}. + * + * @author Chris Beams + */ +public class ExtendedBeanInfoFactoryTests { + + private ExtendedBeanInfoFactory factory = new ExtendedBeanInfoFactory(); + + @Test + public void shouldNotSupportClassHavingOnlyVoidReturningSetter() throws IntrospectionException { + @SuppressWarnings("unused") + class C { + public void setFoo(String s) { } + } + assertThat(factory.supports(C.class), is(false)); + } + + @Test + public void shouldSupportClassHavingNonVoidReturningSetter() throws IntrospectionException { + @SuppressWarnings("unused") + class C { + public C setFoo(String s) { return this; } + } + assertThat(factory.supports(C.class), is(true)); + } + + @Test + public void shouldSupportClassHavingNonVoidReturningIndexedSetter() throws IntrospectionException { + @SuppressWarnings("unused") + class C { + public C setFoo(int i, String s) { return this; } + } + assertThat(factory.supports(C.class), is(true)); + } + + @Test + public void shouldNotSupportClassHavingNonPublicNonVoidReturningIndexedSetter() throws IntrospectionException { + @SuppressWarnings("unused") + class C { + void setBar(String s) { } + } + assertThat(factory.supports(C.class), is(false)); + } + + @Test + public void shouldNotSupportClassHavingNonVoidReturningParameterlessSetter() throws IntrospectionException { + @SuppressWarnings("unused") + class C { + C setBar() { return this; } + } + assertThat(factory.supports(C.class), is(false)); + } + + @Test + public void shouldNotSupportClassHavingNonVoidReturningMethodNamedSet() throws IntrospectionException { + @SuppressWarnings("unused") + class C { + C set(String s) { return this; } + } + assertThat(factory.supports(C.class), is(false)); + } + +} diff --git a/spring-beans/src/test/resources/META-INF/spring.beanInfoFactories b/spring-beans/src/test/resources/META-INF/spring.beanInfoFactories deleted file mode 100644 index c2a8c64a4e8c0e35ff099bbda77369e72b9ea86e..0000000000000000000000000000000000000000 --- a/spring-beans/src/test/resources/META-INF/spring.beanInfoFactories +++ /dev/null @@ -1,3 +0,0 @@ -# Dummy bean info factories file, used by CachedIntrospectionResultsTests - -org.springframework.beans.DummyBeanInfoFactory \ No newline at end of file