Introduce ExtendedBeanInfo
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-90022cba6387
This commit is contained in:
parent
3c25a17ddf
commit
1271acb8eb
|
|
@ -221,7 +221,7 @@ public class CachedIntrospectionResults {
|
|||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Getting BeanInfo for class [" + beanClass.getName() + "]");
|
||||
}
|
||||
this.beanInfo = Introspector.getBeanInfo(beanClass);
|
||||
this.beanInfo = new ExtendedBeanInfo(Introspector.getBeanInfo(beanClass));
|
||||
|
||||
// Immediately remove class from Introspector cache, to allow for proper
|
||||
// garbage collection on class loader shutdown - we cache it here anyway,
|
||||
|
|
|
|||
|
|
@ -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 @@
|
|||
/*
|
||||
* 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