5

One of our customer reported an exception in our application. The problem is, I am completely unable to understand how this bug can be reproduced.

Here is the code :

btn.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        popup.show(btn, 3, btn.getHeight());
    }
});

Notes :

  • btn is a final local variable of type JButton.
  • popup is a final local variable of type JPopupMenu.

The following exception was thrown :

java.awt.IllegalComponentStateException: component must be showing on the screen to determine its location
    at java.awt.Component.getLocationOnScreen_NoTreeLock(Unknown Source)
    at java.awt.Component.getLocationOnScreen(Unknown Source)
    at javax.swing.JPopupMenu.show(Unknown Source)
    at fr.def.iss.vd2.mod_site_watcher_gui.SiteElementPanel$4.actionPerformed(SiteElementPanel.java:117)
    at javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
    at javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source)
    at javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source)
    at javax.swing.DefaultButtonModel.setPressed(Unknown Source)
    at javax.swing.plaf.basic.BasicButtonListener.focusLost(Unknown Source)
    at java.awt.Component.processFocusEvent(Unknown Source)
    at java.awt.Component.processEvent(Unknown Source)
    at java.awt.Container.processEvent(Unknown Source)
    at java.awt.Component.dispatchEventImpl(Unknown Source)
    at java.awt.Container.dispatchEventImpl(Unknown Source)
    at java.awt.Component.dispatchEvent(Unknown Source)
    at java.awt.KeyboardFocusManager.redispatchEvent(Unknown Source)
    at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(Unknown Source)
    at java.awt.DefaultKeyboardFocusManager.dispatchEvent(Unknown Source)
    at java.awt.Component.dispatchEventImpl(Unknown Source)
    at java.awt.Container.dispatchEventImpl(Unknown Source)
    at java.awt.Component.dispatchEvent(Unknown Source)
    at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
    at java.awt.EventQueue.access$000(Unknown Source)
    at java.awt.EventQueue$1.run(Unknown Source)
    at java.awt.EventQueue$1.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.AccessControlContext$1.doIntersectionPrivilege(Unknown Source)
    at java.security.AccessControlContext$1.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue$2.run(Unknown Source)
    at java.awt.EventQueue$2.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.AccessControlContext$1.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue.dispatchEvent(Unknown Source)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.run(Unknown Source)

As far as I understand, the show method complains that btn is not showing. How is it possible that btn is not showing when its actionPerformed method is called ?

The strangest thing in this stacktrace is that the actionPerformed method seems to be triggered while a FocusEvent is being handled (a focusLost, actually).

The question is : can you explain how this stacktrace can possibly happen ?

Epilogue

Thanks to a suggestion from trashgod, I found the problem.

On Windows, when a button disappears while it is being pressed, then its ActionListeners are triggered, as if the button was clicked. This behavior can be observed on Windows, but not on Linux.

I filed a bug on the Oracle/Sun bug database. here is the link :

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7115421

(this link will become valid whithin a few days, after it is reviewed by the Java team).

Thanks for your help. The answers from trashgod and Thomas helped a lot.

barjak
  • 10,842
  • 3
  • 33
  • 47
  • 3
    the problem is in the code you are not showing – kleopatra Nov 22 '11 at 15:35
  • @kleopatra Maybe it is. The surrounding code is not relevant to the question, and would only bring noise to it. I can only say that 1) the JButton is created either enabled or disabled (it is not enabled or disabled afterward) 2) The container of the JButton can be removed from its hierarchy 3) the visibility (`setVisible`) of the JButton is never changed 4) `actionPerformed` is never called explicitly by my code 5) the JButton instance is added to a container, but it is never referenced elsewhere. There is no code that tries to acces the JButton through its container, either – barjak Nov 22 '11 at 17:08
  • 2
    As many have said, a SSCCE is required. We know what you are doing withing the `ActionListener`, but we don't know what else you are doing with your button. We need to see that code if we are to help you, and the piece of code you have posted is insufficient for us to find the problem. If the problem cannot be reproduced if you simply add the same button to an empty frame, then the problem lies somewhere else in your code. – Laf Nov 22 '11 at 18:41
  • @Laf the problem cannot be reproduced at all. So, no, I am unable to give you a SSCCE. The nature of the question prohibits that. – barjak Nov 22 '11 at 19:20
  • "The surrounding code is not relevant to the question" that's obviously wrong, as the snippet you are showing looks fine and cannot produce the error :-) put all your effort into reproducing the problem - that's the only way you can find the underlying reason. Without, everything you get here are wild guesses – kleopatra Nov 23 '11 at 16:27
  • "put all your effort into reproducing the problem" that's precisely what I'm trying to achieve : to reproduce the problem that happened only once, on a machine I can't access. If I can reproduce the problem, I win ! If you really want the full class code, here it is : http://pastebin.com/J2iU2kpd – barjak Nov 23 '11 at 18:38

