FULL PRODUCT VERSION :
java version " 1.7.0_09 "
Java(TM) SE Runtime Environment (build 1.7.0_09-b05)
Java HotSpot(TM) Client VM (build 23.5-b02, mixed mode, sharing)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]
A DESCRIPTION OF THE PROBLEM :
If one adds more than 10 key accelerators using the method getActionMap().put(), you will get NullPointerExceptions when the CTRL key is pressed on the keyboard.
This is caused beause the KeyboardManager::fireKeyboardAction method has changed and a null keystroke is sent to the fireBinding function.
At some point, the actions are searched using the ArrayTable::get but this class some optimization code that when you add more than ARRAY_BOUNDARY (=8), it uses a Hashtable to hold the values.
When the Java code calls the method HashTable::get(null), a NullPointerException is thrown.
REGRESSION. Last worked in version 7
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Create a JFrame with a JMenu and add 10 accelerators to the menu.
Execute the application created.
Press the CTRL key.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
CTRL key should NOT throw an exception.
ACTUAL -
NullPointerException is thrown
ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread " AWT-EventQueue-0 " java.lang.NullPointerException
at java.util.Hashtable.hash(Hashtable.java:262)
at java.util.Hashtable.get(Hashtable.java:459)
at javax.swing.ArrayTable.get(ArrayTable.java:156)
at javax.swing.InputMap.get(InputMap.java:120)
at javax.swing.JComponent.processKeyBinding(JComponent.java:2876)
at javax.swing.JMenuBar.processKeyBinding(JMenuBar.java:664)
at ui.MyMenuBar.processKeyBinding(TestJMenu.java:100)
at javax.swing.KeyboardManager.fireBinding(KeyboardManager.java:306)
at javax.swing.KeyboardManager.fireKeyboardAction(KeyboardManager.java:289)
at javax.swing.JComponent.processKeyBindingsForAllComponents(JComponent.java:2971)
at javax.swing.SwingUtilities.processKeyBindings(SwingUtilities.java:1588)
at javax.swing.UIManager$2.postProcessKeyEvent(UIManager.java:1476)
at java.awt.DefaultKeyboardFocusManager.dispatchKeyEvent(DefaultKeyboardFocusManager.java:772)
at java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent(DefaultKeyboardFocusManager.java:1027)
at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(DefaultKeyboardFocusManager.java:899)
at java.awt.DefaultKeyboardFocusManager.dispatchEvent(DefaultKeyboardFocusManager.java:727)
at java.awt.Component.dispatchEventImpl(Component.java:4731)
at java.awt.Container.dispatchEventImpl(Container.java:2287)
at java.awt.Window.dispatchEventImpl(Window.java:2719)
at java.awt.Component.dispatchEvent(Component.java:4687)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:723)
at java.awt.EventQueue.access$200(EventQueue.java:103)
at java.awt.EventQueue$3.run(EventQueue.java:682)
at java.awt.EventQueue$3.run(EventQueue.java:680)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:87)
at java.awt.EventQueue$4.run(EventQueue.java:696)
at java.awt.EventQueue$4.run(EventQueue.java:694)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:693)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:244)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:163)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:151)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:147)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:139)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:97)
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
package ui;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeListener;
import javax.swing.Action;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;
public class TestJMenu extends JFrame
{
public TestJMenu()
{
MyMenuBar mb = new MyMenuBar();
JMenu menu = new JMenu( " Menu " );
menu.add( " Item 1 " );
mb.add(menu);
setJMenuBar(mb);
setSize(400,400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
InputMap im = mb.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
// We add exactly 10 actions because the ArrayTable is converted from a array to a hashtable when more than 8 values are added.
for (int i=0;i<9;i++)
{
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A+i, java.awt.event.InputEvent.CTRL_DOWN_MASK), " theAction " +i);
final int actId = i;
mb.getActionMap().put( " theAction " +i, new Action(){
@Override
public void actionPerformed(ActionEvent e)
{
JOptionPane.showMessageDialog(TestJMenu.this, " Action executed " + actId);
}
@Override
public Object getValue(String key)
{
return null;
}
@Override
public void putValue(String key, Object value)
{
}
@Override
public void setEnabled(boolean b)
{
}
@Override
public boolean isEnabled()
{
return true;
}
@Override
public void addPropertyChangeListener(
PropertyChangeListener listener)
{
}
@Override
public void removePropertyChangeListener(
PropertyChangeListener listener)
{
}});
}
}
/**
* @param args
*/
public static void main(String[] args)
{
new TestJMenu().setVisible(true);
}
}
class MyMenuBar extends JMenuBar
{
@Override
protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
int condition, boolean pressed)
{
if (ks==null)
{
System.out.println( " Null KS is sent " );
// Remove comment below to fix the issue.
// return false;
}
return super.processKeyBinding(ks, e, condition, pressed);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Inherit from JMenuBar and ignore the null key:
class MyMenuBar extends JMenuBar
{
@Override
protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
int condition, boolean pressed)
{
if (ks==null)
{
return false;
}
return super.processKeyBinding(ks, e, condition, pressed);
}
}
SUPPORT :
YES