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.
The Module system
The File system
Lookups
Window system
Nodes
Explorer Views
Action system
JavaFX and the NetBeans Platform
HTML/Java UI
We apply the above by porting a Swing application to the NetBeans Platform in Chapter 9.
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.
This link (https://platform.netbeans.org/screenshots.html) provides a list of desktop applications written on top of NetBeans RCP.
The Core Platform
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.
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.
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.
the module’s name,
version information,
dependencies, and
a list of its public packages, if any.
- 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.
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.
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.
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.
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.
for example: Map<String, Set<String>> or Map<Provider, Set<Provider>>.
It was created to allow for inter-component binding and dependencies.
Accessing the default lookup for a single implementation
Accessing the default lookup for many implementations
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.
- 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).
Lookup.Provider interface
TopComponent’s Lookup
Adding a LookupListener to a TopComponent
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
Lookup Containing One Object
Lookup with Fixed Number of Objects
Lookup with Dynamic Content
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
Lookup Delegating Searches to Lookup of Some Object
Exclude Classes from a Lookup
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.
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.
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.
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.
ServiceLoader interface
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.
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).
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 .
FileSystem API examples
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.
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
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
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
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
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.
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.
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.
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).
- 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.
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.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.
After clicking on OK, clean and build the Libraries module.
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.
TaskManagerInterface
TaskManager as a service provider
You just added a new dependency from your Model module to the Lookup API module.
Clean and build your module to resolve any errors.
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.
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.
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.