spacer spacer spacer spacer spacer spacer spacer spacer spacer


NetBeans Project Type Module Tutorial

This tutorial demonstrates how to create a new project type in a NetBeans Platform application.

Before going further, make sure this is the tutorial you actually need!

  • Rather than creating a new project type, you might want to extend an existing project type instead, as described in the NetBeans Project Type Extension Module Tutorial.
  • For Maven-based NetBeans Platform applications, see How to Create a Custom Project Type in a Mavenized NetBeans Platform Application.
  • If the projects for which you're creating a project type (whether on Ant or Maven based NetBeans Platform applications) need to use Ant as their build tool, you should use the NetBeans Ant-Based Project Type Module Tutorial instead.

Note: This document uses NetBeans Platform 7.2 and NetBeans IDE 7.2. If you are using an earlier version, see the previous version of this document.

Contents

spacer

  • Introduction to Project Types
  • Creating the Module Project
  • Setting Dependencies
  • Creating the Project Factory
  • Creating the Project
    • Creating and Registering the Project Information
    • Creating and Registering the Project Logical View
    • Creating and Registering the Project Node Children
    • Creating and Registering the Project Customizer
    • Creating and Registering the Project Subprojects
  • Registering the Project Type as Project Sample

To follow this tutorial, you need the software and resources listed in the following table.

Software or Resource Version Required
NetBeans IDE version 7.2 or above
Java Developer Kit (JDK) version 6 or above

You will also make use of these icons, which you can right-click here and download: spacer spacer

Introduction to Project Types

A project type is a NetBeans Platform term for a grouping of folders and files that is treated as a single unit. Treating related folders and files as a single unit makes working with them easier for the end user. One way in which a project type simplifies life for the user is that you are able to fill the Projects window only with those folders and files that the end user is most likely to work. For example, the Java project type in NetBeans IDE helps the end user to work with the folders and files belonging to a single Java application.

Our project type will be defined by the existence of a file named "customer.txt". The tutorial assumes you have available, on disk, multiple folders containing such a file, for example as illustrated below:

spacer

As in the case of the folders named "customer1", "customer2", and "customer3" above, if a folder contains a file named "customer", with a "txt" extension, the NetBeans Platform will recognize the folder as a project. The user will be able to open the project into a NetBeans Platform application. The user will also be able to create new projects, via the New Projects window (Ctrl-Shift-N), which is where we will register some sample projects.

The following are the main NetBeans API classes we will be implementing in this tutorial:

Class Description
org.netbeans.spi.project.ProjectFactory Determines when a folder or file is a valid project and then creates the implemention of org.netbeans.api.project.Project.
org.netbeans.api.project.Project Represents the project.
org.netbeans.spi.project.ui.LogicalViewProvider Provides the logical view for the project.
org.netbeans.api.project.ProjectInformation Provides supplemental information for the project.
org.netbeans.spi.project.ActionProvider Provides one or more actions for the project.
org.netbeans.spi.project.CopyOperationImplementation Provides the Copy operation for the project.
org.netbeans.spi.project.DeleteOperationImplementation Provides the Delete operation for the project.

Creating the Module Project

We begin by working through the New Module Project wizard. At the end of it, we will have a basic source structure, with some default files, that every NetBeans module requires.

  1. Choose File > New Project (Ctrl+Shift+N). Under Categories, select NetBeans Modules. Under Projects, select Module. Click Next.
  2. In the Name and Location panel, type CustomerProjectType in the Project Name field. Change the Project Location to any directory on your computer.

    spacer


    Click Next.
  3. In the Basic Module Configuration panel, type org.customer.project in Code Name Base.

    spacer


    Click Finish.

The IDE creates the CustomerProjectType project. The project contains all of your sources and project metadata, such as the project's Ant build script. The project opens in the IDE. You can view its logical structure in the Projects window (Ctrl-1) and its file structure in the Files window (Ctrl-2).

Setting Dependencies

We will need to make use of several NetBeans APIs. In this step, we select the modules that provide the NetBeans APIs that we will need.

  1. Right-click the project's Libraries node and choose "Add Module Dependency". Select the following modules and click OK:

    • Common Annotations
    • Datasystems API
    • Dialogs API
    • File System API
    • Lookup API
    • Nodes API
    • Project API
    • Project UI API
    • UI Utilities API
    • Utilities API
  2. Expand the Libraries node and check that the following dependencies have been set in the previous step:

    spacer


Creating the Project Factory

We start by implementing the org.netbeans.spi.project.ProjectFactory class.

  1. Create a Java class named CustomerProjectFactory.

  2. Change the default code to the following:

    import java.io.IOException;
    import org.netbeans.api.project.Project;
    import org.netbeans.spi.project.ProjectFactory;
    import org.netbeans.spi.project.ProjectState;
    import org.openide.filesystems.FileObject;
    import org.openide.util.lookup.ServiceProvider;
    
    @ServiceProvider(service=ProjectFactory.class)
    public class CustomerProjectFactory implements ProjectFactory {
    
        public static final String PROJECT_FILE = "customer.txt";
    
        //Specifies when a project is a project, i.e.,
        //if "customer.txt" is present in a folder:
        @Override
        public boolean isProject(FileObject projectDirectory) {
            return projectDirectory.getFileObject(PROJECT_FILE) != null;
        }
    
        //Specifies when the project will be opened, i.e., if the project exists:
        @Override
        public Project loadProject(FileObject dir, ProjectState state) throws IOException {
            return isProject(dir) ? new CustomerProject(dir, state) : null;
        }
    
        @Override
        public void saveProject(final Project project) throws IOException, ClassCastException {
            // leave unimplemented for the moment
        }
    
    }

The @ServiceProvider annotation used in the class signature above will cause a META-INF/services file to be created when the module is compiled. Within that folder, a file named after the fully qualified name of the interface will be found, containing the fully qualified name of the implementing class. That is the standard JDK mechanism, since JDK 6, for registering implementations of interfaces. That is how project types are registered in the NetBeans Plaform.

Creating the Project

