Open rapastranac opened 1 year ago
I think, that you are seeing a bug in the Introspector in the JDK, that just showed up now by "luck".
I traced it into the com.sun.beans.introspect.ClassInfo
. See this:
matthias@enterprise:~/src/netbeans$ ~/bin/jdk-17/bin/jshell --add-exports java.desktop/com.sun.beans.introspect=ALL-UNNAMED
| Welcome to JShell -- Version 17.0.4.1
| For an introduction type: /help intro
jshell> com.sun.beans.introspect.ClassInfo.get(javax.swing.JPanel.class).getMethods().forEach(System.out::println)
public javax.accessibility.AccessibleContext javax.swing.JPanel.getAccessibleContext()
public javax.swing.plaf.PanelUI javax.swing.JPanel.getUI()
public javax.swing.plaf.ComponentUI javax.swing.JPanel.getUI()
public java.lang.String javax.swing.JPanel.getUIClassID()
public void javax.swing.JPanel.setUI(javax.swing.plaf.PanelUI)
public void javax.swing.JPanel.updateUI()
jshell>
matthias@enterprise:~/src/netbeans$ ~/bin/jdk-19/bin/jshell --add-exports java.desktop/com.sun.beans.introspect=ALL-UNNAMED
| Willkommen bei JShell - Version 19
| Geben Sie für eine Einführung Folgendes ein: /help intro
jshell> com.sun.beans.introspect.ClassInfo.get(javax.swing.JPanel.class).getMethods().forEach(System.out::println)
public javax.accessibility.AccessibleContext javax.swing.JPanel.getAccessibleContext()
public javax.swing.plaf.ComponentUI javax.swing.JPanel.getUI()
public javax.swing.plaf.PanelUI javax.swing.JPanel.getUI()
public java.lang.String javax.swing.JPanel.getUIClassID()
public void javax.swing.JPanel.setUI(javax.swing.plaf.PanelUI)
public void javax.swing.JPanel.updateUI()
jshell>
In java.beans.PropertyDescriptor#getReadMethod
is this comment:
// Since there can be multiple write methods but only one getter // method, find the getter method first so that you know what the // property type is. For booleans, there can be "is" and "get" // methods. If an "is" method exists, this is the official // reader method so look for this one first.
The first sentence is not true (as visible above). The claim overlooks, that java supports covariant returns, which is implemented by synthesized bridge methods, that are added by the compiler.
ClassInfo
iterates the methods and uses the first matching method. Then the return type of that method is assumed to find the setter. The problem on JDK 19 public javax.swing.plaf.ComponentUI javax.swing.JPanel.getUI()
is used, but there is no public void javax.swing.JPanel.setUI(javax.swing.plaf.ComponentUI)
.
I think the JDK introspector needs to improved to first consider non-synthesized methods and non-bridge methods.
Please consider to report this upstream against OpenJDK.
The order of methods returned by the "ClassInfo" should not affect the selection of "read" and "write" methods. It is just a "stable" wrapper over "Class.getMethods()" since the returned order is not specified and is random for that method. The next code shows the correct read/write methods for the JPanel class on JDK 18 and JDK 19.
BeanInfo beanInfo = Introspector.getBeanInfo(JPanel.class);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : propertyDescriptors) {
Class<?> propertyType = pd.getPropertyType();
if (propertyType !=null && propertyType.toString().contains("UI")) {
System.out.println("pd = " + pd);
}
}
readMethod=public javax.swing.plaf.PanelUI javax.swing.JPanel.getUI(); writeMethod=public void javax.swing.JPanel.setUI(javax.swing.plaf.PanelUI)
The bug can be reproduced by the next small code when the property descriptor is created w/o usage of Introspector
new PropertyDescriptor("UI", JPanel.class, "getUI", "setUI");
@mrserb I reread your comment and still don't get it.
The order of methods returned by the "ClassInfo" should not affect the selection of "read" and "write" methods. It is just a "stable" wrapper over "Class.getMethods()" since the returned order is not specified and is random for that method.
The order varies between JDK 17 and JDK 19:
17:
public javax.swing.plaf.PanelUI javax.swing.JPanel.getUI()
public javax.swing.plaf.ComponentUI javax.swing.JPanel.getUI()
19:
public javax.swing.plaf.ComponentUI javax.swing.JPanel.getUI()
public javax.swing.plaf.PanelUI javax.swing.JPanel.getUI()
For both cases in the constructor of PropertyDescriptor
getReadMethod
is invoked, which more or less directly delegates to Introspector#findMethod
which delegates to Introspector#internalFindMethod
:
The first method, that has the right name and arguments is returned. And here is the difference between JDK 17 and 19 (see order above). For 17 you will get the getter, that returns javax.swing.plaf.PanelUI
and for 19 you will get javax.swing.plaf.ComponentUI
.
Now execution returns to the PropertyDescriptor
constructor and invokes getWriteMethod
. It will first deduce the property type from the read method (the return type discussed in the previous paragraph) and now it tries to find the write method, that takes the property type as parameter.
So for 17 it looks for setUI(javax.swing.plaf.PanelUI)
and for 19 it will look for setUI(javax.swing.plaf.ComponentUI)
. Only the former can be satisfied:
public void javax.swing.JPanel.setUI(javax.swing.plaf.PanelUI)
I still think, that the introspector is wrong.
My point is that the Instospector via public API(Introspector.getBeanInfo) returns the correct beanifo and property descriptors, but the bug is reproduced if the PropertyDescriptor is created directly. The bug is not in the order of methods returned by the "ClassInfo" but in the internalFindMethod which depends on the order, it should select most specific read method based on the return type, this is already done in the Introspector.getBeanInfo
We ended up fixing the problem creating the PropertyDescriptor
of the UI
property using the constructor that takes Method
s, not method names. This way we can get the PanelUI getUI()
method and not the ComponentUI getUI()
method:
try {
Method readMethod = JPanel.class.getDeclaredMethod("getUI"); // NOI18N
Class<?> returnType = readMethod.getReturnType();
if (!returnType.equals(PanelUI.class)) {
throw new RuntimeException("getUI() returns " + returnType + " instead of PanelUI"); // NOI18N
}
Method writeMethod = JPanel.class.getDeclaredMethod("setUI", PanelUI.class); // NOI18N
properties[PROPERTY_UI] = new PropertyDescriptor("UI", readMethod, writeMethod); // NOI18N
} catch (IntrospectionException | NoSuchMethodException | SecurityException e) {
// XXX: this will not work as it will find the method getUI() returning ComponentUI, not PanelUI
// see https://github.com/apache/netbeans/issues/5774
properties[PROPERTY_UI] = new PropertyDescriptor("UI", com.streamsim.commonsgui.SectionPanel.class, "getUI", "setUI"); // NOI18N
}
Apache NetBeans version
Apache NetBeans 17
What happened
Please find attached two minimum reproducible examples. One uses maven and the other one uses ant.
When creating a BeanInfo using netbeans, in the getBdescriptor() method, we can set the value "containerDelegate" using the following line of code to delegate to another container.
These examples work with all previous JDK versions, except for JDK 19 I also tested JDK 13, 14, 15, 16, 17 and 18 and they all work fine.
DummyAnt.zip DummyMaven.zip
How to reproduce
Using the Dummy Projects.
With any new JPanel, or using MainPanel:
SectionPanel should be recognized as a Container, therefore the widget should get added into the SectionPanel instance
Did this work correctly in an earlier version?
Did not test JDK19 with previous NetBeans versions
Operating System
Windows 11
JDK
19.0.2
Apache NetBeans packaging
Apache NetBeans platform
Anything else
This problem occurs all the time under the aforementioned description
Here's a screenshot after attempting to perform the same steps using JDK19
Here's the Navigator screenshot showing that a widget cannot be added to the SectionPanel instance
Are you willing to submit a pull request?
No