BookNBPlatformCookbookCH1006

Contents

NetBeans Platform Cookbook Chapter 10 06

Create Your Project Type

Some application store and manage data in groups called Project. NetBeans provides the infrastructure to allow you create your own project type.

Preparation

Who needs organize files into projects one can create his own project type. NetBeans Platform offers Project API and Project UI API for that.

ProjectFactory

If you register implementation of the ProjectFactory interface as service provider NetBeans recognizes your project type automatically. The ProjectFactory has three methods:

  • boolean isProject(FileObject projectDirectory) tests whether a given directory probably refers to a project recognized by this factory without actually trying to create it.
  • Project loadProject(FileObject projectDirectory, ProjectState state) creates project instance from resources on disk.
  • void saveProject(Project project) saves project to disk.

The Project instance is created when it is requested by open project action. Checking in the isProject() method would be as rapid as possible because it is called for each directory shown in the Open project file-chooser. The simplest way is to check existence of any artifact, mostly project specific folder or file type.

Project

Look at Project class documentation. There are two methods only

  • getProjectDirectory()
  • getLookup().

This information are important for the ProjectManeger. Other features of each specific project are stored in its Lookup, even the project instance itself. If you need some instance you know ask the lookup excluding some cases described in the documentation. E. g. use ProjectUtils in the case of ProjectInformation and Sources instead of retrieving them from lookup.

You do not need cast the Project class to receive your type. Simply ask for it.

     Myproj myproj = project.getLookup().lookup(Myproj.class);

The NetBeans infrastructure can use following interfaces to its tasks. We show such ones that could be important for your work. See the documentation.

ProjectInformation provides name, display name and icon of the project and could be observed by PropertyChangeListener.

LogicalViewProvider is important. It creates a logical view – a Node instance representing the project visually. You know it from Project window of NetBeans. Which tree structure is your matter. The second service is to find node corresponding given object.

CustomizerProvider shows the ProjectCustomizer with options categories to set project properties.

Sources is provider of SourceGroups that each of them keeps root folder, name, display name and can check if it contains given file.

ActionProvider provides names of supported actions and can invoke them to given context (lookup's content).

SubprojectProvider lists subprojects managed together by this project.

AuxiliaryConfiguration, AuxiliaryProperties and CacheDirectoryProvider allow other modules store arbitrary data into project.

ProjectConfigurationProvider manages several ProjectConfiguration instances.

FileBuiltQueryImplementation tests if the object is up to date, if it have to be processed, build etc.

ProjectOpenedHook allows your module run task while the project is opened or closed.

RecommendedTemplates / PrivilegedTemplates is list of recommended / privileged templates offered when user creates new file within the project.

SearchInfo defines list of DataObjects which can be searched in projects.

other for used java / ant projects.

LogicalViewProvider

creates a Node representing the project visually. Its content is your matter. It can provide hierarchy useful for work with the project, own categories, virtual folders, structure of project's artifacts etc. Hiding some folder (e.g. temporary generated files, compiled files) is possible, too. For known data objects use nodes provided by these DataObjects, e.g. folders, special file types.

How to

We create new project type with this structure:

<project_directory>
|- myproj
|  |- project.properties
|  
|- documents
|  ... files specific for this application
|- data
   ... data associated with this project

image:Nbpcook_10_09_projectOpen.png

Figure 10.9 Open project of type MyProject

image:Nbpcook_10_10_projectView.png

Figure 10.10 Project of type MyProject – logical view

In Project window documents, data and other directories will be shown only. The myproj directory and DTD files (for example) will be hidden.

Create projects

Create new project suite (we need the project suite rather than the application because we will create sample projects later and our application needs to be started in the NetBeans IDE with java support.)

Create new module project in this suite, I called it myproj.

Add dependencies on these modules: Project API, Project UI API, File System API, Lookup API, Utilities API, Nodes API, Datasystem API.

Create ProjectFactory

Create new class MyprojFactory in this module and implement the ProjectFactory interface.

Before class declaration insert an annotation registering your project factory class as a service provider. The NetBeans build process creates an entry in META-INF/services folder called org.netbeans.spi.project.ProjectFactory with MyprojFactory class within it. The class looks like this:

@ServiceProvider(service=ProjectFactory.class)
public class MyprojFactory implements ProjectFactory {

}

NetBeans shows yellow bulb and offers Implement all abstract methods. Let create them.

Add two constants into the class:

    public static final String PROJECT_DIR = "myproj";
    public static final String PROJECT_PROPFILE = "project.properties";

Now implement the isProject() method. The Project API asks your factory if given directory is project directory of your project type. It would be fast because this method is called for each directory in open dialog. We think so: if this directory contains PROJECT_DIR directory it is my project.

    public boolean isProject(FileObject projectDirectory) {
        return projectDirectory.getFileObject(PROJECT_DIR) != null;
    }

Next implement creation of you project:

    public Project loadProject(FileObject projectDirectory, ProjectState state) 
                       throws IOException {
        return isProject (projectDirectory) 
                 ? new Myproj (projectDirectory, state) 
                 : null ;
    }

Ensure yourself if it makes sense to create the project calling isProject() method and create instance of your project. The ProjectState instance is provided by Project API and keeps project's life cycle information. We will notify it later about project changes. The project API takes care to call save action for us. We must implement the saveProject() method only:

    public void saveProject(Project project) 
                     throws IOException, ClassCastException {
       FileObject projectRoot = project.getProjectDirectory();
            
        if (projectRoot.getFileObject(PROJECT_DIR) == null) {
            throw new IOException("Project directory " + projectRoot.getPath() 
                     + " does not exist, cannot save project.");
        }

        // Force creation of the scenes/ dir if it was deleted
        ((Myproj) project).getDocumentsFolder(true);

        // Find the Properties file myproj/project.properties,
        // if it does not exist create it
        String propsPath = PROJECT_DIR + "/" + PROJECT_PROPFILE;
        FileObject propertiesFile = projectRoot.getFileObject(propsPath);
        if (propertiesFile == null) {
            // create the properties file if it does not exist
            propertiesFile = projectRoot.createData(propsPath);
        }

        Properties properties = 
              (Properties) project.getLookup().lookup(Properties.class);

        File f = FileUtil.toFile(propertiesFile);
        properties.store( new FileOutputStream(f)
                        , "Myproj project Properties");
    }

Create Project implementation

Now is time to create Myproj class. Create it and declare it as final and implement the Project interface. Fix imports and let the NetBeans create methods.

Store project directory and state to private members and initialize own Lookup to null. It is created lazily by the first request.

public final class Myproj implements Project {
 
    private static final ImageIcon PROJECT_ICON = 
                ImageUtilities.loadImageIcon(
                 "com/pactkpub/nbpcook/best/myproj/proj/myproj.png", false);

    static final String PROJECT_PROPFILE = "project.properties";
    
    private static final String DOCUMENTS_DIR = "documents";
    private static final String DATA_DIR      = "data";
       
    static String PROP_PROJNAME = "projname";
    
    private FileObject projDir;
    private ProjectState state;
    private Lookup lookup;     
    
    Myproj(FileObject projdir, ProjectState state) {
        this.projDir = projdir;
        this.state = state;
        this.lookup = null;
    }

    public FileObject getProjectDirectory() {
        return this.projDir;
    }

     FileObject getDocumentsFolder(boolean create) {
        FileObject result =
            projDir.getFileObject(DOCUMENTS_DIR);

        if (result == null && create) {
            try {
                result = projDir.createFolder(DOCUMENTS_DIR);
            } catch (IOException ioe) {
                Exceptions.printStackTrace(ioe);
            }
        }
        return result;
    }

    public Lookup getLookup() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

}

Provide project's Lookup content

public Lookup getLookup() {
    if (lookup == null) {
        synchronized(this) {

           lookup = Lookups.fixed(
                      this          // project itself
                    , state         // ProjectState instance 
                                    // received from Project API
                    , new MyprojViewProvider(this)  // logical view provider
                    , new ProjInfo()                // ProjectInformation
                    , loadProperties()              // Properties
                    , new ActionProviderImpl()      // ActionProvider
                    // other that you want provide
                  );         
        }
    }
    return lookup;
}

Note that important objects are stored in the project's lookup and the Project interface is not dirty by next get methods. Each project type can provide its own instances specific for its purposes.

Some items have not been resolved yet. First add the loadProperties() method to Myproj. Create subclass of Properties class which stores the project state and calls its markModified() method when any property was changed.

    private Properties loadProperties() {
        FileObject fob = projDir.getFileObject(Myproj.PROJECT_PROPFILE);
        if (fob == null) { 
            return new NotifyProperties(state);
        } 
        Properties properties = new NotifyProperties(state);
        InputStream is = null;
        try {
            is = fob.getInputStream();
        } catch (FileNotFoundException ex) {
            Exceptions.printStackTrace(ex);
            is = null;
        }
        if (fob != null && is != null) {
            try { 
                properties.load( is );
            } catch (Exception e) {
                Exceptions.printStackTrace(e);
            } 
            finally { 
                if (is!=null) try {
                    is.close();
                } catch (IOException ex) {
                    Exceptions.printStackTrace(ex);
                }
            }              
        } // if fob != null && is != null
        return properties;
    }
    
    private static class NotifyProperties extends Properties {

        private final ProjectState state;

        NotifyProperties(ProjectState state) {
            this.state = state;
        }

        public Object put(Object key, Object val) {
            Object result = super.put(key, val);
            if (((result == null) != (val == null)) 
                || (result != null && val != null && !val.equals(result))) {
                state.markModified();
            }
            return result;
        }
    }  // NotifyProperties

Create ProjInfo. Retrieve the name from project directory name, display name from project properies and icon fromMyproj.

     private class ProjInfo implements ProjectInformation
    {
        private ProjInfo() {
        }

        public String getName() {
            return Myproj.this.getProjectDirectory().getName();
        }

        public String getDisplayName() {
            Properties props = Myproj.this.getLookup().lookup(
                                        Properties.class);
            String n = props.getProperty(Myproj.PROP_PROJNAME);
            if (n==null) return getName();
            else return n;
        } 
        
        public Icon getIcon() {
            return PROJECT_ICON;
        }

        public Project getProject() {
            return Myproj.this;
        }
 
        private PropertyChangeSupport propertyChangeSupport = 
                       new PropertyChangeSupport(this);

        public void addPropertyChangeListener(PropertyChangeListener l) {
               ...
        public void removePropertyChangeListener(PropertyChangeListener l) 
               ...

    }  // ProjInfo

There are three rests. How to create ActionProviderImpl look into example sources. The logical view we are going to create in next section. Create getDocumentDirectory() and getDataDirectory() methods used in the MyprojFactory.

    FileObject getDocumentsFolder(boolean create) {
        FileObject result =
                projDir.getFileObject(DOCUMENTS_DIR);
        if (result == null && create) {
            try {
                result = projDir.createFolder(DOCUMENTS_DIR);
            } catch (IOException ioe) {
                Exceptions.printStackTrace(ioe);
            }
        }
        return result;
    }
    
    FileObject getDataFolder(boolean create) {
        ... similar 

Create Logical View

Now create MyprojViewProvider. Wrap the original Node of the project directory retrieved from Data System API into FilteredNode and provide subclass of FilteredNode.Children to overide or hide some files.

class MyprojViewProvider implements LogicalViewProvider {

    private Myproj project;
    
    public MyprojViewProvider(Myproj proj) {
        this.project = proj;
    }
 
    public Node createLogicalView() {
        try {

            FileObject projectfo = project.getProjectDirectory();
              
            // Get the DataObject that represents it:
            DataFolder projectFolder = DataFolder.findFolder(projectfo);

            // Retrieve the node given by DataSystem module
            Node realProjFolderNode = projectFolder.getNodeDelegate();

            // and wrap it by FilterNode that provides different behaviour
            return new MyprojNode(realProjFolderNode, project);

        } catch (DataObjectNotFoundException donfe) {
            Exceptions.printStackTrace(donfe);
            return new AbstractNode(Children.LEAF);
        }
    }

    public Node findPath(Node root, Object target) {
        // leave unimplemented yet
        return null;
    }
    
    private static final class MyprojNode extends FilterNode {

        final Myproj project; 

        public MyprojNode(Node node, Myproj project) 
                            throws DataObjectNotFoundException {
            super(node,
                  new ProjectChildren(node, project),
                  // The projects system wants the project in the Node's lookup.
                  // NewAction and friends want the original Node's lookup.
                  // Make a merge of both:
                  new ProxyLookup(
                         new Lookup[]  {
                              project.getLookup(), 
                              node.getLookup()
                         }
            ));
            this.project = project;
        }
 
        public Image getIcon(int type) {
            return project.getProjectIcon();
        }
 
        public Image getOpenedIcon(int type) { 
            return getIcon(type);
        }
 
        public String getDisplayName() {
            return project.getProjectDirectory().getName();
        } 

    }  // ProjectNode 
}

Create ProjectChildren class. In addNotify() method filter the project directory loaded by Filesystem and Datasystem API. Omit the myproj directory. Its content will be edited by other way – properties and customize. Add children in wished order: documents, data, other found directories.

For documents and data directories change icon and filter their children using subclass of FilterNode.Children. DTD files are omitted in our example.

class ProjectChildren extends FilterNode.Children  {

    Node parentNode ;
    Myproj project;

    public ProjectChildren(Node node, Myproj project) {
        super(node);
        this.parentNode = node;
        this.project = project; 
    }

    protected void addNotify() {
        FileObject projdir = project.getProjectDirectory();
        DataFolder folder = DataFolder.findFolder(projdir);
        FileObject[] children = projdir.getChildren();
        List<Node> lst = new ArrayList<Node>(8);
        List<Node> others = new ArrayList<Node>(8);
        
        // ensure the document and data folder exist
        project.getDocumentsFolder(true);
        project.getDataFolder(true);
        
        DocumentsNode docNode = null;
        DatasNode ucNode = null; 
        
        for (int i = 0; i < children.length; i++) {
            FileObject fileObject = children[i];
            if (fileObject.isFolder()) {
                DataFolder fld = DataFolder.findFolder(fileObject);
                try {
                    if (fld.getName().equals(Myproj.DOCUMENTS_DIR)) {
                        docNode = new DocumentsNode(
                                        fld.getNodeDelegate(), project);
                    } else if (fld.getName().equals(Myproj.DATA_DIR)) {
                        ucNode = new DatasNode(fld.getNodeDelegate(), project);
                    } else if (fld.getName().equals(
                                        MyprojFactory.PROJECT_DIR)) {
                        // omit
                    } else {
                        others.add( fld.getNodeDelegate() );
                    }
                } catch ( DataObjectNotFoundException ex) {
                    Exceptions.printStackTrace(ex);
                    others.add(fld.getNodeDelegate());
                }
            }  // if isFolder()
        }  // for i children
        
        // serve this order to the user
        // project, documents, datas, other folders  
        
        if (docNode != null)     lst.add( docNode );
        if (ucNode != null)      lst.add( ucNode );
        
        for (Iterator<Node> it = others.iterator(); it.hasNext();) {
            Node node = it.next();
            lst.add(node);
        }
        setKeys(lst);
    }

    protected Node[] createNodes(Node no) {
        return new Node[]{ no };
    }

     public boolean add(Node[] nodes) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

     public boolean remove(Node[] nodes) {
        throw new UnsupportedOperationException("Not supported yet.");
    }
 
   ////////////////////////////////////////////////// 
    
    private static class SpecialNode extends FilterNode {

        final Myproj project;

        public SpecialNode(Node node, Myproj project) 
                               throws DataObjectNotFoundException {
            super(node,
                  new SpecialChildren(node, project) , 
                  // The projects system wants the project in the Node's lookup.
                  // NewAction and friends want the original Node's lookup.
                  // Make a merge of both:
                  new ProxyLookup(
                         new Lookup[]  {
                                    project.getLookup(), 
                                    node.getLookup()
                         }
            ));
            this.project = project;
        }

        public Image getIcon(int type) {
            return ImageUtilities.loadImage(
                    "com/pactkpub/nbpcook/best/myproj/proj/spec.png");
        }

        public Image getOpenedIcon(int type) {
            return getIcon(type);
        }

    }  // SpecialNode

    static class SpecialChildren extends FilterNode.Children { 

        private Myproj project;

        public SpecialChildren (Node owner, Myproj project) {
            super(owner);
            this.project = project;
        }

        protected Node copyNode (Node original) {
            super.copyNode(original);
            return new FilterNode(original);
        } 
    
        protected Node[] createNodes (Node no) {
            List<Node> result = new ArrayList<Node>();
            Node[] nds = super.createNodes( no );
            for (int i = 0; i < nds.length; i++) {
                Node node = nds [i];
                DataObject dataObject = (DataObject)node.getLookup().
                                                  lookup(DataObject.class);
                String s;
                if (dataObject != null) {
                    FileObject fileObject = dataObject.getPrimaryFile();
                    s = fileObject.getExt();
                    if ( ! s.equals("dtd") )  // omit dtd files
                        result.add( node );
                  }
             }  // for i nodes
             return result.toArray( new Node[0] );
          }  // createNodes
      }  // SpecialChildren

    private static final class DatasNode extends SpecialNode {

        public DatasNode(Node node, Myproj project) 
                          throws DataObjectNotFoundException {
            super(node, project);
        }

         public Image getIcon(int type) {
            return ImageUtilities.loadImage(
                    "com/pactkpub/nbpcook/best/myproj/proj/data.png");
        }
  
    }  // DatasNode
 
    private static final class DocumentsNode extends SpecialNode {
  
        public DocumentsNode(Node node, Myproj project) 
                                     throws DataObjectNotFoundException {
            super(node, project);
        }

         public Image getIcon(int type) {
            return ImageUtilities.loadImage(
                    "com/pactkpub/nbpcook/best/myproj/proj/documents.png");
        }
 
    }  // DocumentsNode
}

If you want to have the application as small as possible deselect unused modules from the used Platform. Now open suite project Properties, select Libraries category and deselect all clusters and modules excluding harness (you could use it later) and needed modules of the platform cluster. Then press Resolve button. NetBeans adds modules you have dependency on.

But for creating template and example projects we need start it in NetBeans IDE copy. You can install your module into second copy of NetBeans (and then uninstall it).

Try it

Create folder with wished project structure (This folder myproex is prepared in the project directory of Myprojects example sources and file myproex.zip, too). We will open it and we use it later.

Build and run the application. Try find your project directory by Open Project.

The project directory icon would be our myproj.png icon.

Open it. There is structure with two nodes under project's node: documents and data with associated icon.

Notes/Tips - documentation

This tip was based on

" 10-week Free NetBeans Platform Programming (with Passion!) Online Course" on http://javapassion.com/nbplatform/. This tutorial is great (but not finished yet) and it guides you throuhg many important topics. There are homeworks in it, mostly links to corresponding NetBeans Platform tutorials.


Notes/Tips - examples

The example sources are available in 10_best / Myprojects directory.

Text and sources were created under NB 6.8, 6.9, 7.0 and 7.1.

Navigation

Not logged in. Log in, Register

By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2012, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo