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.
Presenter
s 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.
The separation of APIs and their implementation in NetBeans can be rendered as follows:
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.
This is the implementation of the APIs, in the classes in
org.netbeans.core.*
.
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.
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.
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.
The APIs can be broken down into the following general categories:
The Services API and its Lookup subsystem is particularly concerned
with this, as are Cookie
s. 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.
The Window System, Actions, Wizards and Explorer APIs are all concerned with the actual presentation of functionality in a GUI.
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.
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.
The Datasystems API, and some APIs that use the Services API, such as Compilation, Execution, and Debugger and search functionality.
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.
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.
The module manifest is the first thing that NetBeans looks at when it loads a module. Module manifests contain the following information:
With both a code name and a display name.
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.
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.
This is important! If your module installs a library in
modules/ext, it needs to declare this dependency
or you will encounter NoClassDefFoundException
s.
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.
Provides localized names for attributes such as the display name of the module which can be specified in the manifest.
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, DataLoader
s, the classes
responsible for loading files of known data types, still must be
installed via the manifest, as are Node
s (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 SystemOption
s (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
Node
s. 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 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
DataObject
s 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.
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.
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.
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.
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.
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.
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.
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.
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, 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:
A service (whether extending ServiceType
or not)
must be configurable as a JavaBean. Its property management and user
presentation is done through introspection.
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.
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. DataObject
s 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.
org.openide.nodes.*
Defines the Node
interface.
Node
s
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. Node
s 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.
Node
s are used pervasively throughout the NetBeans
user interface as presentation objects (despite the fact that
Node
s 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.
Node
s 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 Node
s is the JavaBeans
specification, but differs in several respects:
Node
s 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( )
.
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.
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.
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.
AbstractNode
An implementation of the Node
interface that
provides functionality common to most Node
s, 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 Node
s with minimal coding.
Children
Node
s live in a hierarchy, so nodes can have, by
definition,
children—Node
s 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 Node
s
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.
org.openide.loaders.*
Used to manage the loading and
handling of DataObject
s;
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
Cookie
s.
DataLoader
Factories for DataObject
s. 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 DataObject
s.
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
.
org.openide.explorer.*
and subpackages
The Explorer package contains tools
for
visually representing hierarchies of Node
s in a
variety of ways, such as tree views, menus, and property sheets.
org.openide.explorer.view.*
Contains GUI components that can represent Node
s
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
ExplorerManager
s). 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 Node
s 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.
ChoiceView
A drop-down list component that displays an Explorer tree to some fixed depth, as shown in Figure 15-5.
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.
ListView
Displays the children of one Node
in a flat list,
without indentation, illustrated in Figure 15-7.
MenuView
This view presents a set of Node
s in a menu, with
submenus for nodes that have children (see Figure 15-8).
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.
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 Node
s 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.
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.Action
s
(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.
org.openide.options.*
The Options API is the API for
user-modifiable
configuration information and its storage. NetBeans has a set of
singleton SystemOption
s 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).
SystemOption
s 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.
SystemOption
s 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.
SystemOption
s are not nodes, but are presented via
nodes to the user. InstanceDataObject
s 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.
SystemOption
s 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
.
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.
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.
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:
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.
Support for discrete sets of windows organized by task.
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( )
.
Workspace
s contain Mode
s.
Mode
An interface representing a window containing GUI components.
Mode
s 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.
Mode
s contain one or more
TopComponent
s. 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).
WindowManager
This is the class that actually handles the opening and closing of
TopComponent
s and the docking behavior of
Mode
s 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
TopComponent
s 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( )
.
Mode
s, TopComponent
s, and
Workspace
s 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
Workspace
s, Mode
s, and their
contents.
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
Node
s and DataObject
s 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 Node
s, 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.
As just mentioned, the convention for Cookie
s 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.Cookie
s 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
DataObject
s.
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
Executor
s, configured instances of execution
services that can be associated with individual
DataObject
s, 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.
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
ClassElement
s.
ClassElement
Represents a Java class or interface. It contains
MemberElement
s.
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
.
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:
A provider to allow mounting of local directories.
Access to files within a JAR or ZIP archive.
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:
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).
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. FileObject
s also
support annotations, which allow, for example, a version control
system to expose the information that a file is out of date.
FileObject
s 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 MIMEResolver
s in
XML, described in the Open APIs documentation
and
covered in this
book in Chapter 21.
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.
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( )
.
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( )
.
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.
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.
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.
If you are seeing mysterious NoClassDefFoundError
s
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.
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.
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.
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.
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.
18.117.142.230