Node Serialization in NetBeans Platform 7.0
- by Geertjan
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:
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.
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;
}
}
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;
}
}
}
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.