<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |
<html xmlns="http://www.w3.org/1999/xhtml"> | |
<head> | |
<!-- -*- xhtml -*- --> | |
<title>NetBeans Project Type Module Tutorial for NetBeans Platform 7.2</title> | |
<link rel="stylesheet" type="text/css" href="https://netbeans.org/netbeans.css"/> | |
<meta name="AUDIENCE" content="NBUSER"/> | |
<meta name="TYPE" content="ARTICLE"/> | |
<meta name="EXPIRES" content="N"/> | |
<meta name="developer" content="gwielenga@netbeans.org"/> | |
<meta name="indexed" content="y"/> | |
<meta name="description" | |
content="A short guide to using the Project API."/> | |
<!-- Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. --> | |
<!-- Use is subject to license terms.--> | |
</head> | |
<body> | |
<h1>NetBeans Project Type Module Tutorial</h1> | |
<p>This tutorial demonstrates how to create a new project type in a NetBeans Platform application.</p> | |
<p class="tips"><b>Before going further, make sure this is the tutorial you actually need!</b></p> | |
<ul> | |
<li>Rather than creating a new project type, you might want to extend an <i>existing</i> | |
project type instead, as described in | |
the <a href="https://platform.netbeans.org/tutorials/nbm-projectextension.html">NetBeans Project Type Extension Module Tutorial</a>.</li> | |
<li>For Maven-based NetBeans Platform applications, see <a href="http://netbeans.dzone.com/how-create-maven-nb-project-type">How to | |
Create a Custom Project Type in a Mavenized NetBeans Platform Application</a>. </li> | |
<li>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 <a href="https://platform.netbeans.org/tutorials/nbm-projecttypeant.html">NetBeans Ant-Based Project Type Module Tutorial</a> instead.</p> | |
</li> | |
</ul> | |
</p> | |
<p><strong class="notes">Note: </strong>This document uses NetBeans Platform 7.2 and | |
NetBeans IDE 7.2. If you | |
are using an earlier version, see <a href="71/nbm-projecttype.html">the previous version | |
of this document</a>.</p> | |
<p><b>Contents</b></p> | |
<p><img src="../images/articles/72/netbeans-stamp.gif" class="stamp" width="114" height="114" alt="Content on this page applies to NetBeans IDE 7.2" title="Content on this page applies to NetBeans IDE 7.2"/></p> | |
<ul class="toc"> | |
<li><a href="#intro">Introduction to Project Types</a></li> | |
<li><a href="#creatingthemoduleproject">Creating the Module Project</a></li> | |
<li><a href="#settingdependencies">Setting Dependencies</a></li> | |
<li><a href="#creatingtheprojectfactory">Creating the Project Factory</a></li> | |
<li><a href="#creatingtheproject">Creating the Project</a> | |
<ul> | |
<li><a href="#projectinformation">Creating and Registering the Project Information</a></li> | |
<li><a href="#projectlogicalview">Creating and Registering the Project Logical View</a></li> | |
<li><a href="#projectchildren">Creating and Registering the Project Node Children</a></li> | |
<li><a href="#projectcustomizer">Creating and Registering the Project Customizer</a></li> | |
<li><a href="#projectsubtype">Creating and Registering the Project Subprojects</a></li> | |
</ul> | |
</li> | |
<li><a href="#projectsample">Registering the Project Type as Project Sample</a></li> | |
</ul> | |
<p><b>To follow this tutorial, you need the software and resources listed in the following | |
table.</b></p> | |
<table> | |
<tbody> | |
<tr> | |
<th class="tblheader" scope="col">Software or Resource</th> | |
<th class="tblheader" scope="col">Version Required</th> | |
</tr> | |
<tr> | |
<td class="tbltd1"><a href="https://netbeans.org/downloads/index.html">NetBeans IDE</a></td> | |
<td class="tbltd1">version 7.2 or above</td> | |
</tr> | |
<tr> | |
<td class="tbltd1"><a href="http://java.sun.com/javase/downloads/index.jsp">Java Developer Kit (JDK)</a></td> | |
<td class="tbltd1">version 6 or above</td> | |
</tr> | |
</tbody> | |
</table> | |
<p>You will also make use of these icons, which you | |
can right-click here and download: | |
<img alt="" src="../images/tutorials/projecttypes/72pics/icon.png" /> | |
<img alt="" src="../images/tutorials/projecttypes/72pics/sub-icon.png" /></p> | |
<p class="tips">For troubleshooting purposes, you are welcome to download the <a href="http://java.net/projects/nb-api-samples/sources/api-samples/show/versions/7.2/tutorials/CustomerProjectType">completed tutorial source code</a>.</p> | |
<h2 class="tutorial"><a name="intro"></a>Introduction to Project Types</h2> | |
<p>A <i>project type</i> 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. | |
<p>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:</p> | |
<p><img src="../images/tutorials/projecttypes/72pics/result-1.png" alt="installed result"/></p> | |
<p>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.</p> | |
<p>The following are the main NetBeans API classes | |
we will be implementing in this tutorial:</p> | |
<table> | |
<tbody> | |
<tr> | |
<th class="tblheader" scope="col">Class</th> | |
<th class="tblheader" scope="col">Description</th> | |
</tr> | |
<tr> | |
<td class="tbltd1"><tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectapi/org/netbeans/spi/project/ProjectFactory.html">org.netbeans.spi.project.ProjectFactory</a></tt></td> | |
<td class="tbltd1">Determines when a folder or file | |
is a valid project and then creates the implemention | |
of <tt>org.netbeans.api.project.Project</tt>.</td> | |
</tr> | |
<tr> | |
<td class="tbltd1"><tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectapi/org/netbeans/api/project/Project.html">org.netbeans.api.project.Project</a></tt></td> | |
<td class="tbltd1">Represents the project.</td> | |
</tr> | |
<tr> | |
<td class="tbltd1"><tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectuiapi/org/netbeans/spi/project/ui/LogicalViewProvider.html">org.netbeans.spi.project.ui.LogicalViewProvider</a></tt></td> | |
<td class="tbltd1">Provides the logical view for the project.</td> | |
</tr> | |
<tr> | |
<td class="tbltd1"><tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectapi/org/netbeans/api/project/ProjectInformation.html">org.netbeans.api.project.ProjectInformation</a></tt></td> | |
<td class="tbltd1">Provides supplemental information for the project.</td> | |
</tr> | |
<tr> | |
<td class="tbltd1"><tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectapi/org/netbeans/spi/project/ActionProvider.html">org.netbeans.spi.project.ActionProvider</a></tt></td> | |
<td class="tbltd1">Provides one or more actions for the project.</td> | |
</tr> | |
<tr> | |
<td class="tbltd1"><tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectapi/org/netbeans/spi/project/CopyOperationImplementation.html">org.netbeans.spi.project.CopyOperationImplementation</a></tt></td> | |
<td class="tbltd1">Provides the Copy operation for the project.</td> | |
</tr> | |
<tr> | |
<td class="tbltd1"><tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectapi/org/netbeans/spi/project/DeleteOperationImplementation.html">org.netbeans.spi.project.DeleteOperationImplementation</a></tt></td> | |
<td class="tbltd1">Provides the Delete operation for the project.</td> | |
</tr> | |
</tbody> | |
</table> | |
<h2 class="tutorial"><a name="creatingthemoduleproject"></a>Creating the Module Project</h2> | |
<p>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.</p> | |
<div class="indent"> | |
<ol> | |
<li>Choose File > New Project (Ctrl+Shift+N). Under Categories, select NetBeans Modules. | |
Under Projects, select Module. Click Next.</li> | |
<li>In the Name and Location panel, type <tt>CustomerProjectType</tt> in the Project Name field. | |
Change the Project Location to any directory on your computer. | |
<br/><br/><p><img alt="" src="../images/tutorials/projecttypes/72pics/proj-1.png" /></p> | |
<br/>Click Next.</li> | |
<li>In the Basic Module Configuration panel, type <tt>org.customer.project</tt> | |
in Code Name Base. | |
<br/><br/><p><img alt="" src="../images/tutorials/projecttypes/72pics/proj-2.png" /></p> | |
<br/>Click Finish.</li> | |
</ol> | |
</div> | |
<p> The IDE creates the <tt>CustomerProjectType</tt> | |
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).</p> | |
<!-- ===================================================================================== --> | |
<h2><a name="settingdependencies"></a>Setting Dependencies</h2> | |
<p>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.</p> | |
<div class="indent"> | |
<ol> | |
<li>Right-click the project's Libraries node and choose "Add Module Dependency". | |
Select the following modules and click OK: | |
<br/><br/> | |
<ul> | |
<li>Common Annotations</li> | |
<li>Datasystems API</li> | |
<li>Dialogs API</li> | |
<li>File System API</li> | |
<li>Lookup API</li> | |
<li>Nodes API</li> | |
<li>Project API</li> | |
<li>Project UI API</li> | |
<li>UI Utilities API</li> | |
<li>Utilities API</li> | |
</ul> | |
</li> | |
<li>Expand the Libraries node and check that the following dependencies have been set in the previous step: | |
<br/><br/><p><img alt="" src="../images/tutorials/projecttypes/72pics/proj-3.png" /></p> | |
<br/> | |
</li> | |
</ol> | |
</div> | |
<!-- ===================================================================================== --> | |
<h2><a name="creatingtheprojectfactory"></a>Creating the Project Factory</h2> | |
<p>We start by implementing the <tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectapi/org/netbeans/spi/project/ProjectFactory.html">org.netbeans.spi.project.ProjectFactory</a></tt> | |
class.</p> | |
<div class="indent"> | |
<ol> | |
<li><p>Create a Java class named <tt>CustomerProjectFactory</tt>.</p></li> | |
<li><p>Change the default code to the following:</p> | |
<pre class=examplecode>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 <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectapi/org/netbeans/spi/project/ProjectFactory.html">ProjectFactory</a> { | |
public static final String PROJECT_FILE = "customer.txt"; | |
<b>//Specifies when a project is a project, i.e., | |
//if "customer.txt" is present in a folder:</b> | |
@Override | |
public boolean isProject(FileObject projectDirectory) { | |
return projectDirectory.getFileObject(PROJECT_FILE) != null; | |
} | |
<b>//Specifies when the project will be opened, i.e., if the project exists:</b> | |
@Override | |
public Project loadProject(FileObject dir, <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectapi/org/netbeans/spi/project/ProjectState.html">ProjectState</a> 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 | |
} | |
}</pre> | |
</li> | |
</ol> | |
</div> | |
<p class="notes"><b>Note:</b> 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.</p> | |
<p class="tips">Instead of <tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectapi/org/netbeans/spi/project/ProjectFactory.html">ProjectFactory</a></tt>, consider implementing the newer <tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectapi/org/netbeans/spi/project/ProjectFactory2.html">ProjectFactory2</a></tt>. | |
<tt>ProjectFactory2</tt> is a performance correction to <tt>ProjectFactory</tt>, done in a compatible way. | |
If you implement <tt>ProjectFactory2</tt>, the project will not need to be loaded, which can take some time, | |
especially in populating the Lookup, and the project icon appears fast in the Open Project dialog. If you implement only | |
<tt>ProjectFactory</tt>, more memory is consumed and projects are loaded even if not used or opened in the end. | |
The main effective place to see the difference visually is when you have many projects in a single folder. | |
The pattern itself is fairly common in the Eclipse world, for example. Interfaces are extended as | |
InterfaceExt, InterfaceExt2, InterfaceExt3, etc. The general idea is that typically you should always | |
implement the last extension to the base interface. But the core codebase dealing with the interfaces | |
can handle all of the variants.</p> | |
<!-- ===================================================================================== --> | |
<h2><a name="creatingtheproject"></a>Creating the Project</h2> | |
<p>Next, we implement the <tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectapi/org/netbeans/api/project/Project.html">org.netbeans.api.project.Project</a></tt> | |
class.</p> | |
<div class="indent"> | |
<ol> | |
<li><p>Create a Java class named <tt>CustomerProject</tt>.</p></li> | |
<li><p>We'll start with a simple skeleton implementation:</p> | |
<pre class="examplecode">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 <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectapi/org/netbeans/api/project/Project.html">Project</a> { | |
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."); | |
} | |
}</pre> | |
<p class="tips">The <tt>getLookup</tt> 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 <tt>getLookup</tt> method.</p> | |
<li>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 <tt>getLookup</tt> | |
method to prepare it for the following sections. | |
<pre class="examplecode">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; | |
} | |
}</pre> | |
<li>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. | |
<br/><br/> | |
<ul> | |
<li><a href="#projectinformation">Creating and Registering the Project Information</a></li> | |
<li><a href="#projectlogicalview">Creating and Registering the Project Logical View</a></li> | |
<li><a href="#projectchildren">Creating and Registering the Project Node Children</a></li> | |
<li><a href="#projectcustomizer">Creating and Registering the Project Customizer</a></li> | |
<li><a href="#projectsubtype">Creating and Registering the Project Subprojects</a></li> | |
</ul> | |
<div class="indent"> | |
<h3 class="tutorial"><a name="projectinformation"></a>Creating and Registering the Project Information</h3> | |
<p>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.</p> | |
<br/> | |
<div class="indent"> | |
<ol> | |
<li>Put the <tt>icon.png</tt> file, referred to at the start of this | |
tutorial, into the <tt>org.customer.project</tt> package.</li> | |
<li>As an inner class of the <tt>CustomerProject</tt> class, | |
define the project information as follows: | |
<pre class="examplecode">private final class Info implements <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectapi/org/netbeans/api/project/ProjectInformation.html">ProjectInformation</a> { | |
<a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/StaticResource.html">@StaticResource()</a> | |
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; | |
} | |
}</pre> | |
</li> | |
<li> | |
<p>Now register the <tt>ProjectInformation</tt> in the Lookup of the project as follows:</p> | |
<pre class="examplecode">@Override | |
public Lookup getLookup() { | |
if (lkp == null) { | |
lkp = Lookups.fixed(new Object[]{ | |
<b>new Info(),</b> | |
}); | |
} | |
return lkp; | |
}</pre> | |
</li> | |
<li>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 <tt>ProjectInformation</tt> | |
class above: | |
<br/><br/> | |
<p><img src="../images/tutorials/projecttypes/72pics/result-3.png" alt="installed result"/></p> | |
<br/> | |
<p>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:</p> | |
<br/> | |
<p><img src="../images/tutorials/projecttypes/72pics/result-2.png" alt="installed result"/></p> | |
</li> | |
</ol> | |
</div> | |
<p>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. | |
</p> | |
</div> | |
<div class="indent"> | |
<h3 class="tutorial"><a name="projectlogicalview"></a>Creating and Registering the Project Logical View</h3> | |
<p>In this section, you define the logical view of your project, | |
as shown in the Projects window of your application.</p> | |
<br/> | |
<div class="indent"> | |
<ol> | |
<li>As an inner class of the <tt>CustomerProject</tt> class, | |
define the project logical view as follows: | |
<pre class=examplecode>class CustomerProjectLogicalView implements <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectuiapi/org/netbeans/spi/project/ui/LogicalViewProvider.html">LogicalViewProvider</a> { | |
<a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/StaticResource.html">@StaticResource()</a> | |
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; | |
} | |
}</pre> | |
<p class="tips">Many project actions are available | |
for you to use, as you can see from the code completion: | |
<p><img src="../images/tutorials/projecttypes/72pics/proj-4.png" alt="installed result"/></p> | |
</p> | |
</li> | |
<li>As before, register the feature in the Lookup of the project: | |
<pre class="examplecode">@Override | |
public Lookup getLookup() { | |
if (lkp == null) { | |
lkp = Lookups.fixed(new Object[]{ | |
new Info(), | |
<b>new CustomerProjectLogicalView(this),</b> | |
}); | |
} | |
return lkp; | |
}</pre> | |
</li> | |
<li>Run the module again and open a customer project again. You should see the following: | |
<br/><br/> | |
<p><img src="../images/tutorials/projecttypes/72pics/result-4.png" alt="installed result"/></p> | |
<br/> | |
<p>The project node now shows the display name, icon, and popup actions that you defined.</p> | |
</li> | |
</ol> | |
</div> | |
<div class="indent"> | |
<h3 class="tutorial"><a name="projectchildren"></a>Creating and Registering the Project Node Children</h3> | |
<p>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 <tt>FilterNode.Children(node)</tt>, which means | |
"display all the children of the node".</p> | |
<br/> | |
<div class="indent"> | |
<ol> | |
<li>Change the constructor of the ProjectNode as follows: | |
<pre class=examplecode>public ProjectNode(Node node, CustomerProject project) | |
throws DataObjectNotFoundException { | |
super(node, | |
<b><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectuiapi/org/netbeans/spi/project/ui/support/NodeFactorySupport.html#createCompositeChildren(org.netbeans.api.project.Project, java.lang.String)">NodeFactorySupport.createCompositeChildren</a>( | |
project, | |
"Projects/org-customer-project/Nodes"),</b> | |
// new FilterNode.Children(node), | |
new ProxyLookup( | |
new Lookup[]{ | |
Lookups.singleton(project), | |
node.getLookup() | |
})); | |
this.project = project; | |
}</pre></li> | |
<li>Register the project in its own Lookup: | |
<pre class=examplecode>@Override | |
public Lookup getLookup() { | |
if (lkp == null) { | |
lkp = Lookups.fixed(new Object[]{ | |
<b>this,</b> | |
new Info(), | |
new CustomerProjectLogicalView(this),}); | |
} | |
return lkp; | |
}</pre></li> | |
<li>Create a new Java class <tt>TextsNodeFactory</tt> in a new package <tt>org.customer.project.nodes</tt> | |
as follows, while taking special note of the <tt>@NodeFactory.Registration</tt> annotation: | |
<pre class=examplecode>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; | |
<a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectuiapi/org/netbeans/spi/project/ui/support/NodeFactory.Registration.html">@NodeFactory.Registration</a>(projectType = "org-customer-project", position = 10) | |
public class TextsNodeFactory implements <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectuiapi/org/netbeans/spi/project/ui/support/NodeFactory.html">NodeFactory</a> { | |
@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>(); | |
if (textsFolder != null) { | |
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) { | |
} | |
} | |
}</pre></li> | |
<li>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: | |
<br/><br/> | |
<p><img src="../images/tutorials/projecttypes/72pics/text-folder-1.png" alt="installed result"/></p> | |
</li> | |
</ol> | |
<p class="tips">An important point to realize in this section is that the <tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectuiapi/org/netbeans/spi/project/ui/support/NodeFactory.Registration.html">@NodeFactory.Registration</a></tt> 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.</p> | |
</div> | |
<div class="indent"> | |
<h3 class="tutorial"><a name="projectcustomizer"></a>Creating and Registering the Project Customizer</h3> | |
<p>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.</p> | |
<br/> | |
<div class="indent"> | |
<ol> | |
<li>Register the customizer action in the logical view of the project, as follows: | |
<pre class=examplecode>@Override | |
public Action[] getActions(boolean arg0) { | |
return new Action[]{ | |
CommonProjectActions.newFileAction(), | |
CommonProjectActions.copyProjectAction(), | |
CommonProjectActions.deleteProjectAction(), | |
<b>CommonProjectActions.customizeProjectAction(),</b> | |
CommonProjectActions.closeProjectAction() | |
}; | |
}</pre></li> | |
<li>Run the module and right-click the project node. You should see that the | |
Properties popup menu item is present, but disabled: | |
<br/><br/> | |
<p><img src="../images/tutorials/projecttypes/72pics/customizer-1.png" alt="installed result"/></p> </li> | |
<li>Register a skeleton customizer in the Lookup of the project: | |
<pre class=examplecode>@Override | |
public Lookup getLookup() { | |
if (lkp == null) { | |
lkp = Lookups.fixed(new Object[]{ | |
this, | |
new Info(), | |
new CustomerProjectLogicalView(this), | |
<b>new CustomizerProvider() { | |
@Override | |
public void showCustomizer() { | |
JOptionPane.showMessageDialog( | |
null, | |
"customizer for " + | |
getProjectDirectory().getName()); | |
} | |
},</b> | |
}); | |
} | |
return lkp; | |
}</pre></li> | |
<li>Run the module again and right-click the project node. You should see that the | |
Properties popup menu item is now enabled: | |
<br/><br/> | |
<p><img src="../images/tutorials/projecttypes/72pics/customizer-2.png" alt="installed result"/></p> | |
<br/> | |
<p>Click the menu item and you should see your | |
<tt>JOptionPane</tt>:</p> | |
<br/> | |
<p><img src="../images/tutorials/projecttypes/72pics/customizer-3.png" alt="installed result"/></p> | |
<br/> | |
</li> | |
<li>Now we create the infrastructure for our | |
pluggable Project Properties window: | |
<pre class="examplecode">package org.customer.project; | |
import java.awt.Dialog; | |
import java.awt.event.ActionEvent; | |
import java.awt.event.ActionListener; | |
import org.netbeans.api.project.ProjectUtils; | |
import org.netbeans.spi.project.ui.CustomizerProvider; | |
import org.netbeans.spi.project.ui.support.ProjectCustomizer; | |
import org.openide.awt.StatusDisplayer; | |
import org.openide.util.lookup.Lookups; | |
public class CustomerCustomizerProvider implements <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectuiapi/org/netbeans/spi/project/ui/CustomizerProvider.html">CustomizerProvider</a> { | |
public final CustomerProject project; | |
public static final String CUSTOMIZER_FOLDER_PATH = | |
"Projects/org-customer-project/Customizer"; | |
public CustomerCustomizerProvider(CustomerProject project) { | |
this.project = project; | |
} | |
@Override | |
public void showCustomizer() { | |
Dialog dialog = <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectuiapi/org/netbeans/spi/project/ui/support/ProjectCustomizer.html">ProjectCustomizer</a>.createCustomizerDialog( | |
//Path to layer folder: | |
CUSTOMIZER_FOLDER_PATH, | |
//Lookup, which must contain, at least, the Project: | |
Lookups.fixed(project), | |
//Preselected category: | |
"", | |
//OK button listener: | |
new OKOptionListener(), | |
//HelpCtx for Help button of dialog: | |
null); | |
dialog.setTitle(ProjectUtils.getInformation(project).getDisplayName()); | |
dialog.setVisible(true); | |
} | |
private class OKOptionListener implements ActionListener { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
StatusDisplayer.getDefault().setStatusText("OK button clicked for " | |
+ project.getProjectDirectory().getName() + " customizer!"); | |
} | |
} | |
}</pre> | |
</li> | |
<li>Next, rewrite the project's Lookup so that | |
the above class is created within it: | |
<pre class="examplecode">@Override | |
public Lookup getLookup() { | |
if (lkp == null) { | |
lkp = Lookups.fixed(new Object[]{ | |
this, | |
new Info(), | |
new CustomerProjectLogicalView(this), | |
<b>new CustomerCustomizerProvider(this)</b> | |
}); | |
} | |
return lkp; | |
}</pre> | |
</li> | |
<li>In a new package <tt>org.customer.project.panels</tt>, | |
create a new Java class named <tt>GeneralCustomerProperties</tt>, | |
with this content: | |
<pre class="examplecode">package org.customer.project.panels; | |
import javax.swing.JComponent; | |
import javax.swing.JPanel; | |
import org.netbeans.spi.project.ui.support.ProjectCustomizer; | |
import org.netbeans.spi.project.ui.support.ProjectCustomizer.Category; | |
import org.openide.util.Lookup; | |
import org.openide.util.NbBundle; | |
public class GeneralCustomerProperties | |
implements <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectuiapi/org/netbeans/spi/project/ui/support/ProjectCustomizer.html">ProjectCustomizer.CompositeCategoryProvider</a> { | |
private static final String GENERAL = "General"; | |
@ProjectCustomizer.CompositeCategoryProvider.Registration( | |
projectType = "org-customer-project", position = 10) | |
public static GeneralCustomerProperties createGeneral() { | |
return new GeneralCustomerProperties(); | |
} | |
@NbBundle.Messages("LBL_Config_General=General") | |
@Override | |
public Category createCategory(Lookup lkp) { | |
return ProjectCustomizer.Category.create( | |
GENERAL, | |
Bundle.LBL_Config_General(), | |
null); | |
} | |
@Override | |
public JComponent createComponent(Category category, Lookup lkp) { | |
return new JPanel(); | |
} | |
}</pre> | |
<p class="tips">Note the usage | |
of the <tt>@ProjectCustomizer.CompositeCategoryProvider.Registration</tt> | |
annotation above. Using that annotation, you can register new panels | |
in the Project Properties dialog, via the extension point you | |
created in step 5 above. In this way, each panel can be contributed | |
by external modules. For another example, see | |
<a href="http://netbeans.dzone.com/new-tabs-netbeans-project-props">Adding New Tabs to the Project Properties Dialog in NetBeans IDE</a>.</p> | |
</li> | |
<li>Run the module again and right-click the project node. When you click the | |
Properties menu item, you should see the Project Properties dialog, with | |
one category: | |
<br/><br/> | |
<p><img src="../images/tutorials/projecttypes/72pics/customizer-4.png" alt="installed result"/></p> | |
<p>When you click the OK button, you will see a message in the status bar. The | |
message is provided by the <tt>OKOptionListener</tt> you defined above.</p> | |
</li> | |
</ol> | |
<p>You now have the start of a project customizer.</p> | |
</div> | |
<div class="indent"> | |
<h3 class="tutorial"><a name="projectsubtype"></a>Creating and Registering the Project Subprojects</h3> | |
<p>In this section, you learn how to create new project types that are nested | |
within other project types:</p> | |
<br/> | |
<p><img src="../images/tutorials/projecttypes/72pics/sub-proj-2.png" alt="installed result"/></p> | |
<p>Above, you can see that the "customer3" project has several folders. One | |
of those folders is named "reports", containing subfolders, each of | |
which contains a file named "report.xml". In the instructions that follow, | |
you will create a new project type for folders containing a file named | |
"report.xml", while also being shown how to register those projects | |
as subprojects of the customer project.</p> | |
<br/> | |
<div class="indent"> | |
<ol> | |
<li>Following the instructions <a href="#creatingtheprojectfactory">at the start of this tutorial</a>, create a new <tt>ProjectFactory</tt> | |
that recognizes folders containing a file named "report.xml" as a project | |
of type <tt>ReportsSubProject</tt>. Define a <tt>ProjectInformation</tt> | |
and a <tt>ProjectLogicalView</tt> for your <tt>ReportsSubProject</tt>.</li> | |
<li>Create a <tt><a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectapi/org/netbeans/spi/project/SubprojectProvider.html">SubprojectProvider</a></tt> that looks inside the customer project's "reports" | |
folder for projects of your type: | |
<pre class=examplecode>public class ReportsSubprojectProvider implements <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-projectapi/org/netbeans/spi/project/SubprojectProvider.html">SubprojectProvider</a> { | |
private final CustomerProject project; | |
public ReportsSubprojectProvider(CustomerProject project) { | |
this.project = project; | |
} | |
@Override | |
public Set<? extends Project> getSubprojects() { | |
return loadProjects(project.getProjectDirectory()); | |
} | |
private Set loadProjects(FileObject dir) { | |
Set newProjects = new HashSet(); | |
FileObject reportsFolder = dir.getFileObject("reports"); | |
if (reportsFolder != null) { | |
for (FileObject childFolder : reportsFolder.getChildren()) { | |
try { | |
Project subp = ProjectManager.getDefault(). | |
findProject(childFolder); | |
if (subp != null && subp instanceof ReportsSubProject) { | |
newProjects.add((ReportsSubProject) subp); | |
} | |
} catch (IOException ex) { | |
Exceptions.printStackTrace(ex); | |
} catch (IllegalArgumentException ex) { | |
Exceptions.printStackTrace(ex); | |
} | |
} | |
} | |
return Collections.unmodifiableSet(newProjects); | |
} | |
@Override | |
public void addChangeListener(ChangeListener cl) { | |
} | |
@Override | |
public void removeChangeListener(ChangeListener cl) { | |
} | |
}</pre></li> | |
<li>Register the <tt>SubprojectProvider</tt> in the customer project's Lookup: | |
<pre class=examplecode>@Override | |
public Lookup getLookup() { | |
if (lkp == null) { | |
lkp = Lookups.fixed(new Object[]{ | |
this, | |
new Info(), | |
new CustomerProjectLogicalView(this), | |
new CustomerCustomizerProvider(this), | |
<b>new ReportsSubprojectProvider(this)</b> | |
}); | |
} | |
return lkp; | |
}</pre></li> | |
<li>Similar to the <tt>TextsNodeFactory</tt> that you created earlier in this tutorial, | |
create a new Java class <tt>ReportsSubProjectNodeFactory</tt> as follows, | |
while again taking special note of the <tt>@NodeFactory.Registration</tt> annotation, which | |
registers the <tt>NodeFactory</tt> into the logical view of the customer project: | |
<pre class=examplecode>@NodeFactory.Registration(projectType = "org-customer-project", position = 20) | |
public class ReportsSubProjectNodeFactory implements NodeFactory { | |
<a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-api-annotations-common/org/netbeans/api/annotations/common/StaticResource.html">@StaticResource()</a> | |
public static final String SUB_ICON = "org/customer/project/sub/icon.png"; | |
@Override | |
public NodeList<?> createNodes(Project project) { | |
ReportsSubprojectProvider rsp = project.getLookup(). | |
lookup(ReportsSubprojectProvider.class); | |
assert rsp != null; | |
return new ReportsNodeList(rsp.getSubprojects()); | |
} | |
private class ReportsNodeList implements NodeList<Project> { | |
Set<? extends Project> subprojects; | |
public ReportsNodeList(Set<? extends Project> subprojects) { | |
this.subprojects = subprojects; | |
} | |
@Override | |
public List<Project> keys() { | |
List<Project> result = new ArrayList<Project>(); | |
for (Project oneReportSubProject : subprojects) { | |
result.add(oneReportSubProject); | |
} | |
return result; | |
} | |
@Override | |
public Node node(Project node) { | |
FilterNode fn = null; | |
try { | |
fn = new FilterNode(DataObject.find(node. | |
getProjectDirectory()).getNodeDelegate()){ | |
@Override | |
public Image getIcon(int type) { | |
return ImageUtilities.loadImage(SUB_ICON); | |
} | |
@Override | |
public Image getOpenedIcon(int type) { | |
return ImageUtilities.loadImage(SUB_ICON); | |
} | |
}; | |
} catch (DataObjectNotFoundException ex) { | |
Exceptions.printStackTrace(ex); | |
} | |
return fn; | |
} | |
@Override | |
public void addNotify() { | |
} | |
@Override | |
public void removeNotify() { | |
} | |
@Override | |
public void addChangeListener(ChangeListener cl) { | |
} | |
@Override | |
public void removeChangeListener(ChangeListener cl) { | |
} | |
} | |
}</pre> | |
<p class="tips"> Above, reference is made to an icon. Use your own, 16x16 pixels in size, | |
or use one of the two shown at the top of this tutorial.</p> | |
</li> | |
<li>Run the module again, go to the Open Project dialog, | |
and notice that subprojects are recognized | |
and that you can open them: | |
<br/><br/> | |
<p><img src="../images/tutorials/projecttypes/72pics/sub-proj-1.png" alt="installed result"/></p> | |
<br/><p>Also, when you've selected a customer project | |
in the Open Project dialog, the Open Project dialog lets you open the subprojects, too: | |
<br/><br/> | |
<p><img src="../images/tutorials/projecttypes/72pics/sub-proj-3.png" alt="installed result"/></p> | |
</li> | |
</ol> | |
<p>Using the instructions in this subsection, you can create a richly structured and deeply nested project | |
hierarchy, because each subproject can provide its own subprojects, too. | |
For further information on this topic, see <a href="https://blogs.oracle.com/geertjan/entry/org_netbeans_spi_project_subprojectprovider">this blog entry</a>, <a href="https://blogs.oracle.com/geertjan/entry/org_netbeans_spi_project_subprojectprovider1">this blog entry</a>, and <a href="https://blogs.oracle.com/geertjan/entry/org_netbeans_spi_project_subprojectprovider2">this blog entry</a>.</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<p>In this section, you have defined the basic infrastructure of a new type of project | |
in your NetBeans Platform application.</p> | |
<!-- ======================================================================================= --> | |
<h2><a name="projectsample"></a>Registering the Project Type as Project Sample</h2> | |
<p>In this section, we create some project samples that make use of our | |
project type. We also register these project samples in the | |
New Project window of our application.</p> | |
<div class="indent"> | |
<ol> | |
<li><p>Run the module that you created in this tutorial. A new instance | |
of your NetBeans Platform application starts up, with your | |
project type installed via your module. If you're creating the | |
project type for NetBeans IDE, continue to the next step. </p> | |
<p class="tips">If you're creating | |
the project type for some other application on the NetBeans Platform, | |
you will need to include the apisupport modules from NetBeans IDE | |
in your application, temporarily, to complete the steps that follow.</p></li> | |
<li><p> Open the sample projects you created in the previous | |
step, which you're now able to do since you have | |
installed a module providing your project type. </p><br/> | |
<p><img src="../images/tutorials/projecttypes/72pics/proj-temp-0.png" alt="installed result"/></p></li> | |
<li> <p>Also open the module itself. Create a | |
new subpackage, named "samples", as shown below. Then right-click | |
the package and choose New | Other | Module Development, and | |
select Project Template | |
as shown below:</p> | |
<br/> | |
<p><img src="../images/tutorials/projecttypes/72pics/proj-temp-1.png" alt="installed result"/></p></li> | |
<li><p>Use the New Project Template wizard to register your first sample project:</p> | |
<br/> | |
<p><img src="../images/tutorials/projecttypes/72pics/proj-temp-2.png" alt="installed result"/></p> | |
<br/> | |
<p>Click Next. Specify the name of the template, the display text, and the | |
package where the template should be registered:</p> | |
<br/> | |
<p><img src="../images/tutorials/projecttypes/72pics/proj-temp-3.png" alt="installed result"/></p> | |
<li><p>Once you have completed the wizard, use it again to register | |
other customer projects as samples.</p></li> | |
<li><p>Check that the module you're developing now looks something like this in the Projects window:</p> | |
<br/> | |
<p><img src="../images/tutorials/projecttypes/72pics/proj-temp-4.png" alt="installed result"/></p> | |
<br/> | |
<p>You have now used the New Project Template wizard to register some project samples | |
in your application. Also notice that you have some ZIP files | |
containing your sample projects, | |
created by the Project Template wizard, together with several | |
classes from the NetBeans Wizard API. For further information, refer to the | |
<a href="https://platform.netbeans.org/tutorials/nbm-projectsamples.html">NetBeans Project | |
Sample Module Tutorial</a>.</p></li> | |
<li>After closing the second instance of the IDE with the installed module, | |
close and reopen the module in the original IDE before trying to run it. | |
The reason for this is that the nbproject\private\platform-private.properties | |
is changed by the second instance of the IDE to point to the testuserdir | |
of the module, when the module is opened. Reopening the module fixes this problem.</li> | |
<li>Run your module again and go to File | New Project. You should see your new project samples, | |
together with any other project samples registered in the application: | |
<br/><br/> | |
<p><img src="../images/tutorials/projecttypes/72pics/proj-temp-5.png" alt="installed result"/></p></li> | |
<p>Complete the wizard. At the end of the wizard, | |
the ZIP file is unzipped and the new project | |
is created.</p> | |
</ol> | |
</div> | |
<p>You now have support for a new type of project, including a set of samples that your users | |
can use to create skeleton projects of your type.</p> | |
<!-- ======================================================================================= --> | |
<div class="feedback-box"><a href="https://netbeans.org/about/contact_form.html?to=3&subject=Feedback:%20Project%20Type%207.2%20Module%20Tutorial">Send Us Your Feedback</a></div> | |
<!-- ======================================================================================== --> | |
<h2><a name="nextsteps"></a>Next Steps</h2> | |
<p>For more information about creating and developing NetBeans modules, see the following resources:</p> | |
<ul> | |
<li><a href="https://netbeans.org/kb/trails/platform.html">Other Related Tutorials</a></li> | |
<li><a href="http://bits.netbeans.org/dev/javadoc/index.html">NetBeans API Javadoc</a></li> | |
</ul> | |
</body> | |
</html> |