FeaturesPluginsDocs & SupportCommunityPartners

NetBeans IDE 5.0/5.5 FeedReader Tutorial

Download Links:

Welcome to the NetBeans IDE 5.0/5.5 FeedReader tutorial. The FeedReader application that you build in this tutorial is a simple RSS/Atom feed browser, modeled after the Sage plug-in for Mozilla Firefox. It presents a tree of feeds with subnodes representing individual feed entries that you can open in a browser. As an example, here a feed entry from the PlanetNetBeans RSS feed is opened in the IDE's internal browser:

 

Table of Contents

Introduction

Playing with the Application

Setting Up the Application

Creating the FeedReader Window

Trying Out the Application

Adding Code to the Application

Branding the Application

Distributing the Application

Introduction

Before beginning to code the FeedReader application, it's a good idea to familiarize yourself with some of the frequently used terms in the area of application development in NetBeans IDE. In the process, you will build a general understanding of the application that you will create, find out about what you are about to learn, and set up everything that you are going to need.

About Frequently Used Terms

This tutorial assumes that you have a basic conceptual understanding of the infrastructure that is built right into NetBeans. There is less to understand than you might think. Common terms to be familiar with are as follows:

  • NetBeans Platform. The application framework that provides everything most desktop applications need and nothing superfluous. The NetBeans Platform provides an application's common requirements, such as standard menus, toolbars, document management, and settings, right out-of-the-box. Building an application "on top of NetBeans" means that, instead of writing applications from scratch, you only provide the parts of your application that the NetBeans Platform does not already have. And you exclude the parts of the NetBeans Platform that you do not need. At the end of the development cycle, you bundle your application with the NetBeans Platform, but with your own executable and splash screen. Doing so saves you a lot of time and energy and results in a solid, reliable application.
  • System Filesystem. The general registry that contains NetBeans configuration information, built from the layer.xml configuration files of the registered modules. NetBeans stores a wide variety of configuration information in the System Filesystem. For example, the System Filesystem contains a folder called Menu , which contains subfolders with names such as File and Edit . These subfolders contain files that represent Java classes which implement the actions that appear in the "File" and "Edit" menus in the IDE.
  • Plug-in Module. A group of Java classes that provides an application with a specific feature. For example, the feature provided by the Java classes in the plug-in module that you build in this tutorial is an RSS/Atom feed reader. The Java classes use the manifest.mf file to declare the module and the layer.xml configuration file to register their functionality in the System Filesystem.

As an aside: in NetBeans terminology, "plug-in" is an adjective while "module" is a noun. There is no semantic difference between them.

  • NetBeans APIs. The public interfaces and classes which are available to plug-in module writers and application writers. They are divided into specific APIs for dealing with different types of functionality. The contents and behavior of the Java source packages and their subpackages, as specified in the API reference documentation, are the APIs. For the full NetBeans API List, click here .
  • Module Suite. A group of interdependent plug-in modules that are deployed together. The IDE helps you to brand the suite -- for example, you can add a splash screen and you can specify the parts of the NetBeans Platform that you don't want your application to provide.

About the FeedReader Application

While writing the FeedReader application, you will leverage a lot of the NetBeans infrastructure. The first piece you leverage is the System Filesystem. As pointed out earlier, the System Filesystem consists of configuration data: it is built from the configuration files (each of which is stored on disk as " layer.xml " files) of all the plug-in modules in the system, which it writes into the user's settings directory.

The System Filesystem uses the same infrastructure for recognizing files that is used for recognizing a user's files on disk. That means you can show a view of a folder inside the configuration data of the IDE just as easily as you can show a folder on disk. This way you can use all of the plumbing that is built into NetBeans for viewing files and showing trees and so forth. In fact, many views you see in the IDE use the same technique. For example, the Favorites window is a view of a folder in the System Filesystem, which contains links to files on disk. The contents of the Runtime window are also a view of a folder in the System Filesystem, which is why plug-in modules are able to add nodes to it. Since it uses the same mechanisms as are used for recognizing files on disk, the objects inside a folder can have whatever icons and display names you choose to give them.

The other piece you use is the Nodes API . The Nodes API is a generalization of TreeNode, though Nodes can be displayed in a variety of viewer components, not just trees. Nodes typically represent DataObjects. A DataObject is basically a parsed file, in other words, a Java object that knows the meaning of what is in a file or what the file represents and can do something with it. Nodes add features to DataObjects that the user interacts with, such as actions, localized display names, and icons.

So, after using wizards to generate some basic templates, you will use the layer.xml file to create a folder in the System Filesystem for RSS feed objects ( Creating the RssFeeds Folder ). Next, you will provide a view of the folder, similar to the IDE's Projects window or Files window, by building on top of one of the generated files ( Extending the Feed Window ). The view is rooted in your folder for RSS feed objects. Then you get the DataObject representing that folder, and its Node. You will wrap that node in a FilterNode ( Creating the RssFeeds Folder ). A FilterNode is a node that can act as a wrapper for another node; by default it behaves exactly as the other node does, but you can override methods on it to change things, so that you can give it your own icon, display name and actions. Then you wrap each of the node's children as well, doing the same thing for them as for the node.

Next, you will create an Add Feed action on the root node. When the user adds an RSS feed, you do something very simple: you create a new Feed object (really just an object that contains the URL, Creating the Feed Object ) and then serialize that Feed object as a file in your RSSFeeds folder. Since you're using NetBeans built-in infrastructure for visualizing files (because you're just getting the standard node for the folder, which can notice when files are added or removed), in a split second the node for the newly added feed will appear in the user interface. Using the System Filesystem this way means that the amount of code you have to write to save the list of RSS feeds on exit is... none at all! You save a feed when the user creates it, and that data is persisted to disk automatically. So, basically, you are just dropping Feed POJOs into a folder, and you happen to be showing a view of that folder. The system takes care of virtually everything else.

About this Tutorial

This tutorial intends to teach you the following:

  • Creating an application on top of the NetBeans Platform, using wizards and other facilities provided by NetBeans IDE 5.0/5.5.
  • Creating a skeleton windowing component and a skeleton menu item, using wizards in NetBeans IDE 5.0/5.5.
  • Using the NetBeans Nodes API to create nodes for feeds and feed entries.
  • Registering the application in the NetBeans System Filesystem.
  • Branding the application with items such as your own splash screen and titlebar.
  • Providing a distribution of the application.

Once the software is installed, this tutorial can be completed in 60 minutes.

About the Resources

Before you begin, you need to install the following resources on your computer:

  • NetBeans IDE 5.0/5.5 ( download )
  • Java Standard Development Kit (JDK) 1.4.2 ( download ) or 5.0 ( download )
  • Rss and atOM utilitiEs ( download )
  • Rome Fetcher ( download )
  • JDom ( download )
  • FeedReader icon and splash screen ( download ). You can put the icon and splash screen anywhere in your filesystem. Later in this tutorial you will be shown how to include them in your NetBeans project folder.

Playing with the Application

Before you start writing the application, you might want to acquaint yourself with the final product. Fortunately, the FeedReader application is an official NetBeans sample, bundled with the IDE, and waiting for you to pull it from the New Project wizard.

Installing the Application

Take the following steps to install the sample:

  1. Start the IDE.
  2. Choose File > New Project (Ctrl-Shift-N), then expand Samples, then select NetBeans Plug-in Modules, and choose FeedReader. Click Next.
  3. Name the project whatever you like, choose a location to store your project, and click Finish. The IDE opens the FeedReader sample.
  4. Right-click the FeedReader Application project node and choose Run Project. The application starts up. During installation, notice that a splash screen is displayed.

Introducing the Application

The FeedReader application displays the RSS/Atom Feeds window, containing a node called RSS/Atom Feeds.

  1. Right-click RSS/Atom Feeds node, choose Add, and enter a URL to an RSS/Atom feed. For example, enter the NetBeans.org RSS feed ( http://www.netbeans.org/rss-091.xml) or the PlanetNetBeans RSS feed ( http://www.planetnetbeans.org/rss20.xml) .
  2. Click OK. A node is added for the feed; its children are the feed entries. Normally, feed entries are articles or topics that the feed makes available.
  3. Repeat the process and add more feeds.
  4. Double-click a feed entry to open it in the IDE's default browser.

Other functionality provided by the rich-client application:

  • Right-click a node and choose Add Folder to create a new folder, which you can use to organize your feeds.
  • Right-click a feed and choose Delete to remove a feed.
  • Right-click a feed entry and choose Open to open it in the editor pane.

Introducing the Sources

The FeedReader sample consists of main files (Java classes) and supporting files.

Main files:

  • Feed.java
    Encapsulates a URL and its associated Rome feed.
  • FeedAction.java
    Defines the action that appears in the Window menu with the label Open Feed Window. It opens the Feed Window.
  • FeedTopComponent.java
    Defines the action that appears in the Window menu with the label Open Feed Window. It opens the Feed Window.
  • RssNode.java
    Node class for the RSS feeds folder and its children. Note that mainly you proxy the actual DataNode/DataFolder for the RssFeeds folder in the System Filesystem. This gives you a lot of functionality for free, like the ability to delete, listening for changes, etc. So when you add a new RSS feed, all you do is create a new Feed object and serialize it into that folder.

Supporting files:

  • build.xml
    Provides Ant targets for common tasks, such as building and running the application.
  • Bundle.properties
    Localization key-value pairs.
  • FeedTopComponentSettings.xml
    Specifies all the interfaces of the FeedReader application.
  • FeedTopComponentWstcref.xml
    Specifies a reference to the component.
  • layer.xml
    Registers folders and files in the System Filesystem. You will be shown how to use the System Filesystem Browser to work with this file.
  • project.xml
    Declares project metadata, such as module dependencies. You will be shown how to use the Project Properties dialog box to work with this file.
  • rss16.gif
    Icon displayed by the application's menu item and in its Help > About dialog box.
  • splash.gif
    Splash screen.

Setting Up the Application

In NetBeans IDE, building an application on top of NetBeans starts with generating a large number of files which will serve as the foundation of your application. For example, the IDE provides a Module Project wizard, a Module Suite Project wizard, and a Library Wrapper Module Project wizard that set up all the basic files needed by plug-in modules and applications built on the NetBeans Platform.

  • Module Suite Project. A project that groups a set of module projects and library wrapper module projects that have dependencies on each other, and lets you deploy them together as a unit.
  • Library Wrapper Module Project. A project that puts a library JAR file on its classpath and exports some or all of the JAR file's packages from the module as public packages.
  • Module Project. A project for implementing the functionality, business logic, and user interface of a plug-in module or application built on the NetBeans Platform.

Creating the Module Suite Project

  1. Choose File > New Project (Ctrl-Shift-N). Under Categories, select NetBeans Plug-in Modules. Under projects, select Module Suite Project. Click Next.
  2. In the Name and Location panel, type feedreader-suite in Project Name. Change the Project Location to any directory on your computer, such as c:\mymodules. Click Finish.

The IDE creates the feedreader-suite project. The project will contain the module project and library wrapper module projects that you will create in the following subsections.

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).

Wrapping the Libraries

You could bundle the entire FeedReader application into a single plug-in module. However, the application needs the Rome, Rome Fetcher, and JDom libraries:

  • Rome. Reads RSS and Atom feeds, using a very simple API.
  • Rome Fetcher. Allows the retrieval of feeds via HTTP.
  • JDom. Is an XML parsing API. The only reason FeedReader needs it is because the Rome library uses it.

Later, if you want to extend the FeedReader application with more modules that may use these libraries, it would be better for them to depend on just the library modules, rather than the entire FeedReader. Also, library modules can be autoloading, which means that NetBeans will only load them when needed. Until that happens, it won't take up any memory at runtime.

  1. Choose File > New Project (Ctrl-Shift-N). Under Categories, select NetBeans Plug-in Modules. Under projects, select Library Wrapper Module Project. Click Next.
  2. In the Select Library panel, browse to the folder where you downloaded JDom, and then select jdom.jar and LICENSE.txt. Click Next.
  3. In the Name and Location panel, accept all the defaults. Note that the library wrapper module project will be housed within the module suite project. You could also house it somewhere else, but for versioning purposes it is a good idea to put it within the module suite project. Also note that the feedreader-suite module suite project is selected in the Add to Module Suite drop-down. Click Next.
  4. In the Basic Module Configuration panel, accept all the defaults. Click Finish. The new library wrapper module project opens in the IDE and displays in the Projects window.
  5. Create a library wrapper module project for Rome. Accept all the defaults.
  6. Create a library wrapper module project for Rome Fetcher.

You now have a library wrapper module project containing the JDom JAR file and two other library wrapper module projects containing the Rome and Rome Fetcher JAR files.

Creating the Module Project

  1. Choose File > New Project (Ctrl-Shift-N). Under Categories, select NetBeans Plug-in Modules. Under projects, select Module Project. Click Next.
  2. In the Name and Location panel, type FeedReader in Project Name. Accept all the defaults. Click Next.
  3. In the Basic Module Configuration panel, replace yourorghere in Code Name Base with myorg , so that the whole code name base is org.myorg.feedreader. Leave the location of the localizing bundle and XML layer, so that they will be stored in a package with the name org/myorg/feedreader. Click Finish.

The IDE creates the FeedReader project. The project contains all of the module's 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). The Projects window should now show the following:

Right-click the feedreader-suite project node, choose Properties, and click Sources in the Project Properties dialog box. The panel shows the modules that were added to the module suite while you were creating their projects. You should see FeedReader , jdom , rome , and rome-fetcher listed in the Suite Modules list.

Creating the FeedReader Window

In this section you use the Window Component wizard to generate files that create a custom windowing component and an action to invoke it. The wizard also registers the action as a menu item in the layer.xml configuration file and adds entries for serializing the windowing component. Right after finishing this section, you are shown how to try out the files that the Window Component wizard generates for you.

  1. Right-click the FeedReader project node and choose New > File/Folder. Under Categories, select NetBeans Module Development. Under File Types, select Window Component. Click Next.
  2. In the Basic Settings panel, select explorer in the drop-down list and click Open on Application Start. Click Next.
  3. In the Name and Location panel, type Feed as the Class Name Prefix and browse to the location where you saved rss16.gif. The GIF file will be shown in the menu item that invokes the action.
  4. Click Finish.

The IDE creates the following new files:

  • FeedAction.java. Defines the action that appears in the Window menu with the label Open Feed Window and the rss16.gif image. It opens the Feed Window.
  • FeedTopComponent.java. Defines the Feed Window.
  • FeedTopComponentSettings.xml. Specifies all the interfaces of the org.myorg.feedreader rich-client application. Enables easy lookup of instances, without the need to instantiate each. Avoids the need to load classes or create objects and therefore improves performance. Registered in the Windows2/Components folder of the layer.xml file.
  • FeedTopComponentWstcref.xml. Specifies a reference to the component. Enables the component to belong to more than one mode. Registered in the Windows2/Modes folder of the layer.xml file.

The IDE modifies the following existing files:

  • project.xml. Two module dependencies have been added, Utilities API (click here for Javadoc) and Window System API (click here for Javadoc).
  • Bundle.properties. Three key-value pairs have been added:

    CTL_FeedAction.
    Localizes the label of the menu item, defined in FeedAction.java .
    CTL_FeedTopComponent.
    Localizes the label of FeedTopComponent.java .
    HINT_FeedTopComponent. Localizes the tooltip of FeedTopComponent.java .

  • Finally, three registration entries have been added to the layer.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.1//EN"
			"http://www.netbeans.org/dtds/filesystem-1_1.dtd">
<filesystem>
	<folder name="Actions">
		<folder name="Window">
			<file name="org-myorg-feedreader-FeedAction.instance"/>
		</folder>
	</folder>
	<folder name="Menu">
		<folder name="Window">
			<file name="FeedAction.shadow">
				<attr name="originalFile" stringvalue="Actions/Window/org-myorg-feedreader-FeedAction.instance"/>
			</file>
		</folder>
	</folder>
	<folder name="Windows2">
		<folder name="Components">
			<file name="FeedTopComponent.settings" url="FeedTopComponentSettings.xml"/>
		</folder>
		<folder name="Modes">
			<folder name="explorer">
				<file name="FeedTopComponent.wstcref" url="FeedTopComponentWstcref.xml"/
			</folder>
		</folder>
	</folder>
</filesystem>

This is what the entries in the layer.xml file do:

  • <Actions>
    Registers the action as an action in the Window folder.
  • <Menu>
    Registers the action as a menu item in the Window menu.
  • <Windows2>
    Registers the FeedTopComponentSettings.xml, which is used for looking up the windowing component. Registers the component reference file FeedTopComponentWstcref.xml in the `editor' window. To dock the component in a different window, you can manually change `explorer' to one of these values instead:

    commonpalette|
    editor
    properties
    leftSlidingSide
    rightSlidingSide\
    bottomSlidingSide
    navigator
    debugger
    output

Trying Out the Application

Without having typed a single line of code, you can already take your application for a spin. Trying it out means deploying the application to another instance of your installation of NetBeans IDE and then checking to see that the empty Feed Window displays correctly.

The IDE uses an Ant build script to build and install your application. The build script was created for you when you created the module project.

  1. In the Projects window, right-click the feedreader-suite project and choose Run. The application is built and is installed in another instance of your installation of NetBeans IDE. The IDE opens and displays the new Feed Window.
  2. In the IDE's Window menu, you should see the new menu item.
  3. Hover over the window's label. You should see the hint displayed.
  4. Click the small cross on the right side of the Feed Window's label. The window closes.
  5. Choose Window > Open Feed Window to reopen the window.
  6. From the main menu, choose File > Exit to exit the IDE.

Adding Code to the Application

Now that you have laid the basis for your application, it's time to begin adding your own code. Before doing so, you need to specify the application's dependencies. Then, you use the New File wizard and the Source Editor to create and code the main classes that were introduced in the Introducing the Sources section.

Specifying the Application's Dependencies

You need to subclass several classes that belong to the NetBeans APIs. Each has to be declared as a dependency. Use the Project Properties dialog box for this purpose.

  1. In the Projects window, right-click the FeedReader project and choose Properties. In the Project Properties dialog box, click Libraries. Notice that some APIs have already been declared as Module Dependencies. This was done for you by the Window Component wizard.
  2. Click Add... at the top of the Libraries panel.
  3. Add the following APIs:

    Actions API
    Datasystems API
    Dialogs API
    Explorer and Property Sheet API
    File System API
    Nodes API
    rome
    rome-fetcher

  4. Click OK to exit the Project Properties dialog box.
  5. Optionally, in the Projects window, expand the FeedReader module project in the IDE, expand the Important Files node, double-click Project Metadata so that it opens in the IDE. Notice that the APIs you selected have been declared as dependencies.
  6. Next, you need to make Rome dependent on JDom. Right-click the Rome library wrapper moduleproject in the Projects window and choose Properties. In the Project Properties dialog box, click Libraries and then click Add next to the Module Dependencies list. Add JDom. Click OK to exit the Project Properties dialog box.
  7. Finally, since Rome Fetcher depends on both Rome and JDom, you need to make Rome Fetcher dependent on Rome. Because Rome already depends on JDom, you do not need to make Rome Fetcher dependent on JDom.
  8. Optionally, in the Projects window, expand the Rome module in the IDE, expand the Important Files node, double-click Project Metadata so that it opens in the IDE. Notice that the Rome JAR files are on the classpath, that JDom is a module dependency, and that their packages are publicly exposed. You should see the same in Rome Fetcher's Project Metadata file.

Creating the RssFeeds Folder

You will use the System Filesystem Browser to add a folder for RSS feed objects. Later, you will add code to FeedTopComponent.java , which was created for you by the Window Component wizard, to view the content of the folder.

  1. In the Projects window, expand the FeedReader project node, expand the Important Files node, and then expand the XML Layer node. The System Filesystem Browser opens. It shows you the following nodes:

    <this layer>.
    The folders provided by the current plug-in module
    <this layer in context>. All the folders available in the System Filesystem.

  2. Right-click the <this layer> node and choose New > Folder.
  3. Type RssFeeds in the New Folder dialog box. Click OK. Double-click the node for the layer.xml file so that it opens in the Source Editor. Notice that this entry has been added:

    <folder name="RssFeeds"/>

Creating the Feed Object

Next you create a simple POJO that encapsulates a URL and its associated Rome feed.

  1. Right-click the FeedReader project node, choose New > File/Folder and then choose the Java Class file type in the Java Class category. Click Next.
  2. Name the class Feed and select org.myorg.feedreader in the Package drop-down. Click Finish.
  3. In the Source Editor, replace the default Feed class with the following:
public class Feed implements Serializable {
	private static FeedFetcher s_feedFetcher = new HttpURLFeedFetcher( HashMapFeedInfoCache.getInstance());
	private transient SyndFeed m_syndFeed;
	private URL m_url;
	private String m_name;

	protected Feed() {
	}

	public Feed(String str) throws MalformedURLException
	{
		m_url = new URL(str);
		m_name = str;
	}
		
	public URL getURL()
	{
		return m_url;
	}
		
	public SyndFeed getSyndFeed() throws IOException {
		if (m_syndFeed == null) {
			try {
				m_syndFeed = s_feedFetcher.retrieveFeed(m_url);
				if (m_syndFeed.getTitle() != null)
					m_name = m_syndFeed.getTitle();
			} catch(Exception ex) {
				throw new IOException(ex.getMessage());
			}
		}
		return m_syndFeed;
	}

	public String toString(){
		return m_name;
	}

}

A lot of code is underlined, because you have not declared their packages. You do this in the next steps.

Take the following steps to reformat the file and declare its dependencies:

  1. Press Ctrl-Shift-F to format the code.
  2. Press Alt-Shift-F, select java.net.URL , click OK, and the IDE adds the following import statements below the package statement:

    import com.sun.syndication.feed.synd.SyndFeed;
    import com.sun.syndication.fetcher.FeedFetcher;
    import com.sun.syndication.fetcher.impl.HashMapFeedInfoCache;
    import com.sun.syndication.fetcher.impl.HttpURLFeedFetcher;
    import java.io.IOException;
    import java.io.Serializable;
    import java.net.MalformedURLException;
    import java.net.URL;

All the red underlining should now have disappeared. If not, do not continue with this tutorial until you have solved the problem.

Extending the Feed Window

  1. Double-click FeedTopComponent.java so that it opens in the Source Editor.
  2. Type implements ExplorerManager.Provider at the end of the class declaration.
  3. Press Alt-Enter in the line and click on the suggestion. The IDE adds an import statement for the required package org.openide.explorer.ExplorerManager .
  4. Press Alt-Enter again and click on the suggestion. The IDE implements the abstract method getExplorerManager() .
  5. Type return manager; in the body of the new getExplorerManager() method. Press Alt-Enter in the line and let the IDE create a field called manager for you. Replace the default definition with this one:

    private final ExplorerManager manager = new ExplorerManager();

  6. Right below the field declaration in the previous step, declare this one:

    private final BeanTreeView view = new BeanTreeView();

  7. Finally, add the following code to the end of the constructor:
setLayout(new BorderLayout());
add(view, BorderLayout.CENTER);
view.setRootVisible(true);
try {
	manager.setRootContext(new RssNode.RootRssNode());
} catch (DataObjectNotFoundException ex) {
	ErrorManager.getDefault().notify(ex);
}
ActionMap map = getActionMap();
map.put("delete", ExplorerUtils.actionDelete(manager, true));
associateLookup(ExplorerUtils.createLookup(manager, map));

Now a lot of code is underlined, because you have not declared their associated packages. You do this in the next steps.

Take the following steps to reformat the file and declare its dependencies:

  1. Press Ctrl-Shift-F to format the code.
  2. Press Alt-Shift-F, select org.openide.ErrorManager , click OK, and the IDE adds several import statements below the package statement. The complete list of import statements should now be as follows:

    import java.awt.BorderLayout;
    import java.io.Serializable;
    import javax.swing.ActionMap;
    import org.openide.ErrorManager;
    import org.openide.explorer.ExplorerManager;
    import org.openide.explorer.ExplorerUtils;
    import org.openide.explorer.view.BeanTreeView;
    import org.openide.loaders.DataObjectNotFoundException;
    import org.openide.util.NbBundle;
    import org.openide.util.RequestProcessor;
    import org.openide.util.Utilities;
    import org.openide.windows.TopComponent;

  3. Note that the line manager.setRootContext(new RssNode.RootRssNode()); is still underlined in red, because you have not created RssNode.java yet. This you will do in the next subsection. All other red underlining should now have disappeared. If not, do not continue with this tutorial until you have solved the problem.

Creating the RssNode Class

  1. Create RssNode.java in the org.myorg.feedreader package.
  2. Replace the default class with the following:
class RssNode extends FilterNode {

	/** Declaring the children of the root RSS node */
	public RssNode(Node folderNode) throws DataObjectNotFoundException { 
		super(folderNode, new RssFolderChildren(folderNode));
	}
	/** Declaring the Add Feed action and Add Folder action */
	public Action[] getActions(boolean popup) {
		DataFolder df = (DataFolder)getLookup().lookup(DataFolder.class);
		return new Action[] { new AddRssAction(df),
			new AddFolderAction(df) };
	}

	/** Getting the root node */
	public static class RootRssNode extends RssNode {
		public RootRssNode() throws DataObjectNotFoundException {
			super(DataObject.find(
				Repository.getDefault().getDefaultFileSystem()
				.getRoot().getFileObject("RssFeeds")).getNodeDelegate());
		}
		public String getDisplayName() {
			return NbBundle.getMessage(RssNode.class, "FN_title");
		}
	}

	/** Getting the children of the root node */
	private static class RssFolderChildren extends FilterNode.Children {
		RssFolderChildren(Node rssFolderNode) {
			super(rssFolderNode);
		}
		protected Node[] createNodes(Object key) {
			Node n = (Node) key;
			try {
				if (n.getLookup().lookup(DataFolder.class) != null) {
					return new Node[] { new RssNode(n) };
				} else {
					Feed feed = getFeed(n);
					if (feed != null) {
						return new Node[] { new OneFeedNode(n, feed.getSyndFeed()) };
					} else {
						// best effort
						return new Node[] { new FilterNode(n) };
					}
				}
			} catch (IOException ioe) {
				ErrorManager.getDefault().notify(ioe);
			} catch (IntrospectionException exc) {
				ErrorManager.getDefault().notify(exc);
			}
			// Some other type of Node (gotta do something)
			return new Node[] { new FilterNode(n) };
		}
	}

	/** Getting the feed node and wrapping it in a FilterNode */
	private static class OneFeedNode extends FilterNode {
		OneFeedNode(Node feedFileNode, SyndFeed feed) throws IOException, IntrospectionException {
			super(feedFileNode,
				new FeedChildren(feed),
				new ProxyLookup(new Lookup[] {
			Lookups.fixed(new Object[] { feed  }),
					feedFileNode.getLookup() }));
		}
		
		public String getDisplayName() {
			SyndFeed feed = (SyndFeed) getLookup().lookup(SyndFeed.class);
			return  feed.getTitle();
		}

		public Image getIcon(int type) {
			return Utilities.loadImage("org/myorg/feedreader/rss16.gif");
		}

		public Image getOpenedIcon(int type) {
			return getIcon(0);
		} 
		public Action[] getActions(boolean context) {
			return new Action[] { SystemAction.get(DeleteAction.class) };
		}
	}
	
	/** Defining the children of a feed node */
	private static class FeedChildren extends Children.Keys {
		private final SyndFeed feed;
		public FeedChildren(SyndFeed feed) {
			this.feed = feed;
		}
		
		protected void addNotify() {
			setKeys(feed.getEntries());
		}
		
		public Node[] createNodes(Object key) {
			try {
				return new Node[] { new EntryBeanNode((SyndEntry) key) };
			} catch (final IntrospectionException ex) {
				ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ex);
				//Should never happen - no reason for it to fail abov
				return new Node[] { new AbstractNode(Children.LEAF) {
					public String getHtmlDisplayName() {
						return "<font color='red'>" + ex.getMessage() + "</font>";
					}
				}};
			}
		}
	}

	/** Wrapping the children in a FilterNode */
	private static class EntryBeanNode extends FilterNode {
		private SyndEntry entry;
		public EntryBeanNode(SyndEntry entry) throws IntrospectionException {
			super(new BeanNode(entry), Children.LEAF, Lookups.fixed(new Object[] { entry, new EntryOpenCookie(entry) }));
			this.entry = entry;
		}
		
		/** Using HtmlDisplayName ensures any HTML in RSS entry titles are             
		/**properly handled, escaped, entities resolved, etc. */
		public String getHtmlDisplayName() {
			return entry.getTitle();
		}

		/** Making a tooltip out of the entry's description */
		public String getShortDescription() {
			return entry.getDescription().getValue();
		}

		/** Providing the Open action on a feed entry */
		public Action[] getActions(boolean popup) {
			return new Action[] { SystemAction.get(OpenAction.class) };
		}

		public Action getPreferredAction() {
			return (SystemAction) getActions(false) [0];
		}

      }
	
	/** Specifying what should happen when the user invokes the Open action */
	private static class EntryOpenCookie implements OpenCookie {
		private final SyndEntry entry;
		EntryOpenCookie(SyndEntry entry) {
			this.entry = entry;
		}

		public void open() {
			try {
				URLDisplayer.getDefault().showURL(new URL(entry.getUri()));
			} catch (MalformedURLException mue)       {
				ErrorManager.getDefault().notify(mue);
			}
		}
      }

	/** Looking up a feed */
	private static Feed getFeed(Node node) {
		InstanceCookie ck = (InstanceCookie) node.getCookie(InstanceCookie.class);
		if (ck == null) {
			throw new IllegalStateException("Bogus file in feeds folder: " + node.getLookup().lookup(FileObject.class));
		}
		try {
			return (Feed) ck.instanceCreate();
		} catch (ClassNotFoundException ex) {
			ErrorManager.getDefault().notify(ex);
		} catch (IOException ex) {
			ErrorManager.getDefault().notify(ex);
		}
		return null;
	}

	/** Creating an action for adding a folder to organize feeds into groups */
	private static class AddFolderAction extends AbstractAction {
		private DataFolder folder;
		public AddFolderAction(DataFolder df) {
			folder = df;
			putValue(Action.NAME, NbBundle.getMessage(RssNode.class, "FN_addfolderbutton"));
		}
		public void actionPerformed(ActionEvent ae) {
			NotifyDescriptor.InputLine nd = new NotifyDescriptor.InputLine(
				NbBundle.getMessage(RssNode.class, "FN_askfolder_msg"), //NOI18N
				NbBundle.getMessage(RssNode.class, "FN_askfolder_title"), //NOI18N
				NotifyDescriptor.OK_CANCEL_OPTION,
				NotifyDescriptor.PLAIN_MESSAGE);
			Object result = DialogDisplayer.getDefault().notify(nd);
				if (result.equals(NotifyDescriptor.OK_OPTION)) {
					final String folderString = nd.getInputText();
					try {
						DataFolder.create(folder, folderString);
					} catch (IOException ex) {
						ErrorManager.getDefault().notify(ex);
					}
				}
		}
	}

	/** Creating an action for adding a feed */
	private static class AddRssAction extends AbstractAction {
		private DataFolder folder;
		public AddRssAction(DataFolder df) {
			folder = df;
			putValue(Action.NAME, NbBundle.getMessage(RssNode.class, "FN_addbutton"));
		}
		public void actionPerformed(ActionEvent ae) {
			NotifyDescriptor.InputLine nd = new NotifyDescriptor.InputLine(
				NbBundle.getMessage(RssNode.class, "FN_askurl_msg"), //NOI18N
				NbBundle.getMessage(RssNode.class, "FN_askurl_title"), //NOI18N
				NotifyDescriptor.OK_CANCEL_OPTION,
				NotifyDescriptor.PLAIN_MESSAGE);
			Object result = DialogDisplayer.getDefault().notify(nd);
			if (result.equals(NotifyDescriptor.OK_OPTION)) {
				final String urlString = nd.getInputText();
				try {
					Feed f = new Feed(urlString);
					FileObject fld = folder.getPrimaryFile();
					String baseName = "RssFeed"; //NOI18N
					int ix = 1;
					while (fld.getFileObject(baseName + ix, "ser") != null) {
						ix++;
					}
					FileLock lock = null;
					try {
						FileObject writeTo = fld.createData(baseName + ix, "ser");
						lock = writeTo.lock();
						ObjectOutputStream str = new ObjectOutputStream(writeTo.getOutputStream(lock));
						str.writeObject(f);
					} catch (IOException ioe) {
						ErrorManager.getDefault().notify(ioe);
					} finally {
						if (lock != null)
							lock.releaseLock();
					}
				} catch (MalformedURLException ex) {
					IllegalArgumentException iae = new IllegalArgumentException(NbBundle.getMessage(RssNode.class,
						"FN_askurl_err", urlString), ex); //NOI18N
					throw iae;
				}
			}
		}
	}
}

A lot of code is underlined, because you have not declared their packages. You do this in the next step. Now take the following steps to reformat the file and declare its dependencies:

  1. Press Ctrl-Shift-F to format the code.
  2. Press Alt-Shift-F and select the following import statements:

    org.openide.actions.OpenAction
    javax.swing.Action
    org.openide.nodes.Children
    org.openide.filesystems.FileLock
    java.awt.Image
    org.openide.nodes.Node
    org.openide.ErrorManager
    org.openide.util.Utilities
    java.net.URL
    org.openide.filesystems.Repository
    java.beans.IntrospectionException

  3. Click OK, and the IDE adds a lot of import statements below the package statement. All red underlining should now have disappeared. If not, do not continue with this tutorial until you have solved the problem.

Localizing the RssNode Class

  1. Open the FeedReader module's Bundle.properties file.
  2. Add the following key-value pairs:

    FN_title=RSS/Atom Feeds
    FN_addbutton=Add
    FN_askurl_title=New Feed
    FN_askurl_msg=Enter the URL of an RSS/Atom Feed
    FN_askurl_err=Invalid URL: {0}|
    FN_addfolderbutton=Add Folder
    FN_askfolder_msg=Enter the folder name
    FN_askfolder_title=New Folder

