Friday Oct 03, 2008

Serializing Nodes

By default, the NetBeans window system restores the application's customized layout, i.e., window position and size, when the application restarts (assuming the user directory hasn't been deleted). For example, even though the Projects window in the IDE is in the explorer mode, the user can move it to the properties mode. Then, when the application shuts down, the last position (and size) of the window is serialized (i.e., stored) on disk, in the user directory. When the application restarts, these settings are then loaded, so that the user's preferences are restored.

That's the default situation. I.e., only the window layout is serialized by default. Potentially, you'd also like to serialize the data in a window. That's been discussed before (here). In addition, you can use the NbPreferences class (as discussed here) or the JDK's Preferences class, if that's what you prefer. There are several different ways of doing the same thing. The advantages of one over the other depends on your scenario, as well as your personal taste.

Another further step you can take is to serialize the selected node in an explorer view. Here, for example, is what I see after restarting my application, i.e., one of the nodes in the explorer view was already selected when the application started:

And that is the case because that node was the last one I had selected before closing the application. That could be handy because it extends the amount of custom information you can restore to your user upon restart.

Providing this scenario in your application is not really trivial, but less complex than it could be. Start by reading "Serialization and traversal" in the Javadoc. There you'll find that you need Node.getHandle when writing (using writeReplace in the TopComponent) and Node.Handle.getNode when reading (using readResolve in the TopComponent) the serialized node.

Below is the most important section of code, within the TopComponent. In writeReplace I get the selected nodes from the Explorer Manager. By the way, I've limited the number of nodes the user can select, when I defined the BeanTreeView:

beanTreeView.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);

Potentially, you can also serialize ALL the selected nodes. I've managed to do that, but having them automatically selected in the hierarchy after retrieving them at startup has been something I've been unable to do. So, in this scenario, I'm assuming the user can only make a single selection. So writeReplace below turns the Nodes into Handles, which are references to the nodes for serialization purposes. The utility class org.openide.nodes.NodeOp is crucial, extremely handy for converting to/from handles and putting nodes back into the tree when reading the settings back into the application. Then the handles are passed to the ResolvableHelper class, which implements Serializable, thus automatically serializing the received handles (i.e., storing them on disk). This happens every time the user closes the application.

@Override
public Object writeReplace() {
    Handle[] selectedHandles = NodeOp.toHandles(em.getSelectedNodes());
    return new ResolvableHelper(selectedHandles);
}

public final static class ResolvableHelper implements Serializable {

    private static final long serialVersionUID = 1L;
    public Handle[] selectedHandles;

    private ResolvableHelper(Handle[] selectedHandles) {
        this.selectedHandles = selectedHandles;
    }

    public Object readResolve() {
        try {
            DemoTopComponent result = DemoTopComponent.getDefault();
            String path = selectedHandles[0].getNode().getDisplayName();
            Node foundNode = NodeOp.findPath(result.getExplorerManager().getRootContext(), new String[]{path});
            if (foundNode != null) {
                try {
                    result.getExplorerManager().setSelectedNodes(new Node[]{foundNode});
                } catch (PropertyVetoException ex) {
                    Exceptions.printStackTrace(ex);
                }
            }
            return result;
        } catch (IOException ex) {
            Exceptions.printStackTrace(ex);
        }
        return null;
    }
}

The second part of the section of code above applies to the point where the application is restarted. It simply gets the first of the serialized nodes and then gets its display name. Then NodeOp.findPath is used to put it back in the tree. Then the ExplorerManager.setSelectedNodes selects that node in the Explorer Manager.

Two other things are important—you need to specify on your Nodes that you'd like them to create handles. Secondly, it is safe to always define a name (even if you don't need it) on your Nodes, since some other parts of the API assume that your Nodes are named. For example, this is how the children shown above are created:

@Override
protected Node createNodeForKey(String key) {
    AbstractNode result = new AbstractNode(Children.LEAF, Lookups.fixed());
    result.setDisplayName(key);
    result.setName(key);
    result.setIconBaseWithExtension("/org/nb/properties/icon.png");
    result.getHandle();
    return result;
}

So above the AbstractNode.getHandle and the AbstractNode.setName are really important to provide in this scenario. For the rest, I think pretty much everything related to this scenario is discussed in this blog entry.

About

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.

Search

Archives
« October 2008 »
SunMonTueWedThuFriSat
   
2
7
8
11
12
20
24
25
27
31
 
       
Today