Next, we implement the org.netbeans.api.project.Project class.

  1. Create a Java class named CustomerProject.

  2. We'll start with a simple skeleton implementation:

    import org.netbeans.api.project.Project;
    import org.netbeans.spi.project.ProjectState;
    import org.openide.filesystems.FileObject;
    import org.openide.util.Lookup;
    
    public class CustomerProject implements Project {
    
        CustomerProject(FileObject dir, ProjectState state) {
            throw new UnsupportedOperationException("Not yet implemented");
        }
    
        @Override
        public FileObject getProjectDirectory() {
            throw new UnsupportedOperationException("Not supported yet.");
        }
    
        @Override
        public Lookup getLookup() {
            throw new UnsupportedOperationException("Not supported yet.");
        }
        
    }

    The getLookup method, in the code above, is the key to the NetBeans project infrastructure. When you create new features for a project type, such as its logical view, its popup actions, or its customizer, you register them in the project via its getLookup method.

  3. Let's set up our project class so that we can start using it to register the project's features. Fill out the class by setting fields and add code to the getLookup method to prepare it for the following sections.
    import java.beans.PropertyChangeListener;
    import javax.swing.Icon;
    import javax.swing.ImageIcon;
    import org.netbeans.api.annotations.common.StaticResource;
    import org.netbeans.api.project.Project;
    import org.netbeans.api.project.ProjectInformation;
    import org.netbeans.spi.project.ProjectState;
    import org.openide.filesystems.FileObject;
    import org.openide.util.ImageUtilities;
    import org.openide.util.Lookup;
    import org.openide.util.lookup.Lookups;
    
    public class CustomerProject implements Project {
    
        private final FileObject projectDir;
        private final ProjectState state;
        private Lookup lkp;
    
        CustomerProject(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
                
                });
            }
            return lkp;
        }
    
    }
  4. Now let's work on the features that we'd like our project to have. In each case, we define the feature and then we register the feature in the project's Lookup.

    • Creating and Registering the Project Information
    • Creating and Registering the Project Logical View
    • Creating and Registering the Project Node Children
    • Creating and Registering the Project Customizer
    • Creating and Registering the Project Subprojects

    Creating and Registering the Project Information

    In this section, you register minimum NetBeans project support, that is, you create and register a class that provides an icon and a display name for the project.


    1. Put the icon.png file, referred to at the start of this tutorial, into the org.customer.project package.
    2. As an inner class of the CustomerProject class, define the project information as follows:
      private final class Info implements ProjectInformation {
      
          @StaticResource()
          public static final String CUSTOMER_ICON = "org/customer/project/icon.png";
      
          @Override
          public Icon getIcon() {
              return new ImageIcon(ImageUtilities.loadImage(CUSTOMER_ICON));
          }
      
          @Override
          public String getName() {
              return getProjectDirectory().getName();
          }
      
          @Override
          public String getDisplayName() {
              return getName();
          }
      
          @Override
          public void addPropertyChangeListener(PropertyChangeListener pcl) {
              //do nothing, won't change
          }
      
          @Override
          public void removePropertyChangeListener(PropertyChangeListener pcl) {
              //do nothing, won't change
          }
      
          @Override
          public Project getProject() {
              return CustomerProject.this;
          }
      
      }
    3. Now register the ProjectInformation in the Lookup of the project as follows:

      @Override
      public Lookup getLookup() {
          if (lkp == null) {
              lkp = Lookups.fixed(new Object[]{ 
      
                  new Info(),
      
              });
          }
          return lkp;
      }
    4. Run the module. Your application starts up and your module is installed into it. Go to File | Open Project and, when you browse to folders containing a "customer.txt" file, notice that the folders are recognized as projects and show the icon you defined in the ProjectInformation class above:

      spacer


      When you open a project, notice that all the folders and files in the project are shown in the Projects window and that, when you right-click on the project, several default popup actions are shown:


      spacer

    Now that you can open folders as projects into your application, let's work on the project's logical view. The logical view is displayed in the Projects window. The Projects window typically only shows the most important files or folders that the user should work with, together with the related display names, icons, and popup actions.

    Creating and Registering the Project Logical View

    In this section, you define the logical view of your project, as shown in the Projects window of your application.


    1. As an inner class of the CustomerProject class, define the project logical view as follows:
      class CustomerProjectLogicalView implements LogicalViewProvider {
      
          @StaticResource()
          public static final String CUSTOMER_ICON = "org/customer/project/icon.png";
      
          private final CustomerProject project;
      
          public CustomerProjectLogicalView(CustomerProject project) {
              this.project = project;
          }
      
          @Override
          public Node createLogicalView() {
              try {
                  //Obtain the project directory's node:
                  FileObject projectDirectory = project.getProjectDirectory();
                  DataFolder projectFolder = DataFolder.findFolder(projectDirectory);
                  Node nodeOfProjectFolder = projectFolder.getNodeDelegate();
                  //Decorate the project directory's node:
                  return new ProjectNode(nodeOfProjectFolder, project);
              } catch (DataObjectNotFoundException donfe) {
                  Exceptions.printStackTrace(donfe);
                  //Fallback-the directory couldn't be created -
                  //read-only filesystem or something evil happened
                  return new AbstractNode(Children.LEAF);
              }
          }
      
          private final class ProjectNode extends FilterNode {
      
              final CustomerProject project;
      
              public ProjectNode(Node node, CustomerProject project) 
                  throws DataObjectNotFoundException {
                  super(node,
                          new FilterNode.Children(node),
                          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()
                          };
              }
      
              @Override
              public Image getIcon(int type) {
                  return ImageUtilities.loadImage(CUSTOMER_ICON);
              }
      
              @Override
              public Image getOpenedIcon(int type) {
                  return getIcon(type);
              }
      
              @Override
              public String getDisplayName() {
                  return project.getProjectDirectory().getName();
              }
      
          }
      
          @Override
          public Node findPath(Node root, Object target) {
              //leave unimplemented for now
              return null;
          }
      
      }

      Many project actions are available for you to use, as you can see from the code completion:

      spacer

    2. As before, register the feature in the Lookup of the project:
      @Override
      public Lookup getLookup() {
          if (lkp == null) {
              lkp = Lookups.fixed(new Object[]{
                      new Info(),
                      new CustomerProjectLogicalView(this),
              });
          }
          return lkp;
      }
    3. Run the module again and open a customer project again. You should see the following:

      spacer


      The project node now shows the display name, icon, and popup actions that you defined.

    Creating and Registering the Project Node Children

    In this section, you learn how to define which folders and files should be displayed in the logical view, that is, the Projects window. Currently, you are showing all folders and files because the children of the project node are defined by FilterNode.Children(node), which means "display all the children of the node".


    1. Change the constructor of the ProjectNode as follows:
      public ProjectNode(Node node, CustomerProject project) 
          throws DataObjectNotFoundException {
          super(node,
                  NodeFactorySupport.createCompositeChildren(
                          project, 
                          "Projects/org-customer-project/Nodes"),
                  // new FilterNode.Children(node),
                  new ProxyLookup(
                  new Lookup[]{
                      Lookups.singleton(project),
                      node.getLookup()
                  }));
          this.project = project;
      }
    2. Register the project in its own Lookup:
      @Override
      public Lookup getLookup() {
          if (lkp == null) {
              lkp = Lookups.fixed(new Object[]{
                     this,
                     new Info(),
                     new CustomerProjectLogicalView(this),});
          }
          return lkp;
      }
    3. Create a new Java class TextsNodeFactory in a new package org.customer.project.nodes as follows, while taking special note of the @NodeFactory.Registration annotation:
      package org.customer.project.nodes;
      
      import java.util.ArrayList;
      import java.util.List;
      import javax.swing.event.ChangeListener;
      import org.customer.project.CustomerProject;
      import org.netbeans.api.project.Project;
      import org.netbeans.spi.project.ui.support.NodeFactory;
      import org.netbeans.spi.project.ui.support.NodeList;
      import org.openide.filesystems.FileObject;
      import org.openide.loaders.DataObject;
      import org.openide.loaders.DataObjectNotFoundException;
      import org.openide.nodes.FilterNode;
      import org.openide.nodes.Node;
      import org.openide.util.Exceptions;
      
      @NodeFactory.Registration(projectType = "org-customer-project", position = 10)
      public class TextsNodeFactory implements NodeFactory {
      
          @Override
          public NodeList<?> createNodes(Project project) {
              CustomerProject p = project.getLookup().lookup(CustomerProject.class);
              assert p != null;
              return new TextsNodeList(p);
          }
      
          private class TextsNodeList implements NodeList<Node> {
      
              CustomerProject project;
      
              public TextsNodeList(CustomerProject project) {
                  this.project = project;
              }
      
              @Override
              public List<Node> keys() {
                  FileObject textsFolder = 
                      project.getProjectDirectory().getFileObject("texts");
                  List<Node> result = new ArrayList<Node>();
                  for (FileObject textsFolderFile : textsFolder.getChildren()) {
                      try {
                          result.add(DataObject.find(textsFolderFile).getNodeDelegate());
                      } catch (DataObjectNotFoundException ex) {
                          Exceptions.printStackTrace(ex);
                      }
                  }
                  return result;
              }
      
              @Override
              public Node node(Node node) {
                  return new FilterNode(node);
              }
      
              @Override
              public void addNotify() {
              }
      
              @Override
              public void removeNotify() {
              }
      
              @Override
              public void addChangeListener(ChangeListener cl) {
              }
      
              @Override
              public void removeChangeListener(ChangeListener cl) {
              }
              
          }
          
      }
    4. Run the module again and open a customer project again. Make sure the project has a subfolder named "texts", with some content. You should see the following, that is, the content of the "texts" folder is shown in the Projects window, which exists to provide a logical view, while the Files window shows the complete folder structure:

      spacer

    An important point to realize in this section is that the @NodeFactory.Registration annotation can be used to register new child nodes of the customer project node, either within the current module or via external modules. In this way, the logical view of your project is extensible, that is, logical views can be pluggable, if an extension point is created as part of its definition, as shown in step 1 of this section.

    Creating and Registering the Project Customizer

    In this section, you learn how to create a pluggable customizer. When the user right-clicks the project node, they will see a Properties menu item. When they click it, the customizer will open. The categories in the customizer can be contributed by external modules, that is, the customizer will be created to be extensible.


    1. Register the customizer action in the logical view of the project, as follows:
      @Override
      public Action[] getActions(boolean arg0) {
          return new Action[]{
                      CommonProjectActions.newFileAction(),
                      CommonProjectActions.copyProjectAction(),
                      CommonProjectActions.deleteProjectAction(),
                      CommonProjectActions.customizeProjectAction(),
                      CommonProjectActions.closeProjectAction()
                  };
      }
    2. Run the module and right-click the project node. You should see that the Properties popup menu item is present, but disabled:

      spacer

    3. Register a skeleton customizer in the Lookup of the project:
      @Override
      public Lookup getLookup() {
          if (lkp == null) {
              lkp = Lookups.fixed(new Object[]{
                          this,
                          new Info(),
                          new CustomerProjectLogicalView(this),
                          new CustomizerProvider() {
                              @Override
                              public void showCustomizer() {
                                  JOptionPane.showMessageDialog(
                                          null, 
                                          "customizer for " + 
                                          getProjectDirectory().getName());
                              }
                          },
              });
          }
          return lkp;
      }
    4. Run the module again and right-click the project node. You should see that the Properties popup menu item is now enabled:
gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.