ModuleInstall
class. Other classes in the package are generally not useful to module
authors.
All modules are distributed and installed as JAR files. The basic format should be rather familiar; classes constituting the module are archived in the JAR, and special entries in the manifest file are recognized by the IDE.
The basic idea for the format of modules is taken from the Java Extension Mechanism. The basic ideas behind the Package Versioning Specification is used to handle dependencies both between modules and of modules to the system.
All modules have some set of basic properties that indicate which types of features they provide, which Java classes implement these features, and what special option settings should be used when installing these features into the IDE. Some of this information is listed in the manifest file using the customary format, and NetBeans-specific attributes. The Java Activation Framework, as well as JDK-internal features such as support for executable JAR files, was used as a model for how to specify the useful contents of a JAR in a data-driven fashion: many modules will need no special installation code other than attributes in the manifest, and an XML layer giving additional more specific deployment information. JavaHelp is used for the context help.
For basic cases, you should need to do very little to create a module besides writing the basic source code.
Please see a list of tips on writing modules, installing them into the IDE, testing and debugging them.
All module implementation classes must reside in a JAR file. If you want to split up a large application into several pieces, perhaps so as to make independent upgrades possible, you should do so by creating multiple modules and relating them using versioning.
To create the JAR, just use the standard JDK's
JAR
tool. You will need to add a custom manifest file, so use the
-m
option to specify the additional contents. (Note that
-m
will automatically merge the contents of your manifest
file with anything it would normally add, so e.g. signatures will be
automatically created properly.)
OpenIDE-Module
. Its value should be an arbitrary
identifier (with a format similar to that of a Java package name)
identifying the module for purposes of upgrades and
dependencies. You are encouraged (but not required) to use as this
tag the name of a Java package containing the principal classes for
the module; this ensures that there will not be conflicts between
modules produced by different developers or institutions, provided
that you follow the JavaSoft recommendations on naming packages
according to an Internet domain name you control. See the section
on versioning for details on what this tag
is used for.
There are a few other global tags which are optional but encouraged:
OpenIDE-Module-Name
OpenIDE-Module-Name_fr
.
OpenIDE-Module-Short-Description
OpenIDE-Module-Long-Description
OpenIDE-Module-Display-Category
OpenIDE-Module-Description
OpenIDE-Module-Install
OpenIDE-Module-Layer
OpenIDE-Module-Module-Dependency-Message
and
OpenIDE-Module-Package-Dependency-Message
OpenIDE-Module-Requires-Message
Since 1.24:
A more flexible way to provide human-readable information is to declare
the attribute OpenIDE-Module-Localizing-Bundle
. Its value should
be the resource path to the (base locale of) a bundle file providing localizable
attributes for the module (display name, category, short and long description).
The bundle file may have localized variants as usual, and the keys should be the
same as the desired manifest attribute names, e.g. OpenIDE-Module-Name
.
If it is necessary to localize an attribute from a section (currently only filesystem
sections with their Display-Name
attribute), you may again place these
in the bundle, where the key will be prefixed by the section name and a slash, e.g.
org/foo/MyFileSystem.class/Display-Name.
All other tags are bound to a particular module section. Naturally you may use other standard manifest attributes.
In summary, here is an example manifest file that could be included
with the JAR tool's -m
option:
Manifest-Version: 1.0 OpenIDE-Module: com.modulemakers.clip_disp/2 OpenIDE-Module-Specification-Version: 2.0.1 OpenIDE-Module-Implementation-Version: 2.0-beta-rewrite OpenIDE-Module-Build-Version: 2003-12-31 00:23 cron@buildhost.mycorp.com OpenIDE-Module-Name: Clipboard Displayer OpenIDE-Module-Name_cs: Prohlizec schranky OpenIDE-Module-Install: com/modulemakers/clip_disp/Installer.class OpenIDE-Module-Layer: com/modulemakers/clip_disp/Layer.xml X-Comment-1: I am a comment (just a deliberately meaningless X-Comment-2: header) - "Sealed" is a standard manifest attribute. Sealed: true Name: com/modulemakers/clip_disp/DisplayClipboardAction.class OpenIDE-Module-Class: ActionModule authors are strongly encouraged to use the API Support module which among other advantages, provides interactive parsing of manifests that can help the beginning API developer immediately see how the IDE will parse his manifest, including possible parse errors.
If you have troubles with a manifest, check to make sure you have the suggested extra blank line at the end. Some JDKs may have trouble parsing it otherwise.
OpenIDE-Module-Install
attribute, you may specify
a custom class which will handle any complex aspects of the module's
installation (or uninstallation). This class is only necessary to
write if the standard module sections and
layers do not
cover everything you need to do. Even if you do write such a class,
standard sections and layers may still be used for any part of the module's
integration into the IDE which is conventional - the main class need
only handle the exceptional parts.
To use a main install class, just extend the
ModuleInstall
base class. Your class must be able to be
instantiated as a JavaBean.
There are several methods which you may override, and may do anything
which is required to make the module cleanly enter and exit the
IDE.
For example:
package com.modulemakers.clip_disp; import org.openide.modules.ModuleInstall; import org.openide.filesystems.FileUtil; import java.net.*; public class ModuleHandler extends ModuleInstall { public void installed() { // This module has been installed for the first time! Notify authors. HttpURLConnection conn = (HttpURLConnection) (new URL ("http://www.modulemakers.com/clip_disp/installed.cgi").openConnection ()); conn.getResponseCode (); conn.disconnect (); // Handle setup within this session too: restored (); } // Nothing special required here. // public void restored() { // } // Do not need to do anything special on uninstall. // Tools action will be removed automatically. // public void uninstalled() { // } public boolean closing() { // Ask the user to save any open, modified clipboard contents. // If the user selects "Cancel" on one of these dialogs, don't exit yet! return DisplayClipboardAction.askAboutExiting (); } }
The installed
handler may do more or less what it
wishes - it will be called in a running IDE. The same applies to
uninstalled
and closing
. However,
restored
is more delicate in that it may be called
during earlier phases of IDE initialization. Therefore, it should
not use presentation-oriented features of the IDE, such as the
Explorer or Window System APIs. However, the other APIs are
acceptable to use in restored
, including
DialogDisplayer
.
The
ModuleInstall.updated(...)
method will be called just once when
a module is updated to a new version - when the new version is loaded
into the IDE for the first time (in a new session), the method is called
and the previous version number is accessible. Neither installed
nor uninstalled
will be (automatically) called in this case.
It is a module author's responsibility to make sure that new versions of a
module are capable of "cleaning up" obsoleted installations created by older
versions, as far back as a user is likely to be directly upgrading.
Some module installer classes may desire to keep state across
IDE sessions, for example if they require specific instructions on
what to uninstall that can only be gotten during the installation
process. All installers are automatically
Externalizable
and module authors may decide to override the two methods of this interface
to read and write its state from an
object source. Typically you would want to serialize and deserialize
shared class fields
in the object using these methods.
It is a good idea to call the super methods as the first action when overriding.
In some cases, a module relies on some external resource to be present
in order for it to be used. For example, an external application may need
to be installed in order for it to do anything. Or it may require a valid
license key. In such cases, the
ModuleInstall.validate()
method may be overridden to check for this external resource and throw
a (preferably politely localized) IllegalStateException
if something is wrong. Throwing this exception will cancel the module installation
(or restoration) cleanly. Note that such an installer should not expect
anything related to the module to already be loaded when this method is
called, as it may be done when deciding whether to even begin loading.
ModuleInstall
; or
ModuleInstall
but this installer:
ModuleInstall
object (static or instance fields with
initializers or initializer blocks or the initialize
method).
readExternal
nor
writeExternal
methods, nor any other related methods such
as readResolve
or writeReplace
. Thus it has
no externalized state.
installed
nor updated
(these will then just call restored
).
uninstalled
but only to undo the effects
of restored
or other changes made by the module in the
IDE while running. May override closing
and/or
close
but only to undo the effects of changes made by the
module while running.
restored
and/or validate
.
Additionally it is recommended not to use restored
to
add an object to the running IDE where use of the module layer would
suffice, nor to modify persistent state in restored
or
validate
that might affect a future call to the same
method. When possible it is best not to have a
ModuleInstall
at all.
Furthermore, the following practices should be avoided wherever possible in order to simplify installation of the module from Auto Update and via "ad-hoc" addition of the module JAR by a user (note that mentioning options here does not imply that they are officially supported by the APIs or even unofficially possible at any particular time):
Class-Path
from within the module JAR or its
extensions to point to resources which are not in or below the
directory containing the referring JAR (i.e. inclusion of
../ or absolute paths).
Class-Path
beneath the
modules/ directory (for example in
modules/ext/) and any locale variants of the above JARs;
except insofar as the presence of such files does not in and of itself
change the functionality of the IDE and is not required for the module
to operate (for example mounts of documentation ZIPs declared in the
layer); even then this is discouraged if there is an alternative as it
complicates ad-hoc module installation though Auto Update may be fine.
You may specify the global tag
OpenIDE-Module-Layer
in addition to or instead of a
module main class; as a rule, use a layer for a particular task in
preference to a module installer, if you can. The tag value should
point to an XML file inside the module which is in the format
understood by XMLFileSystem
.
Its contents specify some files that the module will add to the
system filesystem
controlling much of the IDE's configuration. This filesystem is composed of many read-only XML layer filesystems
as well as a writable layer corresponding to the config
subdirectory of the user directory.
The XML format is relatively simple. There is an
online DTD
for it, but descriptively: the document element is
<filesystem> and corresponds to the root folder;
subfolders are represented with the <folder>
element; and files within folders with the <file>
element. Files and folders must have names, with the name
attribute. Files need not specify contents, in which case they will be
zero-length; or they may specify contents loaded from some other
resource (url attribute, treated as relative to the base
URL of the document itself); or for small textual contents, the
contents may be included inline inside the element, typically using
the CDATA
syntax, though this usage is deprecated.
Attributes may be specified on folders
or files as empty <attr> elements; the name must be
supplied, and the value in one of several formats: primitive types,
strings, URLs, arbitrary serialized objects in hexadecimal, or
computed on the fly by calling the default constructor of some class,
or a static method (the method may be passed the file object in
question and/or the attribute name, if you wish).
So as an example to install a template:
<?xml version="1.0"?> <!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.0//EN" "http://www.netbeans.org/dtds/filesystem-1_0.dtd"> <filesystem> <folder name="Templates"> <folder name="Other"> <file name="foo.txt" url="actual-contents.txt"> <!-- Make it a template: --> <attr name="template" boolvalue="true"/> <!-- Localized display name of data node: --> <attr name="SystemFileSystem.localizingBundle" stringvalue="com.foo.module.Bundle"/> <!-- HTML description of template for wizard to display: --> <attr name="templateWizardURL" urlvalue="nbresloc:/com/foo/module/foo-template-help.html"/> <!-- Set an iterator; complex object, so --> <!-- easiest to create thus: --> <attr name="templateWizardIterator" newvalue="com.foo.module.FooTemplateIterator"/> </file> </folder> </folder> </filesystem>
where FooTemplateIterator
contains a public constructor
taking no arguments.
The primary advantage of using layers is that they are declarative
rather than procedural, so you can install many kinds of extensions to
the IDE's behavior without any Java code, only XML. A related
advantage is that unlike ModuleInstall
, there is no need
for multiple kinds of logic for module installation, restoration,
uninstallation, and upgrading; the "files" added by the layer are not
stored to disk and are loaded by the IDE in every session from the
XML, so when the module is uninstalled or upgraded its associated
files are correctly removed or changed, respectively, without any
extra work. (If the user customizes the files, however, their
customizations are stored to disk, and thus form a permanent
"patch" against the baseline configuration provided by the module.)
Not all parts of the IDE's configuration can be customized using files on
the system filesystem and thus by layers, but many things can.
Some common things that layers tend to be used for:
By default files listed in XML layers are not ordered in any
particular way, just as files found on disk would not have a
particular order. For purposes of some kinds of installation, it is
desirable to order data objects in data folders in a particular way -
for example, menus are
built from their menu items
in the order the instance-bearing data objects occur in the data
folder. The call
DataFolder.setOrder(DataObject[])
suffices to arbitrarily change folder order, but usually it is
desirable to create the proper order declaratively in the XML. For
this reason, DataFolder
understands special attributes
(set on the FileObject
forming the folder) which provide
relative ordering constraints on the objects in the folder.
Specifically: if there are data objects A and B in the folder,
represented by
primary files
afile and bfile, and there is an attribute
on the folder containing them named afile/bfile whose
value is Boolean.TRUE
, then the folder will attempt to
place A before B in its ordering. For example, the XML:
<folder name="SomeFolder"> <file name="first.txt" url="first.txt"/> <attr name="first.txt/second.txt" boolvalue="true"/> <file name="second.txt" url="second.txt"/> </folder>will cause the IDE to try to keep first.txt before second.txt. Things to remember:
DataFolder.setOrder(DataObject[])
is called, the explicit
order overrides the existing constraints. Subsequently added
constraints might have an effect, however.
The resource path pointing to the layer is automatically localized
by the IDE every time the module is installed or restored; if there
are locale-specific variants, they are merged with the main
layer. More-specific variants can simply add files to the set
installed by the module; however they take precedence over the
less-specific variants, and thus can replace files, or even remove
(suppress) them, using *_hidden masks as used by
MultiFileSystem
.
In fact, if module A depends on module B, then A's layer(s) will take
precedence over B's layers, and it may replace or remove files
installed by B. In the same way, any module may replace or remove
files installed by the core.
NetBeans recognizes two special file attributes, typically set in layers, which can improve UI.
SystemFileSystem.localizingBundle
If set on a file object in the default filesystem serving as
the primary file for a data object, the data node's display name as
annotated by the filesystem will be taken from that resource bundle
(given as a string). The bundle will be found using
NbBundle.getBundle(String)
,
meaning that it should be of the form
org.domain.pkg.Bundle and will be found using the
default classloader and locale. The key in the bundle should be the
full resource path of the file.
SystemFileSystem.icon
If set on a file object on the default filesystem, the data
node as above will be given the 16x16 icon supplied by the attribute
value, given as a URL (java.net.URL
, not
java.lang.String
). Similarly
SystemFileSystem.icon32
for the 32x32 icon, if desired.
A java.awt.Image
may also be
given.
By using these attributes, modules may provide a more pleasant and localized user interface for files they install (typically via layer) whose data nodes would be seen by a user (for example, templates or services). If support for these file attributes is removed or incompatibly changed in the future, such data nodes (and anything driven by display aspects of data nodes, for example the labels of menus created from folders beneath Menu/) will still appear and be functional, however the names and icons will be unattractive.
Modules can do three things with versioning:
OpenIDE-Module
tag, whose value should be a unique
(programmatic) identifier for the module, as mentioned above. Not
be confused with the display name, which is free-form and may be
changed at will, this code name should not be changed
arbitrarily - pick a name and stick with it throughout module
releases.
OpenIDE-Module-Specification-Version
and the
OpenIDE-Module-Implementation-Version
tags.
Modules are also permitted to use OpenIDE-Module-Build-Version
to
give information about when or by whom they were physically built, in case there
is more specialized semantics given to the implementation version.
OpenIDE-Module-Module-Dependencies
), Java packages
(OpenIDE-Module-Package-Dependencies
), Java itself
(OpenIDE-Module-Java-Dependencies
), or the IDE
(OpenIDE-Module-IDE-Dependencies
).
As of 2.3 the tags
OpenIDE-Module-Provides
and OpenIDE-Module-Requires
can also be used to specify dependencies between modules without naming
the exact module to depend on. A module may provide one or more
tokens. These are strings of conventional meaning, in the format of a Java
package or class name - perhaps taken to be the name of a class which will
be supplied to the lookup system, though there could be other meanings. A module
may also require one or more tokens. A module which requires some tokens
may only be enabled by the system if for each such token, there is at least one other
module which provides that token and is already enabled.
1.2.1
), and an implementation version,
which is some free-form text. Specification versions may be
incremented to indicate compatible API extensions, in which case
the check to make sure that the feature requested is available may
be done using a (lexicographical) compare on the Dewey-decimal
numbers (e.g. 1.0
< 1.0.1
<
1.1
). Implementation versions may be requested
according to an exact match only. Thus, specification versions are
generally used when you depend on some general and specified
behavior of another feature; implementation versions are used to
tie builds of different features together closely, as when you
maintain and release all the features yourself, and wish to
distribute them in a well-tested unit.
(You may also specify a build version in your manifest, typically giving a date. This is used only for diagnostic purposes and is otherwise ignored by the module infrastructure.)
So, modules may specify either or both kinds of versions in their manifest, and correspondingly request such versions from other modules. (You may have at most one dependency between any single pair of modules, however.) The Versioning Specification provides similar numbering for (some, but not all) Java packages, as well as the core Java Platform APIs and Virtual Machine Specification. The IDE also has such versions which may be used to request a general level of Open API support (using specification versions) or a particular NetBeans build (using implementation versions).
Since the Versioning Specification does not address incompatible changes (those which would break existing code), but these are in practice occasionally necessary (however painful), both modules and the IDE itself may provide a separate integral number giving the incompatible release version. You may only request a particular release version, not subsequent ones, and you must specify the release version if there is one when giving any module dependency. It only differs from a complete change of the feature in that the module installer tool may prompt the user to make a "major upgrade" if a new release version of a module is available.
Since 2.3 a module dependency may also use a range of release versions. The syntax is that in place of a single major release version, you give a minimum then maximum version separated by dashes:
OpenIDE-Module-Module-Dependencies: base.module/1-2 > 1.0
where the base module might either have the minimum major release version (and the given specification version or later, if that is also requested), or other major release versions up to the maximum (in which case the specification version is irrelevant). You may not ask for an implementation version with this syntax.
To use all of these tags once you understand them is not too
hard. First of all, feature names: a module is named according to
the OpenIDE-Module
tag, which should look like a Java
package name, but may be followed by a slash and release number
(e.g. com.mycom.mymodule/3
); the IDE itself is named
IDE
, again usually with a slash and release number;
Java packages are just named by the package name; the Java Platform
APIs are named by Java
; and the Java Virtual Machine
by VM
.
Now, a module may list its own specification and/or implementation versions using the tags above. To specify a dependency on other features, it may use some or all of the four dependency tags. The value of each will be a comma-separated list of dependencies of that general type, each of which can be of the form:
>
SPECVERSION
Remember, if requesting a dependency on a module which has a release number, you must give that release number as part of the feature name, whether or not you also ask for a specification version.
=
IMPLVERSION
In summary, here is an (unlikely) example that specifies and requests all sorts of versions:
OpenIDE-Module: com.mycom.mymodule/1 OpenIDE-Module-Specification-Version: 1.0.1 OpenIDE-Module-Implementation-Version: 1.0-release-g OpenIDE-Module-Module-Dependencies: com.mycom.mysistermodule/1 = 1.0-release-g, com.othercom.anothermodule > 2.1, org.netbeans.modules.applet/1 > 1.0 OpenIDE-Module-Package-Dependencies: javax.television > 0.9 OpenIDE-Module-Java-Dependencies: Java = 1.2.1b4, VM > 1.0 OpenIDE-Module-IDE-Dependencies: IDE/1 > 1.0 OpenIDE-Module-Provides: javax.television.TunerProvider, javax.television.RemoteControl OpenIDE-Module-Requires: org.netbeans.javahelp.api.Help
There is further special treatment for handling of package dependencies for several reasons:
Class-Path
must be
recognized and used; typically this attribute is used to actually add
extensions to the module classloader, while the Modules API attributes
are used to ensure that specific versions are available.
You may specify a sample class name in square brackets immediately after the package name (or indeed instead of it). Giving a class name together with a package name requests that the IDE first try to load that class (it must be able to, so choose a class in the extension you know will be there); then the versioning information is checked. (If the sample class is simply in the desired package, you may omit the package qualification as a shortcut.) This addresses the second problem. Giving just a class name in square brackets with no preceding package name indicates that the IDE should only ensure that the named class is loadable, bypassing the Versioning specification mechanisms. This addresses the first problem.
Here then is a sample manifest which depends on an extension called
again javax.television
, where it is known that a class
javax.television.TunerProvider
is part of the package.
The extension is stored in modules/ext/television.jar
relative to the IDE's installation directory:
OpenIDE-Module: com.mycom.mymodule/1 OpenIDE-Module-Package-Dependencies: javax.television[TunerProvider] > 0.9 Class-Path: ext/television.jar
Where can you get the version information to depend upon?
Package
class, or examine the manifest of the JAR you depend upon.
java.specification.version
and
java.version
; for the VM,
java.vm.specification.version
and
java.vm.version
.
netbeans.log
.
Important! If your module has compile-time references
to classes in another module, you must specify a
direct dependency on that module using OpenIDE-Module-Module-Dependencies
.
This ensures that the modules will be
installed in the correct order; otherwise a
NoClassDefFoundError
or similar problem could result.
Since 2.19 modules which provide Java-level APIs can
specify that only certain packages in the module form part of the
public API and should be accessible to other modules depending on
that module. The tag OpenIDE-Module-Public-Packages
is optional; if not present, all packages are considered
fair game. If present, it gives a list of package names to export
to other modules, for example:
OpenIDE-Module-Public-Packages: org.netbeans.api.foo.*
indicates that only classes (and resources) in the package
org.netbeans.api.foo
should be available to client
modules. Attempts to use classes from other packages will fail,
for example with a NoClassDefFoundError
.
More than one package can be given; separate them with commas or spaces. A package name may end in .** rather than .* to indicate that any subpackages are also exported, rather than just the package itself. The special value - (a single dash) indicates that no packages are to be exported. (The module might still provide a non-Java-level API, for example by defining and handling a custom XML DTD.) "Private" classes and resources may still be accessed using reflection from the system classloader, just not from a module classloader.
Additionally, sometimes a module with a restricted API nonetheless wishes to make normally private classes available to a "friend" module (for example, an eager module maintained by the same developer). This is possible if the friend module declares a direct dependency on the API module using an implementation version dependency - this kind of dependency indicates that the client module is prepared to track every idiosyncrasy of the API module, and knows how to safely use undocumented classes, as if both modules formed part of the same code base.
Confused by all these restrictions on classloading? See the Class Path document which gives a fuller, more informal explanation.
The tokens supported in version 4.44 are:
OpenIDE-Module-Requires: org.openide.modules.os.Windows OpenIDE-Module-Requires: org.openide.modules.os.Unix OpenIDE-Module-Requires: org.openide.modules.os.MacOSX
Please note, that Mac OS X is in fact also Unix, so requesting Unix also enables your module on Mac OS X.
So if one wants a module to be automatically enabled on Mac OS X and silently
disabled on other platforms the best way is to create an eager module which
requests the MacOSX
token and the module system takes care of
enabling it only on Mac OS X (as is the case of NetBeans
applemenu module).
OpenIDE-Module-Class
attribute, giving a code for the
extension type (see below); and may also have additional entries for
further customization.
Action
installs a system action into the
IDE. Refer to the
Actions API
for details on how to create an action. The class must implement
SystemAction
.
By using this manifest section, your action will be available as
a "tool", i.e. it will appear (when enabled) in a list of all
extension tools in any popup menu (etc.) using
ToolsAction
.
Note that you do not need to use ToolsAction
directly for this purpose: it should already be attached as a popup
to all interesting nodes, as a menu item in the Tools menu,
etc. Just using the manifest tag ensures that your action will be
shown under such submenus.
Note: Actions may also be installed into fixed places in the Main Window's menus and toolbars (this can also include special components like palettes and so on), to keyboard shortcuts, or to the default popup menu of a data loader you did not write. The Actions API describes how to do all of these things - they do not require anything special in the manifest (besides use of a module install class).
Some types of actions make sense only for an object under your direct control. For example, if you are writing a data loader, you should specify which actions will be provided by default in its data objects' popups. This type of use does not require global installation; it can include standard IDE actions, as well as your own. Please see the Actions API for details on attaching an action to your own object.
Also, as mentioned in the Actions API, it is good practice for all actions implemented by modules to be copied into logical subfolders of Actions/ on the system filesystem (for example using a module layer), since this folder is presented to the user as a read-only "actions pool" they can use as a source of all actions to customize their menus, toolbars, data object popup menus, keyboard shortcuts, and so on.
Loader
installs a
data loader
into the system
loader pool.
Refer to the
Datasystems API
for instructions on how to create a loader.
There are a couple of options which may be used to specify the relative precedence of data loaders, so that a more specific loader may take precedence over a more general one:
Install-Before: classname
DataLoader
constructor
for an explanation of how to use representation classes.
You may supply multiple class names separated by commas.
Install-After: classname
Install-Before
, except that the IDE will
attempt to install this loader after another, so as to give
it lower precedence.
Install-Before
and
Install-After
tags (if present) may fail to be honored
if there is no such registered data loader to compare to, or if the
tags are contradictory (imply a cyclic dependency); if so, the loader will still be installed.
By default a loader is placed at the end of the pool (i.e. lowest priority).
Note that these tags have no
effect on module install order or dependencies - for one thing,
loaders will be installed in the correct order regardless of which
order their providing modules are installed in.
You need not specify module dependencies on other modules simply because you use the names of data objects in those modules in your install before and after tags; the ordering hints will simply be ignored unless and until the other module is installed, at which time they will take effect.
This section describes how a module may be deployed to a user's IDE.
Sometimes a module may need to bundle other files besides the
module JAR alongside it. No concrete mechanism for doing so is
specified in the Modules API. However, if there is such a mechanism,
then it is possible for the module to find these other files.
InstalledFileLocator
permits such packaging mechanisms to let modules find their associated
files, as of 3.21.
In the current implementation, modules may be
bundled with other files using
NBM files
as provided by Auto Update, and the NetBeans build process premerges
modules in the selected module config into one large installation
directory, accessible via several system properties. Correspondingly,
the default locator finds files in this structure. The installation
structure might however change in the future, so
InstalledFileLocator
is the only safe way to find such
bundled resources.
Sometimes you may have an API module which you wish to deprecate in its
entirely - it is only available for purposes of backwards compatibility.
Normally such modules are autoloads. To ensure that remaining clients of the
module are properly warned of its status, you may specify the
manifest attribute OpenIDE-Module-Deprecated
with the value true
.
Generally you should also supply a message using the localized manifest attribute
OpenIDE-Module-Deprecation-Message
.
This
message may be displayed somehow if a non-deprecated module depending on your
deprecated module is enabled.
Sometimes as the developer of an API module, you may wish to make major changes in how your APIs are laid out physically in modules. Most commonly, you determine that it would be desirable to split one big API module into several smaller pieces. However you wish to retain compatibility for existing client modules, so that if they used your old monolithic module, they will now automatically get access to the newer, smaller pieces.
This is straightforward: create an XML file according to the DTD
-//NetBeans//DTD Module Automatic Dependencies 1.0//EN
and
install it
in the system filesystem under ModuleAutoDeps/
(system/ModuleAutoDeps/*.xml). Note that it will probably
not work to do this using an XML layer since the information
must be available before any module is even loaded.
As of 3.33
modules/
directory
(where it will be automatically installed upon the next IDE
restart) or manually installed from an arbitrary location using the
context menu on the Modules node in the options dialog. Children of this
node represent available modules and permit them to be dynamically
uninstalled or reinstalled.
More user-friendly is the Update Center, a feature specific to the NetBeans core implementation (i.e. its mechanism is not specified by the APIs), which presents guided dialogs prompting users to automatically upgrade modules or install new ones from an update server. This functionality is provided by a module.
ModuleInstall.updated()
.
This permits you to manually remove obsoleted actions, delete old
templates, etc. - remove anything not handled by the module manifest
which is nevertheless part of the IDE's persistent state.
With the advent of XML layers, most of these reasons for using updated()
have disappeared.
A proper module will store all of its configurable state using
options
(excepting service types, module externalization, etc.), and the
contents of these will be automatically carried over, one option at
a time (according to
option name
and
property name)
into the new module version using Externalization - so as long as your
option values can be externalized and reread into the new version, you
need not worry. Similarly, if the
ModuleInstall
class is externalizable and there is non-project state being stored
this way, you must ensure as usual that your externalization is
forward-compatible for upgrading to proceed smoothly.
First of all, when the IDE starts up, all previously-installed
modules are restored, calling the restored
methods on
their install classes if present. (Sections are installed before
this method is called.) If multiple modules are being restored
(which is normally the case), the IDE installs them in an
arbitrary order. However, if some of them specify
dependencies on other modules, the order may be adjusted so as to
make sure that the one specifying the dependency is installed after
the one it depends on.
Now, when the IDE is running, modules may be installed into it, possibly a cluster of them at once. In such a case, the IDE first checks to make sure that all the prospective modules satisfy their dependencies, either on system or Java features; already-installed modules; or other modules in the prospective cluster. If any modules fail their dependencies (or had syntactical errors in their manifest files, etc.), they are removed from the list and the user is given an explanation of what is missing. The remainder are then ordered according to dependencies as above, and then installed in that order.
Caution: it is forbidden to install modules which have cyclic dependencies on one another, as the IDE would be unable to determine which order to install them in! In fact, it will signal an error and not install such modules. Generally such a situation means that you should "factor out" the common required functionality into a new module which the original ones will then specify legal dependencies on.
Lookup.Template templ = new Lookup.Template(ModuleInfo.class); Lookup.Result result = Lookup.getDefault().lookup(templ); Collection modules = result.allInstances(); // Collection<ModuleInfo>The resulting
ModuleInfo
objects give basic information
about known modules (both enabled and disabled), such as their names
and versioning information, and current enablement status. This API is
strictly read-only and informational.
A limited ability to modify the installation status of modules is provided by the APIs as of version 1.31. In the folder Modules/ on the system filesystem, there will be XML files corresponding to all known modules. In each case the name of the XML file is derived from the code name base of the module with . replaced by -, and followed by .xml; for example, org-netbeans-modules-properties.xml. The contents of the XML file are given by a fixed DTD.
For purposes of the API, only parts of these files are defined. The
root element must be <module>
and its
name
attribute must be the code name base of the module.
Inside the root element are various <param>
elements, each with a name and some textual contents. The APIs define
only one parameter, enabled
, which if present must be
either true
or false
.
The APIs do not currently permit arbitrary changes to these files.
Specifically, addition or deletion of these files, or modification of
other files that may be in the same folder, is not permitted.
Modification of these files is permitted in only one way: if a module
XML file already contains the parameter enabled
, you may
rewrite the file to be identical except with the opposite value of
this parameter. The module system will then make a best effort to
enable or disable the module accordingly (subject to dependencies and
other constraints).
Doing this is not recommended except in unusual circumstances, and
is intended primarily for session support to be able to enable and
disable modules via layer. If your module just needs to prevent itself
from being turned on under some circumstances (for example if it is
missing a valid license key), simply use
ModuleInstall.validate
.
When rewriting the module XML be sure to use
FileSystem.runAtomic(FileSystem.AtomicAction)
to wrap the reading of the old XML and the writing of the new, to
prevent file changes from being fired halfway through.
Code which creates new classloaders and loads other code indirectly
should, however, be aware that the created classloaders are generally
subject to regular security restrictions, unless code is loaded from
within the IDE installation, or certain forms of NbClassLoader
are used. When in doubt, explicitly define the protection domains for
classloaders you create.
The IDE currently loads each module in its own classloader, which means illegal dependencies between modules will result in errors.
The Update Center supports certificate-based signing of downloaded modules, so that the user can be sure that the contents have not been tampered with or accidentally corrupted since their creation.
There is an unsupported provision for
running modules making use of JNI native implementations inside
NetBeans. You may create a directory
modules/bin/ (in either the installation or user
directories) and place native libraries (DLL or shared-object) there;
it will be added to the search path for calls to
System.loadLibrary
made from module code. This avoids the
need to explicitly add some directory to the binary load path. The
module is entirely responsible for distinguishing between various
platforms and operating systems and requesting a library name
appropriate to the current one, however. Use of JNI is of course not
recommended and should be restricted to cases where it is unavoidable.
For autoload modules, use modules/autoload/bin/ etc. - generally, the bin/ subdirectory of the directory containing the module JAR.
Note: as of NetBeans 4.0 the subdirectory name
must be lib
rather than bin
.
If a native library refers to other native libraries, they are likely to be found using the normal search path for the platform. This may mean that if you have several libraries used in a module, you must either put all but the directly referenced one in the system's global library search path, or merge them all together.
Warning: since the JVM cannot load the same native library twice even in different classloaders, a module making use of this feature cannot be enabled more than once in a single VM session. JARs loaded from the classpath (application classloader) are never reloaded, so it may be possible to include the native-dependent classes in such a JAR and make use of it from a module JAR.