Chapter 15. The Open APIs

Part of the process of designing software is deciding what functionality needs to be abstracted and exposed for extensibility and what should be encapsulated and invisible to someone extending the system. One good example is Presenter s. Presenters are NetBeans’ abstraction for components such as menu items and toolbar buttons—usually the default implementation is sufficient, and you simply specify an icon and display name and the runtime takes care of actually creating a user-interface component for them, but you can override this behavior if necessary.

The simplest way to let someone extend menus and toolbars is to give them access to a JMenu and let them add components to it. However, this is unmaintainable—if five third-party modules all add items to the File menu, the resulting interface is not likely to be very usable. In such a situation different modules might do things to the menu that interfere with each other working correctly, because there is no contract for how they should behave.

The solution is, appropriately, an abstraction that wraps concepts such as presentation of functionality, and even the Action abstraction for user-invokable behaviors. The goal is to give someone the ability to add functionality, not menu items per se. So we have the Actions, Menus, and Toolbars hierarchies in Tools Options IDE Configuration Look and Feel. As a result, it becomes possible to consistently integrate new functionality—the core takes care of presenting it to the user and managing the GUI components; you just specify where and how it should be presented. NetBeans’ APIs are divided into sets that cover various functions, such as user-invokable actions or data access.

Another advantage to this approach is that the thing that needs to be maintained is the public API—the underlying implementation can be replaced. As long as the new implementation reliably implements the same set of interfaces and methods that were public in the old implementation, all the modules that depend on that API will continue to work. This is, for example, why it was possible to add MDI[6] windowing support to the IDE. Because modules rely on the NetBeans Window System API (org.openide.windows.*), it was possible to rewrite almost the entire windowing system to support two radically different styles of window management. All of the modules remained compatible, because none of them depended on calling into org.netbeans.core.windows.*, the private implementation of the public window system APIs.

This does not mean that the Open APIs are not an evolving entity. New ways of doing things are sometimes introduced, old ways of doing things are deprecated. Please do read Appendix D to get a sense of the future directions of the Open APIs.

Nonetheless, for most of the APIs in this chapter, there are no large changes expected that will invalidate the information presented here. Deprecated means inadvisable, not removed, at least in the case of NetBeans. Surely some of the APIs and techniques in this book will be deprecated at some time, but the core architecture and concepts they demonstrate will remain valid over time. It is always advisable to check the current Open APIs documentation to learn what improvements time has brought. In general, examples presented here will continue to work well into the future.

APIs versus Core versus Modules

The separation of APIs and their implementation in NetBeans can be rendered as follows:

The Open APIs

These are the public interfaces and classes available to module writers. They are divided into specific APIs for dealing with different types of functionality. The contents and behavior of the Java source packages org.openide.* and its subpackages, as specified in the Open APIs reference documentation, are the APIs.

The Core

This is the implementation of the APIs, in the classes in org.netbeans.core.*.

Modules

Sets of classes, delivered in JARs that conform to the Modules API, that add functionality to NetBeans and interact with the the runtime through the Open APIs.

Module APIs

Modules can implement their own APIs to allow themselves to be extended. For example, the XML and Editor modules both offer APIs allowing other modules to work with them. If your module relies on code in another module, the dependency must be declared in your module’s manifest. For examples of declaring intermodule dependencies in practice, see Chapter 27.

It’s worth noting that the APIs are not just sets of Java classes—for example, the Modules API defines manifest formats and XML DTDs, which are as much a part of the API as the classes therein. The contract between a module developer and the NetBeans environment is that the modules will use the public methods of classes in org.openide.* and other specifications associated with NetBeans as documented. If you do that, your module should continue to work with future releases of NetBeans. The entire set of specifications is included in the documentation installed by the Open APIs Support module.

Service Provider Interfaces and Client APIs

Another distinction is that, while we generally use the blanket term “APIs,” they are divided into two flavors: service provider interfaces (SPIs) and client APIs.

Service provider interfaces are interfaces that allow you to enhance existing functionality. When you are writing to a service provider interface, you are not intending that your code be called directly; client code will access your functionality through the intermediary of an existing service in NetBeans. For example, if you write a Filesystems implementation that allows the IDE to see files via NFS (Network Filesystem), you are using the Filesystems subsystem’s service provider interface. The classes you write will allow the common interface for file access in NetBeans to read and write files over a network using NFS protocol. Modules that manipulate files will simply use the common interface used for all file access in NetBeans to do the reading and writing. Other modules will never directly call your code, but the NetBeans environment has been significantly enhanced by your module.

Client APIs are the APIs other modules will use to access objects or services. A client API generally encapsulates services added via its service provider interface. An example of a call to a client interface is to get a reference to an object representing a file and retrieve the file’s size.

Overview of the APIs and Their Purposes

The APIs can be broken down into the following general categories:

Finding/creating Java objects provided by other parts of NetBeans

The Services API and its Lookup subsystem is particularly concerned with this, as are Cookies. For example, the Java module implements structural parsing of Java files. A module that provided UML diagramming needs a way to find the results of this parsing. These APIs allow modules to find Java objects created by other modules with which they need to interact, and do so in a way that allows them to be loosely coupled so functionality can be added to and removed from the system.

Presenting functionality to the user

The Window System, Actions, Wizards and Explorer APIs are all concerned with the actual presentation of functionality in a GUI.

Managing access to data

The Filesystems and Datasystems APIs provide the engine for representing stored data as Java objects in useful ways. Filesystems is concerned with the raw storage of data, and Datasystems with useful structural, programmatic access to parsed data.

Installing and uninstalling functionality

The meat of the Modules API is in the specifications for manifests and XML layer files, and the ModuleInstall class, which allows a module to perform initialization and uninitialization operations.

Processing of data

The Datasystems API, and some APIs that use the Services API, such as Compilation, Execution, and Debugger and search functionality.

Storing settings and state

The Options API provides generic ways to store the state of user configurable settings in NetBeans when the environment is shut down, and restore them on restart. Modules can define SystemOptions, which are JavaBeans whose properties are exposed to the user in the Options window.

There is excellent Javadoc documentation covering the Open APIs in great detail, for which this chapter is no substitute. The Open APIs documentation consists of Javadoc for accessible API classes, plus extensive prose documentation explaining their purpose and use and giving additional specifications and hints. This prose documentation is packaged with the Open APIs Javadoc. For several APIs such as Modules and Window System, the prose documentation is more important than the Javadoc class documentation. What we will try to do here is sort the wheat from the chaff and point out some of the more commonly used classes and specifications. The Javadoc is included in the Open APIs Support module; if you have that installed, you should be able to find it on the Javadoc tab of your Explorer window. If so, you can access it via Help Local Open APIs Documentation.

Since it is better for this book to complement the APIs documentation in a task-oriented way, we won’t go through it in minute detail here; instead, we will provide a guide to the more interesting classes and aspects of specific APIs.

Modules

org.openide.modules.*

Defines the module abstraction, and in particular, ModuleInstall, a class that modules can use to do any work they need to do on startup, shutdown, installation, or removal.

ModuleInstall

Should be implemented by a module’s main class and identified in the manifest tag OpenIDE-Module-Install. This class contains methods you can implement if your module needs to do custom initialization or cleanup when it is installed, uninstalled, or when the IDE is started or shut down.

Note that many modules will not need a ModuleInstall class. Most common things a module will need to do on install, uninstall, startup, and shutdown can be specified declaratively in XML.

Java classes are not the lion’s share of the Modules API, but the specifications for module manifest files and XML layers, which define how modules register themselves with the system and install functionality declaratively.

Module manifests

The module manifest is the first thing that NetBeans looks at when it loads a module. Module manifests contain the following information:

Identification of the module

With both a code name and a display name.

Versioning information about the module

