NetBeans Platform 6.9 Developer's Guide

By Jürgen Petri
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Modules

About this book

The NetBeans Platform has many features provided out of the box for Swing desktop application developers. It can take you hours just to create menu bars, toolbars, a window system, and other typical desktop application infrastructural needs rather than you focusing on your domain knowledge. Imagine how much time you could save with a hands-on guide for using the NetBeans Platform, which relieves you from creating desktop functions for each of your applications.

This book guides you through the development of a complete Swing application built on the NetBeans Platform. Each chapter introduces a number of new concepts relating to a theme, such as the window system, and then shows you how to implement the concepts in the application you are creating. At the end of the book you have a task manager, which you can adapt to your own purposes. Or you can, of course, create your own applications, now that you have built up a solid basis of NetBeans Platform knowledge.

The NetBeans Platform is a framework for developing large distributed desktop applications. It aims to drastically simplify desktop application development by providing a number of techniques, patterns, and full-blown Swing components out of the box. Most desktop applications have very similar technical requirements, such as: a consistent user interface, extensibility, data display, configuration settings, a help system, distribution mechanisms, on-line update possibilities, and the ability to be deployed to multiple operating systems.

Fulfilling these technical requirements over and over again for each new application is expensive, superfluous, and boring. The NetBeans Platform gives developers a transparent, open source, extensible, and free framework that address all of these technical requirements. This book will guide you through all these topics and show you how you can apply the lessons learned in the context of a real application.

The central driver of the book is the creation of a complete sample application, chapter by chapter, throughout the length of this book. You will learn how to apply the key concepts in your own work environment, so that you will be able to build flexible, reliable, robust and scalable Swing applications yourself. At the end of the book, you will be comfortable creating similar applications yourself and you will know what to do when you get stuck.

Publication date:
August 2010
Publisher
Packt
Pages
288
ISBN
9781849511766

 

Chapter 1. Modules

In this chapter, you will cover the following topics:

  • You learn about modules and modular application development

  • You examine the role that modules play in NetBeans Platform applications while creating your first module

  • You look at how to configure modules, especially at how to define module dependencies and versioning data

  • You round this topic off by looking at the lifecycle of modules and how to programmatically access that lifecycle

 

Modular application development


Modularization becomes more important as an application's complexity increases. The old and trusted programming methodologies struggle to keep pace with continually shortened production cycles, gradually increasing feature sets, and the simultaneously rising importance of software quality. In particular, the pressure of time to market demands a solid application design, providing clear extension points, loosely-coupled components, and a reliable versioning mechanism. Without a strong foundation based on a sustainable architecture, each version increase and new feature introduces a new level of chaos, leading to a scenario where enhancements and bug fixes become impossible to implement, as each change causes increasingly detrimental side effects. An application of this kind can become so problematic that it needs to be rewritten from scratch but, because the underlying application architecture remains unchanged, a rewrite of this kind fails to solve the application's structural defects.

A solution to this problem entails the introduction of modules. A module is a self-contained software component with which other modules can communicate via well-defined interfaces, behind which their implementations are hidden. Each coarsely-grained feature of an application is created within a module, giving each module a clearly defined responsibility within the application. The chaos described above can, of course, continue to exist within a module, exactly as it had existed in the pre-modular application. However, as the classes within a module exist to provide a very specific feature, the module, as a whole, needs to function interdependently with the other modules in the application, meaning that the chances of complete chaos in your code are smaller than they were before. The limited responsibility of a module within the application enables it to be more focused and the application as a whole to be more cleanly designed.

Moreover, the worsening chaos described above is less likely to arise within a modular application, as the implementation of features can only take place via publicly exposed interfaces. The dependencies in the whole application are well-defined and never accidental. Breaking the contract with the interfaces, thereby introducing the chaos described above, can only be done intentionally.

Besides these advantages to the developer, a modular application also benefits the end user. As the NetBeans module system is dynamic, modules (that is, features) can be loaded and unloaded dynamically at runtime. The adaptability and flexibility of applications and the ease with which features can be added and removed by the user at runtime is of particular relevance to enterprise applications, which typically provide support for a variety of user profiles.

Let's now take a more concrete look at modules and their relationship to NetBeans Platform applications.

 

Characteristics of a module


Generically, a module needs to have certain characteristics to provide the desired advantages promised by modular application development.

Deployment format

It must be possible to make all resources required by a module available via a single deployment package. The closest concept in the Java world is the JAR archive. You can bundle an arbitrary number of resources into a JAR and provide metadata via the manifest file. NetBeans modules are packaged in a similar format, together with module-specific metadata in the related manifest file, into an NBM archive.

Uniqueness

Each module must have its own unique identity. The NetBeans module system provides an additional key in the manifest, OpenIDE-Module, which you use to define the application-wide unique name of a module.

Versioning

Each module must be able to specify its version. NetBeans modules differentiate between specification versions and implementation versions.

  • The specification version indicates the version of the officially exposed interfaces. This version is defined via the OpenIDE-Module-Specification-Version key in the manifest file, using the Dewey format. Generally, the assumption is that new versions are backward-compatible.

  • The implementation version indicates the implementation state of a module. This version is not assumed to be backward-compatible and can be used only within a specific version of the application. The OpenIDE-Module-Implementation-Version key in the manifest defines this version.

Exposed interfaces

Each module defines public interfaces, via which features can be accessed. Accepted methods of accessing dependencies in software rely strongly on conventions. One of these is that types in packages can only be accessed indirectly via facades. However, typically, these well-intended conventions are abandoned in the interests of time. On the other hand, component systems with their own lifecycle environment tend to demand that conventions of this kind are followed to the letter. Within a module, any class can be declared public, and thereby, be accessed by any other class within that module. However, other modules can only access a module's exposed classes, that is, classes that are found within explicitly exposed packages.

The NetBeans Platform is one of the systems that provide a mechanism of this kind. The packages that are to be exposed to other modules in the application are declared via the OpenIDE-Module-Public-Packages manifest key. The NetBeans Platform provides a system of ClassLoaders that provide access to those classes that are within the module, as well as those classes that are found within the publicly exposed packages of other modules upon which the module has declared a dependency.

Declarative dependencies

Each module must declare which, if any, of the other modules in the application are required for it to function. To this end, the NetBeans Platform provides the manifest key OpenIDE-Module-Dependencies. As mentioned previously, the NetBeans Platform module system lets other modules access only the explicitly exposed packages of modules within the application.

Lifecycle

Each module must have its own lifecycle, managed by the application's runtime. The NetBeans module system handles the loading and configuration of modules. It also handles the processing of installation code, if any, and the unloading of modules when the application shuts down.

 

Creating an application


Now that you've been exposed to a lot of theory, let's create a new application with two modules. You then configure them so that they can interact with each other via their publicly exposed interfaces.

You start by creating a new NetBeans Platform Application project. The application project serves as a container for modules and represents a single application, while providing a number of NetBeans Platform modules out of the box. You're also able to configure the NetBeans Platform and brand the application via this project type.

  1. Choose File | New Project, which opens the New Project wizard. Then choose NetBeans Modules | NetBeans Platform Application, as shown in the following screenshot. Click Next>.

  2. Type TaskManager as the application name, as shown in the following screenshot:

  3. When you click Finish, you see that a new project structure is created for your application, which can be seen in the Projects window.

Now you add your first new module to the application.

  1. In the Projects window, right-click the Modules node of the application and choose Add New. In the New Module Project wizard, enter HelloService as the name of the module, along with a location for storing it, as shown in the following screenshot:

  2. Click Next> and enter the Code Name Base of the module as com.netbeansrcp.helloservice, as shown in following screenshot:

    Tip

    The Code Name Base is a string defining a unique ID by which the module is known to the rest of the application.

  3. Click Finish and the module source structure will be shown in the Projects window.

  4. Let us now add a public class HelloService to our module. Right-click the module's package node (under HelloService | Source Packages | com.netbeansrcp.helloservice) and then choose New | Java Class. Create a class called HelloService.

  5. Add the following method within the HelloService class:

          public void hello(String name) {
          System.out.println("Hello " + name + "!");
        }

Let's take a look at what you've done so far. First, using a wizard in NetBeans IDE, you created a new module within a new NetBeans Platform application. The module contains various configuration files. You will later learn that the most important of these are the manifest.mf file and the layer.xml file. Both files are found within the Important Files node of the module. The layer.xml is discussed in detail in the later chapters. At the moment, the content of this file is not yet very interesting. The manifest file has been discussed already and, right now, has the following content:

Manifest-Version: 1.0
OpenIDE-Module: com.netbeansrcp.helloservice
OpenIDE-Module-Layer: com/netbeansrcp/helloservice/layer.xml
OpenIDE-Module-Localizing-Bundle: com/netbeansrcp/helloservice/Bundle.properties
OpenIDE-Module-Specification-Version: 1.0
 

