-
Bug
-
Resolution: Unresolved
-
P4
-
None
-
5.0u8
-
Cause Known
-
x86
-
linux_redhat_9.0
There is a leak of TreePaths and their associated object in very specific (but not uncommon) scenario. The necessary conditions are:
1. 2 or more nodes to be removed
2. They have children
3. They are expanded and both the nodes and their children are selected.
If the model signals (single) removal event, all but one of the nodes will leak through the expandedState map.
Here is the mechanism:
1. remove event
2. the removed nodes (and their descendants) are removed from the expandedState map.
3. the removed nodes are removed from selection one by one
4. as the first one is removed, selection change is fired with new selection covering
the second node and its children.
5. during selecting the children, ensureVsible is called, which also "expands" parent
node (the second original node, already removed) by placing it back to the expandedState
map -> the leak
This happens e.g. if you expand few projects in NetBeans, select them using keyboard (with their subnodes) and close them using file->close %d projects.
I'll attach simple test case showin the leak on pure JTree+DefaultModel.
I am adding the test case to the Description field at the request of the jdk.dev.java.net community:
/*
* Tree.java
*
* Created on 19. záÅà 2006, 15:55
*/
package treenodeleak;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeModel;
/**
@author nenik
*/
public class Tree extends javax.swing.JFrame {
DefaultTreeModel model;
DefaultMutableTreeNode root;
DefaultMutableTreeNode[] level1;
/** Creates new form Tree */
public Tree() {
initComponents();
root = new DefaultMutableTreeNode("Root");
level1 = new DefaultMutableTreeNode[] {
new DefaultMutableTreeNode("ch1", true),
new DefaultMutableTreeNode("ch2", true),
new DefaultMutableTreeNode("ch3", true)
};
for (int i=0; i<level1.length; i++) {
level1[i].add(new DefaultMutableTreeNode("A", false));
level1[i].add(new DefaultMutableTreeNode("B", false));
root.add(level1[i]);
}
model = new DefaultTreeModel(root, true);
tree.setModel(model);
tree.expandRow(3);
tree.expandRow(2);
tree.expandRow(1);
tree.setSelectionInterval(1, 6);
}
/** This method is called from within the constructor to
initialize the form.
WARNING: Do NOT modify this code. The content of this method is
always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
private void initComponents() {
scroll = new javax.swing.JScrollPane();
tree = new javax.swing.JTree();
button = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
scroll.setViewportView(tree);
getContentPane().add(scroll, java.awt.BorderLayout.CENTER);
button.setText("Remove Nodes");
button.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
buttonActionPerformed(evt);
}
});
getContentPane().add(button, java.awt.BorderLayout.SOUTH);
pack();
}// </editor-fold>//GEN-END:initComponents
private void buttonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonActionPerformed
int[] childIndex = new int[] {0, 1};
Object[] removedArray = new Object[] { level1[0], level1[1] };
root.remove(0);
root.remove(1);
model.nodesWereRemoved(root, childIndex, removedArray);
button.setEnabled(false);
level1 = null;
}//GEN-LAST:event_buttonActionPerformed
/**
@param args the command line arguments
*/
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new Tree().setVisible(true);
}
});
}
// Variables declaration - do not modify//GEN-BEGIN:variables
javax.swing.JButton button;
javax.swing.JScrollPane scroll;
javax.swing.JTree tree;
// End of variables declaration//GEN-END:variables
}
1. 2 or more nodes to be removed
2. They have children
3. They are expanded and both the nodes and their children are selected.
If the model signals (single) removal event, all but one of the nodes will leak through the expandedState map.
Here is the mechanism:
1. remove event
2. the removed nodes (and their descendants) are removed from the expandedState map.
3. the removed nodes are removed from selection one by one
4. as the first one is removed, selection change is fired with new selection covering
the second node and its children.
5. during selecting the children, ensureVsible is called, which also "expands" parent
node (the second original node, already removed) by placing it back to the expandedState
map -> the leak
This happens e.g. if you expand few projects in NetBeans, select them using keyboard (with their subnodes) and close them using file->close %d projects.
I'll attach simple test case showin the leak on pure JTree+DefaultModel.
I am adding the test case to the Description field at the request of the jdk.dev.java.net community:
/*
* Tree.java
*
* Created on 19. záÅà 2006, 15:55
*/
package treenodeleak;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeModel;
/**
@author nenik
*/
public class Tree extends javax.swing.JFrame {
DefaultTreeModel model;
DefaultMutableTreeNode root;
DefaultMutableTreeNode[] level1;
/** Creates new form Tree */
public Tree() {
initComponents();
root = new DefaultMutableTreeNode("Root");
level1 = new DefaultMutableTreeNode[] {
new DefaultMutableTreeNode("ch1", true),
new DefaultMutableTreeNode("ch2", true),
new DefaultMutableTreeNode("ch3", true)
};
for (int i=0; i<level1.length; i++) {
level1[i].add(new DefaultMutableTreeNode("A", false));
level1[i].add(new DefaultMutableTreeNode("B", false));
root.add(level1[i]);
}
model = new DefaultTreeModel(root, true);
tree.setModel(model);
tree.expandRow(3);
tree.expandRow(2);
tree.expandRow(1);
tree.setSelectionInterval(1, 6);
}
/** This method is called from within the constructor to
initialize the form.
WARNING: Do NOT modify this code. The content of this method is
always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
private void initComponents() {
scroll = new javax.swing.JScrollPane();
tree = new javax.swing.JTree();
button = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
scroll.setViewportView(tree);
getContentPane().add(scroll, java.awt.BorderLayout.CENTER);
button.setText("Remove Nodes");
button.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
buttonActionPerformed(evt);
}
});
getContentPane().add(button, java.awt.BorderLayout.SOUTH);
pack();
}// </editor-fold>//GEN-END:initComponents
private void buttonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonActionPerformed
int[] childIndex = new int[] {0, 1};
Object[] removedArray = new Object[] { level1[0], level1[1] };
root.remove(0);
root.remove(1);
model.nodesWereRemoved(root, childIndex, removedArray);
button.setEnabled(false);
level1 = null;
}//GEN-LAST:event_buttonActionPerformed
/**
@param args the command line arguments
*/
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new Tree().setVisible(true);
}
});
}
// Variables declaration - do not modify//GEN-BEGIN:variables
javax.swing.JButton button;
javax.swing.JScrollPane scroll;
javax.swing.JTree tree;
// End of variables declaration//GEN-END:variables
}