Browse Source
Decorator for instances returned from Introspector#getBeanInfo(Class<?>) that supports detection and inclusion of non-void returning setter methods. Fully supports indexed properties and otherwise faithfully mimics the default BeanInfo#getPropertyDescriptors() behavior, e.g., PropertyDescriptor ordering, etc. This decorator has been integrated with CachedIntrospectionResults meaning that, in simple terms, the Spring container now supports injection of setter methods having any return type. Issue: SPR-8079 git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@4144 50f2f4bb-b051-0410-bef5-90022cba6387pull/1/merge
3 changed files with 914 additions and 1 deletions
@ -0,0 +1,326 @@
@@ -0,0 +1,326 @@
|
||||
/* |
||||
* Copyright 2002-2011 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.awt.Image; |
||||
import java.beans.BeanDescriptor; |
||||
import java.beans.BeanInfo; |
||||
import java.beans.EventSetDescriptor; |
||||
import java.beans.IndexedPropertyDescriptor; |
||||
import java.beans.IntrospectionException; |
||||
import java.beans.Introspector; |
||||
import java.beans.MethodDescriptor; |
||||
import java.beans.PropertyDescriptor; |
||||
import java.lang.reflect.Method; |
||||
import java.util.Comparator; |
||||
import java.util.SortedSet; |
||||
import java.util.TreeSet; |
||||
|
||||
import org.springframework.util.ReflectionUtils; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* Decorates a standard {@link BeanInfo} object (likely created created by |
||||
* {@link Introspector#getBeanInfo(Class)}) by including non-void returning setter |
||||
* methods in the collection of {@link #getPropertyDescriptors() property descriptors}. |
||||
* Both regular and |
||||
* <a href="http://download.oracle.com/javase/tutorial/javabeans/properties/indexed.html"> |
||||
* indexed properties</a> are fully supported. |
||||
* |
||||
* <p>The wrapped {@code BeanInfo} object is not modified in any way. |
||||
* |
||||
* @author Chris Beams |
||||
* @since 3.1 |
||||
* @see CachedIntrospectionResults |
||||
*/ |
||||
public class ExtendedBeanInfo implements BeanInfo { |
||||
private final BeanInfo delegate; |
||||
private final SortedSet<PropertyDescriptor> propertyDescriptors = |
||||
new TreeSet<PropertyDescriptor>(new PropertyDescriptorComparator()); |
||||
|
||||
/** |
||||
* Wrap the given delegate {@link BeanInfo} instance and find any non-void returning |
||||
* setter methods, creating and adding a {@link PropertyDescriptor} for each. |
||||
* |
||||
* <p>The wrapped {@code BeanInfo} is not modified in any way by this process. |
||||
* |
||||
* @see #getPropertyDescriptors() |
||||
* @throws IntrospectionException if any problems occur creating and adding new {@code PropertyDescriptors} |
||||
*/ |
||||
public ExtendedBeanInfo(BeanInfo delegate) throws IntrospectionException { |
||||
this.delegate = delegate; |
||||
|
||||
// PropertyDescriptor instances from the delegate object are never added directly, but always
|
||||
// copied to the local collection of #propertyDescriptors and returned by calls to
|
||||
// #getPropertyDescriptors(). this algorithm iterates through all methods (method descriptors)
|
||||
// in the wrapped BeanInfo object, copying any existing PropertyDescriptor or creating a new
|
||||
// one for any non-standard setter methods found.
|
||||
|
||||
ALL_METHODS: |
||||
for (MethodDescriptor md : delegate.getMethodDescriptors()) { |
||||
Method method = md.getMethod(); |
||||
|
||||
// bypass non-getter java.lang.Class methods for efficiency
|
||||
if (ReflectionUtils.isObjectMethod(method) && !method.getName().startsWith("get")) { |
||||
continue ALL_METHODS; |
||||
} |
||||
|
||||
// is the method a NON-INDEXED setter? ignore return type in order to capture non-void signatures
|
||||
if (method.getName().startsWith("set") && method.getParameterTypes().length == 1) { |
||||
String propertyName = propertyNameFor(method); |
||||
for (PropertyDescriptor pd : delegate.getPropertyDescriptors()) { |
||||
Method readMethod = pd.getReadMethod(); |
||||
Method writeMethod = pd.getWriteMethod(); |
||||
// has the setter already been found by the wrapped BeanInfo?
|
||||
if (writeMethod != null |
||||
&& writeMethod.getName().equals(method.getName())) { |
||||
// yes -> copy it, including corresponding getter method (if any -- may be null)
|
||||
this.addOrUpdatePropertyDescriptor(propertyName, readMethod, writeMethod); |
||||
continue ALL_METHODS; |
||||
} |
||||
// has a getter corresponding to this setter already been found by the wrapped BeanInfo?
|
||||
if (readMethod != null |
||||
&& readMethod.getName().equals(getterMethodNameFor(propertyName)) |
||||
&& readMethod.getReturnType().equals(method.getParameterTypes()[0])) { |
||||
this.addOrUpdatePropertyDescriptor(propertyName, readMethod, method); |
||||
continue ALL_METHODS; |
||||
} |
||||
} |
||||
// the setter method was not found by the wrapped BeanInfo -> add a new PropertyDescriptor for it
|
||||
// no corresponding getter was detected, so the 'read method' parameter is null.
|
||||
this.addOrUpdatePropertyDescriptor(propertyName, null, method); |
||||
continue ALL_METHODS; |
||||
} |
||||
|
||||
// is the method an INDEXED setter? ignore return type in order to capture non-void signatures
|
||||
if (method.getName().startsWith("set") && method.getParameterTypes().length == 2 && method.getParameterTypes()[0].equals(int.class)) { |
||||
String propertyName = propertyNameFor(method); |
||||
DELEGATE_PD: |
||||
for (PropertyDescriptor pd : delegate.getPropertyDescriptors()) { |
||||
if (!(pd instanceof IndexedPropertyDescriptor)) { |
||||
continue DELEGATE_PD; |
||||
} |
||||
IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd; |
||||
Method readMethod = ipd.getReadMethod(); |
||||
Method writeMethod = ipd.getWriteMethod(); |
||||
Method indexedReadMethod = ipd.getIndexedReadMethod(); |
||||
Method indexedWriteMethod = ipd.getIndexedWriteMethod(); |
||||
// has the setter already been found by the wrapped BeanInfo?
|
||||
if (indexedWriteMethod != null |
||||
&& indexedWriteMethod.getName().equals(method.getName())) { |
||||
// yes -> copy it, including corresponding getter method (if any -- may be null)
|
||||
this.addOrUpdatePropertyDescriptor(propertyName, readMethod, writeMethod, indexedReadMethod, indexedWriteMethod); |
||||
continue ALL_METHODS; |
||||
} |
||||
// has a getter corresponding to this setter already been found by the wrapped BeanInfo?
|
||||
if (indexedReadMethod != null |
||||
&& indexedReadMethod.getName().equals(getterMethodNameFor(propertyName)) |
||||
&& indexedReadMethod.getReturnType().equals(method.getParameterTypes()[1])) { |
||||
this.addOrUpdatePropertyDescriptor(propertyName, readMethod, writeMethod, indexedReadMethod, method); |
||||
continue ALL_METHODS; |
||||
} |
||||
} |
||||
// the INDEXED setter method was not found by the wrapped BeanInfo -> add a new PropertyDescriptor
|
||||
// for it. no corresponding INDEXED getter was detected, so the 'indexed read method' parameter is null.
|
||||
this.addOrUpdatePropertyDescriptor(propertyName, null, null, null, method); |
||||
continue ALL_METHODS; |
||||
} |
||||
|
||||
// the method is not a setter, but is it a getter?
|
||||
for (PropertyDescriptor pd : delegate.getPropertyDescriptors()) { |
||||
// have we already copied this read method to a property descriptor locally?
|
||||
for (PropertyDescriptor existingPD : this.propertyDescriptors) { |
||||
if (method.equals(pd.getReadMethod()) |
||||
&& existingPD.getName().equals(pd.getName())) { |
||||
if (existingPD.getReadMethod() == null) { |
||||
// no -> add it now
|
||||
this.addOrUpdatePropertyDescriptor(pd.getName(), method, pd.getWriteMethod()); |
||||
} |
||||
// yes -> do not add a duplicate
|
||||
continue ALL_METHODS; |
||||
} |
||||
} |
||||
if (method == pd.getReadMethod() |
||||
|| (pd instanceof IndexedPropertyDescriptor && method == ((IndexedPropertyDescriptor) pd).getIndexedReadMethod())) { |
||||
// yes -> copy it, including corresponding setter method (if any -- may be null)
|
||||
if (pd instanceof IndexedPropertyDescriptor) { |
||||
this.addOrUpdatePropertyDescriptor(pd.getName(), pd.getReadMethod(), pd.getWriteMethod(), ((IndexedPropertyDescriptor)pd).getIndexedReadMethod(), ((IndexedPropertyDescriptor)pd).getIndexedWriteMethod()); |
||||
} else { |
||||
this.addOrUpdatePropertyDescriptor(pd.getName(), pd.getReadMethod(), pd.getWriteMethod()); |
||||
} |
||||
continue ALL_METHODS; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void addOrUpdatePropertyDescriptor(String propertyName, Method readMethod, Method writeMethod) throws IntrospectionException { |
||||
addOrUpdatePropertyDescriptor(propertyName, readMethod, writeMethod, null, null); |
||||
} |
||||
|
||||
private void addOrUpdatePropertyDescriptor(String propertyName, Method readMethod, Method writeMethod, Method indexedReadMethod, Method indexedWriteMethod) throws IntrospectionException { |
||||
for (PropertyDescriptor existingPD : this.propertyDescriptors) { |
||||
if (existingPD.getName().equals(propertyName)) { |
||||
// is there already a descriptor that captures this read method or its corresponding write method?
|
||||
if (existingPD.getReadMethod() != null) { |
||||
if (readMethod != null && existingPD.getReadMethod().getReturnType() != readMethod.getReturnType() |
||||
|| writeMethod != null && existingPD.getReadMethod().getReturnType() != writeMethod.getParameterTypes()[0]) { |
||||
// no -> add a new descriptor for it below
|
||||
break; |
||||
} |
||||
} |
||||
// update the existing descriptor's read method
|
||||
if (readMethod != null) { |
||||
try { |
||||
existingPD.setReadMethod(readMethod); |
||||
} catch (IntrospectionException ex) { |
||||
// there is a conflicting setter method present -> null it out and try again
|
||||
existingPD.setWriteMethod(null); |
||||
existingPD.setReadMethod(readMethod); |
||||
} |
||||
} |
||||
|
||||
// is there already a descriptor that captures this write method or its corresponding read method?
|
||||
if (existingPD.getWriteMethod() != null) { |
||||
if (readMethod != null && existingPD.getWriteMethod().getParameterTypes()[0] != readMethod.getReturnType() |
||||
|| writeMethod != null && existingPD.getWriteMethod().getParameterTypes()[0] != writeMethod.getParameterTypes()[0]) { |
||||
// no -> add a new descriptor for it below
|
||||
break; |
||||
} |
||||
} |
||||
// update the existing descriptor's write method
|
||||
if (writeMethod != null) { |
||||
existingPD.setWriteMethod(writeMethod); |
||||
} |
||||
|
||||
// is this descriptor indexed?
|
||||
if (existingPD instanceof IndexedPropertyDescriptor) { |
||||
IndexedPropertyDescriptor existingIPD = (IndexedPropertyDescriptor) existingPD; |
||||
|
||||
// is there already a descriptor that captures this indexed read method or its corresponding indexed write method?
|
||||
if (existingIPD.getIndexedReadMethod() != null) { |
||||
if (indexedReadMethod != null && existingIPD.getIndexedReadMethod().getReturnType() != indexedReadMethod.getReturnType() |
||||
|| indexedWriteMethod != null && existingIPD.getIndexedReadMethod().getReturnType() != indexedWriteMethod.getParameterTypes()[1]) { |
||||
// no -> add a new descriptor for it below
|
||||
break; |
||||
} |
||||
} |
||||
// update the existing descriptor's indexed read method
|
||||
try { |
||||
existingIPD.setIndexedReadMethod(indexedReadMethod); |
||||
} catch (IntrospectionException ex) { |
||||
// there is a conflicting indexed setter method present -> null it out and try again
|
||||
existingIPD.setIndexedWriteMethod(null); |
||||
existingIPD.setIndexedReadMethod(indexedReadMethod); |
||||
} |
||||
|
||||
// is there already a descriptor that captures this indexed write method or its corresponding indexed read method?
|
||||
if (existingIPD.getIndexedWriteMethod() != null) { |
||||
if (indexedReadMethod != null && existingIPD.getIndexedWriteMethod().getParameterTypes()[1] != indexedReadMethod.getReturnType() |
||||
|| indexedWriteMethod != null && existingIPD.getIndexedWriteMethod().getParameterTypes()[1] != indexedWriteMethod.getParameterTypes()[1]) { |
||||
// no -> add a new descriptor for it below
|
||||
break; |
||||
} |
||||
} |
||||
// update the existing descriptor's indexed write method
|
||||
if (indexedWriteMethod != null) { |
||||
existingIPD.setIndexedWriteMethod(indexedWriteMethod); |
||||
} |
||||
} |
||||
|
||||
// the descriptor has been updated -> return immediately
|
||||
return; |
||||
} |
||||
} |
||||
|
||||
// we haven't yet seen read or write methods for this property -> add a new descriptor
|
||||
if (indexedReadMethod == null && indexedWriteMethod == null) { |
||||
this.propertyDescriptors.add(new PropertyDescriptor(propertyName, readMethod, writeMethod)); |
||||
} else { |
||||
this.propertyDescriptors.add(new IndexedPropertyDescriptor(propertyName, readMethod, writeMethod, indexedReadMethod, indexedWriteMethod)); |
||||
} |
||||
} |
||||
|
||||
private String propertyNameFor(Method method) { |
||||
return Introspector.decapitalize(method.getName().substring(3,method.getName().length())); |
||||
} |
||||
|
||||
private Object getterMethodNameFor(String name) { |
||||
return "get" + StringUtils.capitalize(name); |
||||
} |
||||
|
||||
public BeanInfo[] getAdditionalBeanInfo() { |
||||
return delegate.getAdditionalBeanInfo(); |
||||
} |
||||
|
||||
public BeanDescriptor getBeanDescriptor() { |
||||
return delegate.getBeanDescriptor(); |
||||
} |
||||
|
||||
public int getDefaultEventIndex() { |
||||
return delegate.getDefaultEventIndex(); |
||||
} |
||||
|
||||
public int getDefaultPropertyIndex() { |
||||
return delegate.getDefaultPropertyIndex(); |
||||
} |
||||
|
||||
public EventSetDescriptor[] getEventSetDescriptors() { |
||||
return delegate.getEventSetDescriptors(); |
||||
} |
||||
|
||||
public Image getIcon(int arg0) { |
||||
return delegate.getIcon(arg0); |
||||
} |
||||
|
||||
public MethodDescriptor[] getMethodDescriptors() { |
||||
return delegate.getMethodDescriptors(); |
||||
} |
||||
|
||||
/** |
||||
* Return the set of {@link PropertyDescriptor}s from the wrapped {@link BeanInfo} |
||||
* object as well as {@code PropertyDescriptor}s for each non-void returning setter |
||||
* method found during construction. |
||||
* @see #ExtendedBeanInfo(BeanInfo) |
||||
*/ |
||||
public PropertyDescriptor[] getPropertyDescriptors() { |
||||
return this.propertyDescriptors.toArray(new PropertyDescriptor[this.propertyDescriptors.size()]); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Sorts PropertyDescriptor instances alphanumerically to emulate the behavior of {@link java.beans.BeanInfo#getPropertyDescriptors()}. |
||||
* |
||||
* @see ExtendedBeanInfo#propertyDescriptors |
||||
*/ |
||||
static class PropertyDescriptorComparator implements Comparator<PropertyDescriptor> { |
||||
public int compare(PropertyDescriptor desc1, PropertyDescriptor desc2) { |
||||
String left = desc1.getName(); |
||||
String right = desc2.getName(); |
||||
for (int i = 0; i < left.length(); i++) { |
||||
if (right.length() == i) { |
||||
return 1; |
||||
} |
||||
int result = left.getBytes()[i] - right.getBytes()[i]; |
||||
if (result != 0) { |
||||
return result; |
||||
} |
||||
} |
||||
return left.length() - right.length(); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,587 @@
@@ -0,0 +1,587 @@
|
||||
/* |
||||
* Copyright 2002-2011 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 static org.hamcrest.CoreMatchers.equalTo; |
||||
import static org.hamcrest.CoreMatchers.instanceOf; |
||||
import static org.hamcrest.CoreMatchers.is; |
||||
import static org.hamcrest.CoreMatchers.notNullValue; |
||||
import static org.hamcrest.CoreMatchers.nullValue; |
||||
import static org.hamcrest.Matchers.greaterThan; |
||||
import static org.hamcrest.Matchers.lessThan; |
||||
import static org.junit.Assert.assertThat; |
||||
import static org.junit.Assert.fail; |
||||
|
||||
import java.beans.BeanInfo; |
||||
import java.beans.IndexedPropertyDescriptor; |
||||
import java.beans.IntrospectionException; |
||||
import java.beans.Introspector; |
||||
import java.beans.PropertyDescriptor; |
||||
|
||||
import org.junit.Ignore; |
||||
import org.junit.Test; |
||||
import org.springframework.beans.ExtendedBeanInfo.PropertyDescriptorComparator; |
||||
|
||||
import test.beans.TestBean; |
||||
|
||||
/** |
||||
* Unit tests for {@link ExtendedBeanInfo}. |
||||
* |
||||
* @author Chris Beams |
||||
* @since 3.1 |
||||
*/ |
||||
public class ExtendedBeanInfoTests { |
||||
|
||||
@Test |
||||
public void standardReadMethodOnly() throws IntrospectionException { |
||||
@SuppressWarnings("unused") class C { |
||||
public String getFoo() { return null; } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); |
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false)); |
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(false)); |
||||
} |
||||
|
||||
@Test |
||||
public void standardWriteMethodOnly() throws IntrospectionException { |
||||
@SuppressWarnings("unused") class C { |
||||
public void setFoo(String f) { } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); |
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(false)); |
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(true)); |
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foo"), is(false)); |
||||
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true)); |
||||
} |
||||
|
||||
@Test |
||||
public void standardReadAndWriteMethods() throws IntrospectionException { |
||||
@SuppressWarnings("unused") class C { |
||||
public void setFoo(String f) { } |
||||
public String getFoo() { return null; } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); |
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(true)); |
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true)); |
||||
} |
||||
|
||||
@Test |
||||
public void nonStandardWriteMethodOnly() throws IntrospectionException { |
||||
@SuppressWarnings("unused") class C { |
||||
public C setFoo(String foo) { return this; } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); |
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(false)); |
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false)); |
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foo"), is(false)); |
||||
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true)); |
||||
} |
||||
|
||||
@Test |
||||
public void standardReadAndNonStandardWriteMethods() throws IntrospectionException { |
||||
@SuppressWarnings("unused") class C { |
||||
public String getFoo() { return null; } |
||||
public C setFoo(String foo) { return this; } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); |
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false)); |
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true)); |
||||
} |
||||
|
||||
@Test |
||||
public void standardReadMethodsAndOverloadedNonStandardWriteMethods() throws Exception { |
||||
@SuppressWarnings("unused") class C { |
||||
public String getFoo() { return null; } |
||||
public C setFoo(String foo) { return this; } |
||||
public C setFoo(Number foo) { return this; } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); |
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false)); |
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true)); |
||||
|
||||
for (PropertyDescriptor pd : ebi.getPropertyDescriptors()) { |
||||
if (pd.getName().equals("foo")) { |
||||
assertThat(pd.getWriteMethod(), is(C.class.getMethod("setFoo", String.class))); |
||||
return; |
||||
} |
||||
} |
||||
fail("never matched write method"); |
||||
} |
||||
|
||||
@Test |
||||
public void standardReadMethodInSuperclassAndNonStandardWriteMethodInSubclass() throws Exception { |
||||
@SuppressWarnings("unused") class B { |
||||
public String getFoo() { return null; } |
||||
} |
||||
@SuppressWarnings("unused") class C extends B { |
||||
public C setFoo(String foo) { return this; } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); |
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false)); |
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true)); |
||||
} |
||||
|
||||
@Test |
||||
public void standardReadMethodInSuperAndSubclassesAndGenericBuilderStyleNonStandardWriteMethodInSuperAndSubclasses() throws Exception { |
||||
abstract class B<This extends B<This>> { |
||||
@SuppressWarnings("unchecked") |
||||
protected final This instance = (This) this; |
||||
private String foo; |
||||
public String getFoo() { return foo; } |
||||
public This setFoo(String foo) { |
||||
this.foo = foo; |
||||
return this.instance; |
||||
} |
||||
} |
||||
|
||||
class C extends B<C> { |
||||
private int bar = -1; |
||||
public int getBar() { return bar; } |
||||
public C setBar(int bar) { |
||||
this.bar = bar; |
||||
return this.instance; |
||||
} |
||||
} |
||||
|
||||
C c = new C() |
||||
.setFoo("blue") |
||||
.setBar(42); |
||||
|
||||
assertThat(c.getFoo(), is("blue")); |
||||
assertThat(c.getBar(), is(42)); |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); |
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false)); |
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "bar"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(bi, "bar"), is(false)); |
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true)); |
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "bar"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(ebi, "bar"), is(true)); |
||||
} |
||||
|
||||
@Test |
||||
public void nonPublicStandardReadAndWriteMethods() throws Exception { |
||||
@SuppressWarnings("unused") class C { |
||||
String getFoo() { return null; } |
||||
C setFoo(String foo) { return this; } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); |
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(false)); |
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false)); |
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foo"), is(false)); |
||||
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(false)); |
||||
} |
||||
|
||||
/** |
||||
* {@link ExtendedBeanInfo} should behave exactly like {@link BeanInfo} |
||||
* in strange edge cases. |
||||
*/ |
||||
@Test |
||||
public void readMethodReturnsSupertypeOfWriteMethodParameter() throws IntrospectionException { |
||||
@SuppressWarnings("unused") class C { |
||||
public Number getFoo() { return null; } |
||||
public void setFoo(Integer foo) { } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); |
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false)); |
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(false)); |
||||
} |
||||
|
||||
@Test |
||||
public void indexedReadMethodReturnsSupertypeOfIndexedWriteMethodParameter() throws IntrospectionException { |
||||
@SuppressWarnings("unused") class C { |
||||
public Number getFoos(int index) { return null; } |
||||
public void setFoos(int index, Integer foo) { } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); |
||||
|
||||
assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true)); |
||||
assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(false)); |
||||
|
||||
assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true)); |
||||
assertThat(hasIndexedWriteMethodForProperty(ebi, "foos"), is(false)); |
||||
} |
||||
|
||||
/** |
||||
* {@link ExtendedBeanInfo} should behave exactly like {@link BeanInfo} |
||||
* in strange edge cases. |
||||
*/ |
||||
@Test |
||||
public void readMethodReturnsSubtypeOfWriteMethodParameter() throws IntrospectionException { |
||||
@SuppressWarnings("unused") class C { |
||||
public Integer getFoo() { return null; } |
||||
public void setFoo(Number foo) { } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); |
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false)); |
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(false)); |
||||
} |
||||
|
||||
@Test |
||||
public void indexedReadMethodReturnsSubtypeOfIndexedWriteMethodParameter() throws IntrospectionException { |
||||
@SuppressWarnings("unused") class C { |
||||
public Integer getFoos(int index) { return null; } |
||||
public void setFoo(int index, Number foo) { } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); |
||||
|
||||
assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true)); |
||||
assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(false)); |
||||
|
||||
assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true)); |
||||
assertThat(hasIndexedWriteMethodForProperty(ebi, "foos"), is(false)); |
||||
} |
||||
|
||||
@Test |
||||
public void indexedReadMethodOnly() throws IntrospectionException { |
||||
@SuppressWarnings("unused") |
||||
class C { |
||||
// indexed read method
|
||||
public String getFoos(int i) { return null; } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class)); |
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foos"), is(false)); |
||||
assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true)); |
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foos"), is(false)); |
||||
assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true)); |
||||
} |
||||
|
||||
@Test |
||||
public void indexedWriteMethodOnly() throws IntrospectionException { |
||||
@SuppressWarnings("unused") |
||||
class C { |
||||
// indexed write method
|
||||
public void setFoos(int i, String foo) { } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class)); |
||||
|
||||
assertThat(hasWriteMethodForProperty(bi, "foos"), is(false)); |
||||
assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(true)); |
||||
|
||||
assertThat(hasWriteMethodForProperty(ebi, "foos"), is(false)); |
||||
assertThat(hasIndexedWriteMethodForProperty(ebi, "foos"), is(true)); |
||||
} |
||||
|
||||
@Test |
||||
public void indexedReadAndIndexedWriteMethods() throws IntrospectionException { |
||||
@SuppressWarnings("unused") |
||||
class C { |
||||
// indexed read method
|
||||
public String getFoos(int i) { return null; } |
||||
// indexed write method
|
||||
public void setFoos(int i, String foo) { } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class)); |
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foos"), is(false)); |
||||
assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(bi, "foos"), is(false)); |
||||
assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(true)); |
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foos"), is(false)); |
||||
assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(ebi, "foos"), is(false)); |
||||
assertThat(hasIndexedWriteMethodForProperty(ebi, "foos"), is(true)); |
||||
} |
||||
|
||||
@Test |
||||
public void readAndWriteAndIndexedReadAndIndexedWriteMethods() throws IntrospectionException { |
||||
@SuppressWarnings("unused") |
||||
class C { |
||||
// read method
|
||||
public String[] getFoos() { return null; } |
||||
// indexed read method
|
||||
public String getFoos(int i) { return null; } |
||||
// write method
|
||||
public void setFoos(String[] foos) { } |
||||
// indexed write method
|
||||
public void setFoos(int i, String foo) { } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class)); |
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foos"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(bi, "foos"), is(true)); |
||||
assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true)); |
||||
assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(true)); |
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foos"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(ebi, "foos"), is(true)); |
||||
assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true)); |
||||
assertThat(hasIndexedWriteMethodForProperty(ebi, "foos"), is(true)); |
||||
} |
||||
|
||||
@Test |
||||
public void indexedReadAndNonStandardIndexedWrite() throws IntrospectionException { |
||||
@SuppressWarnings("unused") |
||||
class C { |
||||
// indexed read method
|
||||
public String getFoos(int i) { return null; } |
||||
// non-standard indexed write method
|
||||
public C setFoos(int i, String foo) { return this; } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class)); |
||||
|
||||
assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true)); |
||||
// interesting! standard Inspector picks up non-void return types on indexed write methods by default
|
||||
assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(true)); |
||||
|
||||
assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true)); |
||||
assertThat(hasIndexedWriteMethodForProperty(ebi, "foos"), is(true)); |
||||
} |
||||
|
||||
@Test |
||||
public void indexedReadAndNonStandardWriteAndNonStandardIndexedWrite() throws IntrospectionException { |
||||
@SuppressWarnings("unused") |
||||
class C { |
||||
// non-standard write method
|
||||
public C setFoos(String[] foos) { return this; } |
||||
// indexed read method
|
||||
public String getFoos(int i) { return null; } |
||||
// non-standard indexed write method
|
||||
public C setFoos(int i, String foo) { return this; } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class)); |
||||
|
||||
assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(bi, "foos"), is(false)); |
||||
// again as above, standard Inspector picks up non-void return types on indexed write methods by default
|
||||
assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(true)); |
||||
|
||||
assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(ebi, "foos"), is(true)); |
||||
assertThat(hasIndexedWriteMethodForProperty(ebi, "foos"), is(true)); |
||||
} |
||||
|
||||
@Test |
||||
public void subclassWriteMethodWithCovariantReturnType() throws IntrospectionException { |
||||
@SuppressWarnings("unused") class B { |
||||
public String getFoo() { return null; } |
||||
public Number setFoo(String foo) { return null; } |
||||
} |
||||
class C extends B { |
||||
public String getFoo() { return null; } |
||||
public Integer setFoo(String foo) { return null; } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); |
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false)); |
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true)); |
||||
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true)); |
||||
|
||||
assertThat(ebi.getPropertyDescriptors().length, equalTo(bi.getPropertyDescriptors().length)); |
||||
} |
||||
|
||||
@Test |
||||
public void nonStandardReadMethodAndStandardWriteMethod() throws IntrospectionException { |
||||
@SuppressWarnings("unused") class C { |
||||
public void getFoo() { } |
||||
public void setFoo(String foo) { } |
||||
} |
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class); |
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); |
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(false)); |
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(true)); |
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foo"), is(false)); |
||||
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true)); |
||||
} |
||||
|
||||
@Test |
||||
public void propertyCountsMatch() throws IntrospectionException { |
||||
BeanInfo bi = Introspector.getBeanInfo(TestBean.class); |
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); |
||||
|
||||
assertThat(ebi.getPropertyDescriptors().length, equalTo(bi.getPropertyDescriptors().length)); |
||||
} |
||||
|
||||
@Test |
||||
public void propertyCountsWithNonStandardWriteMethod() throws IntrospectionException { |
||||
class ExtendedTestBean extends TestBean { |
||||
@SuppressWarnings("unused") |
||||
public ExtendedTestBean setFoo(String s) { return this; } |
||||
} |
||||
BeanInfo bi = Introspector.getBeanInfo(ExtendedTestBean.class); |
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); |
||||
|
||||
boolean found = false; |
||||
for (PropertyDescriptor pd : ebi.getPropertyDescriptors()) { |
||||
if (pd.getName().equals("foo")) { |
||||
found = true; |
||||
} |
||||
} |
||||
assertThat(found, is(true)); |
||||
assertThat(ebi.getPropertyDescriptors().length, equalTo(bi.getPropertyDescriptors().length+1)); |
||||
} |
||||
|
||||
/** |
||||
* {@link BeanInfo#getPropertyDescriptors()} returns alphanumerically sorted. |
||||
* Test that {@link ExtendedBeanInfo#getPropertyDescriptors()} does the same. |
||||
*/ |
||||
@Test |
||||
public void propertyDescriptorOrderIsEqual() throws IntrospectionException { |
||||
BeanInfo bi = Introspector.getBeanInfo(TestBean.class); |
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi); |
||||
|
||||
for (int i = 0; i < bi.getPropertyDescriptors().length; i++) { |
||||
assertThat("element " + i + " in BeanInfo and ExtendedBeanInfo propertyDescriptor arrays do not match", |
||||
ebi.getPropertyDescriptors()[i].getName(), equalTo(bi.getPropertyDescriptors()[i].getName())); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void propertyDescriptorComparator() throws IntrospectionException { |
||||
PropertyDescriptorComparator c = new PropertyDescriptorComparator(); |
||||
assertThat(c.compare(new PropertyDescriptor("a", null, null), new PropertyDescriptor("a", null, null)), equalTo(0)); |
||||
assertThat(c.compare(new PropertyDescriptor("abc", null, null), new PropertyDescriptor("abc", null, null)), equalTo(0)); |
||||
assertThat(c.compare(new PropertyDescriptor("a", null, null), new PropertyDescriptor("b", null, null)), lessThan(0)); |
||||
assertThat(c.compare(new PropertyDescriptor("b", null, null), new PropertyDescriptor("a", null, null)), greaterThan(0)); |
||||
assertThat(c.compare(new PropertyDescriptor("abc", null, null), new PropertyDescriptor("abd", null, null)), lessThan(0)); |
||||
assertThat(c.compare(new PropertyDescriptor("xyz", null, null), new PropertyDescriptor("123", null, null)), greaterThan(0)); |
||||
assertThat(c.compare(new PropertyDescriptor("a", null, null), new PropertyDescriptor("abc", null, null)), lessThan(0)); |
||||
assertThat(c.compare(new PropertyDescriptor("abc", null, null), new PropertyDescriptor("a", null, null)), greaterThan(0)); |
||||
assertThat(c.compare(new PropertyDescriptor("abc", null, null), new PropertyDescriptor("b", null, null)), lessThan(0)); |
||||
|
||||
assertThat(c.compare(new PropertyDescriptor(" ", null, null), new PropertyDescriptor("a", null, null)), lessThan(0)); |
||||
assertThat(c.compare(new PropertyDescriptor("1", null, null), new PropertyDescriptor("a", null, null)), lessThan(0)); |
||||
assertThat(c.compare(new PropertyDescriptor("a", null, null), new PropertyDescriptor("A", null, null)), greaterThan(0)); |
||||
} |
||||
|
||||
|
||||
private boolean hasWriteMethodForProperty(BeanInfo beanInfo, String propertyName) { |
||||
for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) { |
||||
if (pd.getName().equals(propertyName)) { |
||||
return pd.getWriteMethod() != null; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private boolean hasReadMethodForProperty(BeanInfo beanInfo, String propertyName) { |
||||
for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) { |
||||
if (pd.getName().equals(propertyName)) { |
||||
return pd.getReadMethod() != null; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private boolean hasIndexedWriteMethodForProperty(BeanInfo beanInfo, String propertyName) { |
||||
for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) { |
||||
if (pd.getName().equals(propertyName)) { |
||||
assertThat(propertyName + " property is not indexed", pd, instanceOf(IndexedPropertyDescriptor.class)); |
||||
return ((IndexedPropertyDescriptor)pd).getIndexedWriteMethod() != null; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private boolean hasIndexedReadMethodForProperty(BeanInfo beanInfo, String propertyName) { |
||||
for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) { |
||||
if (pd.getName().equals(propertyName)) { |
||||
assertThat(propertyName + " property is not indexed", pd, instanceOf(IndexedPropertyDescriptor.class)); |
||||
return ((IndexedPropertyDescriptor)pd).getIndexedReadMethod() != null; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue