Overview

Contents

3.2 -> 3.3 Upgrade Guide

This upgrade guide outlines notable API changes between NetBeans 3.2 and 3.3.

Introduction

Module developers who have stable and tested modules developed against NetBeans 3.2 should in most cases be able to use them unmodified in NetBeans 3.3 due to API compatibility. However many improvements were also made which module developers are encouraged to take advantage of to ensure the best integration into 3.3. This document is an attempt to point out the most significant changes and how a typical module should be modified to take advantage of them, including some examples and tutorials. The best sources of examples are of course those modules shipped as part of NetBeans 3.3 as in most cases these have already been upgraded to match the new APIs.

The API Changes List

The definitive source of information is the API changes list. This summarizes all known API changes, big and small. For the most part you as a module developer do not need to read all this, because many of the changes simply will not be of interest to you.

Two things are important. One, if you are in fact using a new API, this document will tell you which version of the APIs to depend on in the list of changes by version.

And there are a handful of incompatible changes which everyone should be aware of. See the list of incompatible changes.

Notable Incompatibilities

There is only one incompatibility which is likely to be relevant.

MultiDataObject.cookieSet (getter and setter) are now protected rather than public, affecting compilability of sources (though binary compatibility is retained). See the change summary for the rationale for this change and suggested workarounds.

A similar change was also made to AbstractNode.cookieSet which is likely to have a smaller impact.


Desirable Changes in Modules

This section lists significant tasks that module developers ought to consider undertaking in order to make their modules work smoothly in 3.3. Changes which should have been undertaken for 3.2 already, such as installing templates from XML layers, are not listed. Minor local changes to APIs are not listed; interested developers can read through the complete change list if necessary.

Move settings to layers

Many types of module-provided settings can in 3.3 be registered via XML layer rather than in manifest. Besides permitting greater flexibility, there are performance benefits to doing this.

The basic definition of how settings in layers work is given in the Services API.

Here are the things that might formerly have been present in a manifest which should be moved:

For most of these cases (especially system options, services, filesystems, and JavaHelp) there are copious examples to be found in the layers of standard NetBeans modules in development sources. Do not forget to also look at the localizing bundles referred to by the layers, and the settings files themselves which are normally linked to with the url="..." attribute of <file> for performance reasons (inline file contents are a bit slower to parse). For example, ant/src/org/apache/tools/ant/module/resources/AntModuleLayer.xml has these relevant entries:
<!-- Not the complete layer, just interesting pieces: -->
<folder name="Services">
    <!-- Register two compiler types and an executor into the default service pool. -->
    <folder name="CompilerType">
        <!-- See ant/src/org/apache/tools/ant/module/resources/AntCompiler.settings for contents. -->
        <file name="org-apache-tools-ant-module-AntCompiler.settings" url="AntCompiler.settings">
            <!-- Make any changes specific to the project. -->
            <attr name="SystemFileSystem.layer" stringvalue="project"/>
            <!-- Give it a nice display name; otherwise the service has to be actually loaded in order -->
            <!-- to display its icon. See ant/src/org/apache/tools/ant/module/run/Bundle.properties. -->
            <!-- The key will be named "Services/CompilerType/org-apache-tools-ant-module-AntCompiler.settings". -->
            <attr name="SystemFileSystem.localizingBundle" stringvalue="org.apache.tools.ant.module.run.Bundle"/>
            <!-- Give it a nice icon, as above. -->
            <attr name="SystemFileSystem.icon" urlvalue="nbresloc:/org/apache/tools/ant/module/resources/AntIcon.gif"/>
        </file>
        <file name="org-apache-tools-ant-module-IndirectAntCompiler.settings" url="IndirectAntCompiler.settings">
            <attr name="SystemFileSystem.layer" stringvalue="project"/>
            <attr name="SystemFileSystem.localizingBundle" stringvalue="org.apache.tools.ant.module.run.Bundle"/>
            <attr name="SystemFileSystem.icon" urlvalue="nbresloc:/org/apache/tools/ant/module/resources/AntIcon.gif"/>
        </file>
    </folder>
    <folder name="Executor">
        <file name="org-apache-tools-ant-module-AntExecutor.settings" url="AntExecutor.settings">
            <attr name="SystemFileSystem.localizingBundle" stringvalue="org.apache.tools.ant.module.run.Bundle"/>
            <attr name="SystemFileSystem.icon" urlvalue="nbresloc:/org/apache/tools/ant/module/resources/AntIcon.gif"/>
            <attr name="SystemFileSystem.layer" stringvalue="project"/>
        </file>
    </folder>
    <!-- A system option. -->
    <file name="org-apache-tools-ant-module-AntSettings.settings" url="AntSettings.settings">
        <attr name="SystemFileSystem.localizingBundle" stringvalue="org.apache.tools.ant.module.Bundle"/>
        <attr name="SystemFileSystem.icon" urlvalue="nbresloc:/org/apache/tools/ant/module/resources/AntIcon.gif"/>
    </file>
</folder>
<folder name="Templates">
    <folder name="Services">
        <!-- Make it possible for the user to create new services too. -->
        <folder name="CompilerType">
            <!-- Use the same initial settings as for the default instance in the pool. -->
            <file name="org-apache-tools-ant-module-AntCompiler.settings" url="AntCompiler.settings">
                <!-- They have to be marked as templates, or they will not be included in the wizard. -->
                <attr boolvalue="true" name="template"/>
                <attr name="SystemFileSystem.localizingBundle" stringvalue="org.apache.tools.ant.module.run.Bundle"/>
                <attr name="SystemFileSystem.icon" urlvalue="nbresloc:/org/apache/tools/ant/module/resources/AntIcon.gif"/>
            </file>
            <file name="org-apache-tools-ant-module-IndirectAntCompiler.settings" url="IndirectAntCompiler.settings">
                <attr boolvalue="true" name="template"/>
                <attr name="SystemFileSystem.localizingBundle" stringvalue="org.apache.tools.ant.module.run.Bundle"/>
                <attr name="SystemFileSystem.icon" urlvalue="nbresloc:/org/apache/tools/ant/module/resources/AntIcon.gif"/>
            </file>
        </folder>
        <folder name="Executor">
            <file name="org-apache-tools-ant-module-AntExecutor.settings" url="AntExecutor.settings">
                <attr boolvalue="true" name="template"/>
                <attr name="SystemFileSystem.localizingBundle" stringvalue="org.apache.tools.ant.module.run.Bundle"/>
                <attr name="SystemFileSystem.icon" urlvalue="nbresloc:/org/apache/tools/ant/module/resources/AntIcon.gif"/>
            </file>
        </folder>
    </folder>
</folder>
<folder name="UI">
    <folder name="Services">
        <!-- Display the system option under the "Building" category in Options. -->
        <folder name="Building">
            <!-- Point to the real settings file (always on the system file system). -->
            <file name="org-apache-tools-ant-module-AntSettings.shadow">
                <![CDATA[Services/org-apache-tools-ant-module-AntSettings.settings
SystemFileSystem
]]>
            </file>
        </folder>
    </folder>
</folder>
<folder name="Mount">
    <!-- Add a Javadoc default mount. The user can unmount it but not customize it. -->
    <folder name="Javadoc">
        <!-- The file name does not matter but should be unique. -->
        <file name="org-apache-tools-ant-module-javadoc.xml">
            <!-- The contents could also be given in a separate file. -->
            <![CDATA[<?xml version="1.0"?>
<!-- The DOCTYPE's public ID is mandatory for the document to be processed. -->
<!DOCTYPE Javadoc PUBLIC
          "-//NetBeans IDE//DTD JavadocLibrary//EN"
          "http://www.netbeans.org/dtds/JavadocLibrary-1_0.dtd">
<Javadoc>
  <!-- This is a path inside the NetBeans installation directory. -->
  <!-- See the ant/release/ folder in sources. -->
  <Archive name="docs/ant-api.zip"/>
</Javadoc>
]]>
            <!-- Nice name and icon to show in Filesystems Settings. -->
            <attr name="SystemFileSystem.localizingBundle" stringvalue="org.apache.tools.ant.module.resources.Bundle"/>
            <attr name="SystemFileSystem.icon" urlvalue="nbresloc:/org/apache/tools/ant/module/resources/AntIcon.gif"/>
        </file>
    </folder>
</folder>

The following cannot be moved from a layer:

Define workspaces and windows in layers

If your module for 3.2 defined its own workspaces or modes, or otherwise performed some sort of setup of the window system from the module installer, it should be converted in 3.3 to define these things using an XML layer. There are several benefits to doing so:

There is a migration guide available to help you convert old procedural code to the new XML style. Full details of the XML format and behavior are given in the Window System API.

Avoid persisting closed TopComponents

Before NetBeans 3.3, it was normal for all top components to be persistent in the user's project, even closed ones. This was not always desirable since some components cannot be properly serialized; and others can but there is no need to store them all the time, they just add clutter to user settings. In the new window system, modules have an option to disable persistence of their top components after project switch or IDE shutdown. (The default is still to persist all components, for compatibility.)

If your top component either cannot be properly serialized and deserialized because it includes critical transient information, or for UI reasons it is undesirable to ever keep it open after a switch in the window system, you can add a Swing client property to it giving the window system this hint:

putClientProperty("PersistenceType", "Never"); // NOI18N

If you wish for it to be saved when it is open (at the time of project switch or IDE shutdown) but no information about it should be kept after it is closed, use:

putClientProperty("PersistenceType", "OnlyOpened"); // NOI18N

For efficiency during IDE or project open and in the interests of avoiding clutter in the user directory, it is recommended that most top components use one of these hints (normally OnlyOpened).

Warning: this is only a hint which may not be respected in future releases; so for safety TopComponents which cannot be safely deserialized at all should still writeReplace to null, so as to avoid problems in a future release.

Localize manifests with bundles

In 3.2, all metainformation about a module such as its display name needed to be kept in its manifest. There was a facility for localizing human-visible strings, using variants on attribute keys; unfortunately the JRE does not current support non-ASCII characters in manifests, so this was not very useful. For 3.3, all localizable strings formerly in manifests ought to be moved to resource bundles, where they can be properly localized. This feature is turned on by a new manifest attribute pointing to the bundle.

There is an existing document describing how to do this conversion: Manifest Localization Guide

Install JavaHelp help sets with layers

Before 3.3, there was only one way to add JavaHelp documentation for a module, using the Modules API. You included a helpset in the module and then used the OpenIDE-Module-Description attribute to refer to it. For NetBeans 3.3 JavaHelp functionality can be registered via layer instead of manifest, with several advantages:

To use this new system effectively, first remove the obsolete manifest declaration. Now in your layer you need to add three items:

  1. An instance to the help set itself, conventionally done with a special DTD used for this purpose and underneath Services/JavaHelp/. This registers the existence of the help set, and controls its merging into the master help set.

  2. A shortcut to the help set (typically to its home ID), conventionally using another special DTD and placed under Menu/Help/HelpShortcuts/. This registers the menu item including its name and icon, and controls ordering.

  3. The same shortcut in the actions pool, conventionally under Actions/Help/, permitting users to copy the help set reference elsewhere and safely customize their shortcuts menu knowing it will still be available here.

Here is an excerpt from the API Support module's layer demonstrating these things. Note that the usersguide module (Online Help) provides a few marker files in its layers which help sort other help sets and menu items into the "general zone": the IDE's main Online Help itself comes first, then a separator (in the menu), then module help sets (or menu items), then another separator, then module help sets (or menu items) which represent "third-party" or bundled documentation (such as the Ant User Manual included in the Ant support module).

<filesystem>
    <folder name="Services">
        <!-- Register the HelpSet in lookup: -->
        <folder name="JavaHelp">
            <!-- Merge it after the main online help: -->
            <attr name="org-netbeans-modules-usersguide-above-regular.txt/org-netbeans-modules-apisupport-helpset.xml"
                  boolvalue="true"/>
            <!-- This DTD gives an instance of HelpSet: -->
            <file name="org-netbeans-modules-apisupport-helpset.xml">
                <![CDATA[<?xml version="1.0"?>
<!DOCTYPE helpsetref PUBLIC
          "-//NetBeans//DTD JavaHelp Help Set Reference 1.0//EN"
          "http://www.netbeans.org/dtds/helpsetref-1_0.dtd">
<!-- By default it is set to merge. nbdocs: looks in the module JAR(s) and docs/ folder. -->
<helpsetref url="nbdocs:/org/netbeans/modules/apisupport/help/HelpSet.hs"/>
]]>
            </file>
            <!-- And merge before additional helpsets such as the Ant Manual: -->
            <attr name="org-netbeans-modules-apisupport-helpset.xml/org-netbeans-modules-usersguide-below-regular.txt"
                  boolvalue="true"/>
        </folder>
    </folder>
    <folder name="Menu">
        <folder name="Help">
            <!-- Add a menu item in Help | Help Sets: -->
            <folder name="HelpShortcuts">
                <!-- Again some ordering: -->
                <attr name="org-netbeans-modules-usersguide-sep.instance/org-netbeans-modules-apisupport-mainpage.xml"
                      boolvalue="true"/>
                <!-- Instance of a menu item: -->
                <file name="org-netbeans-modules-apisupport-mainpage.xml">
                    <![CDATA[<?xml version="1.0"?>
<!DOCTYPE helpctx PUBLIC
          "-//NetBeans//DTD Help Context 1.0//EN"
          "http://www.netbeans.org/dtds/helpcontext-1_0.dtd">
<!-- Give the help ID to open. By default shows master help set, you can instead -->
<!-- show just this help set if you prefer: -->
<helpctx id="org.netbeans.modules.apisupport.HOMEID"/>
]]>
                    <!-- Make it appear with a nice name and icon. -->
                    <attr name="SystemFileSystem.localizingBundle"
                          stringvalue="org.netbeans.modules.apisupport.resources.TemplateNames"/>
                    <attr name="SystemFileSystem.icon"
                          urlvalue="nbresloc:/org/netbeans/modules/apisupport/resources/ShowAPIJavadocIcon.gif"/>
                </file>
                <attr name="org-netbeans-modules-apisupport-mainpage.xml/org-netbeans-modules-usersguide-lower-sep.instance"
                      boolvalue="true"/>
            </folder>
        </folder>
    </folder>
    <folder name="Actions">
        <!-- Add to actions pool too: -->
        <folder name="Help">
            <!-- No need for any special ordering here. -->
            <file name="org-netbeans-modules-apisupport-mainpage.xml">
                <![CDATA[<?xml version="1.0"?>
<!DOCTYPE helpctx PUBLIC
          "-//NetBeans//DTD Help Context 1.0//EN"
          "http://www.netbeans.org/dtds/helpcontext-1_0.dtd">
<helpctx id="org.netbeans.modules.apisupport.HOMEID"/>
]]>
                <attr name="SystemFileSystem.localizingBundle"
                      stringvalue="org.netbeans.modules.apisupport.resources.TemplateNames"/>
                <attr name="SystemFileSystem.icon"
                      urlvalue="nbresloc:/org/netbeans/modules/apisupport/resources/ShowAPIJavadocIcon.gif"/>
            </file>
        </folder>
    </folder>
</filesystem>

(In your module it would be better to use url="..." to include these XML files separately rather than inline, which is a little faster.)

Do not forget also that the localized filenames need bundle entries, here from apisupport/src/org/netbeans/modules/apisupport/resources/TemplateNames.properties:

Menu/Help/HelpShortcuts/org-netbeans-modules-apisupport-mainpage.xml=OpenAPIs Support
Actions/Help/org-netbeans-modules-apisupport-mainpage.xml=OpenAPIs Support

The helpset itself must be accessible with the nbdocs protocol. While it still works to copy helpsets unpacked to the docs/ directory of the IDE installation, this practice is discouraged. In most cases using a JavaHelp helpset off-line is not important to users anyway. The proper thing to do is to keep the helpset available from the module classloader, i.e. its JAR, or more cleanly in an extension JAR (or ZIP). Conventionally these go in the modules/docs/ directory. So for example include this in your module manifest:

Class-Path: docs/apisupport-manual.jar

where modules/docs/apisupport-manual.jar contains the complete helpset, starting from the resource org/netbeans/modules/apisupport/help/HelpSet.hs. In such a case you can use the nbresloc rather than nbdocs protocol in the helpset registration URL.

Details of JavaHelp installation are given in the Modules API.

Ability to turn modules on and off, list all modules

In NetBeans 3.2 there was no API-level access to the set of installed modules nor any control over them. In 3.3 there is limited information and control available. Specifically you can:

For purposes of upgrading existing functionality, the last point is the most significant: if in NetBeans 3.2 your module attempted to manually turn off pieces of its GUI upon finding that some critical thing it needed was missing, for 3.3 this check should be done in ModuleInstall.validate().

See the Modules API - Installers for information on using validate, and the Modules API - List of modules for information on the other new features.

Use annotations to control GUI of editor

As of NetBeans 3.3 there is a new API which permits any module to interact with the GUI of the editor, specifically by making marks called annotations in the margin of the editor, highlighting complete lines or fragments of text, displaying tool tips and actions, and several other abilities. This is documented in the Editor API.

If in NetBeans 3.2 you are using the now-deprecated method Line.markError or one of several similar calls to indicate erroneous places in a document, you should for 3.3 create an annotation representing your error and attach it to the line instead. This is much more flexible. A sample implementation of an error annotation including how to attach and detach it at the proper times can be found in ant/src/org/apache/tools/ant/module/run/OutputWriterOutputStream.java. An excerpt from this class showing the essential code follows:

/** This is called when a complete line of process output is collected.
 * It either prints it plain, or prints it with a hyperlink which when
 * clicked opens the offending source and attaches an error annotation.
 * 'writer' is an OutputWriter from a tab in the Output Window.
 */
private void flushLine(String l) throws IOException {
    // ...
    Hyperlink link = /* create one based on erroneous line, or null */;
    if(link != null) {
        writer.println(link.getMessage(), link);
    } else {
        writer.println(l);
    }
}

/** Call this from ModuleInstall.uninstalled() to clean up. */
public static void detachAllAnnotations() {
    synchronized (hyperlinks) {
        Iterator it = hyperlinks.iterator();
        while (it.hasNext()) {
            ((Hyperlink)it.next()).destroy();
        }
    }
}
private static final Set hyperlinks = new WeakSet(); // Set<Hyperlink>

/** This is the annotation itself, which also knows how to behave in the
 * Output Window, and when to attach and detach itself.
 */
private static final class Hyperlink extends Annotation implements OutputListener, PropertyChangeListener {
    private FileObject file; // file to jump to
    private int line1, col1, line2, col2; // line/col number to jump to, 0-based, -1 for unspecified
    private String message; // message it is saying, or null
    private boolean dead = false; // whether it has been destroyed
    /** Create it based on some parsing of the erroneous line, etc. */
    Hyperlink(FileObject file, int line1, int col1, int line2, int col2, String message) {
        this.file = file;
        this.line1 = line1;
        this.col1 = col1;
        this.line2 = line2;
        this.col2 = col2;
        this.message = message;
        synchronized (hyperlinks) {
            hyperlinks.add(this);
        }
    }
    /** See detachAllAnnotations() above. */
    void destroy() {
        doDetach();
        dead = true;
    }
    /** Get a message suitable for printing in the Output Window, based on error, line #, etc. */
    public String getMessage() { /* ... */ }
    // OutputListener methods:
    public void outputLineAction(OutputEvent ev) {
        if (dead) return;
        if (! file.isValid()) {
            Toolkit.getDefaultToolkit().beep();
            return;
        }
        if (message != null) {
            TopManager.getDefault().setStatusText(message);
        }
        try {
            DataObject dob = DataObject.find(file);
            EditorCookie ed = (EditorCookie) dob.getCookie(EditorCookie.class);
            if (ed != null) {
                if (line1 == -1) {
                    // OK, just open it.
                    ed.open();
                } else {
                    // Open the document, add the annotation, jump to the right line.
                    ed.openDocument();
                    Line l = ed.getLineSet().getOriginal(line1);
                    if (! l.isDeleted()) {
                        attachAsNeeded(l);
                        if (col1 == -1) {
                            l.show(Line.SHOW_GOTO);
                        } else {
                            l.show(Line.SHOW_GOTO, col1);
                        }
                    }
                }
            } else {
                Toolkit.getDefaultToolkit().beep();
            }
        } catch (DataObjectNotFoundException donfe) {
            TopManager.getDefault().getErrorManager().notify(ErrorManager.WARNING, donfe);
        } catch (IndexOutOfBoundsException iobe) {
            TopManager.getDefault().getErrorManager().notify(ErrorManager.WARNING, iobe);
        } catch (IOException ioe) {
            TopManager.getDefault().getErrorManager().notify(ErrorManager.WARNING, ioe);
        }
    }
    public void outputLineSelected(OutputEvent ev) {
        // Similar to the above but less aggressive. Do not forcibly open the document.
        if (dead) return;
        if (! file.isValid()) {
            return;
        }
        try {
            DataObject dob = DataObject.find(file);
            EditorCookie ed = (EditorCookie) dob.getCookie(EditorCookie.class);
            if (ed != null) {
                if (ed.getDocument() == null) {
                    // The document is not opened, don't bother with it.
                    return;
                }
                if (line1 != -1) {
                    Line l = ed.getLineSet().getOriginal(line1);
                    if (! l.isDeleted()) {
                        attachAsNeeded(l);
                        if (col1 == -1) {
                            l.show(Line.SHOW_TRY_SHOW);
                        } else {
                            l.show(Line.SHOW_TRY_SHOW, col1);
                        }
                    }
                }
            }
        } catch (DataObjectNotFoundException donfe) {
            TopManager.getDefault().getErrorManager().notify(ErrorManager.WARNING, donfe);
        } catch (IndexOutOfBoundsException iobe) {
            TopManager.getDefault().getErrorManager().notify(ErrorManager.WARNING, iobe);
        }
    }
    public void outputLineCleared(OutputEvent ev) {
        doDetach();
    }
    /** Attach myself to a line if this has not been done already. */
    private synchronized void attachAsNeeded(Line l) {
        if (getAttachedAnnotatable() == null) {
            Annotatable ann;
            // Text of the line, incl. trailing newline.
            String text = l.getText();
            if (text != null && (line2 == -1 || line1 == line2) && col1 != -1) {
                // fits on one line
                if (col2 != -1 && col2 >= col1 && col2 < text.length()) {
                    // one section of the line was specified
                    ann = l.createPart(col1, col2 - col1 + 1);
                } else if (col1 < text.length()) {
                    // some column until end of line
                    ann = l.createPart(col1, text.length() - col1);
                } else {
                    // some problem
                    ann = l;
                }
            } else {
                // multiple lines, something wrong with line, or no column given
                ann = l;
            }
            // actually display the glyph etc.:
            attach(ann);
            // see if this line/fragment is changed or deleted in the future:
            ann.addPropertyChangeListener(this);
        }
    }
    /** Detach from the associated line, if necessary. */
    private synchronized void doDetach() {
        Annotatable ann = getAttachedAnnotatable();
        if (ann != null) {
            ann.removePropertyChangeListener(this);
            detach();
        }
    }
    // PropertyChangeListener method:
    public void propertyChange(PropertyChangeEvent ev) {
        if (dead) return;
        String prop = ev.getPropertyName();
        if (prop == null ||
                prop.equals(Annotatable.PROP_TEXT) ||
                prop.equals(Annotatable.PROP_DELETED)) {
            // Affected line has changed.
            // Assume user has edited & corrected the error.
            doDetach();
        }
    }
    // Annotation:
    public String getAnnotationType() {
        // Unique ID of this kind of annotation:
        return "org-apache-tools-ant-module-error"; // NOI18N
    }
    public String getShortDescription() {
        // This will become the annotation's tool tip text
        return message;
    }
}
The annotation type is registered in the layer:
<filesystem>
    <folder name="Editors">
        <folder name="AnnotationTypes">
            <file name="org-apache-tools-ant-module-error.xml"
                  url="error-annotation.xml"/>
        </folder>
    </folder>
