© Ioannis Kostaras, Constantin Drabo, Josh Juneau, Sven Reimers, Mario Schröder, Geertjan Wielenga 2020
I. Kostaras et al.Pro Apache NetBeanshttps://doi.org/10.1007/978-1-4842-5370-0_7

7. Mastering the Core Platform

Ioannis Kostaras1 , Constantin Drabo2, Josh Juneau3, Sven Reimers4, Mario Schröder5 and Geertjan Wielenga6
(1)
The Hague, South Holland, The Netherlands
(2)
Ouagadougou, Burkina Faso
(3)
Chicago, IL, USA
(4)
Salem, Germany
(5)
Berlin, Germany
(6)
Amsterdam, The Netherlands
 

As we saw in Chapter 4, NetBeans eases the development of desktop applications written with either Swing or JavaFX. While Swing is a stable and well-entrenched library, the newer JavaFX API offers an improved rich media solution. However, both libraries lack a framework. That is, both lack an out-of-the-box solution with a window management system and true Model-View-Controller parts that sophisticated desktop applications require.

You’re probably familiar with the NetBeans IDE as a development tool. However, NetBeans is also a platform for building modular applications. The NetBeans IDE is built on top of the NetBeans Rich Client Platform or RCP.

The NetBeans Rich Client Platform is a generic framework that provides the “plumbing” when developing desktop applications, such as the code for managing windows, connecting actions to menu items, and updating applications at runtime. The NetBeans Platform provides all of these out of the box on top of a reliable, flexible, and well-tested modular architecture. It is for desktop applications what the Java Enterprise Edition framework is for web applications.

This chapter is a big picture description of what the NetBeans Rich Client Platform offers to desktop application developers and an executive summary of what you can expect to learn in Part 2 of the book.

First, you will learn the core of the NetBeans Platform (or RCP) (this chapter):
  • The Module system

  • The File system

  • Lookups

After you have acquired a good background of the core, we move to the GUI part (Chapter 8), which is built on top of Swing:
  • Window system

  • Nodes

  • Explorer Views

  • Action system

We also describe two alternative technologies:
  • JavaFX and the NetBeans Platform

  • HTML/Java UI

We apply the above by porting a Swing application to the NetBeans Platform in Chapter 9.

Finally, we teach you some extras (Chapter 10):
  • Visual Library

  • Dialogs

  • Wizards

  • Branding, distribution, and internationalization

Chapter 11 uses the information you learned in the previous chapters to help you build a plugin for NetBeans.

Figure 7-1 shows the architecture of the NetBeans platform on which Part 2 of the book is based.
../images/479166_1_En_7_Chapter/479166_1_En_7_Fig1_HTML.png
Figure 7-1

Overview of the NetBeans Platform architecture

