From c18cdebd971def20460e35dfa523683962cb3d85 Mon Sep 17 00:00:00 2001 From: robbytx Date: Wed, 2 Sep 2020 20:21:24 -0500 Subject: [PATCH 1/6] Verify default methods in interface are supported --- .../TestSimpleMaterializedInterfaces.java | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestSimpleMaterializedInterfaces.java b/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestSimpleMaterializedInterfaces.java index f4d252a9..f80fc571 100644 --- a/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestSimpleMaterializedInterfaces.java +++ b/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestSimpleMaterializedInterfaces.java @@ -26,13 +26,13 @@ public interface BeanWithY extends Bean { public int getY(); } - + public interface PartialBean { public boolean isOk(); // and then non-getter/setter one: public int foobar(); } - + public interface BeanHolder { public Bean getBean(); } @@ -53,7 +53,13 @@ public interface ArrayBean { interface NonPublicBean { public abstract int getX(); } - + + public interface ExtendedBean extends Bean { + public default boolean anyValuePresent() { + return getX() > 0 || getA() != null; + } + } + /* /********************************************************** /* Unit tests, low level @@ -149,8 +155,8 @@ public void testBeanHolder() throws Exception assertNotNull(bean); assertEquals("b", bean.getA()); assertEquals(-4, bean.getX()); - } - + } + public void testArrayInterface() throws Exception { ObjectMapper mapper = new ObjectMapper(); @@ -172,7 +178,17 @@ public void testSubInterface() throws Exception assertEquals(1, bean.getX()); assertEquals(2, bean.getY()); } - + + public void testDefaultMethods() throws Exception + { + ObjectMapper mapper = newMrBeanMapper(); + ExtendedBean bean = mapper.readValue("{\"a\":\"value\",\"x\":123 }", ExtendedBean.class); + assertNotNull(bean); + assertEquals("value", bean.getA()); + assertEquals(123, bean.getX()); + assertTrue(bean.anyValuePresent()); + } + /* /********************************************************** /* Unit tests, higher level, error handling @@ -212,6 +228,6 @@ public void testNonPublic() throws Exception } catch (JsonMappingException e) { verifyException(e, "is not public"); } - } - + } + } From 286b5d9e1911de3dca54f0192801fbe0a2d79197 Mon Sep 17 00:00:00 2001 From: robbytx Date: Fri, 4 Sep 2020 16:03:05 -0500 Subject: [PATCH 2/6] Compile test sources against Java 1.8 --- mrbean/pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mrbean/pom.xml b/mrbean/pom.xml index 91b754a3..e365c8b0 100644 --- a/mrbean/pom.xml +++ b/mrbean/pom.xml @@ -51,6 +51,16 @@ ${project.groupId}.mrbean.*;version=${project.version} replacer + + org.apache.maven.plugins + maven-compiler-plugin + + + 1.8 + 1.8 + + + org.apache.maven.plugins From c5176abeb2f5aee763259c01f5ad747e5406ffe9 Mon Sep 17 00:00:00 2001 From: robbytx Date: Wed, 2 Sep 2020 20:44:36 -0500 Subject: [PATCH 3/6] Verify non-public class method behavior --- .../jackson/module/mrbean/TestAbstractClasses.java | 10 ++++++++-- .../mrbean/TestAbstractClassesWithOverrides.java | 12 +++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestAbstractClasses.java b/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestAbstractClasses.java index 9d4ffda5..68169699 100644 --- a/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestAbstractClasses.java +++ b/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestAbstractClasses.java @@ -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(); + private Object customMethod() { return new Object(); } } /* @@ -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()); + assertNotNull(bean.customMethod()); } } diff --git a/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestAbstractClassesWithOverrides.java b/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestAbstractClassesWithOverrides.java index a3938c0a..9ca0f6e1 100644 --- a/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestAbstractClassesWithOverrides.java +++ b/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestAbstractClassesWithOverrides.java @@ -16,13 +16,17 @@ public abstract static class Bean int y; protected Bean() { } - + public abstract String getX(); public abstract String roast(int temperature); public String getFoo() { return "Foo!"; } public void setY(int value) { y = value; } + + // also verify non-public methods + protected abstract String getZ(); + private Object customMethod() { return new Object(); } } public abstract static class CoffeeBean extends Bean { @@ -45,10 +49,10 @@ public void testOverrides() throws Exception ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new MrBeanModule()); - Bean bean = mapper.readValue("{ \"x\" : \"abc\", \"y\" : 13 }", CoffeeBean.class); + Bean bean = mapper.readValue("{ \"x\" : \"abc\", \"y\" : 13, \"z\" : \"def\" }", CoffeeBean.class); verifyBean(bean); - Bean bean2 = mapper.readValue("{ \"x\" : \"abc\", \"y\" : 13 }", PeruvianCoffeeBean.class); + Bean bean2 = mapper.readValue("{ \"x\" : \"abc\", \"y\" : 13, \"z\" : \"def\" }", PeruvianCoffeeBean.class); verifyBean(bean2); } @@ -57,6 +61,8 @@ private void verifyBean(Bean bean) { assertEquals("abc", bean.getX()); assertEquals(13, bean.y); assertEquals("Foo!", bean.getFoo()); + assertEquals("def", bean.getZ()); + assertNotNull(bean.customMethod()); assertEquals("The coffee beans are roasting at 123 degrees now, yummy", bean.roast(123)); } } From 92e61bb0de23cb1e163c7e4faee213b83cd7f0af Mon Sep 17 00:00:00 2001 From: robbytx Date: Wed, 2 Sep 2020 20:56:41 -0500 Subject: [PATCH 4/6] Verify current behavior of non-public methods --- .../jackson/module/mrbean/TestAbstractClasses.java | 4 ++-- .../module/mrbean/TestAbstractClassesWithOverrides.java | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestAbstractClasses.java b/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestAbstractClasses.java index 68169699..1307a412 100644 --- a/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestAbstractClasses.java +++ b/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestAbstractClasses.java @@ -24,7 +24,7 @@ protected Bean() { } // also verify non-public methods protected abstract String getZ(); - private Object customMethod() { return new Object(); } + private String customMethod() { return "Private methods rock!"; } } /* @@ -42,6 +42,6 @@ public void testSimpleInteface() throws Exception assertEquals(13, bean.y); assertEquals("Foo!", bean.getFoo()); assertEquals("def", bean.getZ()); - assertNotNull(bean.customMethod()); + assertEquals("Private methods rock!", bean.customMethod()); } } diff --git a/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestAbstractClassesWithOverrides.java b/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestAbstractClassesWithOverrides.java index 9ca0f6e1..82deaac4 100644 --- a/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestAbstractClassesWithOverrides.java +++ b/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestAbstractClassesWithOverrides.java @@ -26,13 +26,18 @@ protected Bean() { } // also verify non-public methods protected abstract String getZ(); - private Object customMethod() { return new Object(); } + private Object customMethod() { return protectedAbstractMethod(); } + protected abstract Object protectedAbstractMethod(); } public abstract static class CoffeeBean extends Bean { @Override public String roast(int temperature) { return "The coffee beans are roasting at " + temperature + " degrees now, yummy"; } + + @Override protected Object protectedAbstractMethod() { + return "Private methods invoking protected abstract methods is the bomb!"; + } } public abstract static class PeruvianCoffeeBean extends CoffeeBean {} @@ -62,7 +67,7 @@ private void verifyBean(Bean bean) { assertEquals(13, bean.y); assertEquals("Foo!", bean.getFoo()); assertEquals("def", bean.getZ()); - assertNotNull(bean.customMethod()); assertEquals("The coffee beans are roasting at 123 degrees now, yummy", bean.roast(123)); + assertEquals("Private methods invoking protected abstract methods is the bomb!", bean.customMethod()); } } From e0bfe3fcca4748a4ed12906447df072243e392bb Mon Sep 17 00:00:00 2001 From: robbytx Date: Wed, 2 Sep 2020 21:01:06 -0500 Subject: [PATCH 5/6] Use Class.getMethod to locate the precise implementation, including default methods in Java 8+ interfaces --- .../jackson/module/mrbean/BeanBuilder.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/mrbean/src/main/java/com/fasterxml/jackson/module/mrbean/BeanBuilder.java b/mrbean/src/main/java/com/fasterxml/jackson/module/mrbean/BeanBuilder.java index e19282f5..038ccfc1 100644 --- a/mrbean/src/main/java/com/fasterxml/jackson/module/mrbean/BeanBuilder.java +++ b/mrbean/src/main/java/com/fasterxml/jackson/module/mrbean/BeanBuilder.java @@ -181,16 +181,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)) { + if (BeanUtil.isConcrete(effectiveMethod)) { return true; } - } catch (NoSuchMethodException e) { } + } catch (NoSuchMethodException e) { + // method must exist on a superclass, continue searching... + } } return false; } @@ -245,7 +257,7 @@ protected final static boolean returnsBoolean(Method m) Class rt = m.getReturnType(); return (rt == Boolean.class || rt == Boolean.TYPE); } - + /* /********************************************************** /* Internal methods, bytecode generation @@ -354,7 +366,7 @@ protected void createUnimplementedMethod(ClassWriter cw, String internalClassNam /* Internal methods, other /********************************************************** */ - + protected String decap(String name) { char c = name.charAt(0); if (name.length() > 1 From e8007265bb39c6c833f9c4b29e92eda38e3c56d3 Mon Sep 17 00:00:00 2001 From: robbytx Date: Wed, 2 Sep 2020 20:28:20 -0500 Subject: [PATCH 6/6] Add failing test case --- .../TestSimpleMaterializedInterfaces.java | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestSimpleMaterializedInterfaces.java b/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestSimpleMaterializedInterfaces.java index f80fc571..7ecd2884 100644 --- a/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestSimpleMaterializedInterfaces.java +++ b/mrbean/src/test/java/com/fasterxml/jackson/module/mrbean/TestSimpleMaterializedInterfaces.java @@ -54,12 +54,20 @@ interface NonPublicBean { public abstract int getX(); } - public interface ExtendedBean extends Bean { + public interface OtherInterface { + public boolean anyValuePresent(); + } + + public interface BeanWithDefaultForOtherInterface extends Bean, OtherInterface { public default boolean anyValuePresent() { return getX() > 0 || getA() != null; } } + public interface BeanWithInheritedDefault extends BeanWithDefaultForOtherInterface { + // in this interface, anyValuePresent() is an inherited (rather than declared) concrete method + } + /* /********************************************************** /* Unit tests, low level @@ -179,10 +187,20 @@ public void testSubInterface() throws Exception assertEquals(2, bean.getY()); } - public void testDefaultMethods() throws Exception + public void testDefaultMethodInInterface() throws Exception + { + ObjectMapper mapper = newMrBeanMapper(); + BeanWithDefaultForOtherInterface bean = mapper.readValue("{\"a\":\"value\",\"x\":123 }", BeanWithDefaultForOtherInterface.class); + assertNotNull(bean); + assertEquals("value", bean.getA()); + assertEquals(123, bean.getX()); + assertTrue(bean.anyValuePresent()); + } + + public void testInheritedDefaultMethodInInterface() throws Exception { ObjectMapper mapper = newMrBeanMapper(); - ExtendedBean bean = mapper.readValue("{\"a\":\"value\",\"x\":123 }", ExtendedBean.class); + BeanWithInheritedDefault bean = mapper.readValue("{\"a\":\"value\",\"x\":123 }", BeanWithInheritedDefault.class); assertNotNull(bean); assertEquals("value", bean.getA()); assertEquals(123, bean.getX());