I am writing a NetBeans Platform (7.4) based application and I have created a custom ProjecType and FileType. The ProjectType has a weak file change listener for new files being dropped in the project folder. The problem is that if I open a project, close it and then try to re-open it again, it runs out of memory and either throws a Mutex exception:
Blockquote java.lang.IllegalStateException: Should not acquire Children.MUTEX while holding ProjectManager.mutex
or the IllegalStateException:
Blockquote java.lang.IllegalStateException: at org.openide.nodes.Node.assignTo(Node.java:354) at org.openide.nodes.EntrySupportDefault.justComputeNodes(EntrySupportDefault.java:212) at org.openide.nodes.ChildrenArray.nodes(ChildrenArray.java:88) at org.openide.nodes.EntrySupportDefault.getNodes(EntrySupportDefault.java:130) at org.openide.nodes.EntrySupportDefault.getNodes(EntrySupportDefault.java:172) at org.openide.nodes.Children.getNodes(Children.java:469)
Another interesting thing is that if I close the project and make any small change to the project folder such as moving it to a new location, or even just opening a file within that folder, when I try to reload it it works fine. I have tried completely removing the weak file listener but no success. And I know is not a file change listener issue, because Windows doesn't prevent me from manipulating the files within the project folder. Bellow is my ProjectNodeFactory that creates the child nodes for each recognized file type in the project folder.
public class MyProjectNodeFactory extends ChildFactory<FileObject> implements FileChangeListener {
private final FileObject projectFolder;
public MyProjectNodeFactory(MyProjectNode project) {
this.projectFolder = project.getProjectDirectory();
project.addFileChangeListener(this);
refresh(true);
}
@Override
protected boolean createKeys(List<FileObject> list) {
for (FileObject fileObject : Collections.list(projectFolder.getData(false)))
{
if (fileObject.getExt().equals(Bundle.MY_FILETYPE_EXTENSION())) {
list.add(fileObject);
}
}
return true;
}
@Override
protected Node[] createNodesForKey(FileObject key) {
ArrayList<Node> nodes = new ArrayList<Node>();
// Add data files
try {
if(key.getExt().equals(Bundle.MY_FYLETYPE_EXTENSION()))
{
nodes.add(DataObject.find(key).getNodeDelegate());
}
} catch (DataObjectNotFoundException ex) {
Exceptions.printStackTrace(ex);
}
return (nodes.toArray(new Node[nodes.size()]));
}
Additionally my ProjecType LogicalView and ProjectNode
public class MyProjectType implements Project {
private final FileObject projectDir;
private final ProjectState state;
private FileChangeListener fileChangeListener;
private Lookup lkp;
MyProjectType(FileObject dir, ProjectState state) {
this.projectDir = dir;
this.state = state;
}
@Override
public FileObject getProjectDirectory() {
return projectDir;
}
@Override
public Lookup getLookup() {
if (lkp == null) {
lkp = Lookups.fixed(new Object[]{
// register your features here
this,
new MyProjectInfo(),
new MyProjectLogicalView(this),});
}
return lkp;
}
public void addFileChangeListener(FileChangeListener regularFileChangeListener) {
this.fileChangeListener = (FileChangeListener)WeakListeners.create(FileChangeListener.class, regularFileChangeListener, this.projectDir.getPath());
this.projectDir.addFileChangeListener(fileChangeListener);
}
public void removeFileChangeListeners() {
if(fileChangeListener!=null)
this.projectDir.removeFileChangeListener(fileChangeListener);
}
private final class MyProjectInfo implements ProjectInformation {
@StaticResource()
public static final String PROJECT_ICON = "projectIcon.png";
..................
}
class MyProjectLogicalView implements LogicalViewProvider {
@StaticResource()
public static final String PROJECT_ICON = "projectIcon.png";
private final MyProjectType project;
public MyProjectLogicalView(MyProjectType project) {
this.project = project;
}
@Override
public Node createLogicalView() {
try {
FileObject projectDirectory = project.getProjectDirectory();
DataFolder projectFolder = DataFolder.findFolder(projectDirectory);
Node nodeOfProjectFolder = projectFolder.getNodeDelegate();
return new ProjectNode(nodeOfProjectFolder, project);
} catch (DataObjectNotFoundException donfe) {
Exceptions.printStackTrace(donfe);
return new AbstractNode(Children.LEAF);
}
}
private final class ProjectNode extends FilterNode {
final MyProjectType project;
public ProjectNode(Node node, MyProjectType project)
throws DataObjectNotFoundException {
super(node,
Children.create(new MyProjectNodeFactory(project),true),
new ProxyLookup(
new Lookup[]{
Lookups.singleton(project),
node.getLookup()
}));
this.project = project;
}
@Override
public Action[] getActions(boolean arg0) {
return new Action[]{
CommonProjectActions.newFileAction(),
CommonProjectActions.copyProjectAction(),
CommonProjectActions.deleteProjectAction(),
CommonProjectActions.closeProjectAction()
};
}
...............
}
}
After a lot of googling and debugging I was not able to detect why this memory leak happens, but I did find a solution at the following link which might be useful to other people running into the same issue. Solution to Open/Close/Re-Open a NetBeans Platform ProjectType
The solution is to get a clone of the child nodes instead of just the delegate.
public Node node(FileObject key) {
DataFolder folder = DataFolder.findFolder(key);
return folder.getNodeDelegate().cloneNode();
}
I still do not understand why the getNodeDelegate() is not sufficient, but I assume when the Project is first closed, some references are not released and it runs into issues when reopening. By getting the clone, it creates a whole new parent node without those issues. I hope this helps somebody because I had a hard time finding a solution to it.