8196373: Introspector does not see overridden generic setter method
authorserb
Wed, 30 May 2018 08:10:41 -0700
changeset 50351 9289c4214a35
parent 50350 668463f93ec0
child 50352 25db2c8f3cf8
8196373: Introspector does not see overridden generic setter method Reviewed-by: malenkov
src/java.desktop/share/classes/com/sun/beans/introspect/PropertyInfo.java
test/jdk/java/beans/Introspector/GenericPropertyType.java
test/jdk/java/beans/Introspector/OverloadedSetter.java
test/jdk/java/beans/Introspector/OverriddenGenericGetter.java
test/jdk/java/beans/Introspector/OverriddenGenericSetter.java
--- a/src/java.desktop/share/classes/com/sun/beans/introspect/PropertyInfo.java	Wed May 30 08:19:59 2018 +0200
+++ b/src/java.desktop/share/classes/com/sun/beans/introspect/PropertyInfo.java	Wed May 30 08:10:41 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -84,19 +84,24 @@
             }
             this.readList = null;
         }
+        Class<?> writeType = this.type;
         if (this.writeList != null) {
             for (MethodInfo info : this.writeList) {
-                if (this.type == null) {
+                if (writeType == null) {
                     this.write = info;
-                    this.type = info.type;
-                } else if (this.type.isAssignableFrom(info.type)) {
+                    writeType = info.type;
+                } else if (writeType.isAssignableFrom(info.type)) {
                     if ((this.write == null) || this.write.type.isAssignableFrom(info.type)) {
                         this.write = info;
+                        writeType = info.type;
                     }
                 }
             }
             this.writeList = null;
         }
+        if (this.type == null) {
+            this.type = writeType;
+        }
         if (this.indexed != null) {
             if ((this.type != null) && !this.type.isArray()) {
                 this.indexed = null; // property type is not an array
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/beans/Introspector/GenericPropertyType.java	Wed May 30 08:10:41 2018 -0700
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+
+/**
+ * @test
+ * @bug 8196373
+ * @summary Introspector should work when generics are used
+ */
+public final class GenericPropertyType {
+
+    /// Nothing is overridden
+    static class ParentNo<T> {
+        public T getValue() {return null;}
+        public void setValue(T value) {}
+    }
+
+    static class ChildNoO extends ParentNo<Object> {}
+    static class ChildNoA extends ParentNo<ArithmeticException> {}
+    static class ChildNoS extends ParentNo<String> {}
+
+    /// no get(), set is overridden
+    static class ParentNoGet<T> {
+        protected void setValue(T value) {}
+    }
+
+    static class ChildNoGetO extends ParentNoGet<Object> {
+        @Override
+        public void setValue(Object value) {}
+    }
+
+    static class ChildNoGetA extends ParentNoGet<ArithmeticException> {
+        @Override
+        public void setValue(ArithmeticException value) {}
+    }
+
+    static class ChildNoGetS extends ParentNoGet<String> {
+        @Override
+        public void setValue(String value) {}
+    }
+
+    /// get() exists, set is overridden
+    static class ParentGet<T> {
+        public final T getValue() {return null;}
+        protected void setValue(T value) {}
+    }
+
+    static class ChildGetO extends ParentGet<Object> {
+        @Override
+        public void setValue(Object value) {}
+    }
+
+    static class ChildGetA extends ParentGet<ArithmeticException> {
+        @Override
+        public void setValue(ArithmeticException value) {}
+    }
+
+    static class ChildGetS extends ParentGet<String> {
+        @Override
+        public void setValue(String value) {}
+    }
+
+    /// Both set/get are overridden
+    static class ParentAll<T> {
+        protected T getValue() {return null;}
+        protected void setValue(T value) {}
+    }
+
+    static class ChildAllO extends ParentAll<Object> {
+        @Override
+        public void setValue(Object value) {}
+        @Override
+        public Object getValue() {return null;}
+    }
+
+    static class ChildAllA extends ParentAll<ArithmeticException> {
+        @Override
+        public void setValue(ArithmeticException value) {}
+        @Override
+        public ArithmeticException getValue() {return null;}
+    }
+
+    static class ChildAllS extends ParentAll<String> {
+        @Override
+        public void setValue(String value) {}
+        @Override
+        public String getValue() {return null;}
+    }
+
+    public static void main(String[] args) throws Exception {
+        testProperty(ChildNoGetA.class, ArithmeticException.class);
+        testProperty(ChildNoGetO.class, Object.class);
+        testProperty(ChildNoGetS.class, String.class);
+
+        testProperty(ChildGetA.class, ArithmeticException.class);
+        testProperty(ChildGetO.class, Object.class);
+        testProperty(ChildGetS.class, String.class);
+
+        testProperty(ChildAllA.class, ArithmeticException.class);
+        testProperty(ChildAllO.class, Object.class);
+        testProperty(ChildAllS.class, String.class);
+
+        testProperty(ChildNoA.class, ArithmeticException.class);
+        testProperty(ChildNoO.class, Object.class);
+        testProperty(ChildNoS.class, String.class);
+    }
+
+    private static void testProperty(Class<?> beanClass, Class<?> type) throws Exception {
+        BeanInfo beanInfo = Introspector.getBeanInfo(beanClass, Object.class);
+        PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
+        if (pds.length != 1) {
+            throw new RuntimeException("Wrong number of properties");
+        }
+        PropertyDescriptor pd = pds[0];
+        System.out.println("pd = " + pd);
+        String name = pd.getName();
+        if (!name.equals("value")) {
+            throw new RuntimeException("Wrong name: " + name);
+        }
+        Class<?> propertyType = pd.getPropertyType();
+        if (propertyType != type) {
+            throw new RuntimeException("Wrong property type: " + propertyType);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/beans/Introspector/OverloadedSetter.java	Wed May 30 08:10:41 2018 -0700
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+
+/**
+ * @test
+ * @bug 8196373
+ * @summary behavior of this class is not specified, but it can be used to check
+ *          our implementation for accidental changes
+ */
+public final class OverloadedSetter {
+
+    class AAA {}
+    class CCC extends AAA {}
+    class BBB extends CCC {}
+    class DDD extends CCC {}
+
+    class ZZZ {}
+
+    // DDD will be selected because it is most specific type.
+    class ParentADC<T> {
+        public void setValue(AAA value) {}
+        public void setValue(DDD value) {}
+        public void setValue(CCC value) {}
+    }
+    // DDD will be selected because it is most specific type.
+    class ParentACD<T> {
+        public void setValue(AAA value) {}
+        public void setValue(CCC value) {}
+        public void setValue(DDD value) {}
+    }
+    // DDD will be selected because it is most specific type.
+    class ParentDAC<T> {
+        public void setValue(DDD value) {}
+        public void setValue(AAA value) {}
+        public void setValue(CCC value) {}
+    }
+    // DDD will be selected because it is most specific type.
+    class ParentDCA<T> {
+        public void setValue(DDD value) {}
+        public void setValue(CCC value) {}
+        public void setValue(AAA value) {}
+    }
+    // DDD will be selected because it is most specific type.
+    class ParentCAD<T> {
+        public void setValue(CCC value) {}
+        public void setValue(AAA value) {}
+        public void setValue(DDD value) {}
+    }
+    // DDD will be selected because it is most specific type.
+    class ParentCDA<T> {
+        public void setValue(CCC value) {}
+        public void setValue(DDD value) {}
+        public void setValue(AAA value) {}
+    }
+    // DDD will be selected because it is most specific type and ZZZ will be
+    // skipped because it will be placed at the end of the methods list.
+    class ParentCDAZ<T> {
+        public void setValue(CCC value) {}
+        public void setValue(DDD value) {}
+        public void setValue(AAA value) {}
+        public void setValue(ZZZ value) {}
+    }
+    // DDD will be selected because it is most specific type which related to
+    // the type of getValue(); BBB will be skipped because it is not a type or
+    // subclass of the type returned by getValue();
+    class ParentDACB<T> {
+        public DDD getValue(){return null;}
+        public void setValue(AAA value) {}
+        public void setValue(DDD value) {}
+        public void setValue(CCC value) {}
+        public void setValue(BBB value) {}
+    }
+
+    public static void main(String[] args) throws Exception {
+        test(ParentADC.class);
+        test(ParentACD.class);
+        test(ParentDAC.class);
+        test(ParentDCA.class);
+        test(ParentCAD.class);
+        test(ParentCDA.class);
+        test(ParentCDAZ.class);
+        test(ParentDACB.class);
+    }
+
+    private static void test(Class<?> beanClass) throws Exception {
+        BeanInfo beanInfo = Introspector.getBeanInfo(beanClass, Object.class);
+        PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
+        if (pds.length != 1) {
+            throw new RuntimeException("Wrong number of properties");
+        }
+        PropertyDescriptor pd = pds[0];
+        String name = pd.getName();
+        if (!name.equals("value")) {
+            throw new RuntimeException("Wrong name: " + name);
+        }
+
+        Class<?> propertyType = pd.getPropertyType();
+        if (propertyType != DDD.class) {
+            throw new RuntimeException("Wrong property type: " + propertyType);
+        }
+        Method writeMethod = pd.getWriteMethod();
+        if (writeMethod == null) {
+            throw new RuntimeException("Write method is null");
+        }
+        Class<?>[] parameterTypes = writeMethod.getParameterTypes();
+        if (parameterTypes.length != 1) {
+            throw new RuntimeException("Wrong parameters " + parameterTypes);
+        }
+        if (parameterTypes[0] != DDD.class) {
+            throw new RuntimeException("Wrong type: " + parameterTypes[0]);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/beans/Introspector/OverriddenGenericGetter.java	Wed May 30 08:10:41 2018 -0700
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+
+/**
+ * @test
+ * @bug 8196373
+ * @summary Introspector should work with overridden generic getter method
+ */
+public final class OverriddenGenericGetter {
+
+    static class Parent<T> {
+        private T value;
+        public T getValue() {return value;}
+        public final void setValue(T value) {this.value = value;}
+    }
+
+    static class ChildO extends Parent<Object> {
+        public ChildO() {}
+        @Override
+        public Object getValue() {return super.getValue();}
+    }
+
+    static class ChildA extends Parent<ArithmeticException> {
+        public ChildA() {}
+        @Override
+        public ArithmeticException getValue() {return super.getValue();}
+    }
+
+    static class ChildS extends Parent<String> {
+        public ChildS() {}
+        @Override
+        public String getValue() {return super.getValue();}
+    }
+
+    public static void main(String[] args) throws Exception {
+        testBehaviour(ChildA.class);
+        testBehaviour(ChildO.class);
+        testBehaviour(ChildS.class);
+        test(ChildA.class, ArithmeticException.class);
+        test(ChildO.class, Object.class);
+        test(ChildS.class, String.class);
+    }
+
+    private static void testBehaviour(Class<?> beanClass) throws Exception {
+        BeanInfo beanInfo = Introspector.getBeanInfo(beanClass, Object.class);
+        PropertyDescriptor pd = beanInfo.getPropertyDescriptors()[0];
+        Object bean = beanClass.getConstructor().newInstance();
+        Method readMethod = pd.getReadMethod();
+        Method writeMethod = pd.getWriteMethod();
+
+        // check roundtrip for default null values
+        writeMethod.invoke(bean,readMethod.invoke(bean));
+        writeMethod.invoke(bean,readMethod.invoke(bean));
+
+        // set property to non-default value
+        // check roundtrip for non-default values
+        Object param = pd.getPropertyType().getConstructor().newInstance();
+        writeMethod.invoke(bean, param);
+        writeMethod.invoke(bean, readMethod.invoke(bean));
+        writeMethod.invoke(bean, readMethod.invoke(bean));
+    }
+
+    private static void test(Class<?> beanClass, Class<?> type) throws Exception {
+        BeanInfo beanInfo = Introspector.getBeanInfo(beanClass, Object.class);
+        PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
+        if (pds.length != 1) {
+            throw new RuntimeException("Wrong number of properties");
+        }
+        PropertyDescriptor pd = pds[0];
+        String name = pd.getName();
+        if (!name.equals("value")) {
+            throw new RuntimeException("Wrong name: " + name);
+        }
+
+        Class<?> propertyType = pd.getPropertyType();
+        if (propertyType != type) {
+            throw new RuntimeException("Wrong property type: " + propertyType);
+        }
+        Method readMethod = pd.getReadMethod();
+        if (readMethod == null) {
+            throw new RuntimeException("Read method is null");
+        }
+        Class<?> returnType = readMethod.getReturnType();
+        if (returnType != type) {
+            throw new RuntimeException("Wrong return type; " + returnType);
+        }
+        Method writeMethod = pd.getWriteMethod();
+        if (writeMethod == null) {
+            throw new RuntimeException("Write method is null");
+        }
+        Class<?>[] parameterTypes = writeMethod.getParameterTypes();
+        if (parameterTypes.length != 1) {
+            throw new RuntimeException("Wrong parameters " + parameterTypes);
+        }
+        if (parameterTypes[0] != Object.class) {
+            throw new RuntimeException("Wrong type: " + parameterTypes[0]);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/beans/Introspector/OverriddenGenericSetter.java	Wed May 30 08:10:41 2018 -0700
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+
+/**
+ * @test
+ * @bug 8196373
+ * @summary Introspector should work with overridden generic setter method
+ */
+public final class OverriddenGenericSetter {
+
+    static class Parent<T> {
+        private T value;
+        public final T getValue() {return value;}
+        protected void setValue(T value) {this.value = value;}
+    }
+
+    static class ChildO extends Parent<Object> {
+        public ChildO() {}
+        @Override
+        public void setValue(Object value) {super.setValue(value);}
+    }
+
+    // For overridden setXXX javac will generate the "synthetic bridge" method
+    // setValue(Ljava/lang/Object). We will use two different types which may be
+    // placed before/after bridge method.
+    static class ChildA extends Parent<ArithmeticException> {
+        public ChildA() {}
+        @Override
+        public void setValue(ArithmeticException value) {super.setValue(value);}
+    }
+
+    static class ChildS extends Parent<String> {
+        public ChildS() {}
+        @Override
+        public void setValue(String value) {super.setValue(value);}
+    }
+
+    public static void main(String[] args) throws Exception {
+        testBehaviour(ChildA.class);
+        testBehaviour(ChildO.class);
+        testBehaviour(ChildS.class);
+        testProperty(ChildA.class, ArithmeticException.class);
+        testProperty(ChildO.class, Object.class);
+        testProperty(ChildS.class, String.class);
+    }
+
+    private static void testBehaviour(Class<?> beanClass) throws Exception {
+        BeanInfo beanInfo = Introspector.getBeanInfo(beanClass, Object.class);
+        PropertyDescriptor pd = beanInfo.getPropertyDescriptors()[0];
+        Object bean = beanClass.getConstructor().newInstance();
+        Method readMethod = pd.getReadMethod();
+        Method writeMethod = pd.getWriteMethod();
+
+        // check roundtrip for default null values
+        writeMethod.invoke(bean,readMethod.invoke(bean));
+        writeMethod.invoke(bean,readMethod.invoke(bean));
+
+        // set property to non-default value
+        // check roundtrip for non-default values
+        Object param = pd.getPropertyType().getConstructor().newInstance();
+        writeMethod.invoke(bean, param);
+        writeMethod.invoke(bean, readMethod.invoke(bean));
+        writeMethod.invoke(bean, readMethod.invoke(bean));
+    }
+
+    private static void testProperty(Class<?> beanClass, Class<?> type) throws Exception {
+        BeanInfo beanInfo = Introspector.getBeanInfo(beanClass, Object.class);
+        PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
+        if (pds.length != 1) {
+            throw new RuntimeException("Wrong number of properties");
+        }
+        PropertyDescriptor pd = pds[0];
+        System.out.println("pd = " + pd);
+        String name = pd.getName();
+        if (!name.equals("value")) {
+            throw new RuntimeException("Wrong name: " + name);
+        }
+        Class<?> propertyType = pd.getPropertyType();
+        if (propertyType != type) {
+            throw new RuntimeException("Wrong property type: " + propertyType);
+        }
+        Method readMethod = pd.getReadMethod();
+        if (readMethod == null) {
+            throw new RuntimeException("Read method is null");
+        }
+        Class<?> returnType = readMethod.getReturnType();
+        if (returnType != Object.class) {
+            throw new RuntimeException("Wrong return type; " + returnType);
+        }
+        Method writeMethod = pd.getWriteMethod();
+        if (writeMethod == null) {
+            throw new RuntimeException("Write method is null");
+        }
+        Class<?>[] parameterTypes = writeMethod.getParameterTypes();
+        if (parameterTypes.length != 1) {
+            throw new RuntimeException("Wrong parameters " + parameterTypes);
+        }
+        if (parameterTypes[0] != type) {
+            throw new RuntimeException("Wrong type: " + parameterTypes[0]);
+        }
+    }
+}