Chapter 3. The Module System: Let's Understand the Basic Building Blocks!

T his chapter focuses on the NetBeans module system, which is the central component of the runtime container and is responsible for loading and managing all the modules in the application. You'll learn how a module is structured, how it connects with other modules, how it integrates into the NetBeans Platform, and how its lifecycle is traced and influenced.

Overview

The NetBeans module system is responsible for managing all modules in the application. It is also responsible for tasks such as creating the classloader, the loading of modules, and their activation and deactivation. The concept of the NetBeans module system is based, as far as possible, on standard Java technologies. The basic idea of the module format originates in the Java Extension Mechanism. The fundamental ideas of the Package Versioning Specification are used to describe and manage dependencies between application modules and dependencies of system modules.

Basic properties, such as the description of a module and its dependencies on other modules, are described in a manifest file. This file has the standard manifest format with additional NetBeans-specific attributes. The Java Activation Framework as well as JDK internal functions (such as the support of executable JAR files) are used as a design model for the module specification. Besides attributes in the manifest file, most modules do not need special installation code, as they are added to the NetBeans Platform declaratively. The XML file layer.xml provides application-specific information and defines the integration of a module into the NetBeans Platform. Everything that a module adds to the NetBeans Platform is specified in this file, ranging from actions to menu items to services.

Module Structure

A module is a simple JAR file, normally consisting of the following parts (see also Figure 3-1):

  • Manifest file (manifest.mf)

  • Layer file (layer.xml)

  • Class files

  • Resources like icons, properties bundles, helpsets, etc.

Only the manifest file is obligatory, since it identifies the module. All other content depends on a module's purpose. In most cases, if the module is only used as a library, the layer file is superfluous.

NetBeans module

Figure 3.1. NetBeans module

An XML config file (e.g., com-galileo-netbeans-module.xml) is needed by each module, located outside the JAR file. This is the first file read—i.e., it announces the module to the platform.

Module Types

All modules are declared in the module system by an XML configuration file, located in the cluster folder config/Modules, outside the module file. This folder is read on application startup by the module system and the modules are loaded according to this information. The content of the configuration file describes the module name, version, and location, as well as whether or not and how the module is loaded. See the document structure in Listing 3-1.

Example 3.1. Module configuration file: com-galileo-netbeans-module.xml

<module name="com.galileo.netbeans.module">
   <param name="autoload">false</param>
   <param name="eager">false</param>
   <param name="enabled">true</param>
   <param name="jar">modules/com-galileo-netbeans-module.jar</param>
   <param name="reloadable">false</param>
   <param name="specversion">1.0</param>
</module>

The enabled attribute defines if the module is loaded and the manner in which it is provided to the NetBeans Platform Application. There are three ways to determine at which point a module should be loaded. If the value of both the attributes autoload and eager is false, the module is type regular. If one of these values is true, the module type is autoload or eager. The module type is defined in the API Versioning section of the module properties dialog (see Figure 3-7). By default, regular mode is used.

Regular

This is the common type of application modules. They are loaded on application start. The application loading time is extended by the time of module initialization. Therefore, it is recommended to keep the module initialization very short. Normally it is not necessary to run anything during module loading, as many tasks can be defined declaratively.

Autoload

These modules are loaded only when another module requires them. Autoload modules correspond to the principle of lazy-loading. This mode is usually used for those modules acting as libraries.

Eager

Eager modules are only loaded when all dependencies are met. This is another option to minimize starting time. For example, if module X depends on the modules A and B, which are not available, it makes no sense to load module X.

Module Manifest

Each module running within the NetBeans Platform has a manifest file. This file is a textual description of the module and its environment. When loading a module, the manifest file is the first file read by the module system. A NetBeans module is recognized if the manifest file contains the OpenIDE-Module attribute. This is the only mandatory attribute. Its value can be any identifier (typically the code name base of the module is used—e.g., com.galileo.netbeans.module); therefore, conflicts cannot occur between modules, even if created by various developers. This identifier is used to distinguish a non-ambiguous module, needed for upgrades or dependency definitions.

Attributes

In the following sections, all attributes of a module manifest are described briefly, each with a short example.

Description

Consider the following frequently used attributes by which a module can be textually described:

OpenIDE-Module:

This attribute defines a unique name for the module used for recognition by the module system. The specification of this attribute is mandatory.

OpenIDE-Module:

com.galileo.netbeans.module

OpenIDE-Module-Name:

This defines a displayable name of the module, also displayed in the Plugin Manager.

OpenIDE-Module-Name:

My First Module

OpenIDE-Module-Short-Description:

This represents a short functionality description provided by the module.

OpenIDE-Module-Short-Description:

This is a short description of my first module

OpenIDE-Module-Long-Description:

This attribute defines a long description of the module-provided functionality. The text is displayed in the Plugin Manager. Setting this attribute is recommended, as it informs the user about features of the module.

OpenIDE-Module-Long-Description:

Here you can put a longer description with more than one

sentence. You can explain the capability of your module.

OpenIDE-Module-Display-Category:

Modules are summarized into a virtual group with this attribute and thus presented to the user as a functional unit.

OpenIDE-Module-Display-Category:

My Modules

OpenIDE-Module-Install:

To run actions at certain times in the module lifecycle, this attribute sets a module installer class (see the "Lifecycle" section later in the chapter).

OpenIDE-Module-Install:

com/galileo/netbeans/module/ModuleLifecycle.class

OpenIDE-Module-Layer:

This is one of the most important attributes. With it, the path is specified to the layer file (see the "Module Layer" section later in the chapter) describing the module integration into the platform.

OpenIDE-Module-Layer:

com/galileo/netbeans/module/resources/layer.xml

OpenIDE-Module-Public-Packages:

To support encapsulation, access to classes from another module is denied by default. This attribute is used to set visible public packages and allow other modules to use these classes. It is especially essential with libraries.

OpenIDE-Module-Public-Packages:

com.galileo.netbeans.module.actions.*,

com.galileo.netbeans.module.util.*

OpenIDE-Module-Friends:

If only certain modules are allowed access to these packages, which are declared as public, then these may be stated here.

OpenIDE-Module-Friends:

com.galileo.netbeans.module2,

com.galileo.netbeans.module3

OpenIDE-Module-Localizing-Bundle:

Here, a properties file is defined, which is used as a localizing bundle (see Chapter 8).

OpenIDE-Module-Localizing-Bundle:

com/galileo/netbeans/module/resource/Bundle.properties

Versioning and Dependencies

The following attributes are used to define differing versions and dependencies. Descriptions and use of these attributes are detailed in the "Versioning and Dependencies" section later in the chapter.

OpenIDE-Module-Module-Dependencies:

Dependencies between modules are defined with this attribute. The least-needed module version can also be specified.

OpenIDE-Module-Module-Dependencies:

org.openide.util > 6.8.1,

org.openide.windows > 6.5.1

OpenIDE-Module-Package-Dependencies:

A module may also depend on a specific package. Such dependencies are defined with this attribute.

OpenIDE-Module-Package-Dependencies:

com.galileo.netbeans.module2.gui > 1.2

OpenIDE-Module-Java-Dependencies:

If a module requires a specific Java version, it can be set with this attribute.

OpenIDE-Module-Java-Dependencies:

Java > 1.5

OpenIDE-Module-Specification-Version:

This attribute indicates the specification version of the module. It is usually written in the Dewey decimal format.

OpenIDE-Module-Specification-Version: 1.2.1

OpenIDE-Module-Implementation-Version:

This attribute sets the module implementation version, usually by a timestamp. This number changes with every change of the module.

OpenIDE-Module-Implementation-Version:

200701190920

OpenIDE-Module-Build-Version:

This attribute has only an optional character and is ignored by the module system. Typically, hereby, a date stamp is given.

OpenIDE-Module-Build-Version:

20070305

OpenIDE-Module-Module-Dependency-Message:

Here, text is set, which is displayed if a module dependency cannot be resolved. In some cases it's quite normal to have an unresolved dependency. In this case, it is a good idea to show the user a helpful message, informing them where the required modules can be found or why none are needed.

OpenIDE-Module-Module-Dependency-Message:

The module dependency is broken. Please go to the following

URL and download the module.

OpenIDE-Module-Package-Dependency-Message:

The message defined by this attribute is displayed if a necessary reference to a package fails.

OpenIDE-Module-Package-Dependency-Message:

The package dependency is broken. The reason could be...

OpenIDE-Module-Deprecated:

Use this to mark a module as deprecated. A warning is logged if the user tries to load the module into the platform.

OpenIDE-Module-Deprecated:

true

OpenIDE-Module-Deprecation-Message:

Use this optional attribute to add information to the deprecated warning in the application log. It is used to notify the user about alternate module availability. Note that this message will only be displayed if the attribute OpenIDE-Module-Deprecated is set to true.

OpenIDE-Module-Deprecation-Message:

Module 1 is deprecated, use Module 3 instead

Services and Interfaces

The following attributes are used to define dependencies on implementations and certain service provider interfaces. Further information on this topic can be found in Chapter 6.

OpenIDE-Module-Provides:

Use this attribute to declare a service interface to which this module furnishes a service provider.

OpenIDE-Module-Provides:

com.galileo.netbeans.spi.ServiceInterface

OpenIDE-Module-Requires:

Alternatively, declare a service interface for modules needing a service provider. It doesn't matter which module provides an implementation to this interface.

OpenIDE-Module-Requires:

org.openide.windows.IOProvider

OpenIDE-Module-Needs:

This attribute is an understated version of the Require attribute and does not need any specific order of modules. This may be useful to API modules, which require specific implementation.

OpenIDE-Module-Needs:

org.openide.windows.IOProvider

OpenIDE-Module-Recommends:

Using this attribute, you can set optional dependencies. If a module provides, for example, a java.sql.Driver implementation, it will be activated, and access to this module will be enabled. Nevertheless, if no provider of this token is available, the module defined by the optional dependency can be executed.

OpenIDE-Module-Recommends:

java.sql.Driver

OpenIDE-Module-Requires-Message:

Like the two previous attributes, this defines a message displayed if a required token is not found.

OpenIDE-Module-Requires-Message:

The required service provider is not available. For more information go to...

Visibility

With the following attributes, the visibility of modules within the Plugin Manager is controlled. In this way, modules can be displayed clearly and plainly to the end user.

AutoUpdate-Show-In-Client:

This attribute can be set to true or false. It determines whether a module is displayed in the Plugin Manager or not.

AutoUpdate-Show-In-Client:

true

AutoUpdate-Essential-Module:

This attribute can be set to true or false. true means that this module is essential to the application so that it cannot be deactivated or uninstalled.

AutoUpdate-Essential-Module:

true

In conjunction with these attributes, the handling of kit modules is introduced in NetBeans Platform 6.5. Each module visible in the Plugin Manager (AutoUpdate-Show-In-Client: true) is handled as a kit module. All modules on which the kit module defines a dependency are handled in the same way, with the exception of non-visible modules that depend on other kit modules. For example, if a kit module is deactivated, all dependent modules will be deactivated as well.

This lets you build wrapper modules to group several related modules for display to the user as a single unit. You can create an empty module in which the attribute AutoUpdate-Show-In-Client is set to true, while defining a dependency on the modules to be grouped. Then, in the dependent modules, set the attribute AutoUpdate-Show-In-Client to false.

Example

Listing 3-2 shows a manifest file with some typical attributes.

Example 3.2. Manifest file example

OpenIDE-Module: com.galileo.netbeans.module
OpenIDE-Module-Public-Packages: -
OpenIDE-Module-Module-Dependencies:
   com.galileo.netbeans.module2 > 1.0,
   org.jdesktop.layout/1 > 1.4,
   org.netbeans.core/2 = 200610171010,
   org.openide.actions > 6.5.1,
   org.openide.awt > 6.9.0,
OpenIDE-Module-Java-Dependencies: Java > 1.5
OpenIDE-Module-Implementation-Version: 200701100122
OpenIDE-Module-Specification-Version: 1.3
OpenIDE-Module-Install: com/galileo/netbeans/module/Install.class
OpenIDE-Module-Layer: com/galileo/netbeans/module/layer.xml
OpenIDE-Module-Localizing-Bundle: com/galileo/netbeans/module/Bundle.properties
OpenIDE-Module-Requires:
   org.openide.windows.IOProvider,
   org.openide.modules.ModuleFormat1

Module Layer

In addition to the manifest file, with which the interfaces and the environment of a module are described, there is a layer file. This is the central configuration file, in which virtually everything is defined that a module adds to the NetBeans Platform. Partly, it can be seen as the interface between the module and the NetBeans Platform, describing declaratively the integration of the module into the NetBeans Platform.

The existence of the layer file is set in the manifest file with the attribute OpenIDE-Module-Layer. This attribute defines the path to the layer file, usually using the file name layer.xml.

OpenIDE-Module-Layer: com/galileo/netbeans/module/layer.xml

The file format is a hierarchical file system containing directories, files, and attributes. During application start, all existing layer files are summarized to a virtual file system (see Figure 3-2). This is the System Filesystem, which is the runtime configuration of the NetBeans Platform.

The System Filesystem

Figure 3.2. The System Filesystem

This layer file contains certain default folders. They are defined by different modules, which are extension points. For example, the default folder Menu looks like Listing 3-3.

Example 3.3. Default folder of the layer file

<folder name="Menu">
  <folder name="Edit">
    <file name="MyAction.shadow">
      <attr name="originalFile"
          stringvalue="Actions/Edit/com-galileo-netbeans-module-MyAction.instance"/>
    </file>
  </folder>
</folder>

In this example, the action class MyAction is added to the Edit menu. Don't worry about exact syntax at this point; it is explained in the context of respective standard folders in later chapters. First of all, we elaborate the basic structure of the layer file. In addition, the NetBeans Platform provides practical features for working with the layer file. That's shown in the following chapters, as our first module is created. An index with important extension points is also found in this book's Appendix.

In this way, every module is able to add new menu items or create new toolbars. As each layer file of a module is merged to the System Filesystem, the entire menu bar content is assembled. The window system responsible for generation of the menu bar only has to read the Menu folder from the System Filesystem to gain the content of the entire menu bar.

This System Filesystem also contributes significantly to the fact that modules can be added or removed at runtime. Filesystem listeners can be registered on this system. For example, this is done by the window system. If any changes occur while a module is loaded, the window system or menu bar notice this and are able to update contents.

Order of Entries

The order in which the entries of the layer file are read (and hence shown in the menu bar) is defined by a position attribute, as shown in Listing 3-4.

Example 3.4. Determining the order of layer file entries

<filesystem>
  <folder name="Menu">
    <folder name="Edit">
      <file name="CopyAction.shadow">
        <attr name="originalFile"
                stringvalue="Actions/Edit/org-openide-actions-CopyAction.instance"/>
        <attr name="position" intvalue="10"/>
      </file>
      <file name="CutAction.shadow">
        <attr name="originalFile"
                stringvalue="Actions/Edit/org-openide-actions-CutAction.instance"/>
        <attr name="position" intvalue="20"/>
      </file>
    </folder>
  </folder>
</filesystem>

Thus, the copy action is shown before the cut action. If necessary, you can also use this attribute to define the order of the folder elements. In practice, positions with greater distance are chosen. This facilitates the subsequent insertion of additional entries. Should the same position be assigned twice, a warning message is logged while running the application.

In order to easily position the layer content, the NetBeans IDE offers a layer tree in the Projects window, in which all entries of the layer files are shown. There, their order is defined by drag-and-drop. The respective entries in the layer file are then handled by the IDE.

After we create our first module, the "Creating Modules" section of this chapter shows where to find the layer tree. You determine the order of an action while creating actions with a wizard (see Chapter 4). The respective attributes are then created by the wizard.

Should positions of entries in the layer tree be changed, some entries will be added into the layer file. Those files overwrite the default positions of the entries affected by the change. The position of an entry (also that of entries of a NetBeans Platform module) is overwritten as follows:

<attr name="Menu/Edit/CopyAction.shadow/position" intvalue="15"/>

Use the complete file path of the affected entry before the attribute name position.

Instance Files

Files of the type .instance in the System Filesystem describe objects of which instances can be created. The filename typically describes the full class name of a Java object (e.g., com-galileo-netbeans-module-MyAction.instance), which by a default constructor or static method creates an instance. An instance is created by using the File Systems and Data Systems APIs, as follows:

public static Object getInstance(String name) {
   FileSystem f = Repository.getDefault().getDefaultFileSystem();
   FileObject o = f.getRoot().getFileObject(name);
   DataObject d = DataObject.find(o);
   InstanceCookie c = d.getCookie(InstanceCookie.class);
   return c.instanceCreate();
}

If seeking a more convenient name for an instance, the full class name can be defined by using the attribute instanceClass. Thereby much shorter names can be used:

<file name="MyWindow.instance">
   <attr name="instanceClass" stringvalue="com.galileo.netbeans.module.MyWindow"/>
</file>

In classes not having a parameterless default constructor, create the instance via a static method defined by the attribute instanceCreate:

<file name="MyWindow.instance">
   <attr name="instanceCreate"
           methodvalue="com.galileo.netbeans.module.MyWindow.getDefault"/>
</file>

In doing so, the FileObject of the entry is passed to the getDefault() method, if declared so in the factory method signature. With this FileObject you read self-defined attributes. Assume you want to define the path of an icon or any other resource in the layer file as an attribute:

<file name="MyWindow.instance">
   <attr name="instanceCreate"
           methodvalue="com.galileo.netbeans.module.MyWindow.getDefault"/>
   <attr name="icon" urlvalue="nbres:/com/galileo/icon.gif"/>
</file>

The method getDefault(), creating an instance of the MyWindow class, looks as follows:

public static MyWindow getDefault(FileObject obj) {
   URL url = (URL) obj.getAttribute("icon");
   ...
   return(new MyWindow(...));
}