5 Answers5

6

One possible source is a race condition that allows an event to fire before the recipient is visible. Verify that your Swing GUI objects are constructed and manuipulated only on the event dispatch thread. The article Debugging Swing, the final summary cited in How to generate exceptions from RepaintManager mentions several approaches to automating the search.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • Thanks for the suggestion. I did a manual static analysis of the code, and I saw no possibility of EDT violation. I also used a tool to find EDT violations at runtime (SwingExplorer), and tried to stimulate this component as much as possible. The tool reported some violations, but nothing related to this specific piece of code. I fixed these violations, though. – barjak Nov 23 '11 at 11:10
  • Looking at [`getLocationOnScreen()`](http://www.docjar.com/html/api/java/awt/Component.java.html), might the peer have become `null` after an inadvertent `dispose()`? Might the popup cause `isShowing()` to return false? – trashgod Nov 23 '11 at 18:00
  • It's one or the other... The question is how can `actionPerformed` be called if its peer is `null` or if the button is not showing ? – barjak Nov 23 '11 at 18:46
  • The peer shouldn't disappear, but it's owned by the host. A misplaced [`dispose()`](http://stackoverflow.com/q/2486200/230513) may be harder to find. Do any of the caveats in [`isShowing()`](http://docs.oracle.com/javase/7/docs/api/java/awt/Component.html#isShowing%28%29) apply? – trashgod Nov 23 '11 at 22:56
  • No, the button is not in a scrollpane, and cannot be obscured by another component. But, the container of the button ca be removed from its hierarchy. – barjak Nov 23 '11 at 23:32
  • 1
    I've also seen latent, non-EDT synchronization problems that were apparent only on certain platforms, e.g. Mac OS on PowerPC. Can you test on something approximating the customer's environment? – trashgod Nov 24 '11 at 02:21
  • 1
    You were right. I can reproduce this bug now, but only on Windows (I develop on Linux). I haven't thought to try on another platform, since we use a cross-platform look-and-feel, and nothing in the stacktrace seems to be related to the underlying platform. I updated my question to explain the problem. – barjak Nov 24 '11 at 14:20
3

•btn is a final local variable of type JButton.

Maybe that is the problem. Maybe you have the reference to a component that is not visible on the screen.

Instead you should be using:

JButton button = (JButton)e.getSource();

Then you know for sure you are referencing the component that generated the event.

Alsom make sure you don't have class variables of the same name.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • The listener itself is registered on `btn`. Thus, the return value of `getSource()` must always be `btn`, doesn't it ? – barjak Nov 22 '11 at 16:32
  • 1
    My point is there is no need for creating a `final` variable. You can get the information from the ActionEvent directly. It is less error prone and has advantages in other situations where you may want to share the ActionListener between multiple buttons. In any case you haven't posted an SSCCE so we are just making wild guesses. – camickr Nov 22 '11 at 16:46
  • I'm afraid my question can't contain a SSCCE, because the question is "how is it possible to reproduce this behavior". If I could post an example that exhibit the problem (as you suggest), I wouldn't have asked the question, then :) Thanks for your input, anyway. – barjak Nov 22 '11 at 16:59
  • 1
    @barjak the only one having the slightest chance to reproduce it, is _you_ - because _you_ are the only one seeing it in your production code – kleopatra Nov 23 '11 at 16:30
  • @kleopatra yes, but I am a bit short on ideas, right now :) The answers I received helped me a lot, so I don't regret having asked this question. – barjak Nov 23 '11 at 18:29
2

Looking at the source code of DefaultButtonModel#setPressed(...) we see the following:

if(!isPressed() && isArmed()) {
        ...
        fireActionPerformed(
            new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
                            getActionCommand(),
                            EventQueue.getMostRecentEventTime(),
                            modifiers));
} 