Setting dependencies


Now, of course, you'd like to be able to use the HelloService class from another module.

  1. In the same way as explained previously, create the second module. Name it as HelloClient and add a class to it named HelloClient, containing the following method:

    public static void main(String[] args) {
      new HelloService().hello("NetBeans");
    }

    Note

    The new class cannot be compiled yet, as the HelloService class cannot be found currently. It is not possible to add an import statement, as the module containing the required class is isolated from all the other modules via its module-specific classloader. Therefore, for a class in the HelloClient module to use a class in the HelloService module, two conditions must first be satisfied:

    • The HelloService class must belong to the publicly exposed interfaces of the module in which it is defined

    • The HelloClient module must declare its dependency on the HelloService module

  2. First of all, you need to add the HelloService class to the module's publicly exposed interfaces. To that end, right-click the HelloService module in the Projects window and choose Properties. In the API Versioning tab, you see the list of packages in the module. Here, you can specify the packages containing the interfaces that you want to expose to the rest of the application, as shown in the following screenshot:

In this case, the package you want to expose is com.netbeansrcp.helloservice. Types in any other packages than those that have been exposed cannot be accessed by other modules.

  1. Now you need to declare the dependency of HelloClient on HelloService. Right-click the HelloClient module and choose Properties. In the Libraries tab, set the dependencies between the current module and other modules in the application. Click Add Dependency and begin typing the name of the class that you would like to access. NetBeans IDE shows, while you are typing, the classes in the available modules that match the entered text, as shown in the following screenshot:

  2. Choose the HelloService module and confirm your choice. Click OK to close the Properties dialog. If you have not created an import statement for the HelloService class in the HelloClient class yet, do so now. It should now be possible to compile the class.

At this point, you've used various tools in NetBeans IDE to define the public interface of the HelloService module. Next, in the HelloClient module, you set a dependency on the HelloService module. Now the classes in the HelloClient module can access the classes that are part of the public interface of the HelloService module.

 

Versioning


Along with the information defining the dependencies between modules, you need to specify exactly which version of the module you are depending on. The NetBeans Platform makes a distinction between the version of the specification and the version of the implementation of the module as follows:

  • Specification versions are set using Dewey notation. In general, it consists of the main version number, that is, the version number that is incremented when new features are added, together with the minor version number, and the patch level. A version number of this kind is comparable to Mac OS X in the version 10.4.10.

  • The implementation version indicates a concrete development state and should only be used after agreement with the module owner.

As an example, you set the HelloService module to version 2.1.5.

  1. Next, let's configure HelloClient such that it requires HelloService to be set at version 2.1.5. Right-click the HelloClient module and click Properties. In the Libraries tab, select the HelloService dependency, and then click Edit. Enter the specification version as 2.1.5, as shown in the following screenshot, and then close the dialog.

    • When you now attempt to build the project, you should see a message that the build is broken, because the HelloService module needs to be available in a version equal to or greater than 2.1.5, as follows:

      Cannot compile against a module: 
      
      /home/geertjan/TaskManager/build/cluster/modules/com-netbeansrcp-helloservice.jar 
      
      because of dependency: com.netbeansrcp.helloservice > 2.1.5
      
      BUILD FAILED (total time: 2 seconds)
  2. To let the build succeed, you need to declare that the specification version of the HelloService module is at least 2.1.5. Right-click the HelloService module, choose Properties, and use the API Versioning tab, as shown in following screenshot, to set the specification number:

Try again to build the application. The build succeeds as the requested specification version now corresponds to the specification version of the HelloService module.

 

Installing modules


A module needs to be installed for the runtime container to manage its lifecycle. Installation takes place declaratively, with all the necessary files being generated by wizards in NetBeans IDE.

You might want to programmatically influence the lifecycle of a module. For that purpose, the ModuleInstall API class is provided by the NetBeans Platform. This class defines a module installer providing methods that interact with the runtime container at specific points in the lifecycle of a module. Generally, these methods are used to initialize settings in the application at the time when the module is installed or to uninitialize those settings when the application closes.

Method

Description

validate()

Override this method to prevent the module from being loaded. That might be something you want to do after failing to determine that certain preconditions, such as the presence of a license, have been met.

restored()

Override this method to initialize the module. Do this only when absolutely necessary, as performance is impacted by the initialization sequences.

closing()

Override this method to return false in order to prevent the application from closing.

close()

Override this method to provide information about the closed state of the application.

To create your own module installer, right-click the Project node of the HelloService project, choose New | Other | Module Installer, accept the default values, and complete the wizard (see figures 9 and 10).

Define the restored() method as follows:

@Override
public void restored() {
  System.out.println("HelloService() restored.");
}

Start the application by right-clicking the Application Project node and choosing Run. The application is started, together with all the modules that define it. In the Output window of the IDE, you should now see the following:

HelloService() restored

In other words, the module installer has been processed.

If you have changed a module, you do not need to restart the whole application. Instead, you can reload the changed module. Without stopping the application, change the definition of the restored() method, then right-click the Module Project node and choose Reload in Target Platform.

From the Output window, you can see that the HelloService, as well as the related HelloClient module, are shut down. Next, the HelloService is installed afresh and then both modules are enabled afresh. The Output window now shows the changed output of our module installer:

HelloService() restored - modified.

The NetBeans module system has dynamically reloaded the module. It has also managed the dependencies between the modules. You can now close the application.

In short, you have seen that you can programmatically influence the lifecycle of a module. You might want to do this to introduce an initialization sequence into the application, though doing so impacts the startup time of the application and should be done only after careful consideration of alternate approaches.

 

Integrating OSGi Bundles


A new feature in NetBeans Platform is its support for OSGi, the de facto standard module system that has seen wide adoption. In essence, OSGi attempts to provide the same features as the NetBeans module system and, as a result, the two are not very different from each other.

Two scenarios are envisioned when using OSGi in the context of the NetBeans Platform. Most typically, you have some OSGi bundles of your own that you would like to integrate into your NetBeans Platform application. For example, the domain model might have been designed in the Eclipse Modeling Framework (EMF), meaning that you have OSGi bundles generated by EMF. Importing those bundles into your application is a seamless process, allowing you to immediately begin creating Swing components on top of your domain model, using the various Swing components described later in this book. A second approach is relevant when you want your entire application to be based on OSGi. Experimental support is available for generating OSGi bundles from all your NetBeans modules, including the modules that make up the NetBeans Platform.

To import OSGi bundles, go to the Project Properties dialog of your application, and browse to a folder containing your bundles, using the Libraries tab to do so. The IDE recognizes that the JARs in the folder are not NetBeans modules and requests to be permitted to add metadata to the folder containing the JARs. The metadata provides information such as when the bundle should be loaded, for example, lazily as needed or when the application starts up. The OSGi bundles now being part of your application, you can set dependencies on them and use the EMF-generated classes, just as you would use any other domain classes as the basis of your application.

 

The sample application


Over the coming chapters, you develop a complete application piece by piece.

The example application is a TaskManager, that is, a tool for managing various kinds of activities. For the purposes of this application, a task is an activity, and each task requires a short description and a long description to provide information about the activity to the user. Each task also has a priority, enabling users to sort the tasks in the categories "low", "medium", and "high". Each task has a due date and needs to be identifiable via a unique ID number.

As the tasks consist of subtasks, they are hierarchically structured, with parent tasks containing child subtasks. Each task therefore has a list for its subtasks and a reference to the parent task to which the child tasks belong. The parent task is the main model object, and, as you anticipate that many components within our application are interested in changes to the model, you add support for a PropertyChangeListener. These features are defined within an interface.

To that end, create a new NetBeans Platform application and name it TaskManager. Within the TaskManager application, create a module named TaskModel. Use com.netbeansrcp.taskmodel as the name of the main package.

Create a subpackage named api and define the Task interface as follows:

public interface Task extends Serializable {
  public java.lang.String getId();
  public java.lang.String getParentId();
  public java.lang.String getName();
  public void setName(java.lang.String name);
  public java.util.Date getDue();
  public void setDue(java.util.Date due);
  public enum Priority { LOW, MEDIUM, HIGH }
  public Priority getPrio();
  public void setPrio(Priority prio);
  public int getProgr();
  public void setProgr(int progr);
  public java.lang.String getDescr();
  public void setDescr(java.lang.String descr);
  public void addChild(Task subTask);
  public java.util.List<Task> getChildren();
  public boolean remove(Task subTask);
  public void addPropertyChangeListener(PropertyChangeListener listener);
  public void removePropertyChangeListener(PropertyChangeListener listener);
  public static final String PROP_NAME = "name";
  public static final String PROP_DUE = "due";
  public static final String PROP_PRIO = "prio";
  public static final String PROP_PROGR = "progr";
  public static final String PROP_DESCR = "descr";
  public static final String PROP_CHILDREN_ADD = "children_add";
  public static final String PROP_CHILDREN_REMOVE ="children_remove";
}

Now that you have an interface, you need to implement it. In the implementation, you need to think about providing support for a PropertyChangeListener class. You delegate the management of the listener to a PropertyChangeSupport class, which handles the methods for adding and removing the listener. Then, for each relevant property change, you fire a PropertyChangeEvent via the PropertyChangeSupport class.

In this way, you end up with the following implementation, which you create in the com.netbeansrcp.taskmodel package:

public class TaskImpl implements Task {
  private String id = "";
  private String name = "";
  private String parentId = "";
  private Date due = new Date();
  private Priority prio = Priority.MEDIUM;
  private int progr = 0;
  private String descr = "";
  private List<Task> children = new ArrayList<Task>();
  private PropertyChangeSupport pss;
  public TaskImpl() {
    this("", "");
  }


  public TaskImpl(String name, String parentId) {

      this.id = "" + System.currentTimeMillis();
      this.name = name;
      this.parentId = parentId;
      this.due = new Date();
      this.prio = Priority.MEDIUM;
      this.progr = 0;
      this.descr = "";
      this.children = new ArrayList<Task>();
      this.pss = new PropertyChangeSupport(this);
    }
  public String getId() {
      return id;
    }
  public String getName() {
      return name;
    }
  public void setName(String name) {
      String old = this.name;
      this.name = name;
      this.pss.firePropertyChange(PROP_NAME, old, name);
    }
  public String getParentId() {
      return parentId;
    }‚
  public Date getDue() {
      return due;
    }
  public void setDue(Date due) {
      Date old = this.due;
      this.due = due;
      this.pss.firePropertyChange(PROP_DUE, old, due);
    }
  public Priority getPrio() {
      return prio;
    }
  public void setPrio(Priority prio) {
      Priority old = this.prio;
      this.prio = prio;
      this.pss.firePropertyChange(PROP_PRIO, old, prio);
    }
  public int getProgr() {
      return progr;
    }
  public void setProgr(int progr) {
      int old = this.progr;
      this.progr = progr;
      this.pss.firePropertyChange(PROP_PROGR, old, progr);
    }
  public String getDescr() {
      return descr;
    }
  public void setDescr(String descr) {
      String old = this.descr;
      this.descr = descr;
      this.pss.firePropertyChange(PROP_DESCR, old, descr);
    }
  public List<Task> getChildren() {
      return Collections.unmodifiableList(this.children);
    }
  public void addChild(Task subTask) {
      this.children.add(subTask);
      this.pss.firePropertyChange(PROP_CHILDREN_ADD, null, this.children);
    }
  public boolean remove(Task subTask) {
      boolean res = this.children.remove(subTask);
      this.pss.firePropertyChange(PROP_CHILDREN_REMOVE, null,this.children);
      return res;
    }
  public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
      this.pss.addPropertyChangeListener(listener);
    }
  public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
      this.pss.removePropertyChangeListener(listener);
    }
  @Override
  public boolean equals(Object obj) {
      if (obj == null) {
          return false;
        }
        if (getClass() != obj.getClass()) {
          return false;
        }
        final TaskImpl other = (TaskImpl) obj;
        return this.id.equals(other.getId());
    }
  @Override
  public int hashCode() {
      int hash = 7;
      hash = 97 * hash + (this.id != null ? this.id.hashCode() : 0);
      return hash;
    }
  @Override
  public String toString() {
      return this.getId() + " - " + this.parentId + " - " +this.getName() +" - " + DateFormat.getInstance().format(this.due) + " - " + this.prio + " - " + this.progr + " - " + this.descr;
    }
}
 

Summary


Though you do not have code that can be run yet, you now have a Task interface, together with its implementation. You have completed the application's model.

Later, you need to make these classes available to the other modules in the application. You can prepare for this by exposing the API package to the rest of the application, as described earlier in this chapter, which adds the "Task" interface to the public interfaces of the module.

In the next chapter, you will continue learning about the NetBeans Platform and the development of the sample application.

About the Author

  • Jürgen Petri

    Jürgen Petri (http://www.juergen-petri.de) is an independent consultant and instructor of primarily enterprise Java courses. As a Sun certified enterprise architect and Java developer, his interests relate primarily to Java EE projects, software architectures, user interface technologies, and tooling. His practical experience is based on over 10 years of work with each of the Java platforms.

    Browse publications by this author
Book Title
Unlock this full book FREE 10 day trial
Start Free Trial