blob: 967323c37f30832ff18e32f773761b6790887c46 [file] [log] [blame]
<!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 the NetBeans Platform</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/81/netbeans-stamp.png" 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 7 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 &gt; 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&lt;?&gt; createNodes(Project project) {
CustomerProject p = project.getLookup().lookup(CustomerProject.class);
assert p != null;
return new TextsNodeList(p);
}
private class TextsNodeList implements NodeList&lt;Node&gt; {
CustomerProject project;
public TextsNodeList(CustomerProject project) {
this.project = project;
}
@Override
public List&lt;Node&gt; keys() {
FileObject textsFolder =
project.getProjectDirectory().getFileObject("texts");
List&lt;Node&gt; result = new ArrayList&lt;Node&gt;();
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&lt;? extends Project&gt; 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&lt;?&gt; createNodes(Project project) {
ReportsSubprojectProvider rsp = project.getLookup().
lookup(ReportsSubprojectProvider.class);
assert rsp != null;
return new ReportsNodeList(rsp.getSubprojects());
}
private class ReportsNodeList implements NodeList&lt;Project&gt; {
Set&lt;? extends Project&gt; subprojects;
public ReportsNodeList(Set&lt;? extends Project&gt; subprojects) {
this.subprojects = subprojects;
}
@Override
public List&lt;Project&gt; keys() {
List&lt;Project&gt; result = new ArrayList&lt;Project&gt;();
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&amp;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>