Extreme Java When vanilla Java is not enough

12May/080

Custom project file structure in NetBeans – adding folder

In a previous post, I've told that I want to use NetBeans and share projects with developers that still use Eclipse. Since we have a custom requirements, I plan to customize the way NB handle these projects.

As you will notice, every NB project customization will go into the layer file, under "Projects" folder. You will be surprised on how easy it goes.

In this post, my task is to make a "config" folder visible under "Projects" tab of NetBeans. Assuming you already have a NetBeans module project ready, steps are:

  1. Create an utility class with this method:
    public static boolean isMyProject(Project proj) {
      // check if it is a nb project
      if (proj.getProjectDirectory().getFileObject("nbproject") ==
        null) {
        return false;
      }
      // check if our custom folder exists
      return (proj.getProjectDirectory().getFileObject("config") !=
        null);
    }
  2. In your layer.xml, create an entry named "Projects / type-of-your-desired-project / Nodes". You can look into "Important Files / XML Layer / Projects / org-netbeans-modules-xxx-project / Nodes" and right click on it to add a new file - NB will add the "folder" tags automatically;
  3. Inside "Nodes", add a file named "org-mycompany-MyCompanyNodeFactory.instance" (or rename the file if you created it on step 1);
  4. Now, create a class named "org.mycompany.MyCompanyNodeFactory" that implements NodeFactory. This is a pretty small one:
    public class MyNodeFactoryImpl implements NodeFactory {
      public NodeList createNodes(Project proj) {
        if (MyUtils.isMyProject(project) {
          try {
            MyFilesNode nd = new MyFilesNode(proj);
            return NodeFactorySupport.fixedNodeList(nd);
          } catch (DataObjectNotFoundException ex) {
            Exceptions.printStackTrace(ex);
          }
        }
        // if we can't use it, return an empty list
        return NodeFactorySupport.fixedNodeList();
      }
    }
  5. Create the MyFilesNode. It must be a child of Node. You can do anything you want here (just Google it and you will see what I mean). Mine is:
    public class MyFilesNode extends AbstractNode {
      private static Image smallImage =
              Utilities.loadImage("/path-to/icon-8x8.gif"); // NOI18N
     
      public MyFilesNode(Project proj) {
        super(Children.create(new MyChildFactory(proj), true);
      }
      public String getDisplayName() {
        return "My Files";
      }
      // A bonus: this snippet will merge NB folder icon and
      // your 8x8 icon
      @Override
      public Image getIcon(int type) {
        DataFolder root = DataFolder.findFolder(
          Repository.getDefault().getDefaultFileSystem().getRoot());
        Image original = root.getNodeDelegate().getIcon(type);
        return Utilities.mergeImages(original, smallImage, 7, 7);
      }
      @Override
      public Image getOpenedIcon(int type) {
        DataFolder root = DataFolder.findFolder(
          Repository.getDefault().getDefaultFileSystem().getRoot());
        Image original = root.getNodeDelegate().getIcon(type);
        return Utilities.mergeImages(original, smallImage, 7, 7);
      }
    }

    And, of course, the ChildFactory is:

    public class MyChildFactory extends ChildFactory<String> {
      private Project proj;
     
      public TransitFilesChildren(Project proj) {
        this.proj = proj;
      }
     
      /* Since a node is the visual representation of
       * something else, this method builds the list
       * of "something else"s we want to show. If this
       * was a business application, the "key" could be
       * a value object. In this module, the key will
       * be the name of the custom folder.
       */
      @Override
      protected boolean createKeys(List<String> keys) {
        keys.add("ant");    // another folder - why not?
        keys.add("config");
        return true;
      }
     
      @Override
      protected Node createNodeForKey(String key) {
        // 1. Find the FileObject for the folder
        FileObject fo = proj.getProjectDirectory().getFileObject(key);
        // 2. Find the DataFolder for the FileObject
        DataFolder df = DataFolder.findFolder(fo);
        // 3. Create a custom filter
        DataFilter filter = new MyDataFilter();
        // 4. Get DataFolder children (with filter)
        Children children = df.createNodeChildren(filter);
        // 5. Create a proxy lookup
        Lookup lookup = new ProxyLookup(new Lookup[]{
          df.getNodeDelegate().getLookup(), proj.getLookup()
        });
        // 6. Create a filter node
        return new FilterNode(df.getNodeDelegate(), children, lookup);
      }
     
      /**
       * Filter DataObjects that must not be shown (like .svn folders)
       */
      static final class MyDataFilter implements ChangeListener,
        ChangeableDataFilter {
     
        private final ChangeSupport changeSupport =
          new ChangeSupport(this);
     
        public MyDataFilter() {
          VisibilityQuery.getDefault().addChangeListener(this);
        }
        public boolean acceptDataObject(DataObject obj) {
          FileObject fo = obj.getPrimaryFile();
          return VisibilityQuery.getDefault().isVisible(fo);
        }
        public void stateChanged(ChangeEvent e) {
          changeSupport.fireChange();
        }
        public void addChangeListener(ChangeListener listener) {
          changeSupport.addChangeListener(listener);
        }
        public void removeChangeListener(ChangeListener listener) {
          changeSupport.removeChangeListener(listener);
        }
      }
    }
  6. Now, go toproject properties and add the following modules (you can also search for the needed classes):
    1. Datasystems API (ChangeableDataFilter, DataFilter, DataFolder and DataObject);
    2. Filesystem API (FileObject);
    3. General Queries API (VisibilityQuery);
    4. Nodes API (ChildFactory, Children, FilterNode and Node);
    5. Project API (Project);
    6. Utilities API (ChangeSupport, Lookup and ProxyLookup);

Now, if you run the project, you will notice the new folder under Projects view.