blob: 829ec2f029246f00a952b525759add4d8dd8ca07 [file] [log] [blame]
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
= NetBeans APIs in a Nutshell
:jbake-type: platform_tutorial
:jbake-tags: tutorials
:jbake-status: published
:syntax: true
:source-highlighter: pygments
:toc: left
:toc-title:
:icons: font
:experimental:
:description: NetBeans APIs in a Nutshell - Apache NetBeans
:keywords: Apache NetBeans Platform, Platform Tutorials, NetBeans APIs in a Nutshell
This overview will quickly familiarize you with how NetBeans modules interact with the NetBeans Platform and with each other. It is not intended as a comprehensive document—the link:http://bits.netbeans.org/dev/javadoc/index.html[NetBeans API List], the link:https://netbeans.apache.org/kb/docs/platform.html[NetBeans Platform Learning Trail], and the video series " link:https://netbeans.apache.org/tutorials/nbm-10-top-apis.html[Top 10 NetBeans APIs]" go into greater detail—but should serve as a guide to understanding the basic concepts of NetBeans module development.
A key thing to understanding the NetBeans Platform is to realize that very often the same API or infrastructure does double-duty—playing one role in dealing with the user's files on disk, and another role when it comes to configuration information and runtime data. For example:
* A link:http://bits.netbeans.org/dev/javadoc/org-openide-filesystems/org/openide/filesystems/FileSystem.html[FileSystem] represents the user's files, but the link:https://netbeans.apache.org/tutorials/nbm-glossary.html[System Filesystem] represents the IDE's configuration data.
* A link:http://bits.netbeans.org/dev/javadoc/org-openide-loaders/org/openide/loaders/DataObject.html[DataObject] represents the parsed content of a Java or other file, but DataObjects also are used to instantiate a Java object installed by a module.
* ` link:http://bits.netbeans.org/dev/javadoc/org-openide-util/org/openide/util/Lookup.html#getDefault()[Lookup.getDefault()]` is the way you access global services and singletons, but you also call ` link:http://bits.netbeans.org/dev/javadoc/org-openide-nodes/org/openide/nodes/Node.html#getLookup()[Node.getLookup()]` to find services specific to an individual file or object.
It is this reuse that has led some to say the NetBeans APIs are confusing, and it is the purpose of this overview to rapidly familiarize you with what these things are and how they are used in both roles.
== link:http://bits.netbeans.org/dev/javadoc/[FileSystems]
In NetBeans 3.x, adding items to the classpath was accomplished by "mounting" FileSystems - a FileSystem had a root directory and everything under it amounted to a virtual namespace in which files lived.
Since NetBeans 4.0, the "mounting" is gone, and FileSystems are not a concept that users are exposed to in the UI - but the infrastructure behind FileSystems - ` link:http://bits.netbeans.org/dev/javadoc/org-openide-filesystems/org/openide/filesystems/FileSystem.html[org.openide.filesystems.FileSystem]` is alive and well under the hood. In coding NetBeans modules, you will typically interact with instances of ` link:http://bits.netbeans.org/dev/javadoc/org-openide-filesystems/org/openide/filesystems/FileObject.html[org.openide.filesystems.FileObject]`, not ` link:https://docs.oracle.com/javase/1.5.0/docs/api/java/io/File.html[java.io.File]`.
=== Differences Between `java.io.File` and `FileObject`s
The main differences between them are as follows:
* You get FileObjects from a FileSystem, rather than create them with a constructor.
* Typically you don't have FileObjects which represent something that doesn't exist (as you can with `new File ("some/place/that/doesnt/exist")`).
* You can listen for changes on FileObjects, including listening on folders for changes that happen anywhere underneath them
* FileObjects don't necessarily represent actual files on disk
* FileObjects can have _attributes_ which are essentially key-value pairs that can be associated with a file. An attribute might be a string, or a serialized object (note that use of attributes on user files on disk is discouraged as of NetBeans 4.0, but they are still commonly used in configuration files).
* The path separator for FileObjects is always `/`, no conversions with File.separator are needed
=== [[What FileSystems Are Used For]]
FileSystems are used in two basic but very distinct ways in NetBeans. The first is representing the user's files on disk. To get a FileObject for some path in NetBeans, just call, e.g.
[source,java]
----
Repository.getDefault().getDefaultFilesystem().getRoot().getFileObject("path/to/some/File.txt");
----
The ` link:http://bits.netbeans.org/dev/javadoc/org-openide-filesystems/org/openide/filesystems/Repository.html[Repository]` is the master registry of all filesystems NetBeans knows about. In 4.0, it is the contents of the user's hard drive(s).
The second usage is to represent configuration data - this is the "System Filesystem", which is where modules can install their files. Folders in the System Filesystem act as "extension points" - there are some which have predefined meanings (for example, NetBeans' main menu is a tree of folders you will place special "files" into to add menu items); modules are free to create their own folders and do as they wish with the contents.
How does all this work? Well, once you have the concept of a virtualized `FileSystem` full of `FileObject`s, it's relatively easy to imagine a `FileSystem` which took several other `FileSystems` as arguments, and presented a merged view of the sub-filesystems as if all the data lived in one tree.
Add into this the notion that the "files" in a `FileSystem` don't actually have to be physical files on disk at all - anything that can be made to walk and talk like a file will do. So you could have an XML "filesystem" where the contents of files lived in an XML document, not a bunch of files on disk.
== XML Layers
That is what the NetBeans Platform does: Each module can define an XML "layer" file, which contains some virtual "files" and folders that are merged into the System Filesystem. In this way modules add their configuration data to the system. And because the System Filesystem is composed from discrete XML fragments from modules, when a module is disabled or unloaded, its XML layer is simply removed. `FileObject`s for the various folders that had files removed from them fire changes indicating some files were deleted, so the UI can get rid of any objects that represented the now-unloaded module's files. This is why you can uninstall and reload modules at runtime.
In its jar manifest, a module will contain a line such as:
[source,java]
----
OpenIDE-Module-Layer: org/netbeans/modules/mymodule/layer.xml
----
This is a pointer to an XML file inside the module jar (meaning that you simply create this file somewhere in your sources so it will be compiled into the jar when your module is built). In its simplest form, that could contain something like:
[source,xml]
----
<filesystem>
<folder name="myFolder">
<file name="myFile.txt" url="resources/aTextFile.txt"/>
</folder>
</filesystem>
----
The `url` attribute is important: It says where the contents of `myFile.txt` lives in the module's jar file. This path is relative to the location of the layer file. So, if the layer file is `org/netbeans/modules/mymodule/layer.xml`, then in the module jar there should also be a text file `org/netbeans/modules/mymodule/resources/aTextFile.txt`. When some code requests an `InputStream` for `myFolder/myFile.txt`, that text file in the module jar is what will actually be read.
Of course, this particular fragment doesn't do much of anything, but it is useful to illustrate what can be done here. Since `myFolder` has no predefined purpose to NetBeans, it is up to the module defining that folder to do something with its contents. But one could imagine a module that provided `myFolder`, let other modules add more files to that folder, and provided one menu item for each file, letting the user view them.
Accessing this file programmatically is quite simple:
[source,java]
----
FileObject myFile = Repository.getDefault().getDefaultFileSystem().findResource ("myFolder/myFile.txt");
InputStream in = myFile.getInputStream();
//...do something with it
----
=== Providing Java Objects through Module Layers
Just being able to install text files isn't terribly interesting. Where the system of layers gets its power is in the ability to make files act as factories for Java objects. This is made possible using the same infrastructure that recognizes user data on disk, which will be discussed in more detail in <<loaders,the section on Loaders>>. Effectively, there is a specific file-extension registered in the system, `.instance` which identifies a file that actually represents a Java object and can create the actual object.
[source,xml]
----
<filesystem>
<folder name="Menu">
<folder name="File">
<file name="org-netbeans-modules-mymodule-MyAction.instance"/>
</folder>
</folder>
</filesystem>
----
The above module layer actually adds a Swing Action (implemented by the class `org.netbeans.modules.mymodule.MyAction`) into the File menu on the main menu bar in NetBeans. The NetBeans core defines the folder `Menu`; the `core/ui` defines common menus that are in NetBeans, and provides the infrastructure that listens on these folders and keeps the GUI up-to-date if things are added or removed. Toolbars work in a similar fashion, as do many other things in NetBeans.
=== Hiding Files in the System Filesystem
The System Filesystem also allows one module to remove what another module adds. The semantics are extremely simple - for example, if you wanted to delete the File menu in NetBeans when your module is enabled, simply put the following into your module layer:
[source,xml]
----
<filesystem>
<folder name="Menu">
<folder name="File_hidden"/>
</folder>
</filesystem>
----
To make this work, modules can, in their manifest, request to be installed only after another module is installed - thus there is a defined stacking order to module layers.
=== The System Filesystem is Read-Write
If it were all just static XML fragments, it wouldn't be possible to actually store configuration changes the user has made - but of course, this is possible. Recall that we have the notion of a filesystem composed of merging multiple other filesystems - and that we know that we have an implementation of `FileSystem` over actual files on disk, which is how a user's data files are accessed.
The top layer to the system filesystem is the `config/` subdirectory of the user's settings directory - typically this lives in the user's home directory under the directory `.netbeans`. So when a user makes changes (like rearranging menu items), the diff of the changes is written to disk in the settings directory; since this layer lives at the top of the stack, whatever changes are there (such as hiding files, as discussed above), override anything a module has in its layer file.
== [[DataLoaders and DataObjects]]
` link:http://bits.netbeans.org/dev/javadoc/org-openide-loaders/org/openide/loaders/DataObject.html[DataObject]` link:http://bits.netbeans.org/dev/javadoc/org-openide-loaders/org/openide/loaders/DataObject.html[s] are wrappers for `FileObjects`. A FileObject simply represents a file-like entity; `DataObject`s are the level at which the system understands what the contents of a file are. So a module that implements handling for a particular file type provides its own subclass of `DataObject` and a factory which can create an instance of that DataObject type when it is passed a `FileObject`. `DataObjects` are what provide programmatic access to the contents of a file - such as parsing a file and providing a model for its content.
The factory for these objects, which a module installs, is called a ` link:http://bits.netbeans.org/dev/javadoc/org-openide-loaders/org/openide/loaders/DataLoader.html[DataLoader]`. It is declared directly in the module's manifest:
[source,java]
----
Name: org/netbeans/modules/povray/PovDataLoader.class
OpenIDE-Module-Class: Loader
----
An example of how to write a DataLoader can be found in the link:nbm-filetype.html[NetBeans DataLoader Module Tutorial]. DataLoaders typically register themselves to support specific file extensions or mime types.
Unless you are writing support for a language or file-type, typically you will be using, not creating, `DataObject`s. Getting the DataObject for a file is simple: Just call ` link:http://bits.netbeans.org/dev/javadoc/org-openide-loaders/org/openide/loaders/DataObject.html#find(org.openide.filesystems.FileObject)[DataObject.find(someFileObject)]`.
=== [[Using `DataObject`s]]
`DataObject`s don't do a lot in and of themselves - that is, it is almost always a mistake to be casting a DataObject as a particular subclass. The way to do most interesting interaction with DataObjects is via the method ` link:http://bits.netbeans.org/dev/javadoc/org-openide-loaders/org/openide/loaders/DataObject.html#getCookie(java.lang.Class)[getCookie()]`. The pattern, which we will see in more detail in the section on <<lookup,`Lookup`>> is:
[source,java]
----
OpenCookie open = someDataObject.getCookie (OpenCookie.class);
open.open();
----
The above code will actually open a file in the editor. The key here is that, rather than providing programmatic access to a file's content as a bunch of instance methods on itself (which would quickly lead to a tangled mess of inheritance issues), you _ask_ a `DataObject` for an instance of some known interface that does what you need. This is accomplished by passing a `Class` object to `getCookie()`, which will return that object if possible, or `null` if not.
As another example, determining if an opened file has unsaved changes is as simple as:
[source,java]
----
boolean needsSaving = someDataObject.getCookie (SaveCookie.class) != null;
----
Modules can provide their own public interfaces, and make instances of those objects available via `getCookie()`. So, for example, a `DataObject` for an XML file might make a DOM tree or some other structural representation of the file available via `getCookie()` for other modules to use to manipulate the file's contents. Some common interfaces modules will typically use via `getCookie()` can be found in the package ` link:http://bits.netbeans.org/dev/javadoc/org-openide-nodes/org/openide/cookies/package-summary.html[org.openide.cookies]`.
Note that the term "cookie" in this context has nothing to do with the web browser concept of cookies.
=== Putting it Together: Why `.instance` Files Work
To illustrate the power of loaders and `DataObjects`, recall that loaders are registered against a file type. And recall that modules can install actual Java objects via `.instance` files. What's going on here?
What is actually happening is that the very same infrastructure (`DataLoader`s) that lets NetBeans recognize a user's `.java` file on disk and create an appropriate `DataObject` is what recognizes `.instance` files - after all, the System Filesystem is a filesystem too. There is simply a `DataLoader` registered in the system that claims all files with the `.instance` extension.
Under the hood, what's really happening is that the `DataObject` for a `.instance` file provides an ` link:http://bits.netbeans.org/dev/javadoc/org-openide-nodes/org/openide/cookies/InstanceCookie.html[InstanceCookie]`. So to get the actual object in question manually, you would do something like this:
[source,java]
----
FileObject file = Repository.getDefault().getDefaultFileSystem().findResource (
"someFolder/com-foo-mymodule-MyClass.instance");
DataObject dob = DataObject.find (file);
InstanceCookie cookie = (InstanceCookie) dob.getCookie (InstanceCookie.class);
MyClass theInstance = (MyClass) cookie.instanceCreate();
----
== Nodes: The Presentation Layer
You've probably noticed that there are quite a few tree components in NetBeans - the Files and Projects tabs, and others. The link:http://bits.netbeans.org/dev/javadoc/org-openide-nodes/org/openide/nodes/doc-files/api.html[Nodes API] is what provides the contents to those trees. Think of `DataObject`s as being the data model; a Node is where interacting with the user comes in.
A ` link:http://bits.netbeans.org/dev/javadoc/org-openide-nodes/org/openide/nodes/Node.html[Node]` provides human-visible things like an icon and a (possibly localized) display name to DataObjects. And a Node provides a list of ` link:http://bits.netbeans.org/dev/javadoc/org-openide-awt/org/openide/awt/Actions.html[Actions]` that can appear in a popup menu for that node.
`Node`s define _context_ for NetBeans - at any given moment, there is usually one or more _activated nodes_ which determine what menu and toolbar actions are enabled - they are the clue to the rest of the system as to what the user is doing. Each UI component (such as the Files tab or the Editor) provides an array of `Node`s which are activated - selected. In a tree component, it is rather obvious how this works; but even when editing in the editor, the activated node triggers what actions are enabled, depending on where the caret is - if the caret is inside the body of a method, the activated node is actually the same node you would find if you expanded the structure tree of that java class in the Projects tab.
So, to get the `Node` corresponding to a `DataObject`, simply call `someDataObject. link:http://bits.netbeans.org/dev/javadoc/org-openide-loaders/org/openide/loaders/DataObject.html#getNodeDelegate()[getNodeDelegate()]`.
=== Nodes, DataObjects and lookup Patterns
`Node`s use the same pattern as `DataObject` - they have a `getCookie()` method that can be used as described above. `Node`s that represent `DataObject`s will typically delegate to their `DataObject`'s `getCookie()` method.
With `Node`, it is common to see a second form of this call: `Node. link:http://bits.netbeans.org/dev/javadoc/org-openide-nodes/org/openide/nodes/Node.html#getLookup()[getLookup()]. link:http://bits.netbeans.org/dev/javadoc/org-openide-util/org/openide/util/Lookup.html#lookup(java.lang.Class)[lookup (SomeClass.class)]`. The latter is a newer idiom, which will eventually replace `getCookie()` in both `Node`s and `DataObject`s. The specific reason is that `getCookie()` requires that the returned object implement an empty marker interface, `Node.Cookie`, which unnecessarily limits what can be returned by `getCookie()`. The only thing you need to remember is that the two are functionally equivalent, and in new code, use `getLookup().lookup()` where possible. There is further discussion of what `Lookup` is <<lookup,below>>.
Note that all `Node`s do not represent `DataObject`s - the Nodes API is useful in and of itself for creating tree like hierarchies.
There are a number of UI components that can represent a tree of nodes as trees, combo boxes, lists, etc. - so typically when one needs to display a UI with a list or tree in it, the natural choice is to use the Nodes API, and simply create the appropriate component and set the root node appropriately.
A key thing to remember is that Nodes are intended as a presentation layer for an underlying data model (which might be files on disk, or whatever you want). If you find you're putting a lot of logic into your `Node` subclass, consider that your model is what needs enhancing - `Node`s should be lightweight and simple, and the model should do the heavy lifting.
== Lookup
` link:http://bits.netbeans.org/dev/javadoc/org-openide-util/org/openide/util/Lookup.html[org.openide.util.Lookup]` is NetBeans' form of link:http://www.martinfowler.com/articles/injection.html[dependency injection]. As with `DataObject`s and `FileObject`s, it has two common usages:
* _Local lookup_ - asking an object for an instance of some interface, as we saw above with `Node.getLookup().lookup (SomeClass.class)`
* _Global lookup_ - services - often singleton instances of some class - can be registered into the _default lookup_.
=== The Default Lookup
The default lookup is an instance of `Lookup` returned by calling `Lookup.getDefault()`. The OpenAPIs define a number of abstract service classes which allow you to get an instance of some object that is of general use - for example, ` link:http://bits.netbeans.org/dev/javadoc/org-openide-util/org/openide/ErrorManager.html[org.openide.ErrorManager]`, used to log errors and exceptions, or ` link:http://bits.netbeans.org/dev/javadoc/org-openide-dialogs/org/openide/DialogDisplayer.html[org.openide.DialogDisplayer]`, which displays dialogs to the user. These are typically things that there only needs to be one of in the system, so they are effectively singleton objects. To get an instance of `ErrorManager`, you could do as follows:
[source,java]
----
ErrorManager err = (ErrorManager) Lookup.getDefault().lookup (ErrorManager.class);
err.log ("log message");
----
In practice this code is a little clunky to ask people to write all the time, so most such abstract classes will have their own method `getDefault()` implemented as:
[source,java]
----
public abstract class MyService {
public static MyService getDefault() {
MyService result = (MyService) Lookup.getDefault().lookup (MyService.class);
if (result == null) {
result = new TrivialImplementationOfMyService();
}
return result;
}
public abstract void doSomething (...);
}
----
Modules can register their own objects into the default lookup in one of two ways - via the Java link:https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Provider%20Configuration%20File[ provider extension mechanism] - putting a file into the `META-INF/services` directory of their module jar, or by putting a `.instance` file in the `Services` folder of the System Filesystem.
The preferred mechanism is the provider extension mechanism, and doing this is extremely simple: To provide your own implementation of ErrorManager, for example, simply create two folders under the `src/` folder of your module: `META-INF/services`. In the `services/` folder, put a file called `org.openide.ErrorManager`. That file will contain one line of text - the name of the class in your module that should be used - e.g. `com.mymodule.MyLog4JErrorManager`.
While we won't go into this in detail here, it is also possible to register multiple instances of an interface into the default lookup, retrieve all of them and even listen for changes on the result of that query.
A very thorough discussion of `Lookup` can be found link:http://openide.netbeans.org/lookup/[here].
== Summary
The salient points to remember are:
* FileObjects wrap files (and sometimes other things)
* DataObjects wrap FileObjects and understand what's in a file
* You typically don't call methods on a DataObject, you ask it for objects via `getCookie()`
* Configuration information is just another filesystem you can get DataObjects out of
* Nodes wrap DataObjects and provide human-displayable information - actions, icons, names
* Nodes are a presentation layer, not the place to put lots of logic
* Lookup is how you get globally registered services
* Lookup is also how you ask individual objects (Nodes, DataObjects, Projects) for the objects that do real work
== Interconverting between Files, DataObjects, FileObjects and Nodes
Very often you may be integrating an external tool that wants to be passed instances of `java.io.File`; also there are many cases where you need to interconvert between the various types NetBeans offers which in some way or other represent files. Here are the typical ways to interconvert between all of the above:
[source,java]
----
Find a file on disk
FileObject f = Repository.getDefault().getDefaultFilesystem().getRoot().getFileObject("some/folder/someFile.txt");
or if something passes you a File...
FileObject f = FileUtil.toFileObject (new File("some/folder/someFile.txt"))
Turn a FileObject into a File (may fail for virtual filesystems)
File f = FileUtil.toFile (someFileObject)
Get the DataObject for a FileObject
DataObject obj = DataObject.find (someFileObject)
Get the FileObject a DataObject represents
FileObject file = someDataObject.getPrimaryFile()
Get the Node that represents a FileObject
Node n = someDataObject.getNodeDelegate()
Get the DataObject a Node represents (if any)
DataObject obj = (DataObject) someNode.getLookup().lookup(DataObject.class)
----
== Other Things Worth Mentioning...
Below we go through two other critical pieces of NetBeans APIs which complete the basic picture of things modules typically interact with; they don't have the type of dual-use issues that the previous topics do, but are included for completeness.
=== Explorer Views
`Node`s provide a hierarchy of objects; the Explorer API provides Swing UI components that display a `Node` and its children. There are a large variety of Explorer view classes which can variously represent a hierarchy of `Node`s as a `JList`, a `JMenu`, a `JComboBox`, a `JTree`, a JTable and more. Typically when you want to display some hierarchical data structure in NetBeans, you locate or implement the appropriate Node, create an appropriate Explorer component for it, and set the Explorer view's root node to be the node you want to display.
In older versions of NetBeans, the place where the Files and Projects tabs live was a separate window with the title "Explorer" - you will see the phrase "open in the Explorer" in older documentation.
=== The Window System
The API of the Window System is found in ` link:http://bits.netbeans.org/dev/javadoc/org-openide-windows/org/openide/windows/package-summary.html[org.openide.windows]`. A basic overview is that in NetBeans, you don't deal with `JFrame`s or `JDialog`s - rather, you supply components which are displayed, and NetBeans window management system decides where and how they appear in terms of top-level frames. The main thing to know is that all components in NetBeans are subclasses or usages of ` link:http://bits.netbeans.org/dev/javadoc/org-openide-windows/org/openide/windows/TopComponent.html[org.openide.windows.TopComponent]`. `TopComponent` has relatively self-explanatory methods such as ` link:http://bits.netbeans.org/dev/javadoc/org-openide-windows/org/openide/windows/TopComponent.html#open()[open()]` and ` link:http://bits.netbeans.org/dev/javadoc/org-openide-windows/org/openide/windows/TopComponent.html#requestActive()[requestActive()]`. `TopComponent`s live in _docking modes_ (the somewhat confusingly named ` link:http://bits.netbeans.org/dev/javadoc/org-openide-windows/org/openide/windows/Mode.html[org.openide.windows.Mode]`). A `Mode` is a container for multiple `TopComponents` - a thing that has Tabs. `Mode` itself is not a GUI component, it is an abstract class that acts as a controller.
`TopComponents` can be instantiated and opened on the fly, but typically a module installs its UI components via several XML files inside its jar file and pointers to those files in the module's XML layer file. Fairly comprehensive examples of usage can be found in the NetBeans source base in `platform/samples/window-system-*/`.
== When You're Wondering Where Something is Implemented
Sometimes you just want to go read the code - but it's a jungle of jars out there. Here are some of the things people often want to track down - the locations are the actual directories in a checkout of NetBeans sources:
* *Where are the standard menus defined?* - core/ui
* *Where is dialog and windowing handled?* - core/windows
* *Where is the tab control NetBeans uses for tabs?* - core/swing/tabcontrol
* *What sets the fonts for NetBeans?* - core/swing/plaf
link:http://netbeans.apache.org/community/mailing-lists.html[Send Us Your Feedback]