Notice that we specified the path with a urlvalue attribute type. Therefore, a URL instance is delivered directly. In addition to the already-known attribute types stringvalue, methodvalue, and urlvalue, there are several others. They are accessed in the Filesystem DTD (http://netbeans.org/dtds/filesystem-1_2.dtd).

One or more instances of a certain type can also be generated by Lookup, rather than via an InstanceCookie, as shown previously. Thereby we create a lookup for a particular folder of the System Filesystem. By using the lookup() or lookupAll() method, one or more instances (if several have been defined) can be delivered.

Lookup lkp = Lookups.forPath("MyInstanceFolder");
Collection<? extends MyClass> c = lkp.lookupAll(MyClass.class);

Such a lookup is used in Chapter 5 to extend the context menu of a TopComponent with your own actions defined in the layer file.

The basic class of the interface can be user-defined by the instanceOf attribute in the layer file. This allows a more efficient working of Lookup and avoids Lookup having to initiate each object in order to determine from which base class the class will inherit, or which interface it implements. Lookup is able to create directly only instances of the desired object type.

If the class MyAction from the prior entry implements, for example, the Action interface, we complete the entry as follows:

<file name="com-galileo-netbeans-module-MyAction.instance">
   <attr name="instanceOf" stringvalue="javax.swing.Action"/>
</file>

Shadow Files

.shadow files are a kind of link or reference to an .instance file. They are used mainly when singleton instances of objects, as with actions, are used. These are defined by an .instance file in the Actions folder. An entry in the Menu or Toolbars folder then refers to the action by using the .shadow file (see Listing 3-5). A .shadow file refers to files in the System Filesystem as well as to files on disk. In this way, the Favorites module stores its entries. The path to the .instance file is specified by the attribute originalFile.

Example 3.5. Connecting a .shadow file with an .instance file

<folder name="Actions">
  <folder name="Window">
    <file name="com-galileo-netbeans-module-MyAction.instance"/>
  </folder>
</folder>
<folder name="Menu">
  <folder name="Window">
    <file name="MyAction.shadow">
      <attr name="originalFile"
        stringvalue="Actions/Window/com-galileo-netbeans-module-MyAction.instance"/>
    </file>
  </folder>
</folder>

Settings Files

.settings files are an extended version of .instance files in the layer file. Information on the type of class and how an instance is created from this class—i.e., information on what these attributes can determine in an .instance file—is defined in a separate XML file. The main difference to an .instance file is that the complete class hierarchy—i.e., all superclasses and also all implemented interfaces—can be specified in the separate XML file.

These .settings entries are used, for example, with TopComponent s (see Chapter 5). The associated XML file looks like Listing 3-6.

Example 3.6. Type informationen for a .settings file

<!DOCTYPE settings PUBLIC
   "-//NetBeans//DTD Session settings 1.0//EN"
   "http://www.netbeans.org/dtds/sessionsettings-1_0.dtd">
<settings version="1.0">
 <module name="com.galileo.netbeans.module" spec="1.0"/>
 <instanceof class="javax.swing.JComponent"/>
 <instanceof class="org.openide.windows.TopComponent"/>
 <instanceof class="com.galileo.netbeans.module.MyTopComponent"/>
 <instance class="com.galileo.netbeans.module.MyTopComponent" method="getDefault"/>
</settings>

The layer file refers to this file by the url attribute, specifying the path relative to the XML file:

<folder name="Windows2">
   <folder name="Components">
      <file name="MyTopComponent.settings" url="MyTopComponentSettings.xml"/>
   </folder>
</folder>

Creating and Using Your Own Contents

Your module may use folders, files, and attributes from the layer file in order to provide extension points to other modules. The readout of entries is accomplished by accessing the System Filesystem:

Repository.getDefault().getDefaultFileSystem();

This call returns a FileSystem object. Its properties and functionality are described in detail in Chapter 7. The "Window: TopComponent" section in Chapter 5 shows you how to define your own entries in the layer file; it also shows how they can be read and, thereby, how they can provide an extension point for other modules in a way that makes them available to additional modules.

Creating Modules

Following the sections on structure and content of modules, we will now create our first module. A good introduction to module development is also offered by the sample applications already integrated in the NetBeans IDE. Here, we will simply design a single module.

First, create a NetBeans Platform Application or a Module Suite. Both are containers for modules. The NetBeans Platform Application project type creates a standalone rich client application, starting from NetBeans Platform modules only, whereas a Module Suite creates a set of related modules, starting from all the modules in the NetBeans IDE. Note, however, that you can also create a standalone application from a Module Suite. To that end, you need simply use the Project Properties dialog of the Module Suite, accessed via its context menu, and use the Build tab to select the Create Standalone Application option. Choose Exclude when asked whether IDE-specific modules should be excluded. That way, only the modules that are part of the NetBeans Platform remain. View them in the Libraries tab. As you continue creating the application, only those modules are available that are set as needed by the application. However, in both cases, you are able to add modules that are not part of the NetBeans Platform. Do this via the Libraries tab.

The NetBeans IDE provides a wizard to create these projects. Start the NetBeans IDE and select File

Creating Modules
Creating a new NetBeans Platform Application project

Figure 3.3. Creating a new NetBeans Platform Application project

On the next page, name the project, such as My Application, and chose the location where the project is to be saved. The remaining fields can be left blank. Click the Finish button to create the application project.

Now the first module can be created: another wizard is available for this task. Open the File

Creating a new NetBeans Platform Application project
Configuration of a new module

Figure 3.4. Configuration of a new module

Looking at the module in the Projects window, you see the folder Source Packages (see Figure 3-5). At the moment, this folder contains only the files Bundle.properties and layer.xml. The file Bundle.properties only provides a localizing bundle for the information registered in the manifest file.

The module in the Projects window

Figure 3.5. The module in the Projects window

A special tree structure is presented for layer files within the Important Files node. The tree structure gives two different views: first, the folder <this layer>, where you view the entries provided by your own layer file; and second, the folder <this layer in context>, where you find the entries of the layer files of all the modules that belong to your application. These views represent those parts of the System Filesystem available to the NetBeans Platform at runtime.

In these views, highlighted folders include contributions by the current module. This way, you can see at a glance the most important folders, and you can easily add, delete, or move new entries. The manifest file, created by the wizard that creates the module, is also found in the Important Files node.

Without further ado, you can run your module as part of your new rich client application. To start the application, simply choose the Run

The module in the Projects window
Your first rich client application

Figure 3.6. Your first rich client application

In these very few steps, we now have the basis of our rich client application. In the following chapters, the applications are enriched with new features by adding new modules that contribute windows, menu items, and other business logic.

Versioning and Dependencies

To ensure that a modular system remains consistent and maintainable, it is crucial that the modules within the system prescribe the modules they need to use. To that end, the NetBeans Platform allows definition of dependencies on other modules. Only by defining a dependency can one module access the code from another module. Dependencies are set in the manifest file of a module. That information is then read by the module system when the module is loaded.

Versioning

To guarantee compatibility between dependencies, you must define versions. In that regard, there is the major release version, the specification version, and the implementation version. These versions are based on the Java Package Versioning Specification and reflect the basic concepts of dependencies. You can define and edit dependencies in the Properties dialog of modules, which you can access via Properties

Versioning

First, define the major release version in this window. This is the version notifying the user of incompatible changes, compared to the previous version of the module. Here, the slash is used to separate the code name base from the version within the manifest file:

OpenIDE-Module: com.galileo.netbeans.module/1
Setting the module version

Figure 3.7. Setting the module version

The most important version is the specification version. The Dewey decimal system is used to define this version:

OpenIDE-Module-Specification-Version: 1.0.4

The implementation version is freely definable text. Typically, a timestamp is used, providing the date and time. In that way, you determine it is unique. If not explicitly set in the Properties dialog of the module, the IDE adds the implementation version when the module is created, using the current timestamp, set within the manifest file:

OpenIDE-Module-Implementation-Version: 200701231820

On the other hand, if you define your own implementation version in the Properties dialog, the IDE adds the OpenIDE-Module-Build-Version attribute with the current timestamp.

In the list of public packages, all packages in your module are listed. To expose a package to other modules, check the box next to the package you want to expose. In doing so, you define the API of your module. Exposed packages are listed as follows in the manifest file:

OpenIDE-Module-Public-Packages:
   com.galileo.netbeans.module.*,
   com.galileo.netbeans.module.model.*

To restrict access to the public packages (e.g., to allow only your own modules to access the public packages), you can define a module's friends. You define them beneath the list of public packages in the API Versioning section of the Properties dialog. These are then listed as follows in the manifest file:

OpenIDE-Module-Friends:
   com.galileo.netbeans.module2,
   com.galileo.netbeans.module3

Defining Dependencies

Based on these various versions, define your dependencies. To that end, three different types of dependencies are available: a module depends on a module, a package, or a version of Java.

Module Dependencies

You define and edit module dependencies via Properties

Module Dependencies
Definition of module dependencies

Figure 3.8. Definition of module dependencies

In this window, use Add Dependency to add dependencies to your module. The NetBeans module system offers different methods to connect dependencies to a particular module.

In the simplest case, no version is required. That means there should simply be a module available, though not a particular version; although where possible you still specify a version:

OpenIDE-Module-Module-Dependencies: com.galileo.netbeans.module2

In addition, you may require a certain version. In this case, the module version should be greater than 7.1. This is the most common manner in which to define dependencies:

OpenIDE-Module-Module-Dependencies: org.openide.dialogs > 7.1

If the module on which you want to depend has a major release version, it must be specified via a slash after the name of the module:

OpenIDE-Module-Module-Dependencies: org.netbeans.modules.options.api/1 > 1.5

Finally, you may also specify a range of major release versions:

OpenIDE-Module-Module-Dependencies: com.galileo.netbeans.module3/2-3 > 3.1.5

To create tight integration to another module, set an implementation dependency. The main reason for this approach is to make use of all the packages in the module, regardless of whether the module has exposed them or not. A dependency of this kind must be set with care, since it negates the principle of encapsulation. To enable the system to guarantee the consistency of the application, the dependency must be set precisely on the version of the given implementation version. However, this version changes with each change to the module.

OpenIDE-Module-Module-Dependencies: com.galileo.netbeans.module2 = 200702031823

Select the required dependency in the list (see Figure 3-8) and click the Edit button. In this window (shown in Figure 3-9), you set various types of dependencies.

Editing module dependencies

Figure 3.9. Editing module dependencies

Java Package Dependency

NetBeans lets you set a module dependency on a specific Java version. A dependency of this kind is set in the manifest file:

OpenIDE-Module-Package-Dependencies: javax.sound.midi.spi > 1.4

Java Version Dependency

If the module depends on a specific Java version, such as Java 5, specify that in the module properties under Properties

Java Version Dependency
OpenIDE-Module-Java-Dependencies: Java > 1.5, VM > 1.0

You can require an exact version using the equal sign or require a version that is greater than the specified version.

Lifecycle

To influence the lifecycle of a module, implement a module installer. The Module System API provides the ModuleInstall class, from which we derive our own module installer class. The following methods can be overridden in the module installer class:

validate():

This method is called before a module is installed or loaded. When needed, certain load sequences, such as the verification of a module license, are set here. Should the sequence not succeed and the module not be loaded, throw an IllegalStateException method. This exception prevents loading or installing the module.

restored():

This method is called when an installed module is loaded. Here, actions required to initialize the module are called.

uninstalled():

When the module is removed from the application, this method is called.

closing():

Before a module is ended, this method is called. Here, you also test whether the module is ready to be removed. Only once this is true for all the modules in the application can the application itself shut down. You can, for example, show the user a dialog to confirm whether the application should really be closed.

close():

If all modules are ready to end, this method is called. Here, you call the actions for the successful verification of the module in question.

Listing 3-7 shows the structure of the module installer class.

Example 3.7. Structure of a module installer class

public class ModuleLifecycleManager extends ModuleInstall {
   public void validate() throws IllegalStateException {
      // e.g., check for a license key and throw an
      // IllegalStateException if this is not valid.
   }
   public void restored() {
      // called when the module is loaded.
   }
   public void uninstalled() {
// called when the module is uninstalled.
   }
   public boolean closing() {
      // called to check if the module can be closed.
   }
   public void close() {
      // called before the module will be closed.
   }
}

To record the state of the module installer class over different sessions, override the methods readExternal() and writeExternal() from the Externalizable interface, which is implemented by the ModuleInstall class. There you store and retrieve necessary data. When doing so, it is recommended to first call the methods to be overridden on the superclass.

To let the module system know right from the start that a module provides a module installer, and where to find it, register it in the manifest file:

OpenIDE-Module-Install: com/galileo/netbeans/module/ModuleLifecycle.class

Look at how the module installer is created. The NetBeans IDE provides a wizard to create this file (see Figure 3-10). Go to File

Structure of a module installer class
Creating a module installer

Figure 3.10. Creating a module installer

Click Next and then click Finish to complete the wizard. Now the ModuleInstall class is created in the specified package and registered in the manifest file. You now need only override the required methods. For example, override the closing() method to show a dialog confirming whether the application should really shut down, as shown in Listing 3-8.

Example 3.8. Dialog for shutting down an application

import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.modules.ModuleInstall;
public class Installer extends ModuleInstall {
   public boolean closing() {
      NotifyDescriptor d = new NotifyDescriptor.Confirmation(
         "Do you really want to exit the application?",
         "Exit",
         NotifyDescriptor.YES_NO_OPTION);
      if (DialogDisplayer.getDefault().notify(d) == NotifyDescriptor.YES_OPTION) {
         return true;
      } else {
         return false;
      }
   }
}

Be aware that this module requires a dependency on the Dialogs API to be able to use the NetBeans dialog support. Defining dependencies was described previously in the "Defining Dependencies" section of this chapter, while information about the Dialogs API can be found in Chapter 8.

To try this new functionality, invoke Run

Dialog for shutting down an application

Module Registry

Modules normally need not worry about other modules. Nor should they need to know whether other modules exist. However, it might sometimes be necessary to create a list of all available modules. The module system provides a ModuleInfo class for each module, where all information about modules is stored. The ModuleInfo objects are available centrally via the Lookup, and can be obtained there as follows:

Collection<? extends ModuleInfo> modules =
   Lookup.getDefault().lookupAll(ModuleInfo.class);

The class provides information such as module name, version, dependencies, current status (activated or deactivated), and the existence of service implementations for the current module. Use the getAttribute() method to obtain this information from the manifest file.

To be informed of changes, register a PropertyChangeListener, which informs you of the activation and deactivation of modules in the system. You can also register a LookupListener that informs you of the installation and uninstallation of modules. For example, a listener could be defined as shown in Listing 3-9.

Example 3.9. Reacting to changes to the module system

Lookup.Result<ModuleInfo> res = Lookup.getDefault().lookupResult(ModuleInfo.class);
result.addLookupListener(new LookupListener() {
   public void resultChanged(LookupEvent lookupEvent) {
      Collection<? extends ModuleInfo> c = res.allInstances();
      System.out.println("Available modules: " + c.size());
   }
});
res.allItems(); // initialize the listener

Using Libraries

When developing rich client applications, you'll more than likely need to include external libraries in the form of JAR files. Since the whole application is based on modules, it is desirable to integrate the external JAR file in the form of a module. That has the advantage of setting dependencies on the module, enhancing the consistency of the application as a whole. You can also bundle multiple JAR files into a single module, after which you need no longer put the physical JAR files on the application classpath, as is normally done when developing applications.

Library Wrapper Module

To achieve the scenario just outlined, create a library wrapper module. The NetBeans IDE provides a project type and a wizard for this purpose. To create a new Library Wrapper project, go to File

Library Wrapper Module
Creating a Library Wrapper project

Figure 3.11. Creating a Library Wrapper project

Click Next to choose the required JAR files. You can choose one or more JAR files here (hold down the Ctrl key to select multiple JAR files). You are also able to add a license file for the JAR you are wrapping as a module. In the next step, provide a project name, as well as a location to store the new module. Specify the Module Suite or Platform Application to which the library wrapper module belongs. Click Next again to fill out the Basic Module Configuration dialog, as shown in Figure 3-12.

Library wrapper module configuration

Figure 3.12. Library wrapper module configuration

Here, you define the code name base. Normally this field is prefilled with the name of the JAR file. Provide the module with a name and a localizing bundle (see the "Module Manifest" section earlier in the chapter) to localize the manifest file. With a click of the Finish button, you create your new module project.

When expanding the Source Packages folder in the Projects window, observe that there is only a Bundle.properties file and a manifest file. The library, which is encapsulated by the module, is found in the folder release/modules/ext, which is seen in the Files window.

To understand how a library wrapper module works, take a look at the related manifest file. It's found in the Projects window, within the Important Files file. Note that the following information (in Listing 3-10) may not reflect exactly what is found in your specific manifest file. Certain information, such as the public packages, are only created when you build the project. To see the entire manifest file, create the module and then look at the manifest file within the created JAR file, found in the build/cluster/modules folder of your application. The exposed packages are also seen in the Properties dialog of the library wrapper modules, under the API Versioning tab. There, you can easily hide packages you do not want to have exposed.

Example 3.10. Manifest file of a library wrapper module

Manifest-Version: 1.0
Ant-Version: Apache Ant 1.7.0
Created-By: 1.6.0-b105 (Sun Microsystems Inc.)
OpenIDE-Module: com.hboeck.mp3
OpenIDE-Module-Public-Packages:
   com.hboeck.mp3.*,
   com.hboeck.mp3.id3.*,
   ...
OpenIDE-Module-Java-Dependencies: Java > 1.4
OpenIDE-Module-Specification-Version: 1.0
OpenIDE-Module-Implementation-Version: 070211
OpenIDE-Module-Localizing-Bundle: com/hboeck/mp3/Bundle.properties
OpenIDE-Module-Requires: org.openide.modules.ModuleFormat1
Class-Path: ext/com-hboeck-mp3.jar

Two very important things have been accomplished by the wizard. First, it marked all packages in the third-party library with the attribute OpenIDE-Module-Public-Packages, making all these packages publicly accessible. That's useful because it means the library can now be used by other modules. Second, the wizard marked the library it found in the ext/ folder with the Class-Path attribute, putting it on the module classpath. In this way, the classes in the library can be loaded by the module classloader. The library wrapper module is automatically assigned the Autoload type (see the "Module Types" section near the beginning of the chapter), so that it is only loaded when needed.

Adding a Library to a Module

It is advisable to always use a library wrapper module when integrating a third-party library into an application. Creating a new module in this way for a third-party library adds to the value and maintainability of the application as a whole, because you can then set dependencies on the library via the module that wraps it.

In some cases, it is desirable to add the third-party library directly to the module using it. Taking this approach is simple and similar to creating library wrapper modules.

Open the Project Metadata file (project.xml) in the Important Files node in your module project, within the Projects window. For each library you want to include in your module, create a class-path-extension entry (see Listing 3-11). Via the runtime-relative-path attribute, define the path where the library is found in the distribution. Before, this was done automatically by the wizard. Use the binary-origin attribute to specify the location where the original library is found. As you see, this is the same approach as taken with library wrapper modules.

Example 3.11. Project metadata file with classpath extension

<project xmlns="http://www.netbeans.org/ns/project/1">
  <type>org.netbeans.modules.apisupport.project</type>
<configuration>
    <data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
      <code-name-base>com.galileo.netbeans.module</code-name-base>
      <class-path-extension>
        <runtime-relative-path>ext/org-hboeck-mp3.jar</runtime-relative-path>
        <binary-origin>release/modules/ext/org-hboeck-mp3.jar</binary-origin>
      </class-path-extension>
    </data>
  </configuration>
</project>

Via this entry in the project metadata file, the creation of the module results in the library being copied to the ext/ folder. In the manifest file, the entry Class-Path: ext/com-hboeck-mp3.jar is added. In contrast to a library wrapper module, the packages of the library are not exposed. As a result, they can only be used by the module where the library is found, which is normally the reason for taking this approach in the first place. It is also possible to define the packages as being public, which is automatically the case with library wrapper modules.

Summary

In this chapter, you learned how the underlying module system of NetBeans Platform applications is structured and how it functions. The module system is part of the runtime container. First, we looked at the structure of a NetBeans module. You learned about the many configuration options that are defined in the manifest file. In addition to the manifest file, a module optionally provides a layer file. You learned how to make contributions to the whole application, via registration entries in a module layer file.

You created your first module, learned how modules use code from other modules, and explored the lifecycle of modules and how third-party libraries integrate in a module via a library wrapper module. Finally, you discovered how those kinds of modules work, and you got some hands-on experience with them.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.171.64