Skip to Main Content

Java SE (Java Platform, Standard Edition)

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

JTree disappears while expanding node. Possibly a hashCode() issue.

815773Nov 17 2010 — edited Nov 18 2010
Hi, I have a JTree with custom model. I test it with the following structure:

+root
----+category
---------+file

When the app starts, root and category are visible, file is hidden. When I try to expand the category node, the whole JTree disappears. Any further clicks in the place previously occupied by the JTree result in a java.lang.NullPointerException at javax.swing.plaf.basic.BasicTreeUI$Handler.handleSelection().

The nodes I use represent a simple tree, where every node has its value and a LinkedList of its children. The model simply translates this to the "language" of TreeModel.

It is essential for me to correctly define equals() and hashCode() for the nodes. I found out that if the nodes' hashCode() doesn't rely on its list of children, everything works well. But I want it to rely on the children!

I use JRE 1.6.0_22 on Win7.

Where do I do something wrong, please?

Thanks very much for any help ;)

PS: I managed to workaround this - in a TreeSelectionListener I call tree.setModel(tree.getModel()) and then I restore the expanded and selected nodes. This works, but of course I'd rather have a cleaner solution. Moreover, this seems as a bug in JRE for me.

SSCCE included:
import java.awt.Dimension;
import java.util.LinkedList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

public class JTreeTest
{
    public static void main(String[] args)
    {
        // setup the tree
        final File list = new File();
        list.setValue("root");

        File category = new File();
        list.getFiles().add(category);
        category.setValue("category");

        File file = new File();
        category.getFiles().add(file);
        file.setValue("file");

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run()
            {
                JFrame frame = new JFrame();

                // setup the JTree
                JTree tree = new JTree(new MyModel(list));
                frame.add(tree);

                frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
                frame.setSize(new Dimension(200, 100));
                frame.setVisible(true);
            }
        });
    }

    // this model is very very basic and I don't see much space here for errors...
    static class MyModel implements TreeModel
    {
        protected File                    root;

        protected List<TreeModelListener> treeModelListeners = new LinkedList<TreeModelListener>();

        public MyModel(File root)
        {
            this.root = root;
        }

        @Override
        public File getRoot()
        {
            return root;
        }

        @Override
        public Object getChild(Object parent, int index)
        {
            if (index < 0)
                return null;

            if (!(parent instanceof File))
                return null;

            File fParent = (File) parent;

            try {
                return fParent.getFiles().get(index);
            } catch (ArrayIndexOutOfBoundsException e) {
                return null;
            }
        }

        @Override
        public int getChildCount(Object parent)
        {
            if (!(parent instanceof File))
                return 0;

            File fParent = (File) parent;

            return fParent.getFiles().size();
        }

        @Override
        public boolean isLeaf(Object node)
        {
            return getChildCount(node) == 0;
        }

        @Override
        public void valueForPathChanged(TreePath path, Object newValue)
        {
            if (!(newValue instanceof File))
                return;

            if (path.getParentPath() == null) {
                fireTreeNodesChanged(new TreeModelEvent(this, path, null, null));
            } else {
                fireTreeNodesChanged(new TreeModelEvent(this, path.getParentPath(), new int[] { getIndexOfChild(path
                        .getParentPath().getLastPathComponent(), newValue) }, new Object[] { newValue }));
            }
        }

        @Override
        public int getIndexOfChild(Object parent, Object child)
        {
            if (parent == null || child == null)
                return -1;

            if (!(parent instanceof File) || !(child instanceof File))
                return -1;

            File fParent = (File) parent;

            return fParent.getFiles().indexOf(child);
        }

        @Override
        public void addTreeModelListener(TreeModelListener l)
        {
            treeModelListeners.add(l);
        }

        @Override
        public void removeTreeModelListener(TreeModelListener l)
        {
            treeModelListeners.remove(l);
        }

        public void fireTreeNodesChanged(TreeModelEvent e)
        {
            for (TreeModelListener listener : treeModelListeners) {
                listener.treeNodesChanged(e);
            }
        }
    }

    static class File
    {
        static int fileId = 0;
        int        id;
        List<File> files  = null;
        String     value  = null;

        public File()
        {
            // ensure each item will have a really unique identifier, so no equals() collisions should occur
            id = fileId++;
        }

        public List<File> getFiles()
        {
            if (files == null)
                files = new LinkedList<File>();
            return files;
        }

        public String getValue()
        {
            return value;
        }

        public void setValue(String value)
        {
            this.value = value;
        }

        // generated by Eclipse code helpers
        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((value == null) ? 0 : value.hashCode());
            // ///////HERE IT IS////////
            // if you uncomment the following line, the list will get empty when you expand the second category and
            // any following clicks in any place that should be occupied by an item will result in a
            // NullPointerException
            //
            result = prime * result + ((files == null) ? 0 : files.hashCode());
            //
            result = prime * result + id;
            return result;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            File other = (File) obj;
            if (value == null) {
                if (other.value != null)
                    return false;
            } else if (!value.equals(other.value))
                return false;
            if (files == null) {
                if (other.files != null)
                    return false;
            } else if (!files.equals(other.files))
                return false;
            if (id != other.id)
                return false;
            return true;
        }

        @Override
        public String toString()
        {
            return "File [value=" + value + "]";
        }
    }

}
This post has been answered by walterln on Nov 18 2010
Jump to Answer
Comments
Locked Post
New comments cannot be posted to this locked post.
Post Details
Locked on Dec 16 2010
Added on Nov 17 2010
6 comments
663 views