JDK-6429812 : NPE after calling JTable.updateUI() when using a header renderer + XP L&F
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 5.0,6,7u75,9,10
  • Priority: P3
  • Status: Open
  • Resolution: Unresolved
  • OS: windows
  • CPU: x86
  • Submitted: 2006-05-24
  • Updated: 2018-09-05
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Duplicate :  
Duplicate :  
Duplicate :  
Relates :  
Description
FULL PRODUCT VERSION :
jre1.5.0_06

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]

A DESCRIPTION OF THE PROBLEM :
When using a JTable with a custom header renderer and the WinXP L&F, calling JTable.updateUI() prior to the first call of setVisible(true) on the containing window will cause a NullPointerException to be thrown from the painting thread.

Please note that this is possibly the same bug as 5049957, but I am reporting it because of the following differences in with the previous bug decription:

- User is not required to "hot swap" out the XP theme in the control panel while the application is running to reproduce the error.

- The error only occurs if updateUI() is called before the window is displayed for the first time.

- Unlike bug 5049957  , the error is NOT caused by the skin pointer in XPDefaultRenderer being set to null. After running the code with in my debuger, I discovered that after the call to updateUI(), the skin field pointer is (correctly) non-null. Rather the header member variable of the outer class (WindowsTableHeaderUI) is made null by the updateUI() call. This is what appears to be causing the NPE. Therefore the fix described in 5049957 would likely not work.



STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile the attached test program. Two YES/NO boxes will appear. Selecting YES to both methods will cause the error.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The window and tables appear normally.
ACTUAL -
The painter thread throws an exception, causing only part of the window to be rendered correctly.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
	at com.sun.java.swing.plaf.windows.WindowsTableHeaderUI$XPDefaultRenderer.paint(Unknown Source)
	at javax.swing.CellRendererPane.paintComponent(Unknown Source)
	at javax.swing.plaf.basic.BasicTableHeaderUI.paintCell(Unknown Source)
	at javax.swing.plaf.basic.BasicTableHeaderUI.paint(Unknown Source)
	at javax.swing.plaf.ComponentUI.update(Unknown Source)
	at javax.swing.JComponent.paintComponent(Unknown Source)
	at javax.swing.JComponent.paint(Unknown Source)
	at javax.swing.JComponent.paintChildren(Unknown Source)
	at javax.swing.JComponent.paint(Unknown Source)
	at javax.swing.JViewport.paint(Unknown Source)
	at javax.swing.JComponent.paintChildren(Unknown Source)
	at javax.swing.JComponent.paint(Unknown Source)
	at javax.swing.JComponent.paintChildren(Unknown Source)
	at javax.swing.JComponent.paint(Unknown Source)
	at javax.swing.JComponent.paintChildren(Unknown Source)
	at javax.swing.JComponent.paint(Unknown Source)
	at javax.swing.JLayeredPane.paint(Unknown Source)
	at javax.swing.JComponent.paintChildren(Unknown Source)
	at javax.swing.JComponent.paintWithOffscreenBuffer(Unknown Source)
	at javax.swing.JComponent.paintDoubleBuffered(Unknown Source)
	at javax.swing.JComponent.paint(Unknown Source)
	at java.awt.GraphicsCallback$PaintCallback.run(Unknown Source)
	at sun.awt.SunGraphicsCallback.runOneComponent(Unknown Source)
	at sun.awt.SunGraphicsCallback.runComponents(Unknown Source)
	at java.awt.Container.paint(Unknown Source)
	at sun.awt.RepaintArea.paintComponent(Unknown Source)
	at sun.awt.RepaintArea.paint(Unknown Source)
	at sun.awt.windows.WComponentPeer.handleEvent(Unknown Source)
	at java.awt.Component.dispatchEventImpl(Unknown Source)
	at java.awt.Container.dispatchEventImpl(Unknown Source)
	at java.awt.Window.dispatchEventImpl(Unknown Source)
	at java.awt.Component.dispatchEvent(Unknown Source)
	at java.awt.EventQueue.dispatchEvent(Unknown Source)
	at java.awt.EventDispatchThread.pumpOneEventForHierarchy(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)

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;

