Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
577 lines
19 KiB
Java
577 lines
19 KiB
Java
/*
|
|
* Copyright (c) 2023, 2025, 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 8347826
|
|
* @summary Verify that the Introspector finds default methods inherited
|
|
* from interfaces
|
|
*/
|
|
|
|
import java.beans.BeanInfo;
|
|
import java.beans.IntrospectionException;
|
|
import java.beans.Introspector;
|
|
import java.beans.MethodDescriptor;
|
|
import java.beans.PropertyDescriptor;
|
|
import java.beans.SimpleBeanInfo;
|
|
import java.lang.reflect.Method;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.HashSet;
|
|
import java.util.NavigableSet;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Stream;
|
|
|
|
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() {
|
|
verifyMethods(D1.class,
|
|
"public static int DefaultMethodBeanPropertyTest$A1.getStaticValue()",
|
|
"public default int DefaultMethodBeanPropertyTest$A1.getValue()",
|
|
"public java.lang.Integer DefaultMethodBeanPropertyTest$D1.getFoo()",
|
|
"public java.lang.Float DefaultMethodBeanPropertyTest$D1.getObj()"
|
|
);
|
|
verifyProperties(D1.class,
|
|
"public final native java.lang.Class java.lang.Object.getClass()",
|
|
"public default int DefaultMethodBeanPropertyTest$A1.getValue()",
|
|
"public java.lang.Integer DefaultMethodBeanPropertyTest$D1.getFoo()",
|
|
"public java.lang.Float DefaultMethodBeanPropertyTest$D1.getObj()"
|
|
);
|
|
}
|
|
|
|
//////////////////////////////////////
|
|
// //
|
|
// 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() {
|
|
verifyMethods(D2.class,
|
|
"public default java.lang.Object DefaultMethodBeanPropertyTest$A2.getFoo()"
|
|
);
|
|
verifyProperties(D2.class,
|
|
"public final native java.lang.Class java.lang.Object.getClass()",
|
|
"public default java.lang.Object DefaultMethodBeanPropertyTest$A2.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() {
|
|
verifyMethods(D3.class,
|
|
"public java.util.NavigableSet DefaultMethodBeanPropertyTest$D3.getFoo()"
|
|
);
|
|
verifyProperties(D3.class,
|
|
"public final native java.lang.Class java.lang.Object.getClass()",
|
|
"public java.util.NavigableSet DefaultMethodBeanPropertyTest$D3.getFoo()"
|
|
);
|
|
}
|
|
|
|
//////////////////////////////////////
|
|
// //
|
|
// SCENARIO 4 //
|
|
// //
|
|
//////////////////////////////////////
|
|
|
|
public interface A4 {
|
|
default Object getDefault0() {
|
|
return null;
|
|
}
|
|
default Object getDefault1() {
|
|
return null;
|
|
}
|
|
default Object getDefault2() {
|
|
return null;
|
|
}
|
|
default Object getDefault3() {
|
|
return null;
|
|
}
|
|
Object getNonDefault();
|
|
}
|
|
|
|
public class B4 implements A4 {
|
|
@Override
|
|
public Object getDefault1() {
|
|
return new B4();
|
|
}
|
|
@Override
|
|
public String getDefault2() {
|
|
return null;
|
|
}
|
|
@Override
|
|
public Float getDefault3() {
|
|
return null;
|
|
}
|
|
public Long getNonDefault() {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static void testScenario4() {
|
|
verifyMethods(B4.class,
|
|
"public default java.lang.Object DefaultMethodBeanPropertyTest$A4.getDefault0()",
|
|
"public java.lang.Object DefaultMethodBeanPropertyTest$B4.getDefault1()",
|
|
"public java.lang.String DefaultMethodBeanPropertyTest$B4.getDefault2()",
|
|
"public java.lang.Float DefaultMethodBeanPropertyTest$B4.getDefault3()",
|
|
"public java.lang.Long DefaultMethodBeanPropertyTest$B4.getNonDefault()"
|
|
);
|
|
verifyProperties(B4.class,
|
|
"public final native java.lang.Class java.lang.Object.getClass()",
|
|
"public default java.lang.Object DefaultMethodBeanPropertyTest$A4.getDefault0()",
|
|
"public java.lang.Object DefaultMethodBeanPropertyTest$B4.getDefault1()",
|
|
"public java.lang.String DefaultMethodBeanPropertyTest$B4.getDefault2()",
|
|
"public java.lang.Float DefaultMethodBeanPropertyTest$B4.getDefault3()",
|
|
"public java.lang.Long DefaultMethodBeanPropertyTest$B4.getNonDefault()"
|
|
);
|
|
}
|
|
|
|
//////////////////////////////////////
|
|
// //
|
|
// SCENARIO 5 //
|
|
// //
|
|
//////////////////////////////////////
|
|
|
|
public interface A5 {
|
|
public default void setParentFoo(Integer num) {
|
|
}
|
|
public default void setFoo(String num) {
|
|
}
|
|
public static int getStaticValue() {
|
|
return 0;
|
|
}
|
|
private int getPrivateValue() {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public class B5 implements A5 {
|
|
public void setFoo(Number num) {
|
|
}
|
|
public void setLocalFoo(Long num) {
|
|
}
|
|
public static int getStaticValue() {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public static void testScenario5() {
|
|
verifyMethods(B5.class,
|
|
"public static int DefaultMethodBeanPropertyTest$B5.getStaticValue()",
|
|
"public default void DefaultMethodBeanPropertyTest$A5.setFoo(java.lang.String)",
|
|
"public default void DefaultMethodBeanPropertyTest$A5.setParentFoo(java.lang.Integer)",
|
|
"public void DefaultMethodBeanPropertyTest$B5.setFoo(java.lang.Number)",
|
|
"public void DefaultMethodBeanPropertyTest$B5.setLocalFoo(java.lang.Long)"
|
|
);
|
|
verifyProperties(B5.class,
|
|
"public final native java.lang.Class java.lang.Object.getClass()",
|
|
"public default void DefaultMethodBeanPropertyTest$A5.setParentFoo(java.lang.Integer)",
|
|
"public void DefaultMethodBeanPropertyTest$B5.setFoo(java.lang.Number)",
|
|
"public void DefaultMethodBeanPropertyTest$B5.setLocalFoo(java.lang.Long)"
|
|
);
|
|
}
|
|
|
|
//////////////////////////////////////
|
|
// //
|
|
// SCENARIO 6 //
|
|
// //
|
|
//////////////////////////////////////
|
|
|
|
public class A6 {
|
|
public void setParentFoo(Integer num) {
|
|
}
|
|
public void setFoo(Integer num) {
|
|
}
|
|
public static int getStaticValue() {
|
|
return 0;
|
|
}
|
|
private int getPrivateValue() {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public class B6 extends A6 {
|
|
public void setFoo(String num) {
|
|
}
|
|
public void setLocalFoo(Long num) {
|
|
}
|
|
public static int getStaticValue() {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public static void testScenario6() {
|
|
verifyMethods(B6.class,
|
|
"public static int DefaultMethodBeanPropertyTest$B6.getStaticValue()",
|
|
"public void DefaultMethodBeanPropertyTest$A6.setFoo(java.lang.Integer)",
|
|
"public void DefaultMethodBeanPropertyTest$A6.setParentFoo(java.lang.Integer)",
|
|
"public void DefaultMethodBeanPropertyTest$B6.setFoo(java.lang.String)",
|
|
"public void DefaultMethodBeanPropertyTest$B6.setLocalFoo(java.lang.Long)"
|
|
);
|
|
verifyProperties(B6.class,
|
|
"public final native java.lang.Class java.lang.Object.getClass()",
|
|
"public void DefaultMethodBeanPropertyTest$A6.setParentFoo(java.lang.Integer)",
|
|
"public void DefaultMethodBeanPropertyTest$B6.setFoo(java.lang.String)",
|
|
"public void DefaultMethodBeanPropertyTest$B6.setLocalFoo(java.lang.Long)"
|
|
);
|
|
}
|
|
|
|
//////////////////////////////////////
|
|
// //
|
|
// SCENARIO 7 //
|
|
// //
|
|
//////////////////////////////////////
|
|
|
|
interface A7<T> {
|
|
T getValue();
|
|
}
|
|
|
|
interface B7 {
|
|
Runnable getValue();
|
|
}
|
|
|
|
interface AB7 extends B7, A7<Object> {
|
|
Runnable getValue();
|
|
}
|
|
|
|
abstract class D7 implements AB7 {
|
|
public void setValue(Runnable value) {
|
|
}
|
|
}
|
|
|
|
public static void testScenario7() {
|
|
verifyMethods(D7.class,
|
|
"public void DefaultMethodBeanPropertyTest$D7.setValue(java.lang.Runnable)"
|
|
);
|
|
verifyProperties(D7.class,
|
|
"public final native java.lang.Class java.lang.Object.getClass()",
|
|
"public void DefaultMethodBeanPropertyTest$D7.setValue(java.lang.Runnable)"
|
|
);
|
|
}
|
|
|
|
//////////////////////////////////////
|
|
// //
|
|
// SCENARIO 8 //
|
|
// //
|
|
//////////////////////////////////////
|
|
|
|
public interface A8 {
|
|
public default void setFoo(Float num) {
|
|
}
|
|
public default void setFoo2(Integer num) {
|
|
}
|
|
}
|
|
public interface B8 extends A8 {
|
|
public default void setFoo(Integer num) {
|
|
}
|
|
public default void setFoo2(Float num) {
|
|
}
|
|
}
|
|
|
|
public class C8 implements B8 {
|
|
}
|
|
|
|
public static void testScenario8() {
|
|
verifyMethods(C8.class,
|
|
"public default void DefaultMethodBeanPropertyTest$A8.setFoo(java.lang.Float)",
|
|
"public default void DefaultMethodBeanPropertyTest$A8.setFoo2(java.lang.Integer)",
|
|
"public default void DefaultMethodBeanPropertyTest$B8.setFoo(java.lang.Integer)",
|
|
"public default void DefaultMethodBeanPropertyTest$B8.setFoo2(java.lang.Float)"
|
|
);
|
|
verifyProperties(C8.class,
|
|
"public final native java.lang.Class java.lang.Object.getClass()",
|
|
"public default void DefaultMethodBeanPropertyTest$B8.setFoo(java.lang.Integer)",
|
|
"public default void DefaultMethodBeanPropertyTest$B8.setFoo2(java.lang.Float)"
|
|
);
|
|
}
|
|
|
|
//////////////////////////////////////
|
|
// //
|
|
// SCENARIO 9 //
|
|
// //
|
|
//////////////////////////////////////
|
|
|
|
public class A9 {
|
|
public void setFoo(Object value) {
|
|
}
|
|
public void setFoo(String value) {
|
|
}
|
|
public void setFoo2(Object value) {
|
|
}
|
|
public void setFoo2(Integer value) {
|
|
}
|
|
// For the same setters with inconvertible arg types PropertyInfo behavior is undefined.
|
|
// public void setLocalFoo3(Long num) { }
|
|
// public void setLocalFoo3(Float num) { }
|
|
}
|
|
|
|
public static void testScenario9() {
|
|
verifyMethods(A9.class,
|
|
"public void DefaultMethodBeanPropertyTest$A9.setFoo(java.lang.String)",
|
|
"public void DefaultMethodBeanPropertyTest$A9.setFoo(java.lang.Object)",
|
|
"public void DefaultMethodBeanPropertyTest$A9.setFoo2(java.lang.Integer)",
|
|
"public void DefaultMethodBeanPropertyTest$A9.setFoo2(java.lang.Object)"
|
|
);
|
|
verifyProperties(A9.class,
|
|
"public final native java.lang.Class java.lang.Object.getClass()",
|
|
"public void DefaultMethodBeanPropertyTest$A9.setFoo(java.lang.String)",
|
|
"public void DefaultMethodBeanPropertyTest$A9.setFoo2(java.lang.Integer)"
|
|
);
|
|
}
|
|
|
|
//////////////////////////////////////
|
|
// //
|
|
// SCENARIO 10 //
|
|
// //
|
|
//////////////////////////////////////
|
|
|
|
public static class A10 {
|
|
public Object getProp() {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static interface B10 {
|
|
Object getProp();
|
|
}
|
|
|
|
public static class C10_1 extends A10 implements B10 {
|
|
}
|
|
|
|
public static class C10_2 extends A10 implements B10 {
|
|
}
|
|
|
|
public static class A10BeanInfo extends SimpleBeanInfo {
|
|
public MethodDescriptor[] getMethodDescriptors() {
|
|
try {
|
|
Class params[] = {};
|
|
MethodDescriptor md = new MethodDescriptor(A10.class.getDeclaredMethod("getProp", params));
|
|
md.setDisplayName("display name");
|
|
MethodDescriptor res[] = { md };
|
|
return res;
|
|
} catch (Exception exception) {
|
|
throw new Error("unexpected exception", exception);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static class C10_1BeanInfo extends SimpleBeanInfo {
|
|
public BeanInfo[] getAdditionalBeanInfo() {
|
|
try {
|
|
BeanInfo res[] = {
|
|
Introspector.getBeanInfo(A10.class),
|
|
Introspector.getBeanInfo(B10.class)
|
|
};
|
|
return res;
|
|
} catch (IntrospectionException exception) {
|
|
throw new Error("unexpected exception", exception);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static class C10_2BeanInfo extends SimpleBeanInfo {
|
|
public BeanInfo[] getAdditionalBeanInfo() {
|
|
try {
|
|
BeanInfo res[] = {
|
|
Introspector.getBeanInfo(B10.class),
|
|
Introspector.getBeanInfo(A10.class)
|
|
};
|
|
return res;
|
|
} catch (IntrospectionException exception) {
|
|
throw new Error("unexpected exception", exception);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void testScenario10() {
|
|
{
|
|
var md = getMethodDescriptor(C10_1.class, A10.class, "getProp");
|
|
assertEquals("display name", md.getDisplayName(), "getDisplayName()");
|
|
}
|
|
{
|
|
var md = getMethodDescriptor(C10_2.class, A10.class, "getProp");
|
|
assertEquals("display name", md.getDisplayName(), "getDisplayName()");
|
|
}
|
|
}
|
|
|
|
// Helper methods
|
|
|
|
private static void verifyEquality(String title, Set<String> expected, Set<String> actual) {
|
|
if (!actual.equals(expected)) {
|
|
throw new Error(title + " mismatch: "
|
|
+ "\nACTUAL:\n "
|
|
+ actual.stream()
|
|
.map(Object::toString)
|
|
.collect(Collectors.joining("\n "))
|
|
+ "\nEXPECTED:\n "
|
|
+ expected.stream()
|
|
.map(Object::toString)
|
|
.collect(Collectors.joining("\n ")));
|
|
}
|
|
}
|
|
|
|
public static void verifyProperties(Class<?> type, String... methodNames) {
|
|
try {
|
|
final Set<String> expected = new HashSet<>(Arrays.asList(methodNames));
|
|
final Set<String> actual = Arrays
|
|
.stream(Introspector.getBeanInfo(type)
|
|
.getPropertyDescriptors())
|
|
.flatMap(pd -> Stream.of(pd.getReadMethod(), pd.getWriteMethod()))
|
|
.filter(Objects::nonNull)
|
|
.map((Method m) -> m.toString())
|
|
.collect(Collectors.toSet());
|
|
verifyEquality("properties", expected, actual);
|
|
} catch (IntrospectionException exception) {
|
|
throw new Error("unexpected exception", exception);
|
|
}
|
|
}
|
|
|
|
public static void verifyMethods(Class<?> type, String... methodNames) {
|
|
try {
|
|
final Set<String> expected = new HashSet<>(Arrays.asList(methodNames));
|
|
final Set<String> actual = Arrays
|
|
.stream(Introspector.getBeanInfo(type, Object.class)
|
|
.getMethodDescriptors())
|
|
.map(MethodDescriptor::getMethod)
|
|
.map(Method::toString)
|
|
.collect(Collectors.toSet());
|
|
verifyEquality("methods", expected, actual);
|
|
} catch (IntrospectionException exception) {
|
|
throw new Error("unexpected exception", exception);
|
|
}
|
|
}
|
|
|
|
private static MethodDescriptor getMethodDescriptor(Class cls, Class stop, String name) {
|
|
try {
|
|
for (var md : Introspector.getBeanInfo(cls, stop).getMethodDescriptors()) {
|
|
if (md.getName().equals(name)) {
|
|
return md;
|
|
}
|
|
}
|
|
return null;
|
|
} catch (IntrospectionException exception) {
|
|
throw new Error("unexpected exception", exception);
|
|
}
|
|
}
|
|
|
|
private static void assertEquals(Object expected, Object actual, String msg) {
|
|
if (!expected.equals(actual)) {
|
|
throw new Error(msg + ":\nACTUAL: " + actual + "\nEXPECTED: " + expected);
|
|
}
|
|
}
|
|
|
|
// Main method
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
testScenario1();
|
|
testScenario2();
|
|
testScenario3();
|
|
testScenario4();
|
|
testScenario5();
|
|
testScenario6();
|
|
testScenario7();
|
|
testScenario8();
|
|
testScenario9();
|
|
testScenario10();
|
|
}
|
|
}
|