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

NullPointerException in tabbed pane #299

Closed
Chrriis opened this issue Mar 31, 2021 · 5 comments
Closed

NullPointerException in tabbed pane #299

Chrriis opened this issue Mar 31, 2021 · 5 comments
Milestone

Comments

@Chrriis
Copy link
Contributor

Chrriis commented Mar 31, 2021

I got an exception which I am not able to reproduce. Still, there was nothing special I did so I report it in case it helps uncovering a bug in FlatLaf.

As I can remember, during initialization of various stuff at application startup, I grabbed the titled bar of the maximized frame and moved it which triggered all sorts of resizing. The exception was thrown.

Hopefully the stack trace gives some information or at least allows to add some test / catch code to prevent such exception from happening.

I am using OpenJDK 13.0.2+8.

java.lang.NullPointerException
	at java.desktop/javax.swing.plaf.basic.BasicTabbedPaneUI.rotateInsets(BasicTabbedPaneUI.java:2538)
	at java.desktop/javax.swing.plaf.basic.BasicTabbedPaneUI.getTabAreaInsets(BasicTabbedPaneUI.java:2116)
	at com.formdev.flatlaf/com.formdev.flatlaf.ui.FlatTabbedPaneUI.getRealTabAreaInsets(FlatTabbedPaneUI.java:725)
	at com.formdev.flatlaf/com.formdev.flatlaf.ui.FlatTabbedPaneUI.getTabAreaInsets(FlatTabbedPaneUI.java:747)
	at java.desktop/javax.swing.plaf.basic.BasicTabbedPaneUI$TabbedPaneScrollLayout.calculateTabRects(BasicTabbedPaneUI.java:3587)
	at java.desktop/javax.swing.plaf.basic.BasicTabbedPaneUI$TabbedPaneLayout.calculateLayoutInfo(BasicTabbedPaneUI.java:2922)
	at com.formdev.flatlaf/com.formdev.flatlaf.ui.FlatTabbedPaneUI$FlatTabbedPaneScrollLayout.calculateLayoutInfo(FlatTabbedPaneUI.java:2598)
	at java.desktop/javax.swing.plaf.basic.BasicTabbedPaneUI.ensureCurrentLayout(BasicTabbedPaneUI.java:1654)
	at java.desktop/javax.swing.plaf.basic.BasicTabbedPaneUI.getTabRunCount(BasicTabbedPaneUI.java:1672)
	at com.formdev.flatlaf/com.formdev.flatlaf.ui.FlatTabbedPaneUI.ensureCurrentLayout(FlatTabbedPaneUI.java:1227)
	at com.formdev.flatlaf/com.formdev.flatlaf.ui.FlatTabbedPaneUI.ensureSelectedTabIsVisible(FlatTabbedPaneUI.java:1398)
	at com.formdev.flatlaf/com.formdev.flatlaf.ui.FlatTabbedPaneUI.lambda$ensureSelectedTabIsVisibleLater$1(FlatTabbedPaneUI.java:1390)
@DevCharly
Copy link
Collaborator

Hmm, one of the two Insets parameters of BasicTabbedPaneUI.rotateInsets() must be null.

Field currentTabAreaInsets is initialized at defination and never changed.
So it must be field tabAreaInsets, which is initialized in BasicTabbedPaneUI.installDefaults()
and changed back to null in BasicTabbedPaneUI.uninstallDefaults().
FlatLaf does not change those fields.

FlatTabbedPaneUI.ensureSelectedTabIsVisible() is invoked via EventQueue.invokeLater().
So it could be possible that the tabbedpane UI delegate was unintalled when this method is invoked.
But there is a null check at that should prevent that:

protected void ensureSelectedTabIsVisible() {
if( tabPane == null || tabViewport == null )
return;

To prevent your exception, I should check there also BasicTabbedPaneUI.tabAreaInsets for null.

But still trying to imagine how this could happen...

At the moment, I have only one idea: when changing JTabbedPane.tabLayoutPolicy, the BasicTabbedPaneUI property change listener temporary uninstalls and installs the UI delegate, which makes tabAreaInsets temporary null:

public void propertyChange(PropertyChangeEvent e) {
    ...
    } else if (name == "tabLayoutPolicy") {
        BasicTabbedPaneUI.this.uninstallUI(pane);
        BasicTabbedPaneUI.this.installUI(pane);
    ...

So if above code is executed on another thread, and ensureSelectedTabIsVisible() on the AWT thread,
then the exception can occur...

Are you using multiple threads?

@Chrriis
Copy link
Contributor Author

Chrriis commented Apr 1, 2021

Are you using multiple threads?

Yes! Now that you say it, this is actually a possibility.
Now we have to discuss why it is done this way and whether it is legal 😄

At startup, we instantiate various UI components in a background thread, and these components are never added to any frame (they are not made visible nor connected to native peers). The purpose of instantiating them is that it loads thousands of classes in the classloaders and some shared caches. When a user navigates to such component, it opens very fast (no cost of loading resources): it greatly improves user experience.

This code has been there for 10+ years and it is the first time I see an exception. We know that "Swing is not thread safe" and that "UI interactions should be done in the UI thread", but in this case the UI components are not displayable and there is no user interaction.

I wonder if uninstallUI/installUI is a proper way of setting things up in case of property change. Also, posting actions to make the selected tab visible when the component is not displayable may not seem very necessary; this code is likely to be triggered anyway if the component were be made visible.

Any thoughts? 😉

@DevCharly
Copy link
Collaborator

This code has been there for 10+ years and it is the first time I see an exception...

Yes, it is a issue in FlatLaf.

I wonder if uninstallUI/installUI is a proper way of setting things up in case of property change.

Well, it is in BasicTabbedPaneUI for 14+ years...
https://github.com/openjdk/jdk/blob/0696fd0e816b6b40aec1a7fead0586789b0ec13a/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicTabbedPaneUI.java#L3983-L3985

Also, posting actions to make the selected tab visible when the component is not displayable may not seem very necessary..

Yes, more checks are needed in ensureSelectedTabIsVisibleLater() and ensureSelectedTabIsVisible().

Still wonder what triggers invocation of ensureSelectedTabIsVisibleLater() in your case.

Do you use leading or trailing components in tab area? (client properties JTabbedPane.leadingComponent or JTabbedPane.trailingComponent)
Or invoking Component.applyComponentOrientation() or setComponentOrientation()?
This are the only cases where ensureSelectedTabIsVisibleLater() may be invoked from another thread.

Have added more checks in commit d31f167 that should fix the NPE.

@DevCharly DevCharly added this to the 1.1.2 milestone Apr 1, 2021
@Chrriis
Copy link
Contributor Author

Chrriis commented Apr 1, 2021

Do you use leading or trailing components in tab area? (client properties JTabbedPane.leadingComponent or JTabbedPane.trailingComponent)

I do use JTabbedPane.leadingComponent 😉

@DevCharly
Copy link
Collaborator

Should be fixed with FlatLaf 1.1.2.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants