FeedReader

Author: Rich Unger
This article was submitted as part of the Win With NetBeans contest.

A NetBeans Platform Sample and Tutorial

What does FeedReader do?

FeedReader is a basic RSS/Atom feed browser, modeled after the Sage plugin for Mozilla Firefox. It consists of:

  • A list of feeds (URLs to rss/rdf/atom descriptor files)

  • A list of headlines from each feed

  • A browser window (mozilla embedded in a JFrame)

The embedded mozilla frame is provided by the JDIC library, which uses JNI calls. I have configured this example to use both the linux and windows versions of the JDIC binaries.

Who Is This Tutorial For?

The primary audience is, of course, people who want to build applications on the NetBeans platform. I will attempt to fully document how I created FeedReader, and what each line in the manifests, layer files, and java source does, especially in cases where I had to do something ... a little bit quirky.

That is the secondary reason for this tutorial. Quirkiness and odd workarounds will be called out with a “Quirk Box”:

You have to do something quirky to get this done.

In all such cases, I will link to the appropriate open issue in IssueZilla. Hopefully, this can be a living document, and these Quirk Boxes can be eliminated over time, resulting in a platform for which it is easier to create applications.

Getting Started

Full source code is available.

For the purposes of this tutorial, I am not going to assume any particular IDE. In fact, let's just say you've got a text editor, JDK 1.5.0 (a bug in 1.4.x prevents compilation of this sample, though it will run in 1.4.x), and Apache Ant 1.6.2 or greater. To simplify my command line examples, I will assume bash (unix or cygwin). Translate to the windows command line if you wish. That works fine, too.

I just don't want to mix you up talking about the NetBeans IDE and the NetBeans platform. Follow this tutorial first. Undertand how modules work. Then worry about tools (like the NetBeans IDE) to make building them a little easier.

The first thing you need to get started is a copy of the NetBeans platform. You don't need to build it from source code. You can just download a binary release and unzip it somewhere. I put mine in /home/rich/netbeans.

Next, it would be helpful to have some sample source code to get you started. I started with my cluster build harness. It's convenient because it already has ant build scripts for building and packaging modules, and it's got a starter module called “snipe”. Of course, now that I've written this, you could just as easily start your application with the source tree for FeedReader.

I unzipped my harness to /home/rich/src/rss.

Finally, you need to configure your build scripts, so they know where to find your netbeans platform installation:

Modify a few lines in /home/rich/src/rss/nbbuild/user.build.properties:

netbeans.dest.dir=/home/rich/netbeans
clustername=rssreader1

By convention, clusters are given version numbers at the end, though it is not strictly required.

Definition: A cluster is a set of modules and associated resource files. A netbeans installation can be made up of a set of clusters, which are selected when you launch netbeans. The NetBeans IDE, for example, runs the platform4, ide4, nb4.0, and extra clusters. The idea behind clusters is that you can have one installation of the NetBeans platform, and many branded applications sharing the same clusters. I could, for example, install the rssreader1 cluster into a Netbeans IDE installation, and by running 2 different launch scripts, I could get the IDE and the FeedReader, each sharing the platform4 cluster, but otherwise being completely separate applications.

Next, modify a few lines in /home/rich/src/rss/nbbuild/user.cluster.properties, in order to bring the cluster name in synch with what we just specified in user.build.properties:

user.cluster=cluster.rssreader1
cluster.rssreader1.dir=rssreader1
cluster.rssreader1= snipe

Now, I could run ant in the nbbuild/ directory, and I'd have a directory /home/rich/netbeans/rssreader1 which contains the snipe module. Of course, I don't want a snipe module, so I then type ant clean to get rid of it.

It's time to move on to the modules we do want.

Library Modules

You could bundle the entire FeedReader application into a single module. It's just not very ... well ... modular. It happens that FeedReader requires the libraries JDOM, Rome, and JDIC. If you ever want to extend this application with more modules that may use these libraries, it would be better to depend on just the library module, rather than the entire FeedReader. Also, you can make the library modules autoloading.

Definition: An autoload module is a module that will be automatically loaded by NetBeans when it is required (by another module). Until that happens, it won't take up any memory at runtime.

Adding Modules to the Source Tree

Whenever you add a new module, you need to let the build harness know about it. Modify two files.

/home/rich/src/rss/nbbuild/user.cluster.properties:

cluster.rssreader1=snipe, \
                   anothermodule, \
                   yetanother

This tells the build scripts which modules are in your cluster, so they know how to build the cluster as a unit, and in which directory to install the resulting jar files.

/home/rich/src/rss/nbbuild/modules.xml:

<module>
  <path>snipe</path>
  <cnb>org.netbeans.modules.snipe</cnb>
</module>

This maps the name of the directory containing the module source code, with the name netbeans will know it by at runtime (“cnb” stands for code-name-base).

JDOM

I assume most readers will know what JDOM is. It's an XML parsing API, and the only reason FeedReader needs it is because the Rome library uses it.

Start by adding “jdom” to the cluster list and

<module>
  <path>jdom</path>
  <cnb>org.jdom.api</cnb>
</module>

to the module list. Now, here's a rundown of the JDOM module file list:

build.xml
manifest.mf
nbproject/project.xml
nbproject/project.properties
lib/jdom.jar
src/org/jdom/api/Bundle.properties

Let's take these one at a time.

build.xml: The build file starts by importing another build file called projectized.xml. Most of the time, that's all you'll need to have in your module's build file. However, in the JDOM module you want to include jdom.jar in the module's packaging, in addition to the module's own jar.

Quirk #1: You shouldn't have to override the build behavior in order to include additional libraries. You should be able to declare such dependencies in the project.xml file and the build scripts should know how to create the appropriate manifest entries and include them in the nbm file.

https://netbeans.org/bugzilla/show_bug.cgi?id=52354

So, override 2 targets. The “files-init” target is an exact copy of the target from projectized.xml, with the addition of the line:

<include name="${nb.modules.dir}/ext/jdom.jar"/>

The “files-init” target lets the build scripts know which files belong with the module. These are the files that get deleted when you run the “clean” target. It is a convention that jars which are not modules go in the ext/ directory.

The other target you need to override is “netbeans-extra”. This is a hook provided by the build scripts to give you a place to do things like copy files as part of the deployment process.

<target name="netbeans-extra" depends="init">
    <mkdir dir="${netbeans.dest.dir}/${cluster.dir}/${nb.modules.dir}/ext"/>
    <copy todir="${netbeans.dest.dir}/${cluster.dir}/${nb.modules.dir}/ext">
      <fileset dir="lib">
        <include name="jdom.jar"/>
      </fileset>
    </copy>
</target>

This copies the jdom.jar file to /home/rich/netbeans/rssreader1/modules/ext.

manifest.mf: Every jar file needs a manifest. A netbeans module is simply a jar file whose manifest contains at least these two lines:

OpenIDE-Module: org.jdom.api/1
OpenIDE-Module-Specification-Version: 1.0

The first line is just the name of the module, and, optionally, a release-version. Notice that this name matches the <cnb> (code-name-base) from the modules.xml file.

The second line is the module's specification-version.