This link (https://platform.netbeans.org/screenshots.html) provides a list of desktop applications written on top of NetBeans RCP.

The Core Platform

In this chapter we will learn the Core NetBeans Rich Client Platform (see Figure 7-1 in Chapter 7), which consists of the following systems (APIs):
  • The Module System : Modularity offers a solution to “JAR hell” by letting you organize code into strictly separated and versioned modules. Only modules that have explicitly declared dependencies on each other are able to use code from each other’s exposed packages. The NetBeans Module system preexisted the Java 9 module system (Jigsaw) and is based on OSGi. A comparison of the two module systems is provided.

  • The Lookup API is a loose coupling mechanism enabling a component to place a query for a particular interface and get back pointers to all registered instances of that interface across all modules of the application. Simply put, Lookup is an observable Map, with Class objects as keys and instances of those Class objects as values, and it allows loosely coupled communication between modules.

  • The File System is a unified API that provides stream-oriented access to flat and hierarchical structures, such as disk-based files on local or remote servers, memory-based files, and even XML documents. FileObjects and DataObjects are the basic classes of the FileSystem API.

The Module System API

Before we start describing the NetBeans Module System API, let’s see some definitions and characteristics of modular systems in general.

Modularization is the act of decomposing a system into self-contained modules. Modules are identifiable artifacts containing code, with metadata describing the module and its relation to other modules. A modular application, in contrast to a monolithic one of tightly coupled code, in which every unit may interface directly with any other, is composed of smaller, separate chunks of code that are well isolated.

Modular systems have a number of characteristics:
  • Strong encapsulation : A module must be able to conceal part of its code from other modules. Consequently, encapsulated code may change freely without affecting users of the module.

  • Well-defined interfaces : Modules should expose well-defined and stable interfaces to other modules.

  • Explicit dependencies : Dependencies must be part of the module definition, in order for modules to be self-contained. In a module graph, nodes represent modules, and edges represent dependencies between modules.

  • Versioning : Dependencies on a specific or a minimum version of a module.

The NetBeans platform Module API is the following:
  • an architectural framework,

  • an execution environment that supports a module system called the Runtime Container.

It provides a way to divide your application into cohesive parts and helps you build loosely coupled applications that can evolve without breaking. It also lets you add/remove features during runtime(!) without breaking your application.

The Runtime Container consists of the minimum modules required to load and execute your application and manages all of the modules in your application.

A module is a collection of functionally related classes stored in a JAR file along with metadata, which provide information to the Runtime Container about the module, such as the following:
  • the module’s name,

  • version information,

  • dependencies, and

  • a list of its public packages, if any.

Figure 7-2 shows an explicit dependency of Module A to Module B.
../images/479166_1_En_7_Chapter/479166_1_En_7_Fig2_HTML.jpg
Figure 7-2

An explicit dependency between two modules

In order to use or access code in another module:
  1. 1.

    You must put Module B classes that identify the module’s interface in a public package, assign a version number, and export it.

     
  2. 2.

    Module A must declare a dependency on a specified version of Module B.

     

Usually you put public interfaces of a module into a public package.

In other words, a module in the NetBeans Module System cannot reference classes in another module without declaring a dependency on that other module, and with that other module agreeing that the classes referenced are the ones that are the actual API of this module. This way you can design applications with high cohesion and low coupling.

All modules have a life cycle, which you can hook into via annotations. Thus, you can execute code when a module starts, shuts down, and when the window system initializes.

The NetBeans Runtime Container consists of the following six modules:
  • Bootstrap: loads and executes the Startup module;

  • Startup: contains the Main method of the application and initializes the module system and the virtual file system;

  • Module system: manages modules, enforces module visibility and dependencies, and provides access to module life-cycle methods;

  • Utilities: provides general utility classes;

  • File System: provides a virtual file system for your application;

  • Lookup: allows modules to communicate with each other.

Figure 7-3 shows the relationships between these modules.
../images/479166_1_En_7_Chapter/479166_1_En_7_Fig3_HTML.jpg
Figure 7-3

NetBeans Platform Runtime Container

Lookup API

NetBeans uses a component-based way of development. Modules or components developed by independent groups or individuals must be able to communicate with each other in a loosely-coupled way.

Previously we learned about how the Module System allows you to break up your application into loosely coupled parts. These modules need a loosely coupled way to communicate with each other, too. This is where the Lookup API fits in.

One of the biggest challenges in the history of software design is how to design loosely-coupled systems with high cohesion. “In computing and systems design a loosely coupled system is one in which each of its components has, or makes use of, little or no knowledge of the definitions of other separate components,” according to Wikipedia (https://en.wikipedia.org/wiki/Loose_coupling).

Cohesion means that all code (and resources) related to a particular feature should be organized within the same module or the same set of modules. Coupling , on the other hand, refers to the degree to which the modules depend on each other. Modules should be independent of each other, as much as possible, enabling components to be modified or moved around without a big impact on the application. The higher the level of cohesion and the lower the level of coupling, the more sustainable and robust with respect to future modifications (less maintenance headache) the architecture is.

Let’s assume that the Client and the ProviderImpl exist in two different modules (see Figure 7-4). How does the Client find the ProviderImpl (in a loosely coupled way)? Spring uses dependency injection or inversion of control via its xml files. Java 6 uses a Query-based approach, the ServiceLoader that we briefly describe later in this chapter.

NetBeans RCP introduces a new way to accomplish loose coupling, the @ServiceProvider:
@ServiceProvider(service = Provider.class, position=1)
public class ProviderImpl implements Provider { }
../images/479166_1_En_7_Chapter/479166_1_En_7_Fig4_HTML.jpg
Figure 7-4

A loosely coupled system

The magic line is the first line that tells NetBeans that this class is an implementation of the service Provider.class. NetBeans creates a text file package.Provider inside build/classes/META-INF/services/ folder of the module that contains the fully qualified names of the implementation classes, for example, package.ProviderImpl. If you have worked with ServiceLoader, then this is not new to you.

However, the big question has not been answered yet. How does the client find the implementation? In NetBeans this is done with the use of lookups! The client looks in the default lookup for the interface (not the implementation). The default Lookup (also known as service registry ) is a Lookup that evaluates the service declarations in the META-INF/services folder. It is callable through the Lookup.getDefault() method . By asking for a service interface in this way, you receive instances of implementing classes registered in the META-INF/services folder.

A lookup is an observable map with class objects as keys and sets of instances of these class objects as values, that is,
Lookup = Map<Class, Set<Class>>

for example: Map<String, Set<String>> or Map<Provider, Set<Provider>>.

It was created to allow for inter-component binding and dependencies.

NetBeans provides a number of methods to access a lookup (see Listing 7-1).
Provider provider = Lookup.getDefault().lookup(Provider.class);
provider.aMethod();
Listing 7-1

Accessing the default lookup for a single implementation

or if you have more than one implementation of Provider (see Listing 7-2).
Collection <? extends Provider> providers =
    Lookup.getDefault().lookupAll(Provider.class);
for (Provider provider : providers) { ... }
Listing 7-2

Accessing the default lookup for many implementations

You may also request a Lookup.Result<T> that allows adding a LookupListener in order to be informed about content changes (Listing 7-3).
Lookup.Result<Provider> providers =
              Lookup.getDefault().lookupResult(Provider.class);
Collection<? extends Provider > allProviders =
                                       providers.allInstances();
providers.addLookupListener = new LookupListener(){
  @override
  public void resultChanged(LookupEvent e){
    // do something
   }}
);
Listing 7-3

Adding a LookupListener

As you can see from the above code examples, the client has no clue about which implementation it uses; it only knows the interface. Loose coupling!

Imagine the lookup as a map in memory that stores implementations of all the services of your application. You put the service implementation in the lookup when you define it with the @ServiceProvider annotation , and then you search for it using the above commands. Service implementations can be in different modules and in non-public packages.

NetBeans also allows you to order implementations by ascending positions, that is, instances with smaller numbers in the position attribute of the @ServiceProvider annotation are returned before instances with larger numbers.

We have seen two attributes of the @ServiceProvider annotation, service and position. There are two more, not that commonly used: path, a path in the System FileSystem where the provider is registered; and supersedes, a list of implementations, which this implementation supersedes.

There are other lookups in NetBeans apart from the default lookup , which is used to store services’ implementations, and this often brings confusion. For example, each OutlineView or TopComponent associates with it a lookup to store the nodes that are selected at a specific time. You should not confuse this lookup with the default lookup. Here is a short summary of Lookups:
  • Global Lookup is a singleton and works like a central registry. There are two important global lookups:
    • Default Lookup or Service Registry , which has been already described earlier, is available by Lookup.getDefault(). It is an application-wide repository for modules to discover and provide services. It is a central registry of services allowing you to look up the service implementation classes inside the META-INF/services folder. A client module can thus use a service without being aware of or dependent on its implementation (loose coupling between modules).

    • Actions Global Context available by Utilities.actionsGlobalContext() proxies the Lookup of whatever has currently the focus in the application.

  • Local Lookup : classes may implement the Lookup.Provider interface and store objects in their own Lookup. You may think of a lookup as a “bag of objects” that an object can carry with it (Listing 7-4).

public interface Lookup.Provider {
      Lookup getLookup();
}
Listing 7-4

Lookup.Provider interface

For example, TopComponent , as we shall see in the next chapter, implements this interface (Listing 7-5).
TopComponent tc = WindowManager.getDefault().findTopComponent("aTopComponent");
Lookup tcLookup = tc.getLookup();
Listing 7-5

TopComponent’s Lookup

TopComponents (are like JFrames) usually put into their Lookup whatever is selected. You can track the selection by adding a listener to the TopComponent (Listing 7-6).
Lookup.result<Node> result = tcLookup.lookupResult(Node.class);
Collection<? extends Node> allNodes = result.allInstances();
result.addLookupListener(myLookupListener);
Listing 7-6

Adding a LookupListener to a TopComponent

You may provide your own Lookup to the TopComponent , too:
tc.associateLookup(mylookup);

Creating Your Own Lookups

In addition to the built-in lookups, you can also create your own. The following subsections show the different possibilities that are available.

Empty Lookup

You may initialize a Lookup to an EMPTY Lookup :
Lookup emptyLookup = Lookups.EMPTY

Lookup Containing One Object

The most basic one is Lookups.Singleton, a Lookup that only contains one object:
Lookup singleObjLookup = Lookups.singleton(obj);

Lookup with Fixed Number of Objects

There's also an implementation for creating a lookup with more than one entry, still with fixed content:
Lookup fixedObjLookup = Lookups.fixed(obj1, obj2);

Lookup with Dynamic Content

You may also use a Lookup to dynamically add content to it. The most flexible way is to use an InstanceContent object to add and remove content:
InstanceContent content = new InstanceContent();
Lookup dynamicLookup = new AbstractLookup(content);
content.add(obj1);
content.add(obj2);

Besides its name, AbstractLookup, it is not an abstract class; it is a Lookup whose contents can change. Attaching a LookupListener to a Lookup.Result assigned to the Lookup lets you receive notifications of changes in the AbstractLookup.

ProxyLookup Merges Other Lookups

If you would like to query more than one Lookup at a time, you may use a ProxyLookup . The following example combines two Lookups into one:
Lookup compoundLookup =
      new ProxyLookup(singleObjLookup, fixedObjLookup);

Lookup Delegating Searches to Lookup of Some Object

A Lookup returned by a given object may change from time to time: for example, under some conditions, it may be the tool that is currently active, such as an ActionMap, etc. The created Lookup delegates to another Lookup that checks if the returned object is the same, fires change events, and updates all existing and referenced results.
 Lookup delegating = Lookups.proxy(
      new Lookup.Provider () {
          public Lookup getLookup () {
            return lookup;
          }
      }
);

Exclude Classes from a Lookup

You may also filter out specific classes from a Lookup:
Lookup filtered =
      Lookups.exclude(originalLookup, SomeObj.class, ActionMap.class);
If you register your service in the layer.xml (see below), you can get a lookup for a certain folder like this:
Lookup lookup = Lookups.forPath("ServiceProviders");

We will see other lookups in the following chapters of Part II of the book.

The NetBeans Module System API versus the Java 9 Module API

The purpose was to make Java SE more flexible, scalable, maintainable, and secure; make it easier to construct, maintain, deploy, and upgrade applications; and enable improved performance.

Before Java 9, classes were arranged into packages . For example, com.company.app.MyClass is stored into the file com/company/app/MyClass.java. Packages are globally visible and open for extension. The unit of delivery is a Java archive (jar) . Access control is only managed at the level of classes/methods. Classes and methods can restrict access by three access modifiers as shown in Table 7-1.
Table 7-1

Package Access Modifiers

Access Modifier

Class

Package

Subclass

Unrestricted

Public

Protected

 

- (default or package)

  

Private

   

How do you access a class from another package while preventing other classes from using it? You can only make the class public, thus exposing it to all other classes (i.e., break encapsulation). There are no explicit dependencies; explicit import statements are only at compile time; there is no way to know which other JAR files your JAR requires at runtime; the developer has to provide correct jars in the classpath during execution and in the correct order.

Maven solves compile-time dependency management by defining POM (Project Object Model) files (Gradle works in a similar way). OSGi solves runtime dependencies by requiring imported packages to be listed as metadata in JARs, which are then called bundles .

Once a classpath is loaded by the JVM, all classes are sequenced into a flat list, in the order defined by the classpath argument. When the JVM loads a class, it reads the classpath in fixed order to find the right one. As soon as the class is found, the search ends and the class is loaded. What happens when duplicate classes are in the classpath? Only one (the first one encountered) wins. The JVM cannot efficiently verify the completeness of the classpath upon starting. If a class cannot be found in the classpath, then you get a runtime exception. The term “Classpath Hell” or “JAR Hell” should now be clearer to you.

With project Jigsaw, Java now has its one module system. Modules can either export or strongly encapsulate packages. Modules express dependencies on other modules explicitly. Each JAR becomes a module, containing explicit references to other modules. A module has a publicly accessible part and an encapsulated part. All this information is available at compile time and runtime accidental dependencies on code from other non-referenced modules can be prevented. Optimizations can be applied by inspecting transitive dependencies.

The benefits of Java 9 module system are these:
  • Reliable configuration: The module system checks whether a given combination of modules satisfies all dependencies before compiling or running code.

  • Strong encapsulation: Modules explicitly express dependencies on other modules.

  • Scalable development: Teams can work in parallel by creating explicit boundaries that are enforced by the module system.

  • Security: No access to internal classes of the JVM (like Unsafe).

  • Optimization: Optimizations can be applied by inspecting (transitive) dependencies. It also opens up the possibility to create a minimal configuration of modules for distribution.

As a consequence, JDK now consists of 19 platform modules.

As already mentioned in Chapter 3, a module has a name (e.g., java.base), groups related code and possibly other resources, and is described by a module descriptor . Like packages are defined in package-info.java, modules are defined in module-info.java (in root package). A modular jar is a jar with a module-info.class inside it. In Chapter 3, we also saw how NetBeans provides support for Java 9 modules.

Table 7-2 provides a comparison between the NetBeans Module API and the Java 9 modular API.
Table 7-2

NetBeans Module API vs. Java 9 Modules Comparison

 

Java 9 modules

NetBeans Module API

Encapsulation

Interfaces

Explicit dependencies

Versioning

Cyclic dependencies∗

Services

ServiceLoader

ServiceProvider

As already described, both modular systems provide support for encapsulation and exporting public interfaces and explicitly handle dependencies while both don’t allow cyclic dependencies. Java 9 doesn’t allow cyclic dependencies during compile time, but it does during runtime. The Java 9 module system doesn’t support versioning, something that has been criticized by the Java community.

As already mentioned, loose coupling between modules is achieved in NetBeans RCP via the lookups and the ServiceProvider interface. Jigsaw, on the other hand, uses a Query-based approach from Java 6, the ServiceLoader (Listing 7-7).
ServiceLoader<Provider> serviceLoader =
      ServiceLoader.load(Provider.class);
for (Provider provider : serviceLoader) { return provider; }
...
ServiceLoader<Provider> serviceLoader =
    ServiceLoader.load(Provider.class).stream().filter(...);
Listing 7-7

ServiceLoader interface

However, the ServiceLoader has a number of problems:
  • It isn’t dynamic (you cannot install/uninstall a plugin/service at runtime).

  • It does all service loading at startup (as a result it requires longer startup time and more memory usage).

  • It cannot be configured; there is a standard constructor and it doesn’t support factory methods.

  • It is a final class with hard-coded behavior so it cannot be extended.

  • It doesn’t allow for ranking/ordering, that is, we cannot choose which service to load first.

Java 9 introduced a number of modifications to Java 6 ServiceLoader :
  • No relative services; the new module-based service locator does not have relative behavior.

  • Ordering of services (as they were discovered) is lost.

  • All service interfaces and implementations on the module path are flattened into a single, global namespace.

  • No extensibility / customizability of service loading; the service layer provider must provide a fixed mapping of available services up front.

  • Multiple-site declarations; every module that uses a service must also declare that the service is being used in the module descriptor; no global layer-wide service registry.

ServiceProvider does not have the drawbacks of ServiceLoader. It is dynamic, so you can plug in/unplug modules while your application is running, it doesn’t load all services at startup, and it allows you to set priorities (with the position attribute). It is extensible, supports listeners, understands the NetBeans platform module system, and one can create as many Lookups as they wish (there can be only one instance of ServiceLoader per classloader).

And now the big questions:
  • Which module system to use?

  • Can the Jigsaw module system and the NetBeans Module API work together?

As we saw, both module systems have their pros and cons. NetBeans Module System API can also work outside of NetBeans; just use the related JAR files (they can be found inside platform/lib folder of your NetBeans IDE installation): boot.jar, core.jar, org-openide-filesystems.jar, org-openide-modules.jar, and org-openide-util.jar.

Don’t mix up module systems, that is, don’t use both Jigsaw and NetBeans Module System in your project. It will unnecessarily complicate things, and it won’t give any extra benefit to your application.

The File System API

The NetBeans Platform File System API provides an abstraction for standard files. The File System API handles both files and folders (you can think of them as resources) on the physical file system as well as on the NetBeans virtual file system (e.g., they may reside in memory, in a JAR or ZIP or XML file, or even on a remote server).

A FileSystem is a collection of FileObjects that can reside anywhere. A FileSystem is hierarchical and has a root. A FileObject represents either a file or a directory (folder) in the FileSystem; it must physically exist in the FileSystem (unlike Java’s java.io.File) and has a MIME type, which determines how the file is handled. A FileObject is an implementation of the Composite design pattern, that is, it can contain other FileObjects (files or subdirectories). A FileObject also includes support for attributes (which are key-value pairs or type String).

You may listen for changes to a FileObject (such as file/folder creation, renaming, modification, deletion, or attribute changing) with the FileChangeListener interface . Finally, FileUtil is a utility class with many methods that manipulates FileSystems, FileObjects, and even standard java.io.File objects .

A summary of the above is depicted in Figure 7-5 in a graphical way.
../images/479166_1_En_7_Chapter/479166_1_En_7_Fig5_HTML.jpg
Figure 7-5

Class diagram of the FileSystem API main classes

Let’s see some code examples (Listings 7-8 and 7-9).
String aPath = ...;
File dir = new File(aPath);
FileObject myfolder = FileUtil.toFileObject(dir);
if (null == myfolder) {
   try {
      // Create folder
      myfolder = FileUtil.createFolder(dir);
   } catch (IOException ex) {
      Exceptions.printStackTrace(ex);
   }
}
if (myfolder != null) {
   // Is there a file called myfile.txt in this folder?
   FileObject myfile = myfolder.getFileObject("myfile.txt");
   if (null == myfile) {
      try {
            // Create file
            myfile = myfolder.createData("myfile.txt");
      } catch (IOException ex) {
            Exceptions.printStackTrace(ex);
      }
   }
}
if (myfile != null) {
      // write some text to myfile
      if (myfile.canWrite()) {
          try (PrintWriter output =
            new PrintWriter(myfile.getOutputStream())) {
            output.println("This is some text");
          } catch (IOException ex) {
            Exceptions.printStackTrace(ex);
          }
      }
      // read file
      if (myfile.canRead()) {
          System.out.println("MIME Type: " + myfile.getMIMEType());
          try {
              for (String line : myfile.asLines()) {
                System.out.println(line);
              }
          } catch (IOException ex) {
            Exceptions.printStackTrace(ex);
          }
      }
}
// Rename myfile.txt; requires a lock
FileLock lock = null;
try {
      lock = myfile.lock();
      // Rename will fail if the new name already exists.
      myfile.rename(lock, "mynewfile", myfile.getExt());
} catch (IOException ex) {
      Exceptions.printStackTrace(ex);
} finally {
      if (lock != null) {
            lock.releaseLock();
      }
}
try {
      // delete file; FileObject delete()  method takes care of
      // acquiring and releasing a lock. Deleting a FileObject
      // folder recursively deletes its contents.
      myfile.delete();
} catch (IOException ex) {
      Exceptions.printStackTrace(ex);
}
Listing 7-8

FileSystem API examples

The previous example created a new folder, a new file inside it, renamed the file, and then deleted it. As already mentioned, you can attach FileChangeListeners to FileObjects (either files or folders) and also attach recursive FileChangeListeners to folders in a directory tree. The FileChangeListener interface has six methods to override as shown in Listing 7-9.
private final FileChangeListener fcl = new FileChangeListener() {
   @Override
   public void fileFolderCreated(FileEvent fe) { ... }
   @Override
   public void fileDataCreated(FileEvent fe) {    ... }
   @Override
   public void fileChanged(FileEvent fe) { ... }
   @Override
   public void fileDeleted(FileEvent fe) { ... }
   @Override
   public void fileRenamed(FileRenameEvent fre) { ... }
   @Override
   public void fileAttributeChanged(FileAttributeEvent fae) { }
}
myfolder.addRecursiveListener(fcl);
Listing 7-9

FileChangeListener interface

System FileSystem or Layer.xml

A file system in the NetBeans Platform is a generic and highly abstract concept. A NetBeans Platform file system is a place where you can hierarchically store and read files, and it doesn’t have to be on a physical disk. A concrete implementation of this is in the MultiFileSystem class, which lets you construct a single namespace that merges a set of discrete file systems called layers and acts as if the content of all of them live in the same namespace. For example, if two different layers contain a folder MyFolder with different content, listing the content of the MultiFileSystem MyFolder folder gives you the content of both. To deal with collisions (i.e., two files with the same name and path), MultiFileSystem contains a stack order of layers, meaning the one on top is used. NetBeans’ implementation of a MultiFileSystem is the so-called System FileSystem . It is used to define all of the actions, menus, menu items, icons, key bindings, toolbars, and services available in your application.

It can be viewed from the Files window under the build directory as classes/METAINF/generated-layer.xml. In previous versions of NetBeans, the developer had to edit a layer.xml file like the above in order to configure actions, menu and toolbar items, key bindings, service providers, etc. Nowadays, all these should be taken care by annotations, as we shall see in the next chapter. However, there are still some specialized things that you can only do by editing this file. Furthermore, the layer.xml file is the gateway to your NetBeans Platform application’s complete configuration data. The FileSystem API handles this virtual file system in the same way (Listing 7-10).
FileObject root = FileUtil.getConfigRoot();
FileObject someFolder = FileUtil.getConfigFile("someFolder");
JToolBar tb = FileUtil.getConfigObject("/path/to/fileobject", JToolBar.class)
Listing 7-10

Accessing layer.xml with the FileSystem API

The System FileSystem provides another type of inter-module communication (the other is the Lookup). Using the layer.xml file, a module can register folders and files that another module can use.

The File System API vs. Java NIO

The NetBeans File System API existed before the Java NIO2, which was introduced in Java 7. Java NIO2 provides similar functionalities as the NetBeans FileSystem API. Table 7-3 is a comparison of the basic classes of the two (not a 100% match).
Table 7-3

FileSystem API vs. Java 7 NIO2

FileSystem API

Java 7 NIO2

org.openide.filesystems.FileSystem

java.nio.file.FileSystem

org.openide.filesystems.FileObject

java.nio.file.Path

-

java.nio.file.attribute

org.openide.filesystems.FileUtil

java.nio.file.Files

java.nio.file.FileVisitor

org.openide.filesystems.FileChangeListener

java.nio.file.WatchService

java.nio.file.Watchable

java.nio.file.StandardWatchEventKinds

-

java.nio.channels.*

Which one to use depends on your application. If you wish to develop an application that will display the files/folders to a GUI using the NetBeans RCP, it would be better for you to use the NetBeans FileSystem API. NIO2 is a more powerful API than the FileSystem API (which was created mainly to manage layer.xml) however the NetBeans FileSystem API is simpler and more straightforward.

DataSystem API

The Data System API is an important bridge between the FileSystem API and the Nodes API (that we shall describe in the next chapter). The main classes of this API are DataObject and DataNode. The DataObject wraps a FileObject, and the DataNode displays it in the UI or presentation layer. All of them have a Lookup, which is important for accessing the capabilities associated with a file. DataLoaders recognize the FileObject type via its MIME type, and create the DataObject associated with it.
DataObject dob = DataObject.find(myfile);
Note that multiple FileObjects can be associated with a DataObject (e.g., a Swing Design form – a DataObject – uses two files – FileObjects - a .java file and a .form file). However, one of them is designated as the primary file:
FileObject fob = dob.getPrimaryFile();
Finally, here is how you can get access to the relevant Node from a DataObject and vice versa:
Node node = dob.getNodeDelegate();
DataObject mydob = ((DataNode)node).getDataObject();

We shall describe the Node API in the next chapter. Additionally , we will see an application of the FileSystem and DataSystem APIs in Chapter 11 where we will develop a plugin for NetBeans. There we will learn how to create a new file type and register this file type with our plugin.

An Example Application

Let’s try to apply what we have learned so far by porting a Swing ToDo application to NetBeans RCP. If you wish to download the ToDo Swing application and build it yourself, please proceed; otherwise you may skip to the section of this chapter “The Todo Swing Application.” You may also find the fixed version in the book’s source code.

Build the Todo Swing Application

Download the original Swing application from here (http://netbeans.org/community/magazine/code/nb-completeapp.zip), unzip it, and open it in NetBeans. Since this is a rather old application, you will encounter a number of problems. Press the Resolve Problems button to get a list of the problems. Click Resolve… in order for NetBeans to try to resolve them.
../images/479166_1_En_7_Chapter/479166_1_En_7_Fig6_HTML.jpg
Figure 7-6

Resolve Project Problems of the ToDo application

NetBeans will resolve the first and the last as shown in Figure 7-6. The other two need to be resolved manually. Follow the instructions in this FAQ (http://wiki.netbeans.org/FaqFormLayoutGenerationStyle) to resolve the “swing-layout” library issue.

Download hsqldb.zip (from http://hsqldb.org), create a lib folder inside TodoNB5, and copy in there the hsqldb.jar file from hsqldb.zip. Right-click on Libraries and select Add JAR/folder… from the pop-up menu. Navigate to the lib folder and select the hsqldb.jar (make sure you have relative path selected).

You should now be able to run it. When you run it, you should see the error in the status bar: “Cannot fetch from the database”. This is due to the version of HSQLDB. Open TaskManager.java, locate method listTasksWithAlert() , and replace curtime() with CURRENT_TIMESTAMP. Rerun the application. You should now be able to see a window with empty tasks.

The Todo Swing Application

As you can see in Figure 7-7, the application consists of two windows. The main window provides a list of tasks. The Task Details dialog box allows the user to create a new or edit an existing task.
../images/479166_1_En_7_Chapter/479166_1_En_7_Fig7_HTML.jpg
Figure 7-7

The ToDo application GUI

Here is a short list of requirements of the application:
  • Tasks should have a priority, so users can focus first on higher-priority tasks.

  • Tasks should have a due date, so users can instead focus on tasks that are closer to their deadline.

  • There should be visual cues for tasks that are either late or near their deadlines.

  • Tasks can be marked as completed, but this doesn’t mean they have to be deleted or hidden.

There are two main windows for the Todo application: a tasks list and a task-editing form. A rough sketch for both is shown in Figure 7-8.
../images/479166_1_En_7_Chapter/479166_1_En_7_Fig8_HTML.jpg
Figure 7-8

Sketch of the ToDo application

The Todo application is going to be developed in three steps:
  • Step 1. Build a “static” visual prototype of the user interface, using a visual GUI builder.

  • Step 2. Build a “dynamic” prototype of the application, coding user interface events and associated business logic, and creating customized UI components as needed.

  • Step 3. Code the persistence logic.

It consists of three packages: todo.model, todo.view, todo.controller (see Figure 7-9).
../images/479166_1_En_7_Chapter/479166_1_En_7_Fig9_HTML.jpg
Figure 7-9

Package structure of the ToDo application

As shown in Figure 7-10, the Swing ToDo application follows the Model-View-Controller architecture.
../images/479166_1_En_7_Chapter/479166_1_En_7_Fig10_HTML.jpg
Figure 7-10

The ToDo application architecture

We shall create a similar architecture using the NetBeans platform module system, using modules instead of packages (see Figure 7-11).
../images/479166_1_En_7_Chapter/479166_1_En_7_Fig11_HTML.jpg
Figure 7-11

The TodoRCP application architecture

Let’s get started. Click on the New Project toolbar button and select the NetBeans Platform Application in the NetBeans Modules category (see Figure 7-12). Use “TodoRCP” as the project name and choose a suitable project location. Then click Finish.
../images/479166_1_En_7_Chapter/479166_1_En_7_Fig12_HTML.jpg
Figure 7-12

Create a new NetBeans Platform Application project

NetBeans creates the NetBeans Platform Application project containing an empty Modules folder and an Important Files folder. This is the container for the modules that will be created in the rest of this part of the book. We shall create the same Model-View-Controller structure for our TodoRCP application.

Right-click the Modules folder icon and choose Add New. Type "View" as the module name and click Next. Type todo.view as the Code Base Name and then click on Finish. You should see the structure shown in Figure 7-13.
../images/479166_1_En_7_Chapter/479166_1_En_7_Fig13_HTML.jpg
Figure 7-13

Structure of the TodoRCP NetBeans Platform Application project

Create the other two modules, "Model" (todo.model) and “Controller” (todo.controller), the same way. Finally, create a new module called “Libraries” that will wrap any external libraries required by the application.

Having one module that includes all the applications’ external libraries has the benefit that we need to add only one new module to our module suite and a single reference to it from the modules that need it. The drawback is that if a module needs only one jar, it needs to reference all other jars that are wrapped inside the Libraries module.

We shall wrap two java libraries (a.k.a. jar files) inside this module: the hsqldb.jar that we downloaded earlier in this chapter, and the SwingX library that is not included any longer in the ide/modules/ext/ folder. Download it instead from here (https://mvnrepository.com/artifact/org.swinglabs.swingx/swingx-all).

Let’s create the module and add the external libraries that we will need for the TodoRCP application:
  1. 1.

    Right-click the Modules folder icon of the TodoRCP module suite and choose Add New. Type Libraries as the module name and click Next. Type lib as the Code Base Name and then Finish.

     
  2. 2.

    Right-click on the newly created module and select Properties ➤ Libraries ➤ Wrapped JARs. Click on Add JAR and add the swingx-all-x.x.x.jar. Repeat the procedure to the add hsqldb.jar.

     
  3. 3.
    Select the API Versioning category from the left panel of the opened Project Properties ➤ Libraries dialog box and make the following packages public by checking them:
    • org.hsqldb

    • org.jdesktop.swingx

     
  4. 4.

    After clicking on OK, clean and build the Libraries module.

     
The TodoRCP module suite should now contain four modules (see Figure 7-14).
../images/479166_1_En_7_Chapter/479166_1_En_7_Fig14_HTML.jpg
Figure 7-14

The TodoRCP suite with all the modules

There is a plugin to visualize the dependencies between modules. Download the DisplayDependencies plugin from https://sourceforge.net/projects/netbeansmoddep/. Click on Tools ➤ Plugins menu and then on the Downloaded tab. Click on the Add Plugins… button and navigate to the location that you saved the downloaded 1475781757_eu-dagnano-showdependencies.nbm file. Click on Install button and follow the wizard to install the plugin. A new button with tooltip Show Dependencies is displayed on the toolbar. Click on a module or on the module suite and then on the Show Dependencies button to view a graph like the one in Figure 7-11 (without all the dependencies yet).

Copy the Task.java from the ToDo Swing application to the todo.model package of the Model module. To ease our development, we shall use a mock TaskManager that stores the tasks to memory instead of the database.

The TaskManager class is a DAO (Data Access Object). Being the only DAO on the application, it contains many methods that would otherwise be in an abstract superclass. Its implementation is very simple, so there’s lots of room for improvement. We can extract the following interface in Model module based on the persistent TaskManager of the original article. Copy TaskManager from ToDo application, inside the todo.model package of the Model module. Select the class name inside the editor and click on menu Refactor ➤ Extract Interface. Select only the methods shown in Figure 7-15.
../images/479166_1_En_7_Chapter/479166_1_En_7_Fig15_HTML.jpg
Figure 7-15

Extract Interface

You may adapt it a bit to look like the code in Listing 7-11.
package todo.model;
import java.util.List;
public interface TaskManagerInterface {
    void addTask(Task task) throws ValidationException;
    void updateTask(final Task task) throws ValidationException;
    void removeTask(final int id);
    List<Task> listAllTasks(boolean priorityOrDate);
    List<Task> listTasksWithAlert() throws ModelException;
    void markAsCompleted(final int id, final boolean completed);
}
Listing 7-11

TaskManagerInterface

Copy ValidationException and ModelException classes from the ToDo application to resolve the errors, too. Click now on the bulb on the left of the TaskManagerInterface name and select Implement Interface. Give it the name TaskManager and click OK. NetBeans generates a skeleton implementation. Refactor-move it to a new package todo.model.impl. It is a good strategy to implement TaskManager as a service provider of the TaskManagerInterface service (Listing 7-12).
package todo.model.impl;
import java.util.*;
import todo.model.ModelException;
import todo.model.Task;
import todo.model.TaskManagerInterface;
import todo.model.ValidationException;
import org.openide.util.lookup.ServiceProvider;
@ServiceProvider(service = TaskManagerInterface.class)
public class TaskManager implements TaskManagerInterface {
    private final List<Task> tasks = new ArrayList<>();
    public TaskManager() { // mock data
        final GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("Europe/Athens"));
        cal.set(2019, Calendar.JULY, 2, 10, 00, 00);
        tasks.add(new Task(1, "Hotel Reservation", 1, cal.getTime(), true));
        cal.set(2019, Calendar.JULY, 15, 16, 30, 00);
        tasks.add(new Task(2, "JCrete 2019", 1, cal.getTime(), true));
        cal.set(2019, Calendar.JULY, 5, 12, 45, 00);
        tasks.add(new Task(3, "Reserve time for visit", 2, cal.getTime(), false));
    }
    @Override
    public List<Task> listAllTasks(final boolean priorityOrDate) {
        Collections.sort(tasks, priorityOrDate ? new PriorityComparator() : new DueDateComparator());
        return Collections.unmodifiableList(tasks);
    }
    @Override
    public List<Task> listTasksWithAlert() throws ModelException {
        final List<Task> tasksWithAlert = new ArrayList<> (tasks.size());
        for (Task task : tasks) {
            if (task.hasAlert()) {
                tasksWithAlert.add(task);
            }
        }
        return Collections.unmodifiableList(tasksWithAlert);
    }
    @Override
    public void addTask(final Task task) throws ValidationException {
        validate(task);
        tasks.add(task);
    }
    @Override
    public void updateTask(final Task task) throws ValidationException {
        validate(task);
        Task oldTask = findTask(task.getId());
        tasks.set(tasks.indexOf(oldTask), task);
    }
    @Override
    public void markAsCompleted(final int id, final boolean completed) {
        Task task = findTask(id);
        task.setCompleted(completed);
    }
    @Override
    public void removeTask(final int id) {
        tasks.remove(findTask(id));
    }
    private boolean isEmpty(final String str) {
        return str == null || str.trim().length() == 0;
    }
    private void validate(final Task task) throws ValidationException {
        if (isEmpty(task.getDescription())) {
            throw new ValidationException("Must provide a task description");
        }
    }
    private Task findTask(final int id) {
        for (Task task : tasks) {
            if (id == task.getId()) {
                return task;
            }
        }
        return null;
    }
    private static class PriorityComparator implements Comparator<Task> {
        @Override
        public int compare(final Task t1, final Task t2) {
            if (t1.getPriority() == t2.getPriority()) {
                return 0;
            } else if (t1.getPriority() > t2.getPriority()) {
                return 1;
            } else {
                return -1;
            }
        }
    }
    private static class DueDateComparator implements Comparator<Task> {
        @Override
        public int compare(final Task t1, final Task t2) {
            return t1.getDueDate().compareTo(t2.getDueDate());
        }
    }
}
Listing 7-12

TaskManager as a service provider

Don’t hesitate to create the new Task constructor. To resolve the error of the ServiceProvider, click on the bulb and select Search Module Dependency for ServiceProvider. The dialog box of Figure 7-16 will be displayed, which will prompt you to select the (surprise, surprise) Lookup API.
../images/479166_1_En_7_Chapter/479166_1_En_7_Fig16_HTML.jpg
Figure 7-16

The Add Module Dependency dialog box

You just added a new dependency from your Model module to the Lookup API module.

Clean and build your module to resolve any errors.

../images/479166_1_En_7_Chapter/479166_1_En_7_Fig17_HTML.jpg
Figure 7-17

META-INF/services folder

In the Files tab, expand the Model module as shown in Figure 7-17 to verify that a new file META-INF/services/todo.model.TaskManagerInterface has been created, which contains the various implementations of the service, in our case todo.model.impl.TaskManager.

TaskManagerDB will be another service provider of TaskManagerInterface . In the next chapter, we shall see how we can use these services. Please notice that TaskManager or TaskManagerDB may exist in other modules of your application, and in non-public packages.

As an exercise, you may wish to refactor these two classes to use Java 8 syntax and libraries, for example, the new java.time.* classes and/or lambdas/streams.

Finally, we need to expose the todo.model package to other modules so that when other modules add dependencies to the Model module, they can access the classes inside this public package. Right-click on the Model module, select Properties | API Versioning, and check the todo.model package from the Public Packages section to make it public (see Figure 7-18). Clean and build the Model module.
../images/479166_1_En_7_Chapter/479166_1_En_7_Fig18_HTML.jpg
Figure 7-18

Make a package public to other modules

In the next chapter we shall see how to add dependencies between modules.

For an overview of the NetBeans platform modules, right-click on the TodoRCP module suite, select Properties, and click on the Libraries category. Expand the platform node to view a list of all platform modules included or not to your application.

Before we close this chapter, we shall take a brief look at the System FileSystem or layer.xml we described earlier.

To generate the layer.xml file, select the View module, right-click and select New ➤ Other from the context menu, then the Module Development category and the XML Layer file type. Follow the wizard and you will see a new layer.xml file created in the module’s Source Package. In the Projects window, expand it,1 and you will see <this layer> and <this layer in context> (see Figure 7-19). Entry <this layer> refers to configuration information in this module, and entry <this layer in context> refers to the entire application (in other words, it combines all layer.xml file contents of all the modules of the application, that is, it is the System FileSystem). You will notice that layer.xml for our module is empty and only contains <filesystem/>. The layer.xml file consists of four main tags: <filesystem>, <folder>, <file>, and <attr>.

One can access/create objects in the layer.xml using the FileSystem API that was described earlier in this chapter.

We shall learn how we can customize our application by editing this file in the next chapter.2
../images/479166_1_En_7_Chapter/479166_1_En_7_Fig19_HTML.jpg
Figure 7-19

layer.xml file

Summary

In this chapter we learned the core of the NetBeans platform: the Module System API, the Lookup API, and the FileSystem API. The module system API allows you to create loosely coupled designed code that explicitly exposes the interfaces and the dependencies between the modules. Loosely coupled communication between the modules is achieved through the Lookup API. Finally, the FileSystem API provides another way to look at the file system, giving you extra capabilities. We explored the classes FileSystem, FileObject, FileUtil, FileChangeListener, and DataObject. Finally, we started applying what we have learned by porting a Swing application to NetBeans RCP.

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

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