Node Serialization in NetBeans Platform 7.0

Node serialization makes sense when you're not interested in the data (since that should be serialized to a database), but in the state of the application. For example, when the application restarts, you want the last selected node to automatically be selected again. That's not the kind of information you'll want to store in a database, hence node serialization is not about data serialization but about application state serialization.

I've written about this topic in October 2008, here and here, but want to show how to do this again, using NetBeans Platform 7.0. Somewhere I remember reading that this can't be done anymore and that's typically the best motivation for me, i.e., to prove that it can be done after all.

Anyway, in a standard POJO/Node/BeanTreeView scenario, do the following:

  1. Remove the "@ConvertAsProperties" annotation at the top of the class, which you'll find there if you used the Window Component wizard. We're not going to use property-file based serialization, but plain old java.io.Serializable  instead.

  2. In the TopComponent, assuming it is named "UserExplorerTopComponent", typically at the end of the file, add the following:
    @Override
    public Object writeReplace() {
        //We want to work with one selected item only
        //and thanks to BeanTreeView.setSelectionMode,
        //only one node can be selected anyway:
        Handle handle = NodeOp.toHandles(em.getSelectedNodes())[0];
        return new ResolvableHelper(handle);
    }
    
    public final static class ResolvableHelper implements Serializable {
        private static final long serialVersionUID = 1L;
        public Handle selectedHandle;
        private ResolvableHelper(Handle selectedHandle) {
            this.selectedHandle = selectedHandle;
        }
        public Object readResolve() {
            WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
                @Override
                public void run() {
                    try {
                        //Get the TopComponent:
                        UserExplorerTopComponent tc = (UserExplorerTopComponent) WindowManager.getDefault().findTopComponent("UserExplorerTopComponent");
                        //Get the display text to search for:
                        String selectedDisplayName = selectedHandle.getNode().getDisplayName();
                        //Get the root, which is the parent of the node we want:
                        Node root = tc.getExplorerManager().getRootContext();
                        //Find the node, by passing in the root with the display text:
                        Node selectedNode = NodeOp.findPath(root, new String[]{selectedDisplayName});
                        //Set the explorer manager's selected node:
                        tc.getExplorerManager().setSelectedNodes(new Node[]{selectedNode});
                    } catch (PropertyVetoException ex) {
                        Exceptions.printStackTrace(ex);
                    } catch (IOException ex) {
                        Exceptions.printStackTrace(ex);
                    }
                }
            });
            return null;
        }
    }

  3. Assuming you have a node named "UserNode" for a type named "User" containing a property named "type", add the bits in bold below to your "UserNode":
    public class UserNode extends AbstractNode implements Serializable {
    
        static final long serialVersionUID = 1L;
    
        public UserNode(User key) {
            super(Children.LEAF);
            setName(key.getType());
        }
    
        @Override
        public Handle getHandle() {
            return new CustomHandle(this, getName());
        }
    
        public class CustomHandle implements Node.Handle {
            static final long serialVersionUID = 1L;
            private AbstractNode node = null;
            private final String searchString;
            public CustomHandle(AbstractNode node, String searchString) {
                this.node = node;
                this.searchString = searchString;
            }
            @Override
            public Node getNode() {
                node.setName(searchString);
                return node;
            }
        }
        
    }

  4. Run the application and select one of the user nodes. Close the application. Start it up again. The user node is not automatically selected, in fact, the window does not open, and you will see this in the output:
    Caused: java.io.InvalidClassException: org.serialization.sample.UserNode; no valid constructor

    Read this article and then you'll understand the need for this class:

    public class BaseNode extends AbstractNode {
    
        public BaseNode() {
            super(Children.LEAF);
        }
    
        public BaseNode(Children kids) {
            super(kids);
        }
        
        public BaseNode(Children kids, Lookup lkp) {
            super(kids, lkp);
        }
        
    }

    Now, instead of extending AbstractNode in your UserNode, extend BaseNode. Then the first non-serializable superclass of the UserNode has an explicitly declared no-args constructor,

Do the same as the above for each node in the hierarchy that needs to be serialized. If you have multiple nodes needing serialization, you can share the "CustomHandle" inner class above between all the other nodes, while all the other nodes will also need to extend BaseNode (or provide their own non-serializable super class that explicitly declares a no-args constructor).

Now, when I run the application, I select a node, then I close the application, restart it, and the previously selected node is automatically selected when the application has restarted.

Comments:

You probably do not need a writeReplace for the TC and all the work with saving the selection state. ExplorerManager is itself serializable, so try just overriding read/writeExternal to call super and then load/store the ExplorerManager field.

Making a Node serializable is not a good idea. There is a bunch of stuff in there like the Lookup and the Children that you probably do not want to persist. In this case, UserNode.getHandle should just return a class with a single field corresponding to key.getType() (or simply key, if User is serializable); the handle's getNode should make a new UserNode based on that key.

Posted by Jesse Glick on June 27, 2011 at 03:18 AM PDT #

Not sure why ResolvableHelper - readResolve is never called..even after following your blog in 7.1. All the connected blog entries seems to be non-existing URLs

Persisting the nodes of a TopComponent seems to be not so well explained in any forums..

//@ConvertAsProperties(dtd = "-//test//UserTop//EN", autostore = false)
public final class UserTopComponent extends TopComponent implements ExplorerManager.Provider, Serializable {

}

Posted by guest on May 08, 2012 at 06:00 AM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
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
« April 2014
SunMonTueWedThuFriSat
  
12
13
14
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today