-
Notifications
You must be signed in to change notification settings - Fork 82
MrBean: Fix detection of inherited default method in Java 8+ interface #102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
498cac3
22b8550
66df757
f8a9a0d
73b8f0c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -176,16 +176,28 @@ protected boolean hasConcreteOverride(Method m0, JavaType implementedType) | |
{ | ||
final String name = m0.getName(); | ||
final Class<?>[] argTypes = m0.getParameterTypes(); | ||
try { | ||
// getMethod returns the most-specific method implementation, for public methods only (which is any method in an interface) | ||
Method effectiveMethod = implementedType.getRawClass().getMethod(name, argTypes); | ||
if (BeanUtil.isConcrete(effectiveMethod)) { | ||
return true; | ||
} | ||
} catch (NoSuchMethodException e) { | ||
// method must be non-public, fallback to using getDeclaredMethod | ||
} | ||
|
||
for (JavaType curr = implementedType; (curr != null) && !curr.isJavaLangObject(); | ||
curr = curr.getSuperClass()) { | ||
// 29-Nov-2015, tatu: Avoiding exceptions would be good, so would linear scan | ||
// be better here? | ||
try { | ||
Method effectiveMethod = curr.getRawClass().getDeclaredMethod(name, argTypes); | ||
if (effectiveMethod != null && BeanUtil.isConcrete(effectiveMethod)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if (BeanUtil.isConcrete(effectiveMethod)) { | ||
return true; | ||
} | ||
} catch (NoSuchMethodException e) { } | ||
} catch (NoSuchMethodException e) { | ||
// method must exist on a superclass, continue searching... | ||
} | ||
} | ||
return false; | ||
} | ||
|
@@ -240,7 +252,7 @@ protected final static boolean returnsBoolean(Method m) | |
Class<?> rt = m.getReturnType(); | ||
return (rt == Boolean.class || rt == Boolean.TYPE); | ||
} | ||
|
||
/* | ||
/********************************************************** | ||
/* Internal methods, bytecode generation | ||
|
@@ -288,7 +300,7 @@ private DynamicType.Builder<?> createSetter(DynamicType.Builder<?> builder, | |
/* Internal methods, other | ||
/********************************************************** | ||
*/ | ||
|
||
protected String decap(String name) { | ||
char c = name.charAt(0); | ||
if (name.length() > 1 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,11 +16,15 @@ public abstract static class Bean | |
int y; | ||
|
||
protected Bean() { } | ||
|
||
public abstract String getX(); | ||
|
||
public String getFoo() { return "Foo!"; } | ||
public void setY(int value) { y = value; } | ||
|
||
// also verify non-public methods | ||
protected abstract String getZ(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if we are expected to implement non-public getters... although probably makes sense (setters def. need to be implemented). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wasn't sure whether non-public methods were supported until I wrote the test, which I decided to do after exploring the differences between |
||
private String customMethod() { return "Private methods rock!"; } | ||
} | ||
|
||
/* | ||
|
@@ -32,10 +36,12 @@ protected Bean() { } | |
public void testSimpleInteface() throws Exception | ||
{ | ||
ObjectMapper mapper = newMrBeanMapper(); | ||
Bean bean = mapper.readValue("{ \"x\" : \"abc\", \"y\" : 13 }", Bean.class); | ||
Bean bean = mapper.readValue("{ \"x\" : \"abc\", \"y\" : 13, \"z\" : \"def\" }", Bean.class); | ||
assertNotNull(bean); | ||
assertEquals("abc", bean.getX()); | ||
assertEquals(13, bean.y); | ||
assertEquals("Foo!", bean.getFoo()); | ||
assertEquals("def", bean.getZ()); | ||
assertEquals("Private methods rock!", bean.customMethod()); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I am specifically trying to understand this part here; why is the extra check needed?
Is that because existing code seems to only look for supertypes (and not type itself) or something else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit convoluted (and took me some time to spot the issue), so let me illustrate with the
BeanWithInheritedDefault
example from the unit test:In
BeanBuilder.implement
,ArrayList<JavaType> implTypes
contains[BeanWithInheritedDefault, BeanWithDefaultForOtherInterface, Bean, OtherInterface]
, and these reduce two relevant executions of the loop withMethod m
:BeanWithDefaultForOtherInterface.anyValuePresent
, which is concrete itself, and is properly skipped at line 108OtherInterface.anyValuePresent
, which is not, and therefore which calls thehasConcreteOverride
method I've modified.In the original implementation of
hasConcreteOverride
, it checks for a concrete declared method on theimplementedType
(in this caseBeanWithInheritedDefault
, which doesn't have it), and then it checks on superclasses of which there is none. Critically, this code fails to locate the concrete implementation of this method inBeanWithDefaultForOtherInterface
, and therefore it incorrectly return false from the method.I started to explore solutions that would either (a) traverse the interface hierarchy, or (b) optimize the loop in
implement
to track known-concrete methods, but I ultimately realized this simple addition tohasConcreteOverride
solves the search problem for all public methods, which coincidentally applies to all Java interfaces and their default methods. I think it also makes thehasConcreteOverride
more efficient, since it is only necessary to search the superclass hierarchy when the method is in question is non-public.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok. So the logic was ok pre-Java8, but the introduction of interface default implementations threw the monkey wrench... makes sense.