A word on version numbers... A module can have 3 different version numbers: a release-version, a specification-version, and an implementation-version.
Let's say, for example, that module A has release-version 1, specification version 2.0, and implementation-version beta3. Now, module B is going to declare a dependency on module A (in its project.xml file). It must specify a release version of 1. It may optionally specify a dependency on speficiation version 2.0. If it does, and the author of A releases a version 2.1, that's okay, the dependency will still work. The contract is that the public API classes exposed by module A (see project.xml's <public-packages> element) will not break compatibility. A dependency on the specification version only gives B access to those API classes.
If module B specifies a dependency on implementation-version beta3, it will only work with that version of module A. However, it will have access to all the public classes in module A. (If you're getting a NoClassDefFoundException when you're running your module, it could be because you're trying to access non-API classes without specifying an implementation dependency.)

Only 2 more lines in the manifest:

OpenIDE-Module-Localizing-Bundle: org/jdom/api/Bundle.properties
Class-Path: ext/jdom.jar

The first line is completely optional. It points to a bundle containing more manifest entries. These manifest entries are all localizable strings, such as a display name and description for this module.

The second line is the standard manifest Class-Path entry, which puts the jdom.jar file in the classpath of the module's jar file. Notice that the path “ext/jdom.jar” matches up with where you copied jdom.jar in the build.xml file.

Quirk #2: Shouldn't have to specify Class-Path. It should be generated automatically (see Quirk #1).

nbproject/project.xml: This is the file that tells the build scripts (and the IDE if you're using it) how to generate dependency delcarations and classpaths. Here, too, you must specify the familiar <code-name-base> and <path> information. Next, you must specify any dependencies on other modules. Now, seeing as though there's no actual code in the module itself, and jdom.jar doesn't require any other code outside the core JRE classes, there's really no module dependencies to declare. Still, you should always declare a dependency on the core OpenAPI classes:

<dependency>
  <code-name-base>org.openide</code-name-base>
    <build-prerequisite/>
    <compile-dependency/>
    <run-dependency>
      <release-version>1</release-version>
      <specification-version>4.5</specification-version>
  </run-dependency>
</dependency>

It's kind of a special case. If you don't specify this, with a recent specification-version, NetBeans will assume this is an old module, and automatically load a bunch of dependencies at runtime, for backwards-compatibility purposes.

Next, you need to declare the module's <public-packages>. This serves two purposes: public packages are viewable to other modules that declare dependencies on this module, and they constitute the set of packages that will get javadocs generated when running the “javadoc” ant target.

You can either specify each package individally:

<public-packages>
  <package>org.jdom</package>
  <package>org.jdom.adapters</package>
  <package>org.jdom.input</package>
  ...
</public-packages>

Or, you can get the whole tree with one line:

<public-packages>
  <subpackages>org.jdom</subpackages>
</public-packages>

However, this method doesn't work with the “javadoc” target.

project.properties: A few more hints to the build process...

is.autoload=true
cp.extra=lib/jdom.jar
module.javadoc.packages=org.jdom

The first line makes this module an autoload module. The second line is appended to the compilation classpath. The third line is required if you used the <subpackages> method to declare your <public-packages> in project.xml.

Quirk #3: Specifying module.javadoc.packages should not be necessary here. The build scripts should have a sane backoff, with the assumption that the user isn't interested in javadocs for this module. Right now, the build fails if this is not specified.

https://netbeans.org/bugzilla/show_bug.cgi?id=52135

* Update: Fixed for NetBeans 4.1

Rome

Rome

The Rome library reads RSS and Atom feeds (with a very simple API, I might add). The Rome module and the JDOM module differ in only two respects. The Rome module bundles two jar files (rome-0.4.jar and rome-fetcher-0.4.jar) instead of one, and project.xml declares a dependency on the JDOM module:

<dependency>
  <code-name-base>org.jdom.api</code-name-base>
  <build-prerequisite/>
  <compile-dependency/>
  <run-dependency>
    <release-version>1</release-version>
    <specification-version>1.0</specification-version>
  </run-dependency>
</dependency>

JDIC

The JDIC library allows java programs to take advantage of certain native desktop facilities such as browsers, mailers, system trays, and MIME type registries. FeedReader uses the embedded native browser component to render web pages in a JFrame with the IE or Mozilla rendering engine.

In order to accomplish this, JDIC makes Java Native Interface (JNI) calls to its shared library (jdic.dll or libjdic.so), as well as run native executables (IeEmbed.exe or mozembed-linux-gtk2). You need to do a little magic in the module's build file to make these libraries and executables available to netbeans at runtime.

Notice the set of files declared in the “files-init” task:

<include name="${nb.modules.dir}/ext/jdic.jar"/>
<include name="lib/libjdic.so"/>
<include name="lib/libmozembed-linux-gtk1.2.so"/>
<include name="lib/libmozembed-linux-gtk2.so"/>
<include name="lib/mozembed-linux-gtk1.2"/>
<include name="lib/mozembed-linux-gtk2"/>
<include name="lib/jdic.dll"/>
<include name="lib/IeEmbed.exe"/>
<include name="lib/nspr4.dll"/>
<include name="bin/${shell.script}"/>
<include name="bin/${batch.script}"/>

Aside from the familiar jar file declaration, there are a list of native libraries and executables in lib/ and a pair of scripts. The decision to put the native stuff in this particular directory is somewhat arbitrary, though it is the suggested location in the architecture document. The only other thing that needs to know this location is the startup scripts.

The shell script and batch script are generated by the shellscript target of the build file. These scripts launch the NetBeans platform with the rssreader1 cluster and the jdic binaries in their proper paths.

FeedReader Module

Now that you've got all the library modules you need, you're ready to make a module that actually does something.

Start by creating the build scripts and declarative elements of the module. The build.xml file is blissfully empty, other than the import line for the standard “projectized.xml”. The nbproject/project.xml file simply declares dependencies on your 3 library modules, and has no public packages (this modules provides no API to other modules). The nbproject/project.properties file is also empty. The manifest.mf file should look pretty familiar, too. There's only one new element there:

OpenIDE-Module-Layer: org/netbeans/modules/feedreader/resources/layer.xml

Unlike the library modules, you're going to declare things about the FeedReader's user interface in a layer file, for inclusion in the “system filesystem.”

Definition: The system filesystem is where NetBeans stores all the system settings, GUI layout information, actions, templates, and just about anything else required to maintain the state of a NetBeans installation. It is comprised of real files located under the "config" directory of each cluster, the "config" directory of the NetBeans user directory, and virtual "files" declared in module layer files.

Layer File

The first entry is under the top level folder called Actions. This is a repository for implementations of javax.swing.Action. They can be used for menu items, toolbar buttons, and keyboard shortcuts. Adding this entry will allow users to assign keyboard shortcuts to the ViewFeedsAction by selecting “Tools ... Keyboard Shortcuts” from the menu.

The second entry puts the ViewFeedsAction under the “View” menu. Notice the shadow file syntax, which is like using a soft link in UNIX, or a shortcut in Windows.

The rest of the entries fall under the Windows2 top level folder. This folder provides the platform with information about what types of TopComponents are going to be instantiated, and where to put them. (Incidentally, this is called “Windows2” because “Windows” is the old, pre-3.6 window system, which is kept around for backwards compatibility reasons.) FeedReader contains two TopComponent definitions: SiteListComponent and EntryListComponent. The latter is meant to be opened in the default location (the center), so nothing is necessary in the layer file to override this behavior.

The SiteListComponent, however, is meant to be docked on the left side, or the “explorer” mode. So, add an entry:

<folder name="Modes">
    <folder name="explorer">            
        <file name="rss_list.wstcref" url="feedList.wstcref"/>
    </folder>
</folder>

This declares that a TopComponent with id “rss_list” will be docked, by default, in the “explorer” mode. (Incidentally, the extension “wstcref” stands for Window System Top Component REFerence.) The placement of “rss_list” within the declared mode is derived from the file feedList.wstcref:

<tc-ref version="2.0">
    <module name="org.netbeans.modules.feedreader/1" spec="1.0" />
    <tc-id id="rss_list" />
    <state opened="true" />
</tc-ref>

the value of <tc-id id> must match the basename of the file declared in the layer (“rss_list”). The same name must also be present in the Windows2/Components folder with a “.settings” extension:

<folder name="Components">
    <file name="rss_list.settings" url="feedList.settings" />
</folder>

The contents of the settings file tells NetBeans how to instantiate an “rss_list”. You can write the contents of this file by hand, or you can comment out the entire Windows2 section of this layer file, start up netbeans, instantiate a SiteListComponent by running the ViewFeedsAction, and grab the settings file that gets autogenerated in $userdir/config/Windows2Local/Component. Then replace the <serialdata> section with:

<instance class="org.netbeans.modules.feedreader.SiteListComponent"/>

This creates the component using the default constructor of SiteListComponent. If the class required a static factory method, you could add a method attribute:

<instance class="org.netbeans.modules.feedreader.SiteListComponent" method=”makeSiteListComponent”/>

ViewFeedsAction.java

Now all that's left to do is the actual java code. The first class declared in the layer file is ViewFeedsAction.java. This is a simple subclass of CallableSystemAction, which is a singleton implementation of javax.swing.Action.

    public void performAction() {
        SiteListComponent.activate();
    }

Performing this action will open and give focus to the singleton instance of SiteListComponent (see implementation of SiteListComponent below).

    public String getName() {
        return NbBundle.getMessage(SiteListComponent.class, "SLC_title");
    }

The name of the action is stored in Bundle.properties, so it can be localized.

    public HelpCtx getHelpCtx() {
        return HelpCtx.DEFAULT_HELP;
    }

You would change this if you had a JavaHelp id or a URL with more specific help for this action.

    protected boolean asynchronous() {
        return false;
    }

See the javadoc entry for more details.

SiteListComponent.java

Quirk #4: Some of the more confusing code in this class (the stuff dealing with IDs) has to do with ensuring a singleton instance of SiteListComponent, with the id specified in the layer file. Ideally, there should be a subclass of TopComponent called something like SingletonTopComponent, as this seems like a common thing to want to do.

https://netbeans.org/bugzilla/show_bug.cgi?id=53252

The following code ensures a singleton:

    /** A hint to the window system for generating a unique id */
    private static final String PREFERRED_ID = "rss_list"; // NOI18N
    
    /** The actual id of the (singleton) instance */
    private static String s_id = PREFERRED_ID;

    ...

    public static synchronized SiteListComponent getInstance()
    {
        TopComponent c;
        c = WindowManager.getDefault().findTopComponent(s_id);
        if (c == null)
        {
            c = new SiteListComponent();
            s_id = WindowManager.getDefault().findTopComponentID(c);
        }
        return (SiteListComponent)c;
    }

    ...

    protected String preferredID() { 
        return PREFERRED_ID;
    }

NetBeans maintains a map of all the TopComponents currently in memory. The key to this map is what I'm referring to as the “ID”. the “preferred” ID is just a hint to the window system to be used when creating a new instance. There's no guarantee that this hint does anything. It's generally just useful so you can recognize the name of the component if you're tracking down problems by perusing the contents of $userdir/config/Windows2Local.

The static field s_id, however, is meant to be the ID of the one instance of SiteListComponent in memory. It defaults to “rss_list” because that's the value of the ID given in feedList.wstcref. So, if this is the first time the user uses this module, the component with ID “rss_list” will be instantiated declaratively. However, if the user closes that component, restarts NetBeans, and then invokes the ViewFeedsAction, the new component may have a different ID. This is why the getInstance() method may assign a new value to s_id.

If the ID does change, there's nothing in the layer file to insist that the new component be docked in the “explorer” mode. To force this, override the open() method:

    private static final String MODE = "explorer"; // NOI18N

    public void open()
    {
        Mode m = WindowManager.getDefault().findMode(MODE);
        m.dockInto(this);
        super.open();
    }

The rest of this class is not NetBeans-specific. The component consists of a JList and two buttons: one to add a new feed, and one to delete a selected feed. The list is backed by a SiteListModel.

SiteListModel

The SiteListModel class adds only serialization to the swing DefaultListModel. Upon construction, it loads the list of feeds from disk. It updates the list on disk with each edit of the list. (Overkill? Perhaps, but it's not going to be a very long list, and once the user sets it up once, edits will be infrequent.)

The important bit is deciding where to serialize the list. By putting it in the system filesystem, you can make the serialized file part of the file structure in $userdir/config:

    private static final String DIR = "FeedReader"; //NOI18N
    private static final String FILENAME = "feeds.ser"; //NOI18N

    private FileObject getSerializedFile(boolean create) throws IOException
    {
        FileSystem sysFs = Repository.getDefault().getDefaultFileSystem();
        FileObject dir = sysFs.findResource(DIR);
        if (dir == null)
        {
            if (create)
                dir = sysFs.getRoot().createFolder(DIR);
            else
                return null;
        }
        
        FileObject fo = dir.getFileObject(FILENAME);
        if (fo == null)
        {
            if (create)
                fo = dir.createData(FILENAME);
            else
                return null;
        }
        
        return fo;
    }

The boolean parameter create will determine whether you want to create the file if it's not already there (true for writing, false for reading).

So, the serialized list will end up in $userdir/config/FeedReader/feeds.ser.

This is a somewhat low-level approach to writing out the list of feeds. There is, in fact, a NetBeans API that takes care of some of this for you. You could implement the serialization by subclassing SystemOption, and just override readExternal() and writeExternal(). I chose to write directly to the system filesystem to give you a better idea of what's going on under the hood. Also, this implementation gives you more flexibility to use a different file format. Once you've got the FileObject returned from getSerializedFile(), you could write XML data to it as easily as using ObjectOutputStream.

EntryListComponent

This class defines a component that will open up in the center, or “editor”, mode, and contain a list of news items from a single feed.

public class EntryListComponent extends TopComponent 
{
    protected static final String PREFERRED_ID = "rss_entry_list"; //NOI18N
    
    protected String preferredID() { 
        return PREFERRED_ID;
    } 

There's nothing funky going on with the IDs in this class. They're just acting as hints to the window system.

    public static TopComponent getInstance(Feed feed)
    {
        // look for an open instance containing this feed
        Iterator opened = TopComponent.getRegistry().getOpened().iterator();
        while (opened.hasNext())
        {
            Object tc = opened.next();
            if (tc instanceof EntryListComponent)
            {
                EntryListComponent elc = (EntryListComponent)tc;
                if (feed.equals(elc.m_feed))
                {
                    elc.initData(feed);
                    return elc;
                }
            }
        }
        
        // none found, make a new one
        return new EntryListComponent(feed);
    }

The getInstance() method ensures that only one instance exists for each unique feed. So, there may be several tabs open at once, but only one for each feed URL.

    
    protected EntryListComponent(Feed feed) 
    {
        super();
        initComponents();
        if (feed != null)
            initData(feed);
    }

Make the constructor protected, so clients have to use getInstance().

    
    protected void initData(Feed feed)
    {
        m_list.setFeed(feed);
        setDisplayName(feed.toString());
        
        m_feed = feed;
    }

Give the feed to the JList, and set the display name, which will be used on the components tab, and in window menus, etc.

    public int getPersistenceType() {
        return PERSISTENCE_NEVER;
    }

When shutting down netbeans, don't bother serializing these components.

EntryList, BrowserFrame, Feed

These classes don't contain much NetBeans-specific code. I'll let the in-code comments speak for themselves.

Try it Out!

In the nbbuild/ directory, run ant. Then run /home/rich/netbeans/rssreader1/bin/rss-reader.sh (or rss-reader.bat on Windows). You should now see an empty list on the left, to which you can add feed URLs.



A working FeedReader! There's just one thing wrong. It still looks like RSS capability tacked onto NetBeans. There are still toolbar buttons and menu items that don't do anything useful for RSS reading.

Branding

FeedReader's branding module is a pretty simple module. There's no java code at all. Just a layer file that hides a bunch of unused toolbar and menu items:

    <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="View">
            <!-- hide the default web browser that doesn't render stuff very well -->
            <file name="org-netbeans-core-actions-HTMLViewAction.instance_hidden"/>
        </folder>
        
        <folder name="Window">
            <file name="org-netbeans-core-actions-GlobalPropertiesAction.instance_hidden"/>
        </folder>
    </folder>

The question I'm sure you're thinking is, “How did he know the names of all those files to hide?” It certainly isn't obvious that, to hide the “File ... Save” menu item, you need to specify <file name=”org-openide-actions-SaveAction.instance_hidden”/>. Well, there's two ways. You can hunt around the netbeans source code, looking for the layer file in which it was originally declared. Or, you can use the Bean Browser, a tool included in the “Open APIs Support” (org-netbeans-modules-apisupport) module on the NetBeans Update Center. This creates a node in the “Windows...Runtime” window called “Bean Browser” which lets you browse the system filesystem.

Very useful little tool.

Aside from the layer file, there is one other unusual aspect to the branding module. In the build script, you'll see the now-familiar sections which add resource files to the deployed file set:

    <!-- 
    Identifies all the files to be considered part of this module when deployed 
    -->
    <target name="files-init" depends="basic-init">
        <patternset id="module.files">
            <include name="${module.jar}"/>
            <include name="${javahelp.jar}" if="has.javahelp"/>
            <include name="${nb.system.dir}/Modules/${code.name.base.dashes}.xml"/>

            <!-- additions for FeedReader begin here -->
            <include name="${nb.lib.dir}/locale/core_rss.jar"/>
            <include name="${nb.modules.dir}/locale/org-netbeans-core-windows_rss.jar"/>
        </patternset>
    </target>

    <!-- 
    netbeans-extra is a hook provided to plug in file copying. 
    -->
    <target name="netbeans-extra" depends="init">
        <mkdir dir="${netbeans.dest.dir}/${cluster.dir}/${nb.lib.dir}/locale"/>
        <mkdir dir="${netbeans.dest.dir}/${cluster.dir}/${nb.modules.dir}/locale"/>

        <jar destfile="${netbeans.dest.dir}/${cluster.dir}/${nb.lib.dir}/locale/core_rss.jar" 
             basedir="core"/>
        <jar destfile="${netbeans.dest.dir}/${cluster.dir}/${nb.modules.dir}/locale/org-netbeans-core-windows_rss.jar" 
             basedir="core-windows"/>
    </target>

In this case, you're adding two jar files: core_rss.jar and org-netbeans-core-windows_rss.jar. These jars correspond to core.jar and org-netbeans-core-windows.jar. In fact, any jar in the standard netbeans install can be “branded” by placing another jar relative to the original:

/path/to/original.jar
/path/to/locale/original_brandname.jar

Recall that the shell script in the JDIC module passes “–branding rss” as a parameter. So the “brandname” in this case is “rss”.

Just about any resource file can be branded. Icons, bundles, layer files, etc. In core_rss.jar, I've branded the splash screen and a resource bundle, which contains keys that affect how the splash screen is displayed. In org-netbeans-core-windows_rss.jar, I've branded just one bundle, to override the main title bar's text.

Try it Again!

Now you should see a screen like the screenshot at the beginning of this article. And now you know how to write a real application for the NetBeans platform.

Questions?

Please direct questions to the mailing list. See https://netbeans.org/community/lists/top.html for information on netbeans mailing lists.

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