public class TestFrame extends JFrame {

	public static void main(String[] args) throws IllegalAccessException, ClassNotFoundException, InstantiationException, UnsupportedLookAndFeelException{
		int res = JOptionPane.showConfirmDialog(null,"Do you want to use the XP L&F?","laffo",JOptionPane.YES_NO_OPTION);
		if (res == JOptionPane.YES_OPTION)
			UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
		new TestFrame().setVisible(true);
	}
	
	public class MyModel extends AbstractTableModel {

		public int getRowCount() {
			return 10;
		}

		public int getColumnCount() {
			return 10;
		}

		public Object getValueAt(int rowIndex, int columnIndex) {
			return ""+rowIndex + " X " + columnIndex;
		}
		
	}
	
	public class MyJTable extends JTable {
		/**
		 *
		 */
		private static final long serialVersionUID = -233098459210523146L;

		public MyJTable(TableModel model){
			super(model);
		}
		
		public void doSomething(){
			System.out.println("HEHE");
		}
	}
	
	
	private JPanel jContentPane = null;
	private JScrollPane jScrollPane = null;
	private JTable table1 = null;
	private JScrollPane jScrollPane1 = null;
	private JTable table2 = null;
	/**
	 * This is the default constructor
	 */
	public TestFrame() {
		super();
		initialize();
		int res = JOptionPane.showConfirmDialog(null,"Do you want to call updateUI() on the tables ?","laffo",JOptionPane.YES_NO_OPTION);
		if (res == JOptionPane.YES_OPTION){
			table2.updateUI();
			table1.updateUI();
		}
	}

	/**
	 * This method initializes this
	 *
	 * @return void
	 */
	private void initialize() {
		this.setSize(753, 658);
		this.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
		this.setContentPane(getJContentPane());
		this.setTitle("JFrame");
	}

	/**
	 * This method initializes jContentPane
	 *
	 * @return javax.swing.JPanel
	 */
	private JPanel getJContentPane() {
		if (jContentPane == null) {
			jContentPane = new JPanel();
			jContentPane.setLayout(null);
			jContentPane.add(getJScrollPane(), null);
			jContentPane.add(getJScrollPane1(), null);
		}
		return jContentPane;
	}

	/**
	 * This method initializes jScrollPane
	 *
	 * @return javax.swing.JScrollPane
	 */
	private JScrollPane getJScrollPane() {
		if (jScrollPane == null) {
			jScrollPane = new JScrollPane();
			jScrollPane.setBounds(new java.awt.Rectangle(358,0,387,618));
			jScrollPane.setViewportView(getTable1());
		}
		return jScrollPane;
	}

	/**
	 * This method initializes table1
	 *
	 * @return javax.swing.JTable
	 */
	private JTable getTable1() {
		if (table1 == null) {
			table1 = new JTable(new MyModel());
		}
		return table1;
	}

	/**
	 * This method initializes jScrollPane1
	 *
	 * @return javax.swing.JScrollPane
	 */
	private JScrollPane getJScrollPane1() {
		if (jScrollPane1 == null) {
			jScrollPane1 = new JScrollPane();
			jScrollPane1.setBounds(new java.awt.Rectangle(0,0,350,618));
			jScrollPane1.setViewportView(getTable2());
		}
		return jScrollPane1;
	}

	/**
	 * This method initializes table2
	 *
	 * @return javax.swing.JTable
	 */
	private JTable getTable2() {
		if (table2 == null) {
			table2 = new MyJTable(new MyModel());
			JTableHeader header = table2.getTableHeader();
			TableCellRenderer render = new DecoratedHeaderRenderer(header.getDefaultRenderer());
			header.setDefaultRenderer(render);
		}
		return table2;
	}
	
	
	 private class DecoratedHeaderRenderer implements TableCellRenderer {

