TopComponent
s
InstanceCookie.Of
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.
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.
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:
System options. They may be placed in the Services/ folder using *.settings syntax, with a link from an appropriate category of UI/Services/. Provide a localized name and icon for the settings file (not its link).
Services. Register as settings in an appropriate Services/ subfolder (conventionally Executor/, CompilerType/, DebuggerType/, SearchType/, IndentEngine/, etc.). Additionally, if the service has any user-configurable properties such that it would make sense to create more than one instance, add an identical settings file into the matching subdirectory of Templates/Services/. Provide a localized name and icon for the settings file(s). Relative order of services (e.g. priority for choosing a default) may be accomplished using folder ordering.
Currently registered services should normally be marked with the
file attribute SystemFileSystem.layer
set to
project
, making them project-specific. This is not yet an
official API, just a hint recognized for the 3.3 release. The same hint
may be applied to other settings, such as certain system options.
Filesystem types. Register as a setting in Templates/Mount/, generally with a localized name and icon as well as optionally a template iterator and description.
Specific filesystem instances. Filesystems to be initially mounted
by the module can also be registered by including settings in
Mount/ rather than via procedural mounting. You can
either use *.settings syntax if you intend for the mount
to be user-configurable, or for the common case that you are trying to
mount a Java library ZIP/JAR intended for the user classpath which is
present in the IDE installation, or a Javadoc ZIP/JAR/folder for the
Javadoc tab which is also present in the IDE installation, you
can create XML files using DTDs supported by the java
and
javadoc
(respectively) which provide preconfigured
filesystem instances for you:
Warning: when doing this you should consider
compatibility of old user settings. Typically in pre-3.3 releases,
mounts (including those made programmatically from module installers)
would have been stored serialized into project settings; you must be
prepared to handle their deserialization. Assuming the new file under
Mount/ (or a subdirectory) handles the mounting needs and
obsoletes the former serialized setting, you can cause the old mount
to disappear after a restart cycle, as in
org.apache.tools.ant.module.AntModule
.
Do not try to return null
from writeReplace
as it will not work (see #17476 for details).
Debuggers can probably be registered via lookup in Services/. (Untested; only one is active at a time anyway. Debugger types are services, q.v.)
Clipboard convertors. If anyone actually uses these, they should be registered via lookup in Services/.
JavaHelp - see below.
Module installers - many tasks formerly done procedurally can now
be done in layers; covered throughout this document, and also see below. Many installers can be deleted
outright; every installer which currently overrides
readExternal
and writeExternal
should be
changed if at all possible to not keep any serialized state.
(It is safe to delete these methods or the entire installer if you no longer keep any state.
The IDE will not attempt to call the default implementations
at all if these methods are not overridden, so you do not need to override
readExternal
just to keep compatibility.)
<!-- 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:
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:
Better performance - avoid running code during startup. Objects are initialized only as the window system needs them.
Simplification of code - rather than procedurally checking for window system objects and creating them and removing them at uninstallation, more is done declaratively in XML.
More power - you can control MDI frame attachment modes and such advanced features not available through the Java-level API. Modules can also cooperate to incrementally supply pieces of a workspace, and so on.
Better uninstallation and project switching - your layer-supplied workspaces, modes, and components will be uninstalled more cleanly with your module, and will serve as a default for all projects (but customizable in each) without added effort.
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.
TopComponent
sIf 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 TopComponent
s which cannot be safely deserialized
at all should still writeReplace
to null
, so
as to avoid problems in a future release.
There is an existing document describing how to do this conversion: Manifest Localization Guide
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:
Performance during startup - while some effort is made to improve performance over 3.2 even for manifest-installed helpsets, they still need to be parsed in order to retrieve helpset title and home ID. Using a layer, this information is specified explicitly and the IDE need do nothing until the helpset is actually displayed.
Flexibility - using the layer syntax, you can explicitly control whether helpsets are merged into the master helpset or not, and if so in which order; control whether links are made in the Help | Help Sets submenu, and if so in which order, and how the link appears; add additional convenience links with the same helpset; and more.
To use this new system effectively, first remove the obsolete manifest declaration. Now in your layer you need to add three items:
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.
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.
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.
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:
obtain a list of all known modules
determine if a module is enabled or not, and listen to this change
programmatically enable or disable a known module
for your own module, refuse to be installable if some criteria (such as presence of a license file or external application) are not met
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.
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" />
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 DataLoader
s 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:
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.
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.
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.
InstanceCookie.Of
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.
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.
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.
Entity resolution. In 3.2 it was possible to add declarations of known entities (in most cases XML DTDs) to the system, using an XML file in a special format. For example, if you had your own DTD for some purpose and wished to ensure that at least from within the IDE a local copy would be used instead of accessing the network, an XML file could be added to the services area with the DTD given by -//NetBeans//Entity Mapping Registration 1.0//EN.
This method continues to work in 3.3 but is deprecated: it adds
some overhead at startup to parse all such entity declarations, and
there were some possible race conditions. Instead you may register
entities according to their public ID directly in a layer: create a
file containing the entity itself and give it a special name beneath
xml/entities/ computed from the public ID. This is more
efficient and safer. See
EntityCatalog
for details.
Processors. In 3.2 you could add your own cookies to XML files
without actually recognizing the files using a data loader. This was
done with an XMLDataObject.Processor
implementation,
registered programmatically via XMLDataObject.Info
.
For 3.3 this is deprecated, as a typical processors may never be used in a given IDE session, and it caused too much work at startup to register them all programmatically. Instead the folder /xml/lookups/ is used, with a file naming convention similar to that given above for entities. But the file names end in *.instance and should give an instance of one of the following things:
Environment.Provider
. This is the best option, new in
3.3. It permits you to provide a lookup for the XML file which may
serve cookies as it likes, including changing the cookies on the fly.
XMLDataObject.Processor
. This is deprecated for 3.3.
XMLDataObject.Info
. Also deprecated for 3.3.
If you were providing a special DataNode
for the XML
file, this is deprecated but no better alternative yet exists in 3.3 -
Looks will probably be used for this in the future. In the meantime it
will still work; you can use XMLDataObject.Processor
subclassing DataNode
, or provide results from a lookup
query on Node
. Either way, register the object in the
layer as above.
The Services API has additional information on associating cookies with XML files.
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:
Turning it on by itself, without something else to use it, would be pointless for the user.
You have made it into an extension, or considered doing so, in order to be able to share it among several first-class modules.
It has no independent user interface, or it does but this UI is just a container to be filled by other modules.
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/.
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 PropertyDescriptor
s in a
BeanInfo
, or to Node.Property
s 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
DataObject
s 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.
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(...)
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
ErrorManager
for loggingSystem.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=0You 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:
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.
Changing an exception type. Use annotate
or
copyAnnotation
before rethrowing to preserve the original
stack trace.
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.
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
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
subclass. The SystemOption
s
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. 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.
SystemOption
s is with
ServiceType
s. ServiceType
s 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.
ModuleInstall
s.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 NodeFactory
s 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.
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.
-J-Dorg.netbeans.log.startup=print
@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 989mswhere 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}
@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.
-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.