JTree disappears all by developing the node. Maybe a problem from hashCode().

Hi, I have a JTree with custom template. I test with the following structure:

+ root
- + category
- + file

When the application starts, root and the category are visible, the file is hidden. When I try to expand the class node, the JTree set disappears. A click away in the place previously occupied by the JTree result in a java.lang.NullPointerException at javax.swing.plaf.basic.BasicTreeUI$ Handler.handleSelection ().

The nodes that I use represent a simple tree, where each node has its value and a LinkedList of his children. The model simply translated this to the 'language' of TreeModel.

It is essential for me to properly define equals() and hashCode() for nodes. I discovered that if hashCode() of the nodes does not rely on a list of the children, everything works fine. But I want to rely on the children!

I use 1.6.0_22 JRE on Win7.

Where should I do something wrong, please?

Thanks a lot for any help ;)

PS: I managed to get around that - in a TreeSelectionListener I call tree.setModel (tree.getModel ()) and then I restore the nodes selected and expanded. It works, but of course I'd rather have a cleaner solution. In addition, this seems like a bug in JRE for me.

Included NBS:
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 + "]";
        }
    }

}

peci1 wrote:
If you think that it is not possible to have directly implemented in nodes to the evolution of codes in a JTree.

If not know because the javadoc, said he. Once again:

In the same way JTree and its associated classes place TreePaths in maps. Thus, if a node is requested twice, return values must be equal (using the equals method) and have the same hash code.

I hope that the treeStructureChanged event would help here...

It might work if you launch a node struct event has changed because you have changed the hash code of the real parent when you changed the children of it. But catch all these traps (restore expand status, etc.) seems too much work just to avoid a node wrapper class.

Tags: Java

Similar Questions

Maybe you are looking for