		public DecoratedHeaderRenderer(TableCellRenderer render){
			this.render = render;
		}
		private TableCellRenderer render;
		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
			Component c = render.getTableCellRendererComponent(table,value,isSelected,hasFocus,row,column);
			return c;
		}
		 
	 }

}  //  @jve:decl-index=0:visual-constraint="10,10"

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
- Do not use custom table header renderers while working with the XP L&F.

Comments
http://mail.openjdk.java.net/pipermail/swing-dev/2017-September/007729.html
14-09-2017

The general bug was fixed in https://bugs.openjdk.java.net/browse/JDK-8046559 But one additional check to the XPDefaultRenderer.paint() should be added.
14-09-2017

EVALUATION Renderer is not something one is supposed to hold on to. (Guess the same goes for all the UIResources). Instead of wrapping the renderer which happened to be the default one at the time of the call we want to wrap current default renderer dynamically. One way to do this for tables is to override TableColumn.getHeaderRenderer to dynamically wrap current JTableHeader's default renderer. [code] public class MyJTable extends JTable { private WeakReference<TableCellRenderer> wrappedHeaderRendererRef = null; private TableCellRenderer wrapperHeaderRenderer = null; private class MyTableColumn extends TableColumn { MyTableColumn(int modelIndex) { super(modelIndex); } @Override public TableCellRenderer getHeaderRenderer() { TableCellRenderer defaultHeaderRenderer = MyJTable.this.getTableHeader().getDefaultRenderer(); if (wrappedHeaderRendererRef == null || wrappedHeaderRendererRef.get() != defaultHeaderRenderer) { wrappedHeaderRendererRef = new WeakReference<TableCellRenderer>(defaultHeaderRenderer); wrapperHeaderRenderer = new DecoratedHeaderRenderer(defaultHeaderRenderer); } return wrapperHeaderRenderer; } } @Override public void createDefaultColumnsFromModel() { TableModel m = getModel(); if (m != null) { // Remove any current columns TableColumnModel cm = getColumnModel(); while (cm.getColumnCount() > 0) { cm.removeColumn(cm.getColumn(0)); } // Create new columns from the data model info for (int i = 0; i < m.getColumnCount(); i++) { TableColumn newColumn = new MyTableColumn(i); addColumn(newColumn); } } } [/code]
05-10-2007

EVALUATION See also 6516888 (closed as a duplicate) that shows another manifestation of this type of problem.
26-01-2007

EVALUATION The potential for this problem has been around for some time. The one recent difference is that JTable.updateUI() now calls JTableHeader.updateUI(). There's two ways I can think of to fix this. The simple one would be to simply change the Windows (and Synth - same problem) renderers to behave like the default if they get into a circumstance where they don't have the information needed to do their job. The second way would be to provide a mechanism to allow Windows (and Synth) to plugin (and re-plugin) any custom handling needed to a DefaultTableCellHeaderRenderer. When the UI is uninstalled, it would remove the plugin from the DefaultTableCellHeaderRenderer, turning it back into a default (and safe to wrap) renderer. The trick will be in figuring out how to re-install the plugin when a new UI is installed. This approach will allow a wrapped renderer to be flexible to changes in the UI. I hope to try this latter approach for dolphin. Note to self: The "trick" may involve weak references and client properties.
31-07-2006

EVALUATION The root cause here is that the example code is replacing the default table header renderer with a custom one that wraps the original. The default table header for Windows, however, references the WindowsTableHeaderUI. When updateUI is called, the WindowTableHeaderUI is uninstalled, and becomes invalid. Unfortunately, the custom renderer is still wrapping the original renderer which has a reference to the, now invalid, WindowsTableHeaderUI. Since the plaf renderer needs a valid WindowsTableHeaderUI to do its work you'll get an NPE. I don't see a simple fix to this one. Is this type of custom renderer a valid way to do wrapping/customization?
13-06-2006

EVALUATION This should be fixed at the same time as 6346886 since they seem to be related, even if they do not have the exact same underlying cause. Since this throws an NPE and can cause a program to crash it should be fixed for Mustang and possibly backported when we do the other Tiger updates for Vista support.
08-06-2006