</filesystem>
and the actual definition mainly gives some GUI properties of the glyph:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE type PUBLIC
          "-//NetBeans//DTD annotation type 1.0//EN"
          "http://www.netbeans.org/dtds/annotation-type-1_0.dtd">
<type name="org-apache-tools-ant-module-error"
      description_key="LBL_error_annotation"
      localizing_bundle="org.apache.tools.ant.module.resources.Bundle"
      visible="true"
      glyph="nbresloc:/org/apache/tools/ant/module/resources/error-glyph.gif"
      highlight="#FF8080"
      type="linepart"
/>

Define file types using MIME resolvers

The MIME resolver facility is actually present in NetBeans 3.2 though it was essentially unused at the time. It provides a way for modules to register knowledge of what the MIME type of a file is. You can add a MIMEResolver implementation to lookup and filesystems will ask it for information of file object MIME type; typically it might scan initial bytes of a file for magic headers and so on. The system can cache the opening bytes of the file, making this reasonably efficient.

For the first time in NetBeans 3.3 it is possible to add a MIME resolver without any coding: a new XML format is defined which makes the most common tasks (decision trees) available. For example, you can ask that all XML files with a certain DTD be given a special MIME type. Or that all files beginning with a certain "magic" byte sequence be recognized as some MIME type.

How are these MIME types supposed to be used? There are a few different ways in which it can be helpful to directly associate proper MIME types to file objects, but the major reason to use them is to make DataLoaders simpler. If a MIME resolver, especially an XML one, can fully define what your file type is, then you may simply use a UniFileLoader with one item in its extension list - that MIME type. There are potential performance benefits here because the system can pool resolvers and apply them in batch.

Which modules should look at these? Ultimately probably all loaders should work this way. For the shorter term, consider switching complex data loader recognition logic to simplified loader logic paired with MIME recognition if you:

  1. Recognize XML files by DTD, root element, or other common mechanism. Adding a new MIME resolver will make your module add very little overhead to the XML recognition process; keeping custom parsing tricks in the data loader adds a significant amount of overhead and prevents back-end optimizations by the IDE. Additionally your code will be much simpler.

  2. Recognize any kind of file based on its contents, especially some distinctive marker in its header. Opening the file from a data loader is a significant source of overhead; the resolver pool caches these headers. Your code will also be simpler here if you can use a declarative resolver.

  3. Need to have a MIME type associated with the actual FileObject anyway. For example, if it should be given some type when served from the internal web server.

The system of MIME resolution using declarative resolvers is documented in the FileSystems API. An example can be found in the Ant module (recognizing XML files based on root element). Consult the layer and data loader.

Lazy instances with InstanceCookie.Of

If you were registering any kind of instance to lookup in NetBeans 3.2, or are considering doing it for the first time in NetBeans 3.3, make sure you understand how to prevent eager classloading from happening. Essentially: if you have some service implementation which you wish to register, and it is not actually used in the course of an IDE session, its class should never be loaded (the interface class probably will be).

This is done technically using InstanceCookie.Of, if you are providing the raw cookie. When using standard means of providing instances, there are matching ways to ensure you split the interface and implementation classes clearly enough to be lazy: the instanceOf attribute on *.instance files, and the <instanceof/> element in XML settings. See the Services API for more information.

Enhanced lookup facilities supersede service types

There are many new features in the lookup system for 3.3. It would not be helpful to enumerate all of them because if you were not using lookup extensively in 3.2, you probably do not need to do much for 3.3 (except for things like moving services to layers, as described elsewhere in this document). If you were considering using ServiceType to define a custom kind of pluggable service, be aware that creating new subclasses of ServiceType is essentially deprecated - you can now use any superclass (or perhaps interface) you like, and lookup will handle it. Existing subclasses such as Executor and so on of course remain for compatibility reasons.

XML entity resolution and processors installed via layer

For 3.3, there is a more efficient and powerful way to add both entity resolution and cookie processors for XML files based on public ID.

Autoload modules

A new feature introduced into the NetBeans 3.3 core module system (though not officially covered by the Modules API) is that of autoload modules. It was observed that a number of modules simply function as libraries with no intrinsic value to the user, but are needed by "real" modules. Using module extensions (modules/ext/ and referencing via Class-Path) is not suitable: as of 3.2, modules do not automatically share extensions even when the file name is the same, meaning the JAR would be loaded twice; and extensions are not full modules, so they cannot register basic services in a layer, and so on.

So autoloads were introduced. An autoload module behaves exactly like ordinary modules except where enablement is concerned. It has no permanent enablement status; it is turned on automatically when the first non-autoload module to depend on it is turned on, and turned off automatically when the last such real module is turn off. Neither the user nor code can turn on or off just the autoload. It will be listed in a segregated category of the Modules node.

If your module built for 3.2 matches these criteria it may be better used as an autoload:

  1. Turning it on by itself, without something else to use it, would be pointless for the user.

  2. You have made it into an extension, or considered doing so, in order to be able to share it among several first-class modules.

  3. It has no independent user interface, or it does but this UI is just a container to be filled by other modules.

  4. Most of the module is an API of some sort.

To make it an autoload is easy: place it in the modules/autoload/ folder of the IDE installation. It may have extensions and so on like regular modules, e.g. modules/autoload/ext/.

Use hints to control property editors

The entire org.openide.explorer.propertysheet.editors package is deprecated, save perhaps generic interfaces such as XMLPropertyEditor, EnhancedPropertyEditor, and EnhancedCustomPropertyEditor. Any code which was using a specific property editor implementation from this package (such as FileEditor or one of its subclasses) should stop doing so for 3.3, and new code should not be written using this package.

There is a cleaner system in place for controlling the UI of property editors. Rather than explicitly creating a PropertyEditor implementation, you rely on the core and the search path to provide one according to property type. Controlling aspects of the editor is done using hints, name/value pairs which may be applied either to PropertyDescriptors in a BeanInfo, or to Node.Propertys when using the Nodes API. A number of hints for common property editors are already defined, and more will be defined as they are needed.

See the Explorer API for details on how to use these.

Here is an example of replacing the deprecated DirectoryOnlyEditor with the default editor for java.io.File plus a hint to use directories. It is similar to that used in the bean info for LocalFileSystem:

PropertyDescriptor rootDirectory = new PropertyDescriptor("rootDirectory", LocalFileSystem.class);
// These are documented in the Explorer API under java.io.File:
rootDirectory.setValue("directories", Boolean.TRUE);
rootDirectory.setValue("files", Boolean.FALSE);
rootDirectory.setDisplayName(/* ... */);
rootDirectory.setShortDescription(/* ... */);

j2eeserver/src/org/netbeans/modules/j2ee/impl/ServerExecSupport.java also demonstrates creating a Node.Property editing DataObjects with a given cookie. (It could probably also use the cookies rather than dataFilter hint.) ant/src/org/apache/tools/ant/module/nodes/DataTypeNode.java in FileProperty demonstrates creating a java.io.File again, but this time specifying a base directory to resolve filenames from. The Services API includes an example of customizing the editor for java.lang.Object.

Also if you are displaying a custom GUI component in which you would like to embed something akin to the Property Sheet or the GUI customizer for a property, avoid directly creating the embedded editor, if it is even available from the APIs. You want to take advantage of future improvements in the editor, and pick up whatever the core search path has to offer. So use PropertyPanel which is a neat way to reuse the property sheet code for editing a property.


Older Changes

There are a few older changes which were available already in 3.2 but sufficiently young that many modules may not have picked up on them, so a few of these are listed for completeness.

Image caching and badging

Most loading of images, such as from BeanInfo.getImage(), loads an icon from a module resource path. The JRE provides a few caching techniques, such as those in Toolkit and SimpleBeanInfo, but they are not great with memory management. Please use the NetBeans standard cache manager: Utilities.loadImage(String).

Your module should never load an image in the static initializer of some class, just load it on demand using the cache:

// This is bad:
static Image myicon = ...;
public Image method() {
    return myicon;
}
// This is better but never collects the image:
static Image myicon = null;
public Image method() {
    if (myicon == null) {
        myicon = ...;
    }
    return myicon;
}
// Better yet, though JRE cache manager is not the best:
public Image method() {
    return Toolkit.getDefault().getImage(...);
    // similar for SimpleBeanInfo.loadImage(...)
}
// Best:
public Image method() {
    return Utilities.loadImage("my/pkg/resources/something.gif");
}
If you are doing icon badging, that is placing special overlays on a basic icon to indicate statuses such as "uncompiled", "in error", "needs checkout", and so on, then you can do this best using the badge management API, which is easy to use and works well with the image cache (both basic images and generated composites are cached sanely): Utilities.mergeImages(...)

Short and long descriptions and categories for modules

All modules ought to have in addition to the display name a short description (tool-tip-length); long description (paragraph-length); and a display category (phrase selected from among some known categories). This will help the user understand what the module does and whether it is important; such information is displayed under the Modules node as well as in the module configuration panel of the Setup Wizard.

For 3.3, such information is all localizable and should go into a bundle, so for example:

OpenIDE-Module-Name=My Module
OpenIDE-Module-Short-Description=This module performs one simple task relating to something.
OpenIDE-Module-Long-Description=\
        This module is a complete solution for developing something objects. \
        It can be used in two modes, one to generate things, and one to display them. \
        You may want to use it in conjunction with the Power Something module.
OpenIDE-Module-Display-Category=Data Types

Nicer-looking wizards

Wizards got a face lift - you can customize a number of aspects of wizard GUI, such as creating a list of steps to be performed in the left margin of the wizard. All wizards used in 3.3 should try to look pleasant to the user, especially in the list of steps. Pending official documentation, see the UI website.

Use ErrorManager for logging

Do not use System.err.println for keeping a trace of what your module is doing. There is a better way:
static final ErrorManager err = TopManager.getDefault().getErrorManager().
    getInstance("org.netbeans.modules.mymodule"); // NOI18N
// ...
if (somethingWeirdHappened) {
    err.log("something weird happened here");
    // continue with it
}
if (isStrange(result)) {
    if (err.isLoggable(ErrorManager.UNKNOWN)) {
        err.log("strange result: " + formatForDebugging(result));
    }
    // deal with it
}
You can log messages to the system log file (and also check whether one will be logged before doing some expensive calculation). To turn on logging for your module, just add this to your command line or ide.cfg:
-J-Dorg.netbeans.modules.mymodule=0
You should see in system/ide.log e.g.:
[org.netbeans.modules.mymodule] something weird happened here
[org.netbeans.modules.mymodule] strange result: Result<....>
You can also set the level to -1 to get output on the console too.

Regardless of whether you use it for logging, ErrorManager should be used by all modules for several purposes:

  1. Adding information to an exception. Use annotate. You can add unlocalized information to help debug it, or localized information (preferably with a severity level) to present it comfortably to the user, if it is expected that a user action could cause it.

  2. Changing an exception type. Use annotate or copyAnnotation before rethrowing to preserve the original stack trace.

  3. Reporting an exception you have caught. Use notify to include all information available - never printStackTrace which bypasses the manager and can lose information.

See the documentation for ErrorManager for more details.


Performance Optimizations

NetBeans 3.3 release is targeted mainly on performance and stability. For performance improvements, some compatible changes in API were done to allow modules to be less aggressive during the IDE startup. The standard NetBeans modules were changed accordingly to these changes but compatibility was ensured for other modules. The other modules work in NetBeans 3.3 without modifications but they are not so kind to the user's computer resources so it is desirable to change them to use the new APIs as well. The following paragraphs show the key areas which should be changed, and the methods for checking modules performance.

DataLoader changes

DataLoaders needs to be loaded into the IDE and initialized during the startup. They are necessary for the IDE functionality and may be considered entry point for file-type oriented modules. But there are things tightly coupled with DataLoaders which are not necessary until the DataLoader spot its file.
The first thing is DataObject for given DataLoader. Resolving DataObject too early can bring a lot of unneeded stuff into the IDE, but it was a common use-case of the DataLoader because the DataLoader have to pass its DataObject Class to the super constructor. As of OpenAPIs 1.10, it is possible to pass DataObject Class's name instead of the DataObject itself, so you should change your DataLoader constructor from
public TXTDataLoader() {
    super(TXTDataObject.class);
}
to
public TXTDataLoader() {
    super("org.netbeans.modules.text.TXTDataObject"); // NOI18N
}
See also API changes

DataLoaders also don't have to set actions and display name during the initialization. For this purpose, two methods were added some time ago, even before NetBeans 3.2 release but they were not much used before. These are defaultActions() and defaultDisplayName(). You should remove calls to setActions() and setDisplayName() and override the above mentioned methods instead.
Example:
protected void initialize() {
    setExtensions(...);
    setDisplayName(NbBundle.getBundle(TXTDataLoader.class).
                            getString("PROP_TXTLoader_Name"));
    setActions(new SystemAction[] {
                        SystemAction.get(OpenAction.class),
                        SystemAction.get(FileSystemAction.class),
                        null,
                        SystemAction.get(CutAction.class),
                        SystemAction.get(CopyAction.class),
                        SystemAction.get(PasteAction.class),
                        null,
                        SystemAction.get(DeleteAction.class),
                        SystemAction.get(RenameAction.class),
                        null,
                        SystemAction.get(SaveAsTemplateAction.class),
                        null,
                        SystemAction.get(ToolsAction.class),
                        SystemAction.get(PropertiesAction.class),
    });
}
should be changed to
protected void initialize() {
    setExtensions(...);
}

protected String defaultDisplayName() {
    return NbBundle.getBundle(TXTDataLoader.class).getString("PROP_TXTLoader_Name");
}

protected SystemAction[] defaultActions() {
    return new SystemAction[] {
                        SystemAction.get(OpenAction.class),
                        SystemAction.get(FileSystemAction.class),
                        null,
                        SystemAction.get(CutAction.class),
                        SystemAction.get(CopyAction.class),
                        SystemAction.get(PasteAction.class),
                        null,
                        SystemAction.get(DeleteAction.class),
                        SystemAction.get(RenameAction.class),
                        null,
                        SystemAction.get(SaveAsTemplateAction.class),
                        null,
                        SystemAction.get(ToolsAction.class),
                        SystemAction.get(PropertiesAction.class),
    };
}
See also API changes

SystemOption usage

Modules often have persistent configuration and it is often implemented as a SystemOption subclass. The SystemOptions singletons were previously mentioned in module's manifest as OpenIDE-Module-Class: Option and their serialized state was loaded during the IDE startup, although it was not necessary in most cases. Now, you can move the option declaration into the layer and it will be deserialized the first time you'll ask for it. This also means you have to be careful to not reference the option too early. Common mistake is to place static reference to the option instance somewhere where it would be touched too early, like
    static MyOptions OPTIONS = (MyOptions)SharedClassObject.findObject(MyOptions.class, true);
inside a MyNode class or something like this.
In short, you should isolate the access to the SystemOption instances by lazy getters or lazy loaded classes. It you believe you can initialize your module without SystemOption and your SystemOption is still loaded, place a Thread.dumpStack() in a static initializer of your SystemOption, it will tell you who references it.

Services declaration

The similar issue as with SystemOptions is with ServiceTypes. ServiceTypes hold their configuration serialized and were used to be deserialized/initialized during the IDE startup. You should move all services from the module's manifest to layer, it will prevent the services from eager initialization.

WindowSystem changes

Modules which define their own Workspace or Mode, or starting with some TopComponents open were used to do this programmatically from their ModuleInstall. It is wrong from two perspectives: Why should the IDE create the window with its content when it will be hidden for most of the time, and why this thing have to be performed on every IDE startup (common case). This is solved by the new WindowSystem implementation which allows declarative specification of Workspaces, Modes and TopComponents allowing the module author to specify more docking constraints for the modes and TopComponents.
You should replace the layout creation code in ModuleInstall by XML documents placed at proper locations in the module's layer.
See the Window System API.

ModuleInstall cleanup

There is a generic guide why to cleanup ModuleInstalls.
After all of the above, your ModuleInstall.restored() should be empty in a lot of cases. If it is not the case, you should try to come up with some replacement solution. Be creative. Just think about what you're doing in the ModuleInstall, what is the impact on the IDE startup time, number of classes loaded on the startup and the memory footprint of the IDE. Then think whether it is necessary to do it during startup, how can you get it out of ModuleInstall and so on. There it one thing to think about: Your module will NOT be really used by most of the users of the IDE with your module enabled.

We can't cover all the possibilities in this guide, so I'm placing here an example for removing hooking into other modules: The cases where you're hooking some functionality in other module code can be replaced by lookup, but it requires cooperation with the other module's author. This was the case with Beans and JavaDoc modules hooking their NodeFactorys into Java module's Nodes. Now they only place instance files into their layers and Java module will pick up the instances (using a FolderInstance over some negotiated folders, namely NodeFactories/java/explorer and NodeFactories/java/objectbrowser) as soon as it will need them, which means way later than with direct registration. This kind of communication can be developed between other modules as well, but there is no generic one size fits all recommendation.
Just think about what you're doing in the ModuleInstall, what is the impact on the IDE startup time, number of classes loaded on the startup and the memory footprint of the IDE. Then think whether it is necessary to do it during startup, how can you get it out of ModuleInstall and so on. Be creative. There it one thing to think about: Your module will NOT be really used by most of the users of the IDE with your module enabled.

BeanInfo implementations

This is not an upgrade issue but surely thing you should care of.
There are some BeanInfo classes in nearly all modules. The problem with their implementation is that they are "over-cached" most of the times. The BeanInfo is a class that is brought inside the VM mostly for one-time usage like deserialization or IDE configuration so it should be optimized for size, not the speed. Holding of tons of reflective information in static fields is not an option for such a class. See for example issue #14938 for more on this topic. You could look for rewrite example here
In future, we plan to replace standard BeanInfo lookup mechanism by a XML based bean descriptions but it is not fully specified yet.

PropertySupport.Reflection

This is another thing that should be considered while rewriting the module. There is a lot of subclasses of PropertySupport.ReadWrite/ReadOnly. In most cases they can be replaced by using PropertySupport.Reflection. The reason is simple: the less classes are loaded in the VM the better.

Startup performance logging

If you'd like to see how does your module perform during the startup, turn on the startup profiling by using the command line switch -J-Dorg.netbeans.log.startup=print
You'll see cut-through the startup process in the following format:
    @10116 - module preparation started
        @10276 - prepared org.netbeans.modules.text/1 dT=160
	...
        @11104 - prepared org.netbeans.modules.vcs.cmdline.compat/1 dT=1
    @11105 - module preparation finished, took 989ms
where the @10116 is the timestamp from the start of logging (early in the startup phase), there are bigger section ending with label in the form @timestamp - label, took {time for the whole block}ms. The bigger blocks are nested and also contain checkpoints which show the time from the latest checkpoint in the form of dT={time}
The most important parts for module authors are:

@10555 - prepared org.netbeans.modules.editor/1 dT=73: This covers enabling the module's ClassLoader with dependencies, parsing manifest, loading ModuleInstall class and possibly calling ModuleInstall.validate(). In most cases you can't affect this much.

@14272 - sections for org.netbeans.my.module/1 loaded dT=21: covers installation of module actions, nodes and loaders.

@19795 - ModuleInstall for org.netbeans.my.modules/1 called dT=171: covers ModuleInstall.restored().

You should also check the time spent in Project opened and Main window opened checkpoints with and without your module.

Class loading listing

Class loading is one of the most time and memory consuming thing on the IDE startup, so you should avoid loading unnecessary classes during the startup. You can use JVM switch -J-verbose:class, grepping the output for classes of your module and also checking the impact of enabling your module on other classes (wc -l, diff). The most powerful is the combination of both the switches which will show you where are the unnecessary classes are loaded so you can better investigate why are they loaded.
Have fun with NetBeans 3.3!
Built on December 12 2001.  |  Portions Copyright 1997-2001 Sun Microsystems, Inc. All rights reserved.