As you can see the an ActionEvent is fired when the button was "armed", i.e. had focus but was not pressed. That is consistent with the "FocusLost" event.

Thomas
  • 87,414
  • 12
  • 119
  • 157
  • The caller's code (in `BasicButtonListener`) is `setArmed(false); setPressed(false);`. I still don't understand how the `if` condition can evaluate to `true`. – barjak Nov 22 '11 at 16:26
  • +1 in retrospect. I'd have thought all platforms would use [`DefaultButtonModel`](http://docs.oracle.com/javase/7/docs/api/javax/swing/DefaultButtonModel.html), but it might be worth trying the [cross-platform L&F](http://docs.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html) on Windows. – trashgod Nov 24 '11 at 16:57
0

Use mode specific action instead:

final JButton button = new JButton();
button.addMouseListener(new MouseAdapter(){
    public void mouseClicked(MouseEvent e) {
       popup.show(btn, 3, btn.getHeight());
    }
})
Stanislav Levental
  • 2,165
  • 1
  • 14
  • 28
  • Thanks for the suggestion. It does not answer the question, but it may be a workaround for my particular problem. – barjak Nov 22 '11 at 16:19
  • This isn't a good idea. You should manage button clicks using a `ActionLlistener`, else you will miss button clicks generated from the keyboard (pressing space or enter when the focus is on the button). – Laf Nov 22 '11 at 18:38
  • @Laf you are right, but the advantage is that if only a mouse click can trigger `popup.show`, then we can be sure that the button is actually showing. I agree it's not a great thing to do, but it could be a workaround. – barjak Nov 22 '11 at 19:16
-1

I can't resist, I only to preffer PopupFactory

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;

public class UsePopupFactory {

    private JFrame frame = new JFrame("PopupFactory Sample");
    private PopupFactory factory = PopupFactory.getSharedInstance();
    private Popup popup;

    public UsePopupFactory() {
        JPanel btnPanel = new JPanel();
        btnPanel.setBorder(new EmptyBorder(20, 20, 20, 20));
        btnPanel.setLayout(new GridLayout(0, 3));
        ActionListener actionListener = new ShowPopup(frame);
        JButton start3 = new JButton("Pick Me for Popup");
        JButton start = new JButton("Pick Me for Popup");
        JButton start2 = new JButton("Pick Me for Popup");
        btnPanel.add(start3);
        btnPanel.add(start);
        btnPanel.add(start2);
        start3.setVisible(false);
        start2.setVisible(false);
        start.addActionListener(actionListener);
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(btnPanel, BorderLayout.SOUTH);
        frame.setSize(new Dimension(d.width / 4, d.height / 4));
        frame.setVisible(true);
    }

    private class ShowPopup implements ActionListener {

        private Component component;

        ShowPopup(Component component) {
            this.component = component;
        }

        public synchronized void actionPerformed(ActionEvent actionEvent) {
            JPanel pnl = new JPanel();
            JComboBox combo = new JComboBox();
            JButton button = new JButton("any action");
            pnl.add(combo);
            pnl.add(button);
            pnl.setPreferredSize(new Dimension(250, 40));
            popup = factory.getPopup(component, pnl,
                    frame.getWidth() / 2 - pnl.getPreferredSize().width / 2,
                    frame.getHeight() / 2 - pnl.getPreferredSize().height / 2);
            popup.show();
            Timer timer = new Timer(3000, hider);
            timer.start();
        }
    }
    private Action hider = new AbstractAction() {

        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(ActionEvent e) {
            popup.hide();
        }
    };

    public static void main(final String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                UsePopupFactory uPF = new UsePopupFactory();
            }
        });
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319