Fixed problem in default method support, where the bridge method and the related reified method appears as overloaded methods with same parameter types, if the reified method comes from a default method, and the bridge method comes from a class.
Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/d095f5ae Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/d095f5ae Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/d095f5ae Branch: refs/heads/2.3 Commit: d095f5ae38b0bdefa87a5d732aceb9acb6997581 Parents: cbc4495 Author: ddekany <[email protected]> Authored: Sun Mar 12 13:42:59 2017 +0100 Committer: ddekany <[email protected]> Committed: Sun Mar 12 13:42:59 2017 +0100 ---------------------------------------------------------------------- .../freemarker/ext/beans/ClassIntrospector.java | 72 +++++++++++++++----- .../beans/BeansWrapperBridgeMethodsTest.java | 47 +++++++++++++ .../freemarker/ext/beans/BridgeMethodsBean.java | 12 ++++ .../ext/beans/BridgeMethodsBeanBase.java | 11 +++ .../BridgeMethodsWithDefaultMethodBean.java | 11 +++ .../BridgeMethodsWithDefaultMethodBean2.java | 5 ++ .../BridgeMethodsWithDefaultMethodBeanBase.java | 13 ++++ ...BridgeMethodsWithDefaultMethodBeanBase2.java | 10 +++ 8 files changed, 163 insertions(+), 18 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d095f5ae/src/main/java/freemarker/ext/beans/ClassIntrospector.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/ext/beans/ClassIntrospector.java b/src/main/java/freemarker/ext/beans/ClassIntrospector.java index 8fa0037..911b14d 100644 --- a/src/main/java/freemarker/ext/beans/ClassIntrospector.java +++ b/src/main/java/freemarker/ext/beans/ClassIntrospector.java @@ -597,25 +597,47 @@ class ClassIntrospector { // java.beans.Introspector was good enough then. return introspectionMDs; } - - boolean anyDefaultMethodsAdded = false; - findDefaultMethods: for (Method method : clazz.getMethods()) { - if (_JavaVersions.JAVA_8.isDefaultMethod(method)) { - if (!anyDefaultMethodsAdded) { - for (MethodDescriptor methodDescriptor : introspectionMDs) { - // Check if java.bean.Introspector now finds default methods (it did not in Java 1.8.0_66): - if (_JavaVersions.JAVA_8.isDefaultMethod(methodDescriptor.getMethod())) { - break findDefaultMethods; - } - - // Recreate introspectionMDs so that its size can grow: - ArrayList<MethodDescriptor> newIntrospectionMDs - = new ArrayList<MethodDescriptor>(introspectionMDs.size() + 16); - newIntrospectionMDs.addAll(introspectionMDs); - introspectionMDs = newIntrospectionMDs; - } - anyDefaultMethodsAdded = true; + + Map<String, List<Method>> defaultMethodsToAddByName = null; + for (Method method : clazz.getMethods()) { + if (_JavaVersions.JAVA_8.isDefaultMethod(method) && !method.isBridge()) { + if (defaultMethodsToAddByName == null) { + defaultMethodsToAddByName = new HashMap<String, List<Method>>(); + } + List<Method> overloads = defaultMethodsToAddByName.get(method.getName()); + if (overloads == null) { + overloads = new ArrayList<Method>(0); + defaultMethodsToAddByName.put(method.getName(), overloads); } + overloads.add(method); + } + } + + if (defaultMethodsToAddByName == null) { + // We had no interfering default methods: + return introspectionMDs; + } + + // Recreate introspectionMDs so that its size can grow: + ArrayList<MethodDescriptor> newIntrospectionMDs + = new ArrayList<MethodDescriptor>(introspectionMDs.size() + 16); + for (MethodDescriptor introspectorMD : introspectionMDs) { + Method introspectorM = introspectorMD.getMethod(); + // Prevent cases where the same method is added with different return types both from the list of default + // methods and from the list of Introspector-discovered methods, as that would lead to overloaded method + // selection ambiguity later. This is known to happen when the default method in an interface has reified + // return type, and then the interface is implemented by a class where the compiler generates an override + // for the bridge method only. (Other tricky cases might exist.) + if (!containsMethodWithSameParameterTypes( + defaultMethodsToAddByName.get(introspectorM.getName()), introspectorM)) { + newIntrospectionMDs.add(introspectorMD); + } + } + introspectionMDs = newIntrospectionMDs; + + // Add default methods: + for (Entry<String, List<Method>> entry : defaultMethodsToAddByName.entrySet()) { + for (Method method : entry.getValue()) { introspectionMDs.add(new MethodDescriptor(method)); } } @@ -623,6 +645,20 @@ class ClassIntrospector { return introspectionMDs; } + private boolean containsMethodWithSameParameterTypes(List<Method> overloads, Method m) { + if (overloads == null) { + return false; + } + + Class<?>[] paramTypes = m.getParameterTypes(); + for (Method overload : overloads) { + if (Arrays.equals(overload.getParameterTypes(), paramTypes)) { + return true; + } + } + return false; + } + private void addPropertyDescriptorToClassIntrospectionData(Map<Object, Object> introspData, PropertyDescriptor pd, Class<?> clazz, Map<MethodSignature, List<Method>> accessibleMethods) { if (pd instanceof IndexedPropertyDescriptor) { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d095f5ae/src/test/java/freemarker/ext/beans/BeansWrapperBridgeMethodsTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/freemarker/ext/beans/BeansWrapperBridgeMethodsTest.java b/src/test/java/freemarker/ext/beans/BeansWrapperBridgeMethodsTest.java new file mode 100644 index 0000000..5f73d0e --- /dev/null +++ b/src/test/java/freemarker/ext/beans/BeansWrapperBridgeMethodsTest.java @@ -0,0 +1,47 @@ +package freemarker.ext.beans; + +import static org.junit.Assert.*; + +import java.util.Collections; + +import org.junit.Test; + +import freemarker.template.Configuration; +import freemarker.template.TemplateHashModel; +import freemarker.template.TemplateMethodModelEx; +import freemarker.template.TemplateModelException; + +public class BeansWrapperBridgeMethodsTest { + + @Test + public void testWithoutDefaultMethod() throws TemplateModelException { + test(BridgeMethodsBean.class); + } + + @Test + public void testWithDefaultMethod() throws TemplateModelException { + test(BridgeMethodsWithDefaultMethodBean.class); + } + + @Test + public void testWithDefaultMethod2() throws TemplateModelException { + test(BridgeMethodsWithDefaultMethodBean2.class); + } + + private void test(Class<?> pClass) throws TemplateModelException { + BeansWrapper ow = new BeansWrapperBuilder(Configuration.VERSION_2_3_26).build(); + TemplateHashModel wrapped; + try { + wrapped = (TemplateHashModel) ow.wrap(pClass.newInstance()); + } catch (Exception e) { + throw new IllegalStateException(e); + } + + TemplateMethodModelEx m1 = (TemplateMethodModelEx) wrapped.get("m1"); + assertEquals(BridgeMethodsBean.M1_RETURN_VALUE, "" + m1.exec(Collections.emptyList())); + + TemplateMethodModelEx m2 = (TemplateMethodModelEx) wrapped.get("m2"); + assertNull(m2.exec(Collections.emptyList())); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d095f5ae/src/test/java/freemarker/ext/beans/BridgeMethodsBean.java ---------------------------------------------------------------------- diff --git a/src/test/java/freemarker/ext/beans/BridgeMethodsBean.java b/src/test/java/freemarker/ext/beans/BridgeMethodsBean.java new file mode 100644 index 0000000..a32853c --- /dev/null +++ b/src/test/java/freemarker/ext/beans/BridgeMethodsBean.java @@ -0,0 +1,12 @@ +package freemarker.ext.beans; + +public class BridgeMethodsBean extends BridgeMethodsBeanBase<String> { + + static final String M1_RETURN_VALUE = "m1ReturnValue"; + + @Override + public String m1() { + return M1_RETURN_VALUE; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d095f5ae/src/test/java/freemarker/ext/beans/BridgeMethodsBeanBase.java ---------------------------------------------------------------------- diff --git a/src/test/java/freemarker/ext/beans/BridgeMethodsBeanBase.java b/src/test/java/freemarker/ext/beans/BridgeMethodsBeanBase.java new file mode 100644 index 0000000..4823400 --- /dev/null +++ b/src/test/java/freemarker/ext/beans/BridgeMethodsBeanBase.java @@ -0,0 +1,11 @@ +package freemarker.ext.beans; + +public abstract class BridgeMethodsBeanBase<T> { + + public abstract T m1(); + + public T m2() { + return null; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d095f5ae/src/test/java/freemarker/ext/beans/BridgeMethodsWithDefaultMethodBean.java ---------------------------------------------------------------------- diff --git a/src/test/java/freemarker/ext/beans/BridgeMethodsWithDefaultMethodBean.java b/src/test/java/freemarker/ext/beans/BridgeMethodsWithDefaultMethodBean.java new file mode 100644 index 0000000..0c18991 --- /dev/null +++ b/src/test/java/freemarker/ext/beans/BridgeMethodsWithDefaultMethodBean.java @@ -0,0 +1,11 @@ +package freemarker.ext.beans; + +public class BridgeMethodsWithDefaultMethodBean implements BridgeMethodsWithDefaultMethodBeanBase<String> { + + static final String M1_RETURN_VALUE = "m1ReturnValue"; + + public String m1() { + return M1_RETURN_VALUE; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d095f5ae/src/test/java/freemarker/ext/beans/BridgeMethodsWithDefaultMethodBean2.java ---------------------------------------------------------------------- diff --git a/src/test/java/freemarker/ext/beans/BridgeMethodsWithDefaultMethodBean2.java b/src/test/java/freemarker/ext/beans/BridgeMethodsWithDefaultMethodBean2.java new file mode 100644 index 0000000..85bc232 --- /dev/null +++ b/src/test/java/freemarker/ext/beans/BridgeMethodsWithDefaultMethodBean2.java @@ -0,0 +1,5 @@ +package freemarker.ext.beans; + +public class BridgeMethodsWithDefaultMethodBean2 implements BridgeMethodsWithDefaultMethodBeanBase2 { + // All inherited +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d095f5ae/src/test/java/freemarker/ext/beans/BridgeMethodsWithDefaultMethodBeanBase.java ---------------------------------------------------------------------- diff --git a/src/test/java/freemarker/ext/beans/BridgeMethodsWithDefaultMethodBeanBase.java b/src/test/java/freemarker/ext/beans/BridgeMethodsWithDefaultMethodBeanBase.java new file mode 100644 index 0000000..68960de --- /dev/null +++ b/src/test/java/freemarker/ext/beans/BridgeMethodsWithDefaultMethodBeanBase.java @@ -0,0 +1,13 @@ +package freemarker.ext.beans; + +public interface BridgeMethodsWithDefaultMethodBeanBase<T> { + + default T m1() { + return null; + } + + default T m2() { + return null; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d095f5ae/src/test/java/freemarker/ext/beans/BridgeMethodsWithDefaultMethodBeanBase2.java ---------------------------------------------------------------------- diff --git a/src/test/java/freemarker/ext/beans/BridgeMethodsWithDefaultMethodBeanBase2.java b/src/test/java/freemarker/ext/beans/BridgeMethodsWithDefaultMethodBeanBase2.java new file mode 100644 index 0000000..be46d4f --- /dev/null +++ b/src/test/java/freemarker/ext/beans/BridgeMethodsWithDefaultMethodBeanBase2.java @@ -0,0 +1,10 @@ +package freemarker.ext.beans; + +public interface BridgeMethodsWithDefaultMethodBeanBase2 extends BridgeMethodsWithDefaultMethodBeanBase<String> { + + @Override + default String m1() { + return BridgeMethodsWithDefaultMethodBean.M1_RETURN_VALUE; + } + +}
