-
Bug
-
Resolution: Fixed
-
P4
-
5.0
-
b53
-
x86
-
windows_xp
A DESCRIPTION OF THE PROBLEM :
The documentation for the TreeNode class doesn't mention that an implementation must override the Object#hashCode() method.
However, the JTree#setSelectionPath(TreePath) method, and maybe a few others, depend on this being done.
Attaching the nodes for Document Tree
/**
* A TreeNode representing a file in a filesystem.
* Each instance is associated with a File object, accessed with {@link getFile()},
* and keeps a reference to the root of the file structure, used to limit paths to
* @author Olivier Pernet
*/
public class FileTreeNode
implements TreeNode {
private final File file;
private final File root;
public FileTreeNode(File file, File root) {
if (file == null)
throw new IllegalArgumentException("The file represented by this tree node shall not be null.");
if (root == null)
throw new IllegalArgumentException("The root of the tree structure shall not be null.");
this.file = file;
this.root = root;
}
/**
* @return The children of this node as an Enumeration.
*/
public Enumeration<FileTreeNode> children() {
File[] children = file.listFiles();
if (children != null) {
ArrayList<FileTreeNode> list = new ArrayList<FileTreeNode>();
for (File f : children)
list.add(new FileTreeNode(f, root));
return Collections.enumeration(list);
} else {
return Collections.enumeration(new ArrayList<FileTreeNode>());
}
}
/**
* Returns whether this node allows having children.
* Here, only nodes associated with a directory allow children.
* Thus, this is equivalent to isLeaf(). But other TreeNode implementations
* might differ on this point, and the constructor {@link JTree(TreeNode, boolean)}
* allows to account for this difference.
*/
public boolean getAllowsChildren() {
return file.isDirectory();
}
/**
* @return The node at the specified index among this node's children.
*/
public TreeNode getChildAt(int childIndex) {
return new FileTreeNode(file.listFiles()[childIndex], root);
}
/**
* @return The number of nodes under this node.
*/
public int getChildCount() {
String[] list = file.list();
return list == null ? 0 : list.length;
}
/**
* Returns the index of one of this node's children.
* Returns -1 if the node is not found.
* @return The index of node in this node's children.
*/
public int getIndex(TreeNode node) {
if (node instanceof FileTreeNode) {
FileTreeNode n = (FileTreeNode) node;
File [] children = file.listFiles();
int i = 0;
while (i < children.length) {
if (children[i].equals(n.file))
return i;
i++;
}
}
return -1;
}
/**
* Returns the parent of this node.
* @return The parent of this node, or null if this is the root node.
*/
public TreeNode getParent() {
if (file.equals(root)) return null;
else {
assert file.getParentFile() != null;
return new FileTreeNode(file.getParentFile(), root);
}
}
/**
* Returns whether this node is a leaf node.
* Here, a node is a leaf node if it is not a directory.
* @return True if and only if this node is a leaf node.
*/
public boolean isLeaf() {
return ! file.isDirectory();
}
/**
* Returns a String representation of this node, used as the node's label
* in JTrees. Here, the label is the associated file's name.
* @return A String representation of this node.
*/
public String toString() {
return file.getName();
}
/**
* Returns this node's associated file. This method is not part
* of the TreeNode interface.
* @return This node's associated file.
*/
public File getFile() {
assert file != null;
return file;
}
/**
* Returns the TreePath used to designate this node in the
* tree structure.
*/
public FileTreeNode[] getPath() {
ArrayList<FileTreeNode> nodes = new ArrayList<FileTreeNode>();
FileTreeNode node = this;
while (node != null) {
// Adds the new node at the beginning of the list.
nodes.add(0, node);
node = (FileTreeNode) node.getParent();
}
// The "new FileTreeNode[0]" parameter is only used to
// tell the toArray method which runtime type the returned
// array should have.
return nodes.toArray(new FileTreeNode[0]);
}
public TreePath getTreePath() {
return new TreePath(getPath());
}
public boolean equals(Object other) {
return (other instanceof FileTreeNode
&& ((FileTreeNode) other).file.equals(file));
}
}
And here is my test method, with the working code using DefaultMutableTreeNodes commented out :
public static void test1() {
final File rootDir = new File("/Users/taton/Dev-Raffin/CCSL");
final File subDir = new File("/Users/taton/Dev-Raffin/CCSL/depouil-D3/ARRNGD");
JFrame f = new JFrame();
/*
DefaultMutableTreeNode root = new DefaultMutableTreeNode(rootDir);
init(root, rootDir);
*/
FileTreeNode root = new FileTreeNode(rootDir, rootDir);
JTree tree = new JTree(root);
JScrollPane scroll = new JScrollPane(tree);
f.getContentPane().add(scroll);
f.setBounds(0, 0, 250, 600);
f.setVisible(true);
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
JOptionPane.showMessageDialog(f, "Path will be selected");
TreePath path = new FileTreeNode(subDir, rootDir).getTreePath();
/*
TreePath path = new TreePath(
find(root, subDir).getPath()
);
*/
tree.setSelectionPath(path);
}
private static void init(DefaultMutableTreeNode top, File root) {
if (root.listFiles() != null) {
for (File f : root.listFiles()) {
DefaultMutableTreeNode node = new DefaultMutableTreeNode(f);
top.add(node);
if (f.isDirectory()) init(node, f);
}
}
}
private static DefaultMutableTreeNode find(DefaultMutableTreeNode root, Object object) {
if (root != null) {
if (root.getUserObject().equals(object)) return root;
else return find(root.getNextNode(), object);
} else return null;
}
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
In the interface description :
Implementations of this interface should override {@link Object#hashCode} in order for some of the JTree methods to work.
Or something similar.
ACTUAL -
Nothing regarding hashCode().
URL OF FAULTY DOCUMENTATION :
http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/tree/TreeNode.html
The documentation for the TreeNode class doesn't mention that an implementation must override the Object#hashCode() method.
However, the JTree#setSelectionPath(TreePath) method, and maybe a few others, depend on this being done.
Attaching the nodes for Document Tree
/**
* A TreeNode representing a file in a filesystem.
* Each instance is associated with a File object, accessed with {@link getFile()},
* and keeps a reference to the root of the file structure, used to limit paths to
* @author Olivier Pernet
*/
public class FileTreeNode
implements TreeNode {
private final File file;
private final File root;
public FileTreeNode(File file, File root) {
if (file == null)
throw new IllegalArgumentException("The file represented by this tree node shall not be null.");
if (root == null)
throw new IllegalArgumentException("The root of the tree structure shall not be null.");
this.file = file;
this.root = root;
}
/**
* @return The children of this node as an Enumeration.
*/
public Enumeration<FileTreeNode> children() {
File[] children = file.listFiles();
if (children != null) {
ArrayList<FileTreeNode> list = new ArrayList<FileTreeNode>();
for (File f : children)
list.add(new FileTreeNode(f, root));
return Collections.enumeration(list);
} else {
return Collections.enumeration(new ArrayList<FileTreeNode>());
}
}
/**
* Returns whether this node allows having children.
* Here, only nodes associated with a directory allow children.
* Thus, this is equivalent to isLeaf(). But other TreeNode implementations
* might differ on this point, and the constructor {@link JTree(TreeNode, boolean)}
* allows to account for this difference.
*/
public boolean getAllowsChildren() {
return file.isDirectory();
}
/**
* @return The node at the specified index among this node's children.
*/
public TreeNode getChildAt(int childIndex) {
return new FileTreeNode(file.listFiles()[childIndex], root);
}
/**
* @return The number of nodes under this node.
*/
public int getChildCount() {
String[] list = file.list();
return list == null ? 0 : list.length;
}
/**
* Returns the index of one of this node's children.
* Returns -1 if the node is not found.
* @return The index of node in this node's children.
*/
public int getIndex(TreeNode node) {
if (node instanceof FileTreeNode) {
FileTreeNode n = (FileTreeNode) node;
File [] children = file.listFiles();
int i = 0;
while (i < children.length) {
if (children[i].equals(n.file))
return i;
i++;
}
}
return -1;
}
/**
* Returns the parent of this node.
* @return The parent of this node, or null if this is the root node.
*/
public TreeNode getParent() {
if (file.equals(root)) return null;
else {
assert file.getParentFile() != null;
return new FileTreeNode(file.getParentFile(), root);
}
}
/**
* Returns whether this node is a leaf node.
* Here, a node is a leaf node if it is not a directory.
* @return True if and only if this node is a leaf node.
*/
public boolean isLeaf() {
return ! file.isDirectory();
}
/**
* Returns a String representation of this node, used as the node's label
* in JTrees. Here, the label is the associated file's name.
* @return A String representation of this node.
*/
public String toString() {
return file.getName();
}
/**
* Returns this node's associated file. This method is not part
* of the TreeNode interface.
* @return This node's associated file.
*/
public File getFile() {
assert file != null;
return file;
}
/**
* Returns the TreePath used to designate this node in the
* tree structure.
*/
public FileTreeNode[] getPath() {
ArrayList<FileTreeNode> nodes = new ArrayList<FileTreeNode>();
FileTreeNode node = this;
while (node != null) {
// Adds the new node at the beginning of the list.
nodes.add(0, node);
node = (FileTreeNode) node.getParent();
}
// The "new FileTreeNode[0]" parameter is only used to
// tell the toArray method which runtime type the returned
// array should have.
return nodes.toArray(new FileTreeNode[0]);
}
public TreePath getTreePath() {
return new TreePath(getPath());
}
public boolean equals(Object other) {
return (other instanceof FileTreeNode
&& ((FileTreeNode) other).file.equals(file));
}
}
And here is my test method, with the working code using DefaultMutableTreeNodes commented out :
public static void test1() {
final File rootDir = new File("/Users/taton/Dev-Raffin/CCSL");
final File subDir = new File("/Users/taton/Dev-Raffin/CCSL/depouil-D3/ARRNGD");
JFrame f = new JFrame();
/*
DefaultMutableTreeNode root = new DefaultMutableTreeNode(rootDir);
init(root, rootDir);
*/
FileTreeNode root = new FileTreeNode(rootDir, rootDir);
JTree tree = new JTree(root);
JScrollPane scroll = new JScrollPane(tree);
f.getContentPane().add(scroll);
f.setBounds(0, 0, 250, 600);
f.setVisible(true);
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
JOptionPane.showMessageDialog(f, "Path will be selected");
TreePath path = new FileTreeNode(subDir, rootDir).getTreePath();
/*
TreePath path = new TreePath(
find(root, subDir).getPath()
);
*/
tree.setSelectionPath(path);
}
private static void init(DefaultMutableTreeNode top, File root) {
if (root.listFiles() != null) {
for (File f : root.listFiles()) {
DefaultMutableTreeNode node = new DefaultMutableTreeNode(f);
top.add(node);
if (f.isDirectory()) init(node, f);
}
}
}
private static DefaultMutableTreeNode find(DefaultMutableTreeNode root, Object object) {
if (root != null) {
if (root.getUserObject().equals(object)) return root;
else return find(root.getNextNode(), object);
} else return null;
}
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
In the interface description :
Implementations of this interface should override {@link Object#hashCode} in order for some of the JTree methods to work.
Or something similar.
ACTUAL -
Nothing regarding hashCode().
URL OF FAULTY DOCUMENTATION :
http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/tree/TreeNode.html