Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8071693: Introspector ignores default interface methods #13544

Closed
wants to merge 13 commits into from
Closed
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 140 additions & 22 deletions test/jdk/java/beans/Introspector/DefaultMethodBeanPropertyTest.java
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it expected that the test file doesn't compile with simple javac DefaultMethodBeanPropertyTest.java? It complains about BeanUtils not found. When run with jtreg, the test compiles successfully.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it expected that the test file doesn't compile with simple javac DefaultMethodBeanPropertyTest.java?

Definitely not. For example, many regression tests use classes that are not part of the standard JDK such as toolbox.ToolBox.

Original file line number Diff line number Diff line change
@@ -24,16 +24,29 @@
/*
* @test
* @bug 8071693
* @summary Verify that the Introspector finds default methods inherited from interfaces
* @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.util.Arrays;
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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we please add two additional tests to verify the "diamond" case:

  • getFoo is in the top interfaceA, two empty subinterfaces B anc C , and one class D of B and C, will the D have one correct prop Foo?
  • getFoo is in the top interfaceA, two non-empty subinterfaces B and C and each override getFoo by the different return types, and then one class D of B and C which override getFoo again by compatible type from B and C, will the D have one correct prop Foo?

We also can test the case if the D from the cases above is interface and implemented by the class E.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing... see 6b43627.


public interface IfaceA {
//////////////////////////////////////
// //
// SCENARIO 1 //
// //
//////////////////////////////////////

public interface A1 {
default int getValue() {
return 0;
}
@@ -46,14 +59,14 @@ public static int getStaticValue() {
}
}

public interface IfaceB extends IfaceA {
public interface B1 extends A1 {
}

public interface IfaceC extends IfaceA {
public interface C1 extends A1 {
Number getFoo();
}

public class ClassB implements IfaceC {
public class D1 implements C1 {
@Override
public Integer getFoo() {
return null;
@@ -64,30 +77,135 @@ public Float getObj() {
}
}

public static void findProperty(Class<?> type, String name) {
PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(type, name);
if (pd == null) {
throw new Error("property \"" + name + "\" not found in " + type);
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 static void main(String[] args) throws Exception {
findProperty(ClassB.class, "foo");
public interface B2 extends A2 {
}

// Expected properties
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<PropertyDescriptor> expected = new HashSet<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please split the long lines to use 80 chars per line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e6a2ecb.

expected.add(new PropertyDescriptor("class", ClassB.class, "getClass", null)); // inherited method
expected.add(new PropertyDescriptor("value", ClassB.class, "getValue", null)); // inherited default method
expected.add(new PropertyDescriptor("foo", ClassB.class, "getFoo", null)); // overridden interface method
expected.add(new PropertyDescriptor("obj", ClassB.class, "getObj", null)); // overridden default method
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);
});

// Actual properties
final HashSet<PropertyDescriptor> actual = new HashSet<>(
Arrays.asList(BeanUtils.getPropertyDescriptors(ClassB.class)));
// Gather actual properties
final Set<PropertyDescriptor> actual = Set.of(
BeanUtils.getPropertyDescriptors(type));

// Verify they are the same
// Verify the two sets are the same
if (!actual.equals(expected)) {
throw new Error("mismatch:\n actual: " + actual + "\n expected: " + 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();
}
}