Here is an explanation of the new key-value pairs, which localize strings defined in RssNode.java :

  • FN_title. Localizes the label of the highest node in the Feed Window.

Localization of user interface for adding a feed:

  • FN_addbutton. Localizes the label of the Add menu item that appears in the highest node's pop-up.
  • FN_askurl_title. Localizes the title of the New Feed dialog box.
  • FN_askurl_msg. Localizes the message that appears in the New Feed dialog box.
  • FN_askurl_err. Localizes the error string that is displayed if the URL is invalid.

Localization of user interface for adding a folder:

  • FN_addfolderbutton. Localizes the label of the Add Folder menu item that appears in the highest node's pop-up.
  • FN_askfolder_msg. Localizes the message that appears in the Add Folder dialog box.
  • FN_askfolder_title. Localizes the title of the Add Folder dialog box.

Branding the Application

Now that you are at the end of the development cycle, while you are wrapping up the application, you are concerned with the following questions:

  • What should the name of the application's executable be?
  • What should the user see when starting up my application? A progress bar? A splash screen? Both?
  • When my application starts up, what should be displayed in the title bar?
  • Do I need all the menus and toolbar buttons that the NetBeans Platform provides by default?

These questions relate to branding, the activity of personalizing an application built on top of the NetBeans Platform. The IDE provides a panelin the Project Properties dialog box of module suite projects to help you with branding.

  1. Right-click the feedreader-suite project node (not the FeedReader project node) and choose Properties. In the Project Properties dialog box, click Application.
  2. In the Application panel, click Create Standalone Application. The IDE prompts you to exclude the IDE-related modules. Click Exclude, because the FeedReader application will not need IDE-related modules, such as those that provide the Source Editor in NetBeans IDE.
  3. Type feedreader in Branding Name. Type Feed Reader Application in Application Title. The value in branding name sets the executable's name, while the value in application title sets the application's title bar.
  4. Click Browse to browse to the rss16.gif icon. The icon will be displayed in the Help > About dialog box.
  5. In the Splash Screen panel, click Browse to browse to splash.gif . Optionally, change the color and text size of the progress bar. Or, if you do not want a progress bar, unselect Enabled.
  6. Click OK.
    The branding folder is created in the FeedReader Application project. It is visible in the Files window (Ctrl-2).
  7. In the Files window, expand the FeedReader Application project node. Then continue expanding nodes until you find this one:
    branding/modules/org-netbeans-core-window.jar/org/netbeans/core/windows
  8. Right-click the node, choose New > File/Folder, and select Folder in the Other category. Click Next and name the folder resources . Click Finish.
  9. Right-click the new resources node, choose New > File/Folder, and select XML Document in the XML category. Click Next. Name the file layer . Click Next and then click Finish. Replace the content of the new layer.xml file with the following:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.1//EN" "http://www.netbeans.org/dtds/filesystem-1_1.dtd">
<!--
This is a `branding' layer.  It gets merged with the layer file it's branding.
In this case, it's just hiding menu items and toolbars we don't want.
-->
<filesystem>

	<!-- hide unused toolbars -->
	<folder name="Toolbars">
		<folder name="File_hidden"/>
		<folder name="Edit_hidden"/>
	</folder>

	<folder name="Menu">
		<folder name="File">
			<file name="org-openide-actions-SaveAction.instance_hidden"/>
			<file name="org-openide-actions-SaveAllAction.instance_hidden"/>
			<file name="org-netbeans-core-actions-RefreshAllFilesystemsAction.instance_hidden"/>            
			<file name="org-openide-actions-PageSetupAction.instance_hidden"/>
			<file name="org-openide-actions-PrintAction.instance_hidden"/>
		</folder>
		<folder name="Edit_hidden"/>
		<folder name="Tools_hidden"/>
	</folder>

</filesystem>

Distributing the Application

The IDE uses an Ant build script to create a distribution of your application. The build script is created for you when you create the project.

  1. In the Projects window, right-click the FeedReader Application project node and choose Build ZIP Distribution. The Output window shows you where the ZIP distribution is created.
  2. In your filesystem, find the feedreader.zip distribution in the dist folder in your project directory. Unzip it. Launch the application, which you will find in the bin folder. During start up, the splash screen is displayed. When the application has started up, go to the Help > About dialog box and notice the icon and splash screen that you specified in the Branding the Application section.

When it is up and running, the FeedReader application displays the RSS/Atom Feeds window, containing a node called RSS/Atom Feeds. See Playing with the Application for details.

Congratulations! You have completed the FeedReader tutorial.


Companion
Projects:
MySQL Database Server   Open JDK: an Open SourceJDK   GlassFish Community: an Open Source Application Server    Mobile & Embedded Community    Open Solaris   java.net - The Source for Java Technology Collaboration   Open ESB - The Open Enterprise Service Bus Powered by