Identifies what version of the module the module JAR file contains, using the Java Package Versioning Specification (http://java.sun.com/products/jdk/1.4/docs/guide/versioning/index.html) in the form of a specification version and an implementation version.

Dependency information

Using the same versioning specification to specify other modules or components that need to be in the system in order for that module to function. NetBeans will not attempt to load a module whose dependencies are not satisfied.

Classpath entries required by this module

This is important! If your module installs a library in modules/ext, it needs to declare this dependency or you will encounter NoClassDefFoundExceptions. Your module has its own class loader, which can see the core Java APIs, the Open APIs, any libraries globally installed in NetBeans, and whatever it declares a dependency on. For details, see Figure 15-11 near the end of this chapter.

A localizing bundle

Provides localized names for attributes such as the display name of the module which can be specified in the manifest.

NetBeans-specific manifest sections

With the specification for XML layers being both more powerful and less memory intensive, a number of manifest sections are now deprecated. There are still a few cases where things must be installed via the manifest, but these are also planned to be replaced with XML module layer specifications in NetBeans 3.4 or 4.0.

As of NetBeans 3.3, DataLoaders, the classes responsible for loading files of known data types, still must be installed via the manifest, as are Nodes (for example, adding a Node to the Runtime tab in Explorer) and actions. This will probably be done declaratively with XML layers in NetBeans 3.4 or 4.0, though the manifest approach will continue to work. Other things, such as SystemOptions (user configuration settings) and filesystem storage providers can be installed either via the manifest or via the module’s XML layer. Wherever there is a way to install via XML, this is the preferred way.

Let’s examine the manifest of the Debugger Core module, shown in Example 15-1.

Example 15-1. Sample module manifest—the Debugger Core module

OpenIDE-Module: org.netbeans.modules.debugger.core/3
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/debugger/Bundle.properties
OpenIDE-Module-Install: org/netbeans/modules/debugger/multisession/EnterpriseModule.class
OpenIDE-Module-Layer: org/netbeans/modules/debugger/resources/mf-layer.xml
OpenIDE-Module-IDE-Dependencies: IDE/1 > 1.24
OpenIDE-Module-Specification-Version: 2.2
OpenIDE-Module-Implementation-Version: 200205011235

Name: org/netbeans/modules/debugger/multisession/EnterpriseDebugger.class
OpenIDE-Module-Class: Debugger

Name: org/netbeans/modules/debugger/support/nodes/DebuggerNode.class
OpenIDE-Module-Class: Node
Type: Environment

The first entry is the module’s code name. The code name is used in the log file and is primarily useful to code that needs to identify a given module (for instance, to declare a dependency on it), or for support engineers who need to know precisely what modules a user has installed. Following this is a reference to a “localizing bundle” that will be used at runtime to supply internationalized text for the display name and description of the module itself. Note that objects installed from the module’s layer refer to their own localizing bundle(s) independently, which need not be the same bundle as is referenced in OpenIDE-Module-Localizing-Bundle.

The next item identifies a Java class that is a subclass of org.openide.modules.ModuleInstall, and has methods that should be run to initialize or clean up on install, uninstall, start up, and shut down. Generally these are to be avoided where possible, as they can add to startup time, but there are cases where they are needed.[7] The OpenIDE-Module-Layer line identifies the path in the JAR file to the module’s XML layer file. This is followed by a declared dependency on a specific minimum version of the APIs. Following this is versioning information used by NetBeans for dependency management and to determine if a version of a module available through the Update Center is really newer than this one and thus an update.

Following these attributes, after the blank line, are groups of lines that are the NetBeans-specific module sections: The first section installs the debugger—unless you plan to write a wholesale replacement for NetBeans’ debugger, you will probably never use this particular type of module section. This is followed by an entry for a Node—the one you can find in the Runtime tab of the Explorer window, which lists under it all of the running debugging sessions.

Several other types of module sections exist, such as for installing Nodes. The entire manifest specification is covered in the Modules API prose documentation. Note that the Open APIs Support module has support for automatic generation of the common entries in a module’s manifest—to see this, create a new module JAR from template, and look on the Module properties tab of its property sheet. If you have the Open APIs Support module installed, many of the common manifest and layer entries can be created using cut and paste operations into the children of your module’s node. For example, once you have created a module’s layer file, simply copy and paste it into the Layer subnode of the module JAR in the Explorer window.

XML layers

XML layers are used to install data and factories for Java objects in the system filesystem. The system filesystem contains a number of folders that are special because NetBeans uses them for a specific purpose. For example, menus, toolbars, and the settings UI are all created from folders and files inside the system filesystem. Persistent settings can be managed using .settings files, which are factories for Java objects, as are .instance files. These files differ only in that, when an instance is created from a .settings file, NetBeans will try to attach a PropertyChangeListener to it. If a PropertyChangeEvent is fired, the changes will be saved so that they will persist for future NetBeans sessions.

The primary use of XML layers is to place Java classes into folders in the system filesystem that have special functions, such as the registration of services or menu items. Many systems in NetBeans use this functionality. For example, there are extensible wizards which can have panels added to them by adding files to a particular folder. Modules can also define their own folders in the system filesystem and use them, which can be useful for ensuring lazy instantiation of objects and separating a module’s configuration from its code. Declaring a Java class instance in a module layer is illustrated in Example 15-2.

Example 15-2. A simple XML layer declaring a Java instance

<!DOCTYPE filesystem PUBLIC
          "-//NetBeans//DTD Filesystem 1.1//EN"
          "http://www.netbeans.org/dtds/filesystem-1_1.dtd">
<filesystem>
  <folder name="Services">
    <folder name="Hidden">
      <file name="com-mymodule-MyClass.instance">
        <attr name="instanceOf" stringvalue="com.mymodule.MyClass"/>
      </file>
    </folder>
  </folder>
</filesystem>

By using the special Services folder of the system filesystem, this file entry is also automatically registered with the lookup subsystem. When some code calls Lookup.getDefault( ).lookup(com.mymodule.MyClass.class), the system will call the default constructor to create an instance of com.mymodule.MyClass and return it. Because the class name is specified in the XML, the JVM need not actually load the implementation class (taking up valuable memory and CPU cycles in the process) unless client code requests an instance. The mechanism underlying this is a class called InstanceDataObject—files with the extension .instance are represented by DataObjects that possess an InstanceCookie, as shown in Figure 15-2. The InstanceCookie interface has a method called instanceCreate( ) that will instantiate and weakly cache an instance of the class specified in the file’s name or attributes. This mapping is shown in Figure 15-1.

Mapping of files to Java class instances

Figure 15-1. Mapping of files to Java class instances

Mapping from files to InstanceDataObjects to lookup

Figure 15-2. Mapping from files to InstanceDataObjects to lookup

XML layers allow you to define not only what class is instantiated, but also how it is instantiated. Perhaps you need to do some initialization and the default constructor is not appropriate. This is not a problem. Replace the file entry in Example 15-2 with the example in Example 15-3.

Example 15-3. Specifying a static factory method for instances in a module layer

<file name="com-mymodule-MyClass.instance">
  <attr name="instanceOf" stringvalue="com.mymodule.MyClass"/>
  <attr name="instanceCreate"
        methodvalue="com.mymodule.MyFactory.createMyInstance"/>
</file>

A file attribute has been added. instanceCreate specifies a factory method that should be called to do the work of creating and initializing the instance.

Warning

File attribute names are case sensitive, just like Java identifiers. No warning message will generally be shown if you mistype a file attribute name, so check the spelling if you are having problems.

com.mymodule.MyFactory.createMyInstance( ) can be any static method on any class in your module. When the system requests your instance, this factory method will be called. The factory method could look like Example 15-4.

Tip

Note that it is encouraged, but not required, to use the instanceOf attribute; this is an optimization that avoids instantiating the class simply so that a query can determine if it is a match or not. The attribute must list every interesting interface or superclass implemented by the actual instance class; lookup queries on a class or interface that is not listed in instanceOf will not succeed. It is safe to omit the attribute—then lookup will always try to load your class and check if it really matches.

Example 15-4. Factory method to instantiate a class from a .instance file

package com.mymodule;
public class MyFactory {
  public static MyInterface createMyInstance( ) {
    // whatever constructor or initialization you need:
    return new MyClass("someParam", true);
  }
}

Encapsulation is an important thing. Perhaps your module specifies a public interface, and the implementation of that class is private. You need to return an instance of the interface in question. This is not a problem, as demonstrated in Example 15-5.

Example 15-5. Specifying the class that will be created in a layer

<file name="com-mymodule-my-instance.instance">
  <attr name="instanceClass" stringvalue="com.mymodule.MyClass"/>
  <attr name="instanceCreate"
        methodvalue="com.mymodule.MyFactory.createMyInstance"/>
  <attr name="instanceOf" stringvalue="com.mymodule.MyInterface"/>
</file>

Another file attribute has been added. instanceClass defines what class you want the returned instance to be. Consider the case where you define a public interface for which you want to produce an instance from the system filesystem (perhaps via Lookup). You don’t want to expose the class that implements this interface, just an instance of that interface. instanceClass allows you to restrict what class appears to be returned when an instance is created from this file entry.

Tip

In NetBeans 3.4 instanceClass would be optional in this example: if omitted, the instanceCreate attribute is sufficient.

The filename has also ceased to matter, since the class to be instantiated is declared as an attribute of the file. So it is also now possible to have more than one instance of the same class in the same folder.

But that still might not be quite enough—you may want to specify some arguments to the factory method in order to use the same factory for differently configured instances. This is possible too; Example 15-6 is an example of this.

Example 15-6. Specifying initialization data for a Java instance declared in a layer

<file name="com-mymodule-my-instance.instance">
  <attr name="instanceClass" stringvalue="com.mymodule.MyClass"/>
  <attr name="instanceCreate"
        methodvalue="com.mymodule.MyFactory.createMyInstance"/>
  <attr name="instanceOf" stringvalue="com.mymodule.MyInterface"/>
  <attr name="myInitString" stringvalue="myValue"/>
</file>

A fourth attribute has been added: myInitString. This attribute does not have any special meaning to NetBeans’ infrastructure. File attributes can be arbitrarily named strings. But the factory method that instantiates the class has access to file attributes. There are two possible signatures for the factory method; the first is shown in Example 15-7.

Example 15-7. Simple factory method for an XML layer instance

public static MyInterface createMyInstance( ) {
  return new MyClass(/* fixed arguments */);
}

Depending on the method signature of createMyInstance( ), it can be passed the FileObject that is being used, utilizing the more complex method signature shown in Example 15-8.

Example 15-8. Complex factory method for an XML layer instance

public static MyInterface createMyInstance(FileObject fo) {
  String initString = (String)fo.getAttribute("myInitString");
  return new MyClass(initString);
}

With this second style, you can attach any sort of arbitrary initialization data you want to the file via its attributes. The result is that you have complete declarative control of how your class is instantiated, but there is no need for the JVM to actually load your class until something asks for it via lookup.

Supplying actual data in a module layer file is possible simply by providing an XML CDATA section between the opening and closing file tags in the XML. However, this technique is generally not recommended for reasons of memory performance, and can be tricky with respect to whitespace and non-ASCII characters. Instead, the preferred method is to use a URL pointing to the data the file should appear to contain, where the file referenced by the URL is inside the module JAR. The format for this is quite simple, and shown in Example 15-9.

Example 15-9. Declaring file contents using URLs in XML layers

<file name="MyTemplate.java"
      url="templates/MyTemplate.java.tmpl"/>

The URL is resolved relative to the root of the module JAR archive, and for client code that accesses the file, the contents of the file pointed to by the URL will appear to be its content.

Ordering files in XML layers

Given that files in XML layers are used to install things such as menu items, some means of establishing the order in which files appear is necessary. The Filesystems API does not specify the order in which files will occur in a folder—just as file order is not consistent when listing the same directory on Unix and Windows.

It is the Datasystems API that specifies ordering constraints for files. The ordering of the children of a FileObject is undefined; the ordering of children of a DataFolder can be specified.

Ordering of files in an XML filesystem is accomplished by adding attributes to the containing folder. The syntax is fairly simple. The name of the attribute specifies an ordering constraint for two files, separated by a slash, for example, file.before/file.after. Neither filename mentioned in the constraint need be a file supplied by the module installing the constraint (though in practice, at least one of them will be). The module author need know only the filenames in question. Example 15-10 shows what this looks like in practice, using the XML layer from the Form Editor module as a template.

Example 15-10. Ordering attributes in XML layers

<folder name="View">
  <attr name="org-netbeans-core-actions-HTMLViewAction.instance/org-netbeans-
modules-form-actions-FormEditorAction.instance" boolvalue="true"/>
  <file name="org-netbeans-modules-form-actions-FormEditorAction.instance"/>
  <attr name="org-netbeans-modules-form-actions-FormEditorAction.instance/org-
netbeans-modules-objectbrowser-ShowBrowserAction.instance" boolvalue="true"/>
</folder>

In Example 15-10, the Form Editor is installing a single menu item, yet it installs two constraints. NetBeans is an extensible system, and other modules may also want to include a menu item before or after the same element. For example, two modules might want to put a menu item after Web Browser (org-netbeans-core-actions-HTMLViewAction.instance). Without a second constraint, either one could end up following the web browser. If you are aware of the other module’s desire to follow Web Browser, you can preempt that by declaring a second constraint that the file from the other module should follow yours.

The other curious thing you may have noticed in the preceding listing is the presence of boolvalue="true" for each constraint. Since all of the ordering information needed is contained in the attribute name, this looks superfluous. In a sense, it is, but the contract for FileObject specifies that attributes map names to Java objects. Thus, it is required that the value of an ordering constraint be Boolean.TRUE.

Lastly, note the order in which the attributes occur in the example. The ordering attribute that specifies the file occurring before the visible file is placed before the file in the listing. The attribute that specifies the file occurring after the visible file is placed after it. There is no technical requirement that files or attributes in an XML folder be declared in any particular order; however, it is a convention adopted in NetBeans sources to improve readability, and we encourage you to use it.

Lookup and the Services API

NetBeans contains a generic registration for services. A common use of services is to register a service that can perform some operation over a group of files, such as compilation or text searching. But services can be used for most any sort of functionality. NetBeans also contains an even more general registry of interesting objects, called lookup. Lookup is a system by which a module can place a query for a particular interface and get back pointers to all registered instances of it.

Lookup, .settings files, the system filesystem, and layers

Various packages in the APIs

Permits different kinds of functionality to be installed and queried in a general way. Services can be registered in the lookup system and then can be found by other modules. This allows a module to find and interact with objects produced by other modules. Modules can register objects with the lookup system very simply by declaring them in their XML layer inside the Services/ folder. The Lookup API also provides a service provider interface that can be implemented by modules that provide factories for objects of potential interest to other modules. Those modules can then register their lookup implementation with the system-wide lookup system (Lookup.getDefault( )) and thus be queried whenever any module queries the default lookup system.

Implementing lookup and registering your lookup provider with the system sounds like a lot of of work, but there is an easy way out. Say you have a class that is the entry point for the functionality of your module. You don’t actually have to do anything except to define, in your module’s XML layer, a .settings file that dereferences a bean the same way .instance files do, and define it as living in the Services folder. The system filesystem defines two ways to instantiate objects. The first is .instance files, which specify that a class NetBeans should instantiate on demand via InstanceCookie.createInstance( ). .settings files follow the same model, but are generally used for JavaBeans only and expand on the model in the following ways:

  • Property change listeners will be added if and when your bean is created.

  • Introspection will be used to detect the addPropertyChangeListener( ) method.

  • If properties are changed, they are saved to the user or project layer of the system filesystem and will persist for future IDE sessions. In NetBeans 3.3, serialization is used for this purpose.

Commonly used or interesting classes in this package

Lookup

This class provides a very generic interface for looking up objects. Lookup provides a generic “NetBeans Yellow Pages.” Callers can query a Lookup implementation for instances of a given interface. The system has a default lookup implementation available via Lookup.getDefault( ). The default implementation allows other implementations to register themselves with it and be queried via this call. Thus, any module that makes objects available using Lookup can add itself to this central point for queries. Simple queries happen via the method Lookup.lookup(Class clazz)—note here the resemblance to Node.getCookie(Class clazz), its logical precursor. This method will return an object of the desired class, or null. If there may be more than one result of your query, or the result might change over time, use the call lookup(Lookup.Template template).

Lookup.Template

This class represents complex queries of the lookup subsystem. Queries can include a class, a specific instance, and/or an identifying string.

Lookup.Result

Represents the results of a query of the lookup system, including all available instances (not just the first). If it is desirable to listen for changes in the result of a query, it is possible to hold a reference to a Result and attach a listener to it.

AbstractLookup

This is a partial convenience implementation of Lookup that modules may subclass and register if it is useful for them to implement Lookup. If the set of items available in this lookup implementation will not change, an AbstractLookup instance can also be instantiated by passing its constructor an instance of InstanceContent that determines the contents.

LookupListener

A listener for changes that can be attached to an instance of Lookup.Result and be notified if the results of the lookup have changed.

ProxyLookup

An implementation of Lookup that can delegate the actual work of looking something up to a collection of other implementations of Lookup.

FolderLookup

Though part of the Datasystems API by package, this very important class allows lookups on the contents of a folder (which may be a physical folder on disk, an XML filesystem installed by a module, or anything else handled by Filesystems). It is this mechanism that allows modules to register objects with the lookup system by simply declaring them as virtual files in their XML layers. Furthermore, if a FolderLookup finds an implementation of Lookup among the instance files in its folder, it will proxy to that Lookup instance. If a module needs to create one or more registries of objects that may change dynamically at runtime (for example, for an application broken out into domains with string IDs), this is a convenient tool.

Services

Services, or service types, are a slightly older idiom in the NetBeans world—originally NetBeans had separate compiler, debugger, and execution infrastructure. The generalization that all of these things are “services” was retrofitted to this several years ago; yet even this was not generic enough, so the more generic Lookup infrastructure was born. New kinds of services can be any JavaBeans, and no longer need to extend ServiceType.

The general aspects of services are:

Services are JavaBeans

A service (whether extending ServiceType or not) must be configurable as a JavaBean. Its property management and user presentation is done through introspection.

A class of service is registered, and the environment can contain multiple discrete instances of that class with different configurations

An example of this is the External Compilation service. You can copy and paste the External Compilation node to create a second instance of this service and then configure it to call a different compiler.

Individual instances of services can be looked up by name, for example:
ServiceType.Registry reg = TopManager.getDefault( ).getServices( );
CompilerType type = (CompilerType)reg.find("External Compilation");

You can also find services in Lookup by matching on a superclass. DataObjects will often want to be associated with one or more services (for example, a Java file with a compiler and execution service), and there are standard support classes that make this easy, such as ExecSupport and CompilerSupport. You can read about the user aspect of this technique in Chapter 5.

Nodes

org.openide.nodes.*

Defines the Node interface. Nodes represent JavaBeans or other “property containers”—that is to say, objects with properties that potentially may be exposed to the user via the user interface. Nodes exist in a tree-like hierarchy, so any given node probably has a parent node and potentially a collection of child nodes, which in turn may have their own child nodes.

Nodes are used pervasively throughout the NetBeans user interface as presentation objects (despite the fact that Nodes are not visual components). For those familiar with the Model-View-Controller design model, a node is the controller object; the object it represents is the model, and the view is the Explorer component (an Explorer view, as detailed in Figure 15-3) being used to render the node in NetBeans’ user interface.

Anatomy of a node

Figure 15-3. Anatomy of a node

Nodes are not containers for data themselves, but are more like pointers that aggregate a set of related objects (refer to Figure 15-3). For example, a subnode of a node representing a Java source file may represent a method within a class in that file. Modules can access the contents and structure of the method source code by asking the node for a cookie of the class SourceCookie. The node exposes properties of the method source code, such as the arguments passed to the method, via its properties. User-invokable actions are available via the Node.getActions( ) method, and so forth.

The inspiration for Nodes is the JavaBeans specification, but differs in several respects:

Hierarchy

Nodes live in a tree-like hierarchy; nodes can have a parent (containing) node and child nodes. These are supplied by an instance of the class Children owned by the node and retrieved via the method someNodeInstance.getChildren( ).

Dynamic properties

In the JavaBeans spec, properties are determined at compile time, depending on method signatures. Properties of nodes are instances of Node.Property and can potentially change at runtime. If you want to represent a standard JavaBean as a node, this is quite easy using the BeanNode wrapper class.

Dynamic actions

A node has a set of actions that can be performed on it, which may vary depending on the state of the object it represents.

Cookies

A node has a set of cookies, which are objects that implement the Node.Cookie marker interface. A cookie is an arbitrary Java class instance that can provide access to objects relating to the node. A cookie can be any Java object that is useful to expose to other code that might interact with the Node. For example, a cookie may implement actions that can be performed on the Node by the user, or provide structural access to the data in the underlying DataObject. Cookies are retrieved by passing the Class object for the desired interface to someNode.getCookie(Class clazz) and casting the result (if non-null) to that interface.

For example, a node representing a file the user has opened in the Editor will provide an EditorCookie. The EditorCookie implementation that is returned provides both methods for opening the document in the editor and for accessing the document object the editor uses, finding all open editor panes that are editing this file, and other editing-related functionality.

Commonly used or interesting classes in this package

AbstractNode

An implementation of the Node interface that provides functionality common to most Nodes, such as icon and name handling. When creating your own Node implementations, you will usually want to subclass AbstractNode.

BeanNode

A wrapper for JavaBeans that can expose an existing JavaBean and its properties as a node. It also adopts a name and icon from the JavaBean if that information is available in its BeanInfo. BeanNode is essentially an adapter to allow you to represent and work with JavaBeans as Nodes with minimal coding.

Children

Nodes live in a hierarchy, so nodes can have, by definition, childrenNodes that live below them in the hierarchy. When you call someNode.getChildren( ), you will be returned an org.openide.nodes.Children object, which you can query for the child nodes of someNode.

FilterNode

This is a node that actually represents another node somewhere else in memory. It delegates some or all of its functionality to the original node, depending on its implementation. A common use is to provide a node that provides a different set of child nodes, properties, or cookies than the original.

Node.Cookie

This is an empty marker interface for cookies. Node and DataObject instances both have a set of cookies that can change dynamically. You can listen for changes in the set of cookies available.

Node.Property

An object representing a property of a node. Properties are arbitrary name-value pairs, as displayed in a Node’s property sheet.

Node.PropertySet

Yet another layer of indirection in the relationship between nodes and their properties. Node.Property objects are contained in one or more PropertySet objects owned by the Node. This allows Nodes to have different categories of properties, such as Expert properties that are occasionally useful to expose but that will not routinely be modified by the casual user. One Node.PropertySet corresponds to one tab of the Property Sheet window.

Datasystems

org.openide.loaders.*

Used to manage the loading and handling of DataObjects; this is largely the API used when support for a new data type is implemented.

DataObject

An object that encapsulates one or several files. If the data it represents is structured, a common thing for a DataObject to do is to provide structural, programmatic representations of the data via its Cookies.

DataLoader

Factories for DataObjects. When NetBeans encounters a new file, it figures out what type of data it is and invokes the DataLoader for that type of data to create a DataObject to represent it.

MultiDataObject

Why is a layer between files and nodes needed? One of the most compelling reasons is that it may be desirable to present multiple files as a single object. In practice, almost all data objects extend MultiDataObject, even though only one file can be contained in the object.

DataFolder

A DataObject implementation that represents a folder that contains other DataObjects.

DataNode

A Node implementation that represents a DataObject.

UniFileLoader

Registers a set of extensions or MIME types and is responsible for loading data from files of these types.

MultiFileLoader

Similar to UniFileLoader, but responsible for loading data from multiple files that should be represented as a single entity within NetBeans (for example, a Java source file with an associated compiled class file and possibly an XML form file that defines the GUI layout). Almost all loaders extend MultiFileLoader.

OpenSupport

This class implements methods from OpenCookie, ViewCookie, and CloseCookie, but does not declare itself to implement any of these interfaces. To implement any of these cookies, simply subclass OpenSupport and declare your subclass to implement any of the interfaces it supports. OpenSupport provides generic support for opening documents. Depending on the type of editor(s) to be instantiated, this functionality may be better referred to as Viewing, Opening, or Editing.

FolderInstance

This class is particularly useful if you need to create a component that assembles itself from instances or subfolders in a folder. FolderInstance contains logic for accepting or rejecting instances contained in that folder, or for overriding the supplied instance (this is the mechanism by which it is possible to paste compiled classes into menus and toolbars). In particular, it is sensitive to the addition or deletion of files within the folder and can be used to rebuild the component in question with minimal coding on the part of the module author. For a good example of its usage, see the sources for org.openide.awt.MenuBar .

Explorer

org.openide.explorer.* and subpackages

The Explorer package contains tools for visually representing hierarchies of Nodes in a variety of ways, such as tree views, menus, and property sheets.

org.openide.explorer.view.*

Contains GUI components that can represent Nodes graphically, including tree, menu, drop-down, list, and table views. An Explorer view is created and is given a root node. Some views display the root node and its children (perhaps recursively). Others display nodes contained in the explored context (another parent node). Still others display the selected node (or nodes) only.

ExplorerManager

This interface is the entry point for communicating with Explorer views. An ExplorerManager tracks changes in the node selection, as well as the root node and explored context, for one or more Explorer views associated with it. To listen for changes on an open Explorer view, attach a listener to its manager.

ExplorerPanel

A very convenient visual class that you can use to create compound components that contain one or more Explorer views. ExplorerPanel extends org.openide.windows.TopComponent, the base class for dockable windows in NetBeans, and implements ExplorerManager.Provider (a way of finding ExplorerManagers). To associate an Explorer view with the ExplorerPanel so you can listen to it for changes in selection, and so on, simply add your view to the ExplorerPanel as a Swing component.

explorer.propertysheet.*

This package contains classes that are responsible for property sheets in NetBeans. Property sheets and instances of the PropertyPanel class in this package provide one of the primary ways for users to adjust settings and attributes of objects in NetBeans. NetBeans contains some enhancements to the JavaBeans property editor infrastructure. Also present is the PropertySheetView visual class, an Explorer view that, when given a node selection from an explorer manager, displays the user-editable properties of that node (or set of nodes) as a familiar property sheet.

There are a variety of useful components in the subpackage org.openide.explorer.view. Explorer views are GUI components such as the Filesystems tree in the main Explorer window of NetBeans. A variety of components are provided which implement different styles of views. What these classes have in common is that they each can be managed by an ExplorerManager. The ExplorerManager connects an explorer component with the rest of NetBeans’ infrastructure so that when the user selects a different node, NetBeans is aware of it (changes in the set of activated Nodes are fired and actions are enabled or disabled as appropriate, and so on.). Here are several of these views:

BeanTreeView

This is the standard tree view you see in the main Explorer window in the IDE, as shown in Figure 15-4.

Explorer views—BeanTreeView

Figure 15-4. Explorer views—BeanTreeView

ChoiceView

A drop-down list component that displays an Explorer tree to some fixed depth, as shown in Figure 15-5.

Explorer views—ChoiceView

Figure 15-5. Explorer views—ChoiceView

IconView

A component that displays nodes as icons, reminiscent of the Microsoft Windows Explorer (see Figure 15-6). This class is not used in the current NetBeans distribution, but was part of the original New Wizard and is maintained as part of the APIs.

Explorer views—IconView

Figure 15-6. Explorer views—IconView

ListView

Displays the children of one Node in a flat list, without indentation, illustrated in Figure 15-7.

Explorer views—ListView

Figure 15-7. Explorer views—ListView

MenuView

This view presents a set of Nodes in a menu, with submenus for nodes that have children (see Figure 15-8).

Explorer views—MenuView

Figure 15-8. Explorer views—MenuView

ContextTreeView

This looks the same as BeanTreeView, but there is a significant difference. When you select a node in this view, the ExplorerManager managing the Explorer views changes the explored context for all other Explorer views to the node you have selected in your ContextTreeView component. If you’ve used Windows, think of Windows Explorer here, with its left-hand pane to select the current directory and its right-hand pane to display that directory’s contents. By changing the context for all other components managed by its manager, you can actually create a component very much like the Windows Explorer within NetBeans, as seen in Figure 15-9.

A “Pseudo-Windows Explorer” mock-up created using IconView and ContextTreeView

Figure 15-9. A “Pseudo-Windows Explorer” mock-up created using IconView and ContextTreeView

TreeTableView

It is not uncommon in NetBeans to have a dialog box or window that displays a number of nodes, the purpose being to configure only a few properties of all of these nodes. An example of this is the panel for enabling and disabling modules in the Setup Wizard.

This class provides a flexible way to display specific properties of a given set of Nodes in a table alongside a tree. A TreeTableView displays a tree view of nodes, and can be given one or more properties (if present) to display for each node in the tree. What is displayed in the view depends on the model supplied to the tree and the array of property names provided for the component to expose. Take a look at Figure 15-10 for an idea of this view.

Explorer views—TreeTableView

Figure 15-10. Explorer views—TreeTableView

Actions

org.openide.actions.* , org.openide.util.actions.*

Defines user-invokable behaviors. User-invokable actions in NetBeans take the form of subclasses of org.openide.actions.SystemAction which is an extension of the Swing Action API. SystemAction instances in the IDE should be singletons—all state information should be static. As of NetBeans 3.3, javax.swing.Actions (which need not be singletons) can also be used in some, but not all, circumstances.[8]

An action is an abstraction for user-invokable behavior. An action has a display name and optionally an icon; if the action is added to the Menu or Toolbars folder of the system filesystem (by creating an empty file such as com-foo-MyAction.instance in the module’s XML layer) a menu item or toolbar button will appear in the user interface to represent it.

Some actions are always enabled, such as those that bring up a wizard or window. Actions can also be context sensitive. An action may be enabled or disabled depending on whether the currently selected node (e.g., a file selected in the Explorer window when it has focus, or the file currently open in the editor when it has focus) can have that action performed on it.

SystemAction

All actions in NetBeans subclass SystemAction, which is an extension to the Swing action API, extending the Open APIs class SharedClassObject and expecting to be a singleton.

CallableSystemAction

A convenience base class for non-context-sensitive actions. Implementing a single abstract method allows your action to be invoked.

CallbackSystemAction

An action which has a separate performer object associated with it that actually performs the action. The performer can be changed on the fly. This is typically used for actions whose meaning depends on the selected window. When no performer is present, the action is disabled.

NodeAction

An action which can listen to what the currently selected node is, and set its enabled state accordingly.

CookieAction

This very useful base class provides an action whose state is enabled or disabled depending on what Node.Cookie classes are held by the activated nodes.

Options

org.openide.options.*

The Options API is the API for user-modifiable configuration information and its storage. NetBeans has a set of singleton SystemOptions that are exposed to the user through the configuration window available via Tools Options. You don’t need to write code to do the dirty work of persistent storage of these settings if you use SystemOption and as long as your data is serializable (this is a requirement for persistent storage).

SystemOptions are user-configurable settings that may be installed by modules to configure the behavior of that module. Examples of SystemOption properties are the settings for which HTML browser to use when a user views an HTML file, or for whether to display the Welcome screen on startup. SystemOptions are singletons, and all configuration information that a user can modify should be stored by the class in static variables or its properties via the putProperty( ) and getProperty( ) methods. As with SystemAction, SystemOption is a subclass of org.openide.util.SharedClassObject, which provides infrastructure for creating singleton classes (which enforce the existence of only one instance per class). The getters and setters of a SystemOption should be non-static instance methods, despite the fact that the data storage is class-scope.

SystemOptions are not nodes, but are presented via nodes to the user. InstanceDataObjects can provide nodes that represent the object instance in question, but will not actually instantiate the object unless it is needed, for example when a property sheet must be displayed.

SystemOptions are installed via the module manifest or XML layer, which should contain a reference to your SystemOption class. To discover the properties of your SystemOption that should be saved, introspection is used. For greater control over the process, always create a BeanInfo class describing your SystemOption.

SystemOption

An object that represents a group of related user-modifiable settings (exposed through the SystemOption subclass’s bean properties).

ContextSystemOption

A SystemOption that is a container for additional child SystemOptions.

Compiler

org.openide.compiler.*

Defines the compilation process, using the infrastructure of the Services API.

CompilerJob

NetBeans does not assume compilation is a one-step process. A compiler job consists of a set of compilers with possible dependencies on one another. Compilation may proceed in one or more stages, each of which can run varied compilation procedures.

Compiler

An object that represents one compilable file or other unit in a compiler group.

CompilerGroup

An object that represents one step in a compiler job, perhaps compiling a cluster of similar files.

CompilerType

A ServiceType that has a method for contributing to a compiler job.

CompilerSupport

A cookie implementation that, when present in a node’s cookie set, indicates that the thing represented by the node can be compiled. Provides methods for adding a DataObject to a compiler job.

Editor

org.openide.text.*

The editing infrastructure of the IDE. This API covers both the infrastructure for clients to interact with editors that are installed in NetBeans (for example, opening a file in an editor, finding the selected region, and so on) and the SPI for integrating new editor kits.

The editor API uses the Swing EditorKit interface; it is possible to plug any editor into NetBeans that implements this interface. “Documents” are instances of javax.swing.text.StyledDocument.

CloneableEditorSupport

A base class that can provide implementations of the following Cookie interfaces as needed:

EditorCookie

A cookie class that supports opening documents in an editor window and finding instances of editors already editing a file. This cookie further permits nonblocking loads of documents and queries of document load status.

LineCookie

A cookie class that provides access to a document as a set of lines, represented as Java objects that can be held even if the document they reference is edited and line numbers change.

OpenCookie

A cookie class that provides a method for opening a document in the default editor for that document type.

EditCookie

A cookie very similar to OpenCookie, but used to emphasize that this is an editing operation on the object, whereas OpenCookie opens it graphically.

ViewCookie

Also similar to OpenCookie, but used to emphasize that the operation will not modify the object but only allow the user to view it.

PrintCookie

Supports printing of a document.

DataEditorSupport

A class that extends CloneableEditorSupport to support associating an editor with a DataObject representing the file or files to be edited.

NbDocument

This is a utility class with static methods and interfaces for handling NetBeans’ document conventions.

Line

Represents one line in a document. If a reference to a Line object is held, it will continue to represent the same line of text, even if the document is altered so that its position in the document has changed.

Annotation

This is the base class for objects representing some user-presentable marker that applies to a line or part of a line within a document. The graphic displayed in the Editor’s line-number gutter and background color of a line in a Java file that has been set as a breakpoint is an example of an annotation. Another example is the highlighting that occurs when you click a compiler error in the Output Window.

Windowing System

org.openide.windows.*

NetBeans has a rich window management system that allows you to concentrate on the layout of those UI components you need to create without having to perform the grunt work of managing windows, window positioning, and so on. The windowing system provides base classes you can subclass, and using the Open APIs Support module, you can use the NetBeans Form Editor to design the GUI for your module.

Among the features of NetBeans’ windowing system are:

Docking

UI components can be mixed inside a window, with tabs to access the different components. Components can also be docked into the sides, top, and bottom of windows that support this, such as the Editor window.

Workspaces

Support for discrete sets of windows organized by task.

MDI & SDI modes

SDI (single document interface) means you have multiple windows on the screen. MDI (multiple document interface) means that there is one large window with subwindows inside that window.

Workspace

A workspace is a set of open windows, their contents, positions, and frame styles. To get the current workspace, call TopManager( ).getDefault( ).getWindowManager( ).getCurrentWorkspace( ). Workspaces contain Modes.

Mode

An interface representing a window containing GUI components. Modes define docking. Windows may contain multiple components on a tabbed pane or components that are docked into the top, bottom, left, right, or the center of the window (using draggable splitters so the user can adjust the relative sizes). Generally, modules don’t need to interact directly with the mechanics of modes; however, if there’s a particular window, such as the Explorer or Editor windows, that you want your component to open in, you can locate that Mode by calling Mode mode = TopManager.getDefault( ).getWindowManager( ).findMode(String name) and add your TopComponent to it by calling the dockInto( ) method with your component as the argument.

Modes contain one or more TopComponents. This can also be done via XML layers, as explained at the end of this list.

TopComponent

This is the base class for all dockable window components in the IDE. A TopComponent is a generic GUI container component, subclassing javax.swing.JComponent. The difference is that it has the hooks to be connected to NetBeans’ window management system (see Figure 15-11 for a big-picture view).

Modes, TopComponents, and Nodes—anatomy of the Explorer window

Figure 15-11. Modes, TopComponents, and Nodes—anatomy of the Explorer window

WindowManager

This is the class that actually handles the opening and closing of TopComponents and the docking behavior of Modes and that you can listen to for changes in the current workspace and available set of workspaces. To get the default instance, call TopManager.getDefault( ).getWindowManager( ).

TopComponent.Registry

This is another singleton class; it has methods you can call to get the set of currently active nodes. All TopComponents instantiated in NetBeans are registered here. When a TopComponent gets input focus, it becomes the “active” TopComponent and gains control over what the active nodes are. The active nodes are used in turn to enable or disable functionality (such as menu items) depending on what nodes the user has selected. To get this registry, call TopManager.getDefault( ).getWindowManager( ).getRegistry( ).

Modes, TopComponents, and Workspaces can also be installed declaratively by modules, using XML layers. For an overview of interacting with the window system through the system filesystem, see Chapter 17. There are XML formats for files to define Workspaces, Modes, and their contents.

Cookies

org.openide.cookies.*

Cookies are a design pattern to allow the lookup of arbitrary Java objects. Any class that implements the empty Node.Cookie interface is a “cookie.” All Nodes and DataObjects can hold cookies; for the convenience of subclasses, AbstractNode and MultiDataObject normally hold cookies in their protected CookieSet. A cookie can be retrieved by calling someNode.getCookie(Class cookieClass) or someDataObject.getCookie(Class cookieClass). Generally one defines an interface that extends a Cookie and provides an implementation of it that can be fetched from a DataObject or Node. There are some useful basic implementations of common cookies in the Open APIs—generally these are classes whose names end with Support.

This package defines some cookie classes that apply broadly to NetBeans, specifying such common actions as printing, opening files in an editor, opening files in a viewer, saving, and so forth.

OpenCookie

A cookie class that has a single method called open( ), which tells NetBeans to open an object (normally in an editor, whether as text or graphically).

EditorCookie

A cookie to allow access to a document object and the editor instances that have the document open and determine if the document is modified.

SaveCookie

A cookie with a single method save( ) to instruct NetBeans to save an object.

InstanceCookie

Some Nodes, such as JavaBeans on the Form Editor’s Component Palette, are able to create instances of some arbitrary object (for example, when you drop a JButton on a form). InstanceCookie provides support for creating this instance via its instanceCreate( ) method. Another powerful use of InstanceCookie is its use to create a node that represents a Java class instance without NetBeans having to load that class unless something actually calls instanceCreate( ).

SourceCookie

Objects representing Java source files will have a SourceCookie providing access to a SourceElement object, which has methods such as getClasses( ) and getImports( ), which give the caller structured access to the contents of that source file.

Cookies and supports

As just mentioned, the convention for Cookies is that the interface be distinct from the implementation. This helps to enforce a clean separation between an interface that is to be exposed to client modules and the implementation of the functionality. The implementation might be in a class implementing the interface that exposes members that client code should not manipulate. The following are support classes that provide implementations of various Node.Cookies within NetBeans, which you may subclass or use “as is” in your own code:

CloneableEditorSupport or the more concrete DataEditorSupport

These classes can implement EditorCookie, OpenCookie, ViewCookie, EditCookie, LineCookie, CloseCookie, and PrintCookie, all representing functionality common to most text editing. SaveCookie is often implemented manually in conjunction with an editor support.

InstanceSupport

Implements InstanceCookie and InstanceCookie.Of, providing support for dereferencing a JavaBean that is being represented by the cookie holder.

ExecSupport

Implements ArgumentsCookie, DebuggerCookie, and ExecCookie to provide the ability to execute or debug a DataObject.

CompilerSupport

Implements CompilerCookie and has inner classes for supporting compile, clean, and build operations on DataObjects.

Execution

org.openide.execution.*

Support for executing data objects. Many types of data in an IDE can be executed in one way or another. The Execution API provides generic support for execution. This API supports the preparation of an environment in which a program will be run, running it, and managing any output from that process. NetBeans has Executors, configured instances of execution services that can be associated with individual DataObjects, such as those representing Java or C++ classes, in order to override project-wide defaults. They are invoked against those objects to run them. For example, the NetBeans Java support module contains two such execution services, one for starting an external JVM and one for loading classes within the same JVM in which NetBeans is running. Modules can install additional execution services, and users can create additional instances of a given class of execution service with varying arguments (such as creating a special execution service for running code in a Java 1.1 VM to test applets). Execution may be as simple as running a Java class, or as complex as deploying code to a remote application server and running it there.

Executor

A class that is able to launch an execution process via its execute(DataObject obj) method.

ProcessExecutor

Launches an external process including passing command-line arguments.

ThreadExecutor

Launches a process in a new thread in the VM in which NetBeans is running.

ScriptType

NetBeans can support a variety of scripting languages, such as Jython, via the Scripting module. This class provides basic support for executing a script.

ExecSupport

An implementation of ExecCookie and DebuggerCookie, which provide methods to execute and debug objects such as Java classes.

Java Hierarchy

org.openide.src.* and org.openide.src.nodes.*

The Java Hierarchy API provides classes that structurally represent Java sources and classes and the subcomponents that comprise them, such as methods, fields, inner classes, and so on, so they can be manipulated programmatically. The general structure is as follows:

SourceElement

The top-level container representing an entire source file, which can be retrieved via SourceCookie. It contains ClassElements.

ClassElement

Represents a Java class or interface. It contains MemberElements.

MemberElement

Comprises the appropriately named ClassElement (for named inner classes), MethodElement, FieldElement, or ConstructorElement.

Using the preceding classes, it is possible to programmatically explore and modify source files in NetBeans.

Element

A base class for structural elements of source files, such as fields, methods, classes, and so on.

InitializerElement

A class to represent an initializer section in a Java file, for example:

public class MyClass {
  static {
    doSomething( );
  }
}
Import

Represents an import statement in a source file.

Identifier

Represents an identifier, such as String or com.foo.MyClass.

Filesystems

org.openide.filesystems.*

The Filesystems API implements the concept of virtual filesystems and layered filesystems within NetBeans. It provides an abstraction on top of java.io.File that is not tied directly to files on disk; a file can be any data-containing entity that has a name and can live in a hierarchy. It is the concept of merged layers making up a single filesystem, or namespace, that allows modules to add functionality to NetBeans declaratively, without programmatic intervention.

The Filesystems API is divided into two sections—the public APIs and the service provider interface. A module written to the Filesystems SPI can add support to NetBeans for new ways of storing data. And all other code that references files through the Filesystems public API in all other modules will be able to use files transparently in this new data store without changes. To add support for a new storage medium, a module needs to include support for reading and writing data and for listening for changes on file objects within a data store. By implementing the FileSystem interface, such a module contracts with the rest of NetBeans that files in the store peculiar to that storage medium will be accessible via the interfaces defined in the Filesystems API. Without any modules, the NetBeans platform supports the following types of storage:

Local filesystems

A provider to allow mounting of local directories.

JAR/ZIP filesystems

Access to files within a JAR or ZIP archive.

XML filesystems

Access to “files” that are really entries in an XML file in a predefined format.

The standard distribution of the IDE also contains modules to support the following additional forms of data storage:

JavaCVS filesystems

Allows for access to a remote CVS (Concurrent Versioning System) repository, and all the common operations done in CVS. The implementation is written entirely in Java, using the JavaCVS library (http://javacvs.netbeans.org/library/index.html).

Generic VCS filesystems

This module supports access to a variety of version control systems by defining commands that launch that system’s utilities in a shell and parse the output of the external process.

Another interesting aspect of Filesystems is the concept of layered filesystems. One can create a MultiFileSystem that contains a set of other filesystems and presents them as a single filesystem with a common namespace. At the outset, this seems trivial, but taken in the context of being able to do the things listed here, you can begin to see the power of such a model:

  • Define a filesystem as the contents of an XML document.

  • Define Java objects within that filesystem by specifying a file whose name, contents, or attributes can be dereferenced to load a class and create an instance of that class via the default (no argument) JavaBean constructor.

  • Merge multiple filesystems into a single namespace, as if the entire contents of the filesystem lived in a single namespace.

If a filesystem can be an XML file, can specify a set of Java classes to instantiate on demand, and can be merged with other similar filesystems, you have an infrastructure by which components can be arbitrarily added to an infrastructure and cleanly removed without dependencies on initially loading Java classes or on the contents of the virtual filesystem remaining static. Many components in NetBeans (some wizards, dialog boxes, and menus) assemble themselves from a folder containing .instance files and other folders that define their contents.

Additionally, it is possible to use the Filesystems API as a standalone library—it does not depend on other parts of NetBeans to do its work. For more information about how to do this, see the Filesystems Library (http://openide.netbeans.org/fs/index.html) page. This is also true of a number of other APIs and subsystems, notably Nodes and Explorer. More information can be found on the NetBeans web site under the Platform tab.

FileSystem

The abstract base class that all filesystems (such as local, ZIP/JAR, CVS) extend. FileSystem is the interface module that code will use to interact with any filesystem.

FileObject

An abstraction for a file. FileObjects also support annotations, which allow, for example, a version control system to expose the information that a file is out of date. FileObjects can have attributes that go beyond the typical operating system file attributes. Arbitrary Java objects may be added to a FileObject as named attributes.

AbstractFileSystem

This is a base class that makes implementing a new filesystem much easier than if you subclass FileSystem directly. It contains a few simple interfaces that, when implemented, will take care of most of the work of interfacing to a new kind of storage system.

MIMEResolver

Files in NetBeans are identified by their MIME type. When you write a DataLoader to load a new file type, you may need to create and register a MIMEResolver. When NetBeans encounters a new file type, it queries the pool of available resolvers. If a resolver recognizes the file type, it will provide an appropriate DataLoader for the file. There is a declarative syntax for defining MIMEResolvers in XML, described in the Open APIs documentation and covered in this book in Chapter 21.

Modules, JARs, and Class Loaders

Like many Java applications that actually host a number of independently written and distributed components, NetBeans tries to insulate modules from one another to some extent using class loaders. This strategy permits modules to act in some ways like independent programs and helps to enforce dependencies between modules.

There are several levels of class loading used in NetBeans. Each has at least one class loader in it. Each level inherits from the previous levels so that a class (or resource) loadable from one level is also loadable from the following ones, as shown by Figure 15-12.

NetBeans class loaders (with three modules installed)

Figure 15-12. NetBeans class loaders (with three modules installed)

  1. The Java bootstrap class loader is created by the Java VM and is normally not touched by applications (NetBeans included). Assuming you are running the J2SDK needed for NetBeans, it typically loads from jre/lib/rt.jar (the JRE or Java Platform) as well as extensions in jre/lib/ext/*.jar.

    When NetBeans starts up, the full list of these JARs is printed in the log file, as of NetBeans 3.4. In NetBeans 3.3 or earlier, you can look at Tools Options Debugging and Executing Execution Types External Execution Expert Boot Classpath, and the other paths shown there may be useful to look at, too. Using the NetBeans APIs, you can also find the boot classpath using the call org.openide.execution.NbClassPath.createBootClassPath( ).

  2. The application class loader contains what is normally thought of as the “classpath.” ClassLoader.getSystemClassLoader( ) retrieves this loader. In NetBeans, it is used for NetBeans core and API platform classes loaded from lib/core.jar and lib/openide.jar; any core patches in lib/patches/*.jar (these come at the front of the classpath); and any other basic libraries, such as XML parsers found in lib/ext/*.jar. It will also include lib/tools.jar from the J2SDK, which contains code for tools such as the javac compiler.

    The NetBeans classpath is determined by its launch script. It normally does not include anything supplied by modules, only the platform, and except in special circumstances users should not try to modify this classpath. You can see your classpath in the log file using all versions of NetBeans. Programmatically, use NbClassPath.createClassPath( ).

  3. Each NetBeans module has its own dedicated class loader. This class loader always loads from the module JAR itself in modules/, of course. (Some modules reside in modules/autoload/*.jar or modules/eager/*.jar, but that is not important for this discussion.)

    It may also load from related JAR files. If a module manifest contains the attribute Class-Path, this points to other associated JAR files that the module owns. For example, the Java Source module (modules/java.jar) also uses a separate parser living in modules/ext/javac.jar and modules/ext/java-gj.jar. NetBeans opens these JARs and uses them automatically. Conventionally these live in the modules/ext/ directory. Sometimes modules/docs/ is also used to store online help archives. The Class-Path syntax is part of the JRE and is covered in the Extension Mechanism Architecture (http://java.sun.com/j2se/1.4/docs/guide/extensions/spec.html#bundled).

    Modules that have resources specific to several languages or national regions may also have some JAR files in modules/locale/ that are loaded by NetBeans automatically, too. Each module might also have some patches applied to it. Classes are loaded from patch JARs before the module JAR itself is searched. These can reside in modules/patches/ the-module-name/*.jar where the-module-name is the programmatic name of the module (determined by replacing the dashes in the filename with dots).[9]

    In the current NetBeans APIs, there is not a simple way to find the list of all JAR files used by a particular module’s class loader. The call org.netbeans.core.modules.Module.getAllJars( ) works but is in the core, not the public APIs, and so may not be used from module code. In the future, you will be able to see a module’s “classpath” in the GUI.

    Besides being able to load from the module and extension JAR files and from the bootstrap and application class loaders, a module class loader can load from other module class loaders. This is possible whenever the module declares a dependency on those other modules. For example, the beans module declares a dependency on the java module, because the NetBeans JavaBeans support makes heavy use of the Java source code support. That means that classes in modules/beans.jar can directly refer to classes in modules/java.jar, if that is necessary. The reverse is not true: classes in java.jar cannot refer to classes in beans.jar. Unlike conventional Java class loaders, then, NetBeans module class loaders can have more than one parent and delegate to all of them.

    Module class loaders are created when a module is turned on (perhaps during startup, perhaps later) and destroyed when it is turned off. If you mark the module as reloadable, you can turn off the module, change its JAR file, turn it back on, and immediately see the new code in action: there will be a fresh class loader for the new copy.

  4. The NetBeans system class loader is a special class loader accessible via the API call org.openide.TopManager.systemClassLoader( ). This class loader delegates to all module class loaders (as well as the bootstrap and application loaders). It does not add any JAR files: it exists only for delegation.

    It is often used as a default class loader in the APIs, whenever a class needs to be loaded by name but might be present in any module. Throughout this section, you will see many examples of settings and configuration files that give class names and create Java objects as a result; the NetBeans platform code looks in the system class loader to find these classes.

    The identity of the system class loader is left unchanged when modules are turned on, though as new modules are added, it becomes able to delegate to their class loaders dynamically. When modules are turned off, a fresh system class loader must be created, which will not delegate to the now-defunct module class loaders.

  5. Finally, the current class loader (accessible via TopManager.currentClassLoader( )) delegates to the system class loader and can also load classes or resources from the contents of all mounted filesystems (if they are marked by the user to be used during execution). See Chapter 2 for background.

    While this class loader is not used very heavily, it is handy for some purposes. For example, Java classes run via “internal execution” can be loaded from this class loader or one like it. (See Chapter 14 for an example.) JavaBeans components added to the Form Editor’s Component Palette are also loaded from this class loader. Its advantage is that a user can easily modify classes used in this class loader, and modules then get an easy way to let the user provide code to be run inside NetBeans.

    The current class loader is supposed to always reflect the “current” state of code in mounted filesystems. For example, if a user makes changes to a Java source inside NetBeans and recompiles it, and this class was loaded by the current class loader in the past, it will be reset if requested again: the old class loader is discarded and a fresh one created. This feature is automatic, which means that when using internal execution, you can edit code and try again without restarting NetBeans.

Refer to the preceding list whenever you are unsure where a class is coming from, why you seem to be using an “old” version of a class you changed recently, and so on. The Java method call Class.getClassLoader( ) can be invaluable during debugging, too.

Note

Reversed compile-time references are errors

If you are seeing mysterious NoClassDefFoundErrors coming from your module or customized NetBeans installation, usually this means you tried to make a compile-time reference going in the wrong direction: from a parent class loader to a child class loader. A typical mistake is placing a JAR in the application classpath (lib/ext/) and then expecting classes in it to be able to refer to classes in your module.

Overriding classes does not work

Another common mistake is thinking that classes in your module JAR will be able to override classes in a parent class loader. This will not work without some explicit class loader wizardry.

Splitting packages across JARs

The NetBeans module and system class loaders can have several parents; the order is arbitrary, and it is expected (for reasons of class loading speed) that a given Java package is never distributed across more than one class loader. So do not put part of a package in one module and use the same package name in another module that depends on it. If two independent modules use the same package name, a third module depending on both of them cannot safely load from this package; it could get either copy at random, since one class loader cannot load two versions of the same class.

Threading, Deadlocks, andHow to Avoid Them

The power of multithreaded programming bears with it a requirement for a degree of discipline on the part of the programmer. A common source of difficult-to-trace showstopper bugs is careless use of threads, resulting in deadlock. A deadlock is the situation in which two threads are waiting for each other to complete their work—in which case, each is paralyzed by the other.

In Java GUI applications, a deadlock in the event thread is easy to recognize: all windows turn solid gray and are unresponsive to input. While on occasion this just means that some computation running in the VM has spun out of control and is preventing anything else from running, in most cases, the problem is that two or more threads have attempted to hold the same synchronization locks in different orders, and now none of them can continue. Since NetBeans is a multithreaded application, a carelessly written module can produce such a situation.

The most important debugging technique for deadlocks is the thread dump, which lets you analyze what threads were doing at the time the deadlock occurred. To get a thread dump from NetBeans, it is best to have run it from the command line: on Unix, typing runide.sh at a shell prompt; on Windows, using runide.exe rather than runidew.exe. Now you can press Control- on Unix or Control-Break on Windows to see a thread dump.

Tip

On Windows it is useful to have already set the console window to have a large enough scroll-back buffer to contain a lengthy thread-dump.

On Unix, if you have run NetBeans in the background (for example, using a launcher icon under Gnome), find the Java process using ps and then signal it using kill -QUIT process-ID. You can then see the thread dump in the command’s output log, for example ~/.gnomerc-errors. JDK 1.3 gives usable information, but JDK 1.4 gives a richer dump including a list of locks held.

There are a number of scenarios in which deadlocks are likely in NetBeans:

  • Synchronization locks are acquired in a haphazard order. Make sure you ask for locks in a well-defined sequence in all your code.

  • Your event listener is called while an important operation, such as a file change, is in progress, and you try to do something complicated in the listener body. Try running your handling code later (asynchronously) so that the operation can complete before or while your code is run. org.openide.util.RequestProcessor is a good way to do this. When writing an event source that might fire changes to unknown listeners, carefully document what these listeners are permitted to do.

  • One task (for example, using RequestProcessor) waits for another task to finish before it can continue. If the other task was posted to the same RequestProcessor, you will deadlock! Any calls to Task.waitFinished( ) should be examined carefully. When in doubt, use private RequestProcessor instances for each kind of functionality. NetBeans 3.4 makes this easier, but you can do it in NetBeans 3.3, too.

  • You display a modal dialog box and wait for the result. Sometimes programmers do not even realize they are blocking until the dialog box closes. If the call is made from a sensitive thread, you could deadlock. Display a non-modal dialog box or make the call to show( ) from a fresh Thread. In NetBeans 3.4, use RequestProcessor.getDefault( ).post( ).

  • You try to do some kind of computation in code run inside the AWT/Swing event thread or other critical shared thread. If it takes too long, NetBeans may freeze. A common offender is XML parsing; be aware that parsing an XML document can make HTTP connections to resolve a DTD. While it may appear to work on your machine, if the user is not connected to the Internet, he or she will be very unhappy! Use an explicit EntityResolver to ensure that no network connections are made, or perform the parsing in another thread.

Detailed information on the threading models used in the NetBeans Open APIs is included in the API documentation. Choose the Threading Models link from the summary page of the APIs.

The Open APIs offer a powerful mechanism by which applications and extensions to the NetBeans IDE can be built. Having gone over them, in the next chapter, we will put them to use creating a simple module.



[6] MDI, or Multiple Document Interface, is a windowing style that originated on Microsoft Windows, in which a container window holds child windows belonging to a particular application. Support for this windowing style was added to NetBeans in the spring of 2000.

[7] An example of this is the HTTP Server module. Imagine NetBeans has been restarted, but a browser window is left open and aimed at some Javadoc being served by the previous run of NetBeans. The right behavior is for the user to be able to still use that browser window. If the HTTP server were not started when NetBeans starts, and the user tries to browse the documentation that’s already open, the result will be a 404 error—there will be no web server listening.

[8] This change precedes the move to fully declarative actions whose activation constraints are specified in the module XML layer, which do not require complementary action implementation classes.

[9] Patches for autoload and eager modules use a slightly different path: modules/autoload/patches/ the-module-name/*.jar or modules/eager/patches/ the-module-name/*.jar respectively. See Chapter 27 for details on these kinds of modules.

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

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