Skip to content

Commit b0eff81

Browse files
authored
Merge pull request #109 from bazaarvoice/patch-default-in-interfaces
MrBean: Fix detection of inherited default method in Java 8+ interface
2 parents 344acb5 + e800726 commit b0eff81

File tree

5 files changed

+90
-17
lines changed

5 files changed

+90
-17
lines changed

mrbean/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ ${project.groupId}.mrbean.*;version=${project.version}
5151
<artifactId>replacer</artifactId>
5252
</plugin>
5353

54+
<plugin>
55+
<groupId>org.apache.maven.plugins</groupId>
56+
<artifactId>maven-compiler-plugin</artifactId>
57+
<configuration>
58+
<!-- Enable testing with Java 8 features, especially default methods in interfaces -->
59+
<testSource>1.8</testSource>
60+
<testTarget>1.8</testTarget>
61+
</configuration>
62+
</plugin>
63+
5464
<plugin>
5565
<!-- We will shade ASM, to simplify deployment, avoid version conflicts -->
5666
<groupId>org.apache.maven.plugins</groupId>

mrbean/src/main/java/com/fasterxml/jackson/module/mrbean/BeanBuilder.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,16 +181,28 @@ protected boolean hasConcreteOverride(Method m0, JavaType implementedType)
181181
{
182182
final String name = m0.getName();
183183
final Class<?>[] argTypes = m0.getParameterTypes();
184+
try {
185+
// getMethod returns the most-specific method implementation, for public methods only (which is any method in an interface)
186+
Method effectiveMethod = implementedType.getRawClass().getMethod(name, argTypes);
187+
if (BeanUtil.isConcrete(effectiveMethod)) {
188+
return true;
189+
}
190+
} catch (NoSuchMethodException e) {
191+
// method must be non-public, fallback to using getDeclaredMethod
192+
}
193+
184194
for (JavaType curr = implementedType; (curr != null) && !curr.isJavaLangObject();
185195
curr = curr.getSuperClass()) {
186196
// 29-Nov-2015, tatu: Avoiding exceptions would be good, so would linear scan
187197
// be better here?
188198
try {
189199
Method effectiveMethod = curr.getRawClass().getDeclaredMethod(name, argTypes);
190-
if (effectiveMethod != null && BeanUtil.isConcrete(effectiveMethod)) {
200+
if (BeanUtil.isConcrete(effectiveMethod)) {
191201
return true;
192202
}
193-
} catch (NoSuchMethodException e) { }
203+
} catch (NoSuchMethodException e) {
204+
// method must exist on a superclass, continue searching...
205+
}
194206
}
195207
return false;
196208
}
@@ -245,7 +257,7 @@ protected final static boolean returnsBoolean(Method m)
245257
Class<?> rt = m.getReturnType();
246258
return (rt == Boolean.class || rt == Boolean.TYPE);
247259
}
248-
260+
249261
/*
250262
/**********************************************************
251263
/* Internal methods, bytecode generation
@@ -354,7 +366,7 @@ protected void createUnimplementedMethod(ClassWriter cw, String internalClassNam
354366
/* Internal methods, other
355367
/**********************************************************
356368
*/
357-
369+
358370
protected String decap(String name) {
359371
char c = name.charAt(0);
360372
if (name.length() > 1

mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestAbstractClasses.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@ public abstract static class Bean
1616
int y;
1717

1818
protected Bean() { }
19-
19+
2020
public abstract String getX();
2121

2222
public String getFoo() { return "Foo!"; }
2323
public void setY(int value) { y = value; }
24+
25+
// also verify non-public methods
26+
protected abstract String getZ();
27+
private String customMethod() { return "Private methods rock!"; }
2428
}
2529

2630
/*
@@ -32,10 +36,12 @@ protected Bean() { }
3236
public void testSimpleInteface() throws Exception
3337
{
3438
ObjectMapper mapper = newMrBeanMapper();
35-
Bean bean = mapper.readValue("{ \"x\" : \"abc\", \"y\" : 13 }", Bean.class);
39+
Bean bean = mapper.readValue("{ \"x\" : \"abc\", \"y\" : 13, \"z\" : \"def\" }", Bean.class);
3640
assertNotNull(bean);
3741
assertEquals("abc", bean.getX());
3842
assertEquals(13, bean.y);
3943
assertEquals("Foo!", bean.getFoo());
44+
assertEquals("def", bean.getZ());
45+
assertEquals("Private methods rock!", bean.customMethod());
4046
}
4147
}

mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestAbstractClassesWithOverrides.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,28 @@ public abstract static class Bean
1616
int y;
1717

1818
protected Bean() { }
19-
19+
2020
public abstract String getX();
2121

2222
public abstract String roast(int temperature);
2323

2424
public String getFoo() { return "Foo!"; }
2525
public void setY(int value) { y = value; }
26+
27+
// also verify non-public methods
28+
protected abstract String getZ();
29+
private Object customMethod() { return protectedAbstractMethod(); }
30+
protected abstract Object protectedAbstractMethod();
2631
}
2732

2833
public abstract static class CoffeeBean extends Bean {
2934
@Override public String roast(int temperature) {
3035
return "The coffee beans are roasting at " + temperature + " degrees now, yummy";
3136
}
37+
38+
@Override protected Object protectedAbstractMethod() {
39+
return "Private methods invoking protected abstract methods is the bomb!";
40+
}
3241
}
3342

3443
public abstract static class PeruvianCoffeeBean extends CoffeeBean {}
@@ -45,10 +54,10 @@ public void testOverrides() throws Exception
4554
ObjectMapper mapper = new ObjectMapper();
4655
mapper.registerModule(new MrBeanModule());
4756

48-
Bean bean = mapper.readValue("{ \"x\" : \"abc\", \"y\" : 13 }", CoffeeBean.class);
57+
Bean bean = mapper.readValue("{ \"x\" : \"abc\", \"y\" : 13, \"z\" : \"def\" }", CoffeeBean.class);
4958
verifyBean(bean);
5059

51-
Bean bean2 = mapper.readValue("{ \"x\" : \"abc\", \"y\" : 13 }", PeruvianCoffeeBean.class);
60+
Bean bean2 = mapper.readValue("{ \"x\" : \"abc\", \"y\" : 13, \"z\" : \"def\" }", PeruvianCoffeeBean.class);
5261
verifyBean(bean2);
5362
}
5463

@@ -57,6 +66,8 @@ private void verifyBean(Bean bean) {
5766
assertEquals("abc", bean.getX());
5867
assertEquals(13, bean.y);
5968
assertEquals("Foo!", bean.getFoo());
69+
assertEquals("def", bean.getZ());
6070
assertEquals("The coffee beans are roasting at 123 degrees now, yummy", bean.roast(123));
71+
assertEquals("Private methods invoking protected abstract methods is the bomb!", bean.customMethod());
6172
}
6273
}

mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestSimpleMaterializedInterfaces.java

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ public interface BeanWithY extends Bean
2626
{
2727
public int getY();
2828
}
29-
29+
3030
public interface PartialBean {
3131
public boolean isOk();
3232
// and then non-getter/setter one:
3333
public int foobar();
3434
}
35-
35+
3636
public interface BeanHolder {
3737
public Bean getBean();
3838
}
@@ -53,7 +53,21 @@ public interface ArrayBean {
5353
interface NonPublicBean {
5454
public abstract int getX();
5555
}
56-
56+
57+
public interface OtherInterface {
58+
public boolean anyValuePresent();
59+
}
60+
61+
public interface BeanWithDefaultForOtherInterface extends Bean, OtherInterface {
62+
public default boolean anyValuePresent() {
63+
return getX() > 0 || getA() != null;
64+
}
65+
}
66+
67+
public interface BeanWithInheritedDefault extends BeanWithDefaultForOtherInterface {
68+
// in this interface, anyValuePresent() is an inherited (rather than declared) concrete method
69+
}
70+
5771
/*
5872
/**********************************************************
5973
/* Unit tests, low level
@@ -149,8 +163,8 @@ public void testBeanHolder() throws Exception
149163
assertNotNull(bean);
150164
assertEquals("b", bean.getA());
151165
assertEquals(-4, bean.getX());
152-
}
153-
166+
}
167+
154168
public void testArrayInterface() throws Exception
155169
{
156170
ObjectMapper mapper = new ObjectMapper();
@@ -172,7 +186,27 @@ public void testSubInterface() throws Exception
172186
assertEquals(1, bean.getX());
173187
assertEquals(2, bean.getY());
174188
}
175-
189+
190+
public void testDefaultMethodInInterface() throws Exception
191+
{
192+
ObjectMapper mapper = newMrBeanMapper();
193+
BeanWithDefaultForOtherInterface bean = mapper.readValue("{\"a\":\"value\",\"x\":123 }", BeanWithDefaultForOtherInterface.class);
194+
assertNotNull(bean);
195+
assertEquals("value", bean.getA());
196+
assertEquals(123, bean.getX());
197+
assertTrue(bean.anyValuePresent());
198+
}
199+
200+
public void testInheritedDefaultMethodInInterface() throws Exception
201+
{
202+
ObjectMapper mapper = newMrBeanMapper();
203+
BeanWithInheritedDefault bean = mapper.readValue("{\"a\":\"value\",\"x\":123 }", BeanWithInheritedDefault.class);
204+
assertNotNull(bean);
205+
assertEquals("value", bean.getA());
206+
assertEquals(123, bean.getX());
207+
assertTrue(bean.anyValuePresent());
208+
}
209+
176210
/*
177211
/**********************************************************
178212
/* Unit tests, higher level, error handling
@@ -212,6 +246,6 @@ public void testNonPublic() throws Exception
212246
} catch (JsonMappingException e) {
213247
verifyException(e, "is not public");
214248
}
215-
}
216-
249+
}
250+
217251
}

0 commit comments

Comments
 (0)