Avoid 'type mismatch' errors in ExtendedBeanInfo

Certain edge cases around return type covariance can trigger an
IntrospectionException when trying to create a new PropertyDescriptor;
particularly around the addition of write methods with parameter types
that do not match read method return types.

These type mismatch exceptions are raised during normal Introspector
operations as well (i.e. without ExtendedBeanInfo in the mix), but
the Introspector intentionally supresses them. In covariance cases,
there is often already a suitable write method present, e.g. discovered
in a supertype or superinterface, that, with the benefit of bridge
methods works just fine in practice at runtime.  That is to say, in
these suppression cases, the rejection of the write method is 'OK' in
that there is already a write method present that can handle a call.

ExtendedBeanInfo now mirrors this suppression behavior, but does issue
a WARN-level log message to let the user know.

An important effect of this change is that ExtendedBeanInfo now modifies
the delegate BeanInfo object, whereas previously it did not. In practice
this probably matters very little, but it is a design change worth
noting. The reason for this change was to avoid the need to create new
PropertyDescriptors wherever possible. It was discovered that by updating
existing PDs, one can avoid these IntrospectionExceptions a greater
percentage of the time.

Issue: SPR-8806
This commit is contained in:
Chris Beams
2011-11-19 21:07:10 +00:00
parent 947b5fefff
commit b2ae3dbb47
2 changed files with 167 additions and 24 deletions

View File

@@ -16,6 +16,7 @@
package org.springframework.beans;
import static java.lang.String.format;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
@@ -29,7 +30,9 @@ import java.beans.IndexedPropertyDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.ExtendedBeanInfo.PropertyDescriptorComparator;
@@ -116,11 +119,15 @@ public class ExtendedBeanInfoTests {
}
BeanInfo bi = Introspector.getBeanInfo(C.class);
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
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));
}
@@ -134,11 +141,15 @@ public class ExtendedBeanInfoTests {
}
BeanInfo bi = Introspector.getBeanInfo(C.class);
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
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));
@@ -161,11 +172,15 @@ public class ExtendedBeanInfoTests {
}
BeanInfo bi = Introspector.getBeanInfo(C.class);
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
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));
}
@@ -200,7 +215,6 @@ public class ExtendedBeanInfoTests {
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));
@@ -208,6 +222,14 @@ public class ExtendedBeanInfoTests {
assertThat(hasReadMethodForProperty(bi, "bar"), is(true));
assertThat(hasWriteMethodForProperty(bi, "bar"), is(false));
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
assertThat(hasWriteMethodForProperty(bi, "foo"), is(true));
assertThat(hasReadMethodForProperty(bi, "bar"), is(true));
assertThat(hasWriteMethodForProperty(bi, "bar"), is(true));
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
@@ -430,13 +452,19 @@ public class ExtendedBeanInfoTests {
}
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));
BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class));
assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true));
assertThat(hasWriteMethodForProperty(bi, "foos"), is(true));
// 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));
@@ -454,11 +482,15 @@ public class ExtendedBeanInfoTests {
}
BeanInfo bi = Introspector.getBeanInfo(C.class);
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
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));
@@ -514,13 +546,19 @@ public class ExtendedBeanInfoTests {
public Object setDateFormat(int dateStyle, int timeStyle) { return new Object(); }
}
BeanInfo bi = Introspector.getBeanInfo(C.class);
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "dateFormat"), is(false));
assertThat(hasWriteMethodForProperty(bi, "dateFormat"), is(false));
assertThat(hasIndexedReadMethodForProperty(bi, "dateFormat"), is(false));
assertThat(hasIndexedWriteMethodForProperty(bi, "dateFormat"), is(true));
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "dateFormat"), is(false));
assertThat(hasWriteMethodForProperty(bi, "dateFormat"), is(true));
assertThat(hasIndexedReadMethodForProperty(bi, "dateFormat"), is(false));
assertThat(hasIndexedWriteMethodForProperty(bi, "dateFormat"), is(true));
assertThat(hasReadMethodForProperty(ebi, "dateFormat"), is(false));
assertThat(hasWriteMethodForProperty(ebi, "dateFormat"), is(true));
assertThat(hasIndexedReadMethodForProperty(ebi, "dateFormat"), is(false));
@@ -625,4 +663,75 @@ public class ExtendedBeanInfoTests {
return false;
}
@Test
public void reproSpr8806_y() throws IntrospectionException, SecurityException, NoSuchMethodException {
Introspector.getBeanInfo(LawLibrary.class);
}
@Ignore @Test
public void reproSpr8806_x() throws IntrospectionException, SecurityException, NoSuchMethodException {
BeanInfo info = Introspector.getBeanInfo(LawLibrary.class);
for (PropertyDescriptor d : info.getPropertyDescriptors()) {
if (d.getName().equals("book")) {
Method readMethod = d.getReadMethod();
Method writeMethod = d.getWriteMethod();
System.out.println(format("READ : %s.%s (bridge:%s)",
readMethod.getDeclaringClass().getSimpleName(), readMethod.getName(), readMethod.isBridge()));
System.out.println(format("WRITE: %s.%s (bridge:%s)",
writeMethod.getDeclaringClass().getSimpleName(), writeMethod.getName(), writeMethod.isBridge()));
new PropertyDescriptor("book", readMethod, writeMethod);
}
}
Method readMethod = LawLibrary.class.getMethod("getBook");
Method writeMethod = LawLibrary.class.getMethod("setBook", Book.class);
System.out.println(format("read : %s.%s (bridge:%s)",
readMethod.getDeclaringClass().getSimpleName(), readMethod.getName(), readMethod.isBridge()));
System.out.println(format("write: %s.%s (bridge:%s)",
writeMethod.getDeclaringClass().getSimpleName(), writeMethod.getName(), writeMethod.isBridge()));
System.out.println("--------");
for (Method m : LawLibrary.class.getMethods()) {
if (m.getDeclaringClass() == Object.class) continue;
System.out.println(format("%s %s.%s(%s) [bridge:%s]",
m.getReturnType().getSimpleName(), m.getDeclaringClass().getSimpleName(),
m.getName(),
m.getParameterTypes().length == 1 ? m.getParameterTypes()[0].getSimpleName() : "",
m.isBridge()));
}
//new ExtendedBeanInfo(info);
}
@Test
public void reproSpr8806() throws IntrospectionException {
BeanInfo bi = Introspector.getBeanInfo(LawLibrary.class);
new ExtendedBeanInfo(bi); // throws
}
interface Book { }
interface TextBook extends Book { }
interface LawBook extends TextBook { }
interface BookOperations {
Book getBook();
void setBook(Book book);
}
interface TextBookOperations extends BookOperations {
TextBook getBook();
}
abstract class Library {
public Book getBook() { return null; }
public void setBook(Book book) { }
}
class LawLibrary extends Library implements TextBookOperations {
public LawBook getBook() { return null; }
}
}