diff --git a/src/java.desktop/share/classes/com/sun/beans/introspect/MethodInfo.java b/src/java.desktop/share/classes/com/sun/beans/introspect/MethodInfo.java index 5216f4423d4..25c95988393 100644 --- a/src/java.desktop/share/classes/com/sun/beans/introspect/MethodInfo.java +++ b/src/java.desktop/share/classes/com/sun/beans/introspect/MethodInfo.java @@ -25,18 +25,35 @@ package com.sun.beans.introspect; +import java.io.Closeable; +import java.io.Externalizable; +import java.io.Serializable; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Set; import com.sun.beans.TypeResolver; import com.sun.beans.finder.MethodFinder; final class MethodInfo { + + // These are some common interfaces that we know a priori + // will not contain any bean property getters or setters. + static final Set> IGNORABLE_INTERFACES = Set.of( + AutoCloseable.class, + Cloneable.class, + Closeable.class, + Comparable.class, + Externalizable.class, + Serializable.class + ); + final Method method; final Class type; @@ -66,6 +83,8 @@ final class MethodInfo { static List get(Class type) { List list = null; if (type != null) { + + // Add declared methods boolean inaccessible = !Modifier.isPublic(type.getModifiers()); for (Method method : type.getMethods()) { if (method.getDeclaringClass().equals(type)) { @@ -81,10 +100,19 @@ final class MethodInfo { } } if (method != null) { - if (list == null) { - list = new ArrayList<>(); - } - list.add(method); + (list = createIfNeeded(list)).add(method); + } + } + } + + // Add default methods inherited from interfaces + for (Class iface : type.getInterfaces()) { + if (IGNORABLE_INTERFACES.contains(iface)) { + continue; + } + for (Method method : iface.getMethods()) { + if (!Modifier.isAbstract(method.getModifiers())) { + (list = createIfNeeded(list)).add(method); } } } @@ -96,6 +124,10 @@ final class MethodInfo { return Collections.emptyList(); } + private static List createIfNeeded(List list) { + return list != null ? list : new ArrayList<>(); + } + /** * A comparator that defines a total order so that methods have the same * name and identical signatures appear next to each others. diff --git a/test/jdk/java/beans/Introspector/DefaultMethodBeanPropertyTest.java b/test/jdk/java/beans/Introspector/DefaultMethodBeanPropertyTest.java new file mode 100644 index 00000000000..a1e528880ef --- /dev/null +++ b/test/jdk/java/beans/Introspector/DefaultMethodBeanPropertyTest.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2023, 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. + */ + +/* + * @test + * @bug 8071693 + * @summary Verify that the Introspector finds default methods inherited + * from interfaces + */ + +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.HashSet; +import java.util.NavigableSet; +import java.util.Set; +import java.util.stream.Collectors; + +public class DefaultMethodBeanPropertyTest { + +////////////////////////////////////// +// // +// SCENARIO 1 // +// // +////////////////////////////////////// + + public interface A1 { + default int getValue() { + return 0; + } + default Object getObj() { + return null; + } + + public static int getStaticValue() { + return 0; + } + } + + public interface B1 extends A1 { + } + + public interface C1 extends A1 { + Number getFoo(); + } + + public class D1 implements C1 { + @Override + public Integer getFoo() { + return null; + } + @Override + public Float getObj() { + return null; + } + } + + public static void testScenario1() { + verifyProperties(D1.class, + "getClass", // inherited method + "getValue", // inherited default method + "getFoo", // overridden interface method + "getObj" // overridden default method + ); + } + +////////////////////////////////////// +// // +// SCENARIO 2 // +// // +////////////////////////////////////// + + public interface A2 { + default Object getFoo() { + return null; + } + } + + public interface B2 extends A2 { + } + + public interface C2 extends A2 { + } + + public class D2 implements B2, C2 { + } + + public static void testScenario2() { + verifyProperties(D2.class, + "getClass", + "getFoo" + ); + } + +////////////////////////////////////// +// // +// SCENARIO 3 // +// // +////////////////////////////////////// + + public interface A3 { + default Object getFoo() { + return null; + } + } + + public interface B3 extends A3 { + @Override + Set getFoo(); + } + + public interface C3 extends A3 { + @Override + Collection getFoo(); + } + + public class D3 implements B3, C3 { + @Override + public NavigableSet getFoo() { + return null; + } + } + + public static void testScenario3() { + verifyProperties(D3.class, + "getClass", + "getFoo" + ); + } + +// Helper methods + + public static void verifyProperties(Class type, String... getterNames) { + + // Gather expected properties + final HashSet expected = new HashSet<>(); + for (String methodName : getterNames) { + final String suffix = methodName.substring(3); + final String propName = Introspector.decapitalize(suffix); + final Method getter; + try { + getter = type.getMethod(methodName); + } catch (NoSuchMethodException e) { + throw new Error("unexpected error", e); + } + final PropertyDescriptor propDesc; + try { + propDesc = new PropertyDescriptor(propName, getter, null); + } catch (IntrospectionException e) { + throw new Error("unexpected error", e); + } + expected.add(propDesc); + } + + // Verify properties can be found directly + expected.stream() + .map(PropertyDescriptor::getName) + .filter(name -> BeanUtils.getPropertyDescriptor(type, name) == null) + .findFirst() + .ifPresent(name -> { + throw new Error("property \"" + name + "\" not found in " + type); + }); + + // Gather actual properties + final Set actual = + Set.of(BeanUtils.getPropertyDescriptors(type)); + + // Verify the two sets are the same + if (!actual.equals(expected)) { + throw new Error("mismatch: " + type + + "\nACTUAL:\n " + + actual.stream() + .map(Object::toString) + .collect(Collectors.joining("\n ")) + + "\nEXPECTED:\n " + + expected.stream() + .map(Object::toString) + .collect(Collectors.joining("\n "))); + } + } + +// Main method + + public static void main(String[] args) throws Exception { + testScenario1(); + testScenario2(); + testScenario3(); + } +}