File Type Integration Tutorial
This tutorial needs a review. You can edit it in GitHub following these contribution guidelines. |
This tutorial shows you how to write a module that lets the IDE, or any other application built on the NetBeans Platform, recognize a new file type.
For troubleshooting purposes, you are welcome to download the completed tutorial source code.
Introduction to File Type Integration
File types that are recognized in the IDE have their own icons, menu items, and behavior. The "files" being shown are FileObjects
—wrappers around java.io.File
or, in the case of configuration files, typically wrappers around data stored in some other way, such as inside XML files in modules. What you actually see are Nodes
, which provide functionality like actions and localized names to objects like files. In between Nodes
and FileObjects
are DataObjects
. A DataObject
is like a FileObject
, except that it knows what kind of file is being shown, and there are usually different types of DataObject
for files with different extensions and XML files with different namespaces. Each DataObject
is provided by a different module, each implementing support for one or more file types—for example, the Image module makes it possible to recognize and open .gif
and .png
files.
A module that recognizes a file type installs a DataLoader
—a factory for a file-type-specific DataObject
. When a folder is expanded, the IDE asks each known DataLoader
, "Do you know what this is?" The first one that says "Yes" creates the DataObject
for the file. In order to actually display something for each file, the system calls DataObject.getNodeDelegate()
for each DataObject
and the Nodes
are what you actually see in the IDE.
Below, the diagram on the left shows what each item mentioned above makes available:
In this tutorial, you create a module that installs a DataLoader
for imaginary "Abc" files ( .abc
file extension). By default, a file with the "abc" extension is treated as any other file that the IDE does not recognize—it is treated as a text file and, as a result, the IDE provides the same functionality for Abc files as it does for text files. Once you have created the module, you will be shown how to enhance it with functionality that will be available to Abc files only. When you complete the development cycle, you can easily let others make use of your module—the IDE lets you create a binary that you can share with others, who can then install it through the Plugin Manager of their application.
Creating the Module Project
In this section, we use a wizard to create the source structure that every NetBeans module requires. The source structure consists of certain folders in specific places and a set of files that are always needed. For example, every NetBeans module requires a nbproject
folder, which holds the project’s metadata, such as a build.xml
file with Ant targets for building and running the module.
-
Choose File > New Project (Ctrl-Shift-N). Under Categories, select NetBeans Modules. Under Projects, select Module. Click Next.
1.
In the Name and Location panel, type AbcFileType
in Project Name. Change the Project Location to any directory on your computer. Click Next.
-
In the Basic Module Configuration panel, type
org.myorg.abcfiletype
as the Code Name Base. Click Finish.
The IDE creates the AbcFileType
project. The project contains all of your sources and project metadata, such as the project’s Ant build script. The project opens in the IDE. You can view its logical structure in the Projects window (Ctrl-1) and its file structure in the Files window (Ctrl-2).
Recognizing Abc Files
In this section, we use the New File Type wizard to create the classes and other files necessary for recognizing Abc files as being distinct from all other files.
-
Right-click the project node and choose New > Other > Module Development > File Type:
-
In the File Recognition panel, do the following:
-
In the MIME Type field, type
text/x-abc
. -
In the Filename Extension field, type
abc ABC
.
-
The File Recognition panel should now look as follows:
Note the following about the fields in the File Recognition panel:
-
MIME Type. Specifies the data object’s unique MIME type.
-
by
-
Filename Extension. Specifies one or more file extensions that the IDE will recognize as belonging to the specified MIME type. Separators are commas, spaces, or both. Therefore, all of the following are valid:
-
abc,def
-
abc def
-
abc def
-
abc, def ghi, wow
Let’s imagine that Abc files can be case-sensitive. For this reason, you specify two MIME types in this tutorial— abc
and ABC
.
-
XML Root Element. Specifies a unique namespace that distinguishes the XML file type from all other XML file types. Since many XML files have the same extension (
xml
), the IDE distinguishes between XML files via their XML root elements. More specifically, the IDE can distinguish between namespaces and the first XML element in XML files. You can use this to, for example, distinguish between a JBoss deployment descriptor and a WebLogic deployment descriptor. Once you have made this distinction, you can ensure that menu items added to the JBoss deployment descriptor’s contextual menu are not available to the WebLogic deployment descriptor. For an example, see the NetBeans Component Palette Module Tutorial.
Click Next.
-
In the Name, Icon and Location panel, type
Abc
as the Class Name Prefix and browse to any 16x16 pixel image file as the new file type’s icon, as shown below:
Note: You can use any icon of a 16x16 pixel dimension. If you like, you can right-click on this one and save it locally, and then specify it in the wizard step above:
-
Click Finish.
The Projects window should now look as follows:
Each of the newly generated files is briefly introduced:
-
AbcDataObject.java. Wraps a
FileObject
. DataObjects are produced by DataLoaders. For more information, see What is a DataObject?. -
AbcTemplate.abc. Provides the basis of a file template that is registered in the
layer.xml
via an annotation defined in thepackage-info.java
file, such that it will be installed in the New File dialog as a new template. -
AbcVisualElement.java. Sample visual tab in multiview editor.
Installing and Trying Out the Functionality
Let’s now install the module and then use the basic functionality we’ve created so far.
-
In the Projects window, right-click the
AbcFileType
project and choose Run. A new instance of the IDE starts, installing your module into itself.
-
Choose File | New Project and create a new Java application.
-
Once you have a new application, go to the New File dialog and you will see your new file template:
Click Next, choose a folder to store the template, and click Finish.
-
Open the file and you see a text editor:
Click the "Visual" tab and notice that you have a starting point for creating a visual page in a multiview editor:
Creating Features for Abc Files
Now that the NetBeans Platform is able to distinguish Abc files from all other types of files, it is time to add features specifically for these types of files. In this section, we add a menu item on the right-click contextual menu of the file’s node in the explorer windows, such as in the Projects window, and we enable the file to open into a window, instead of into an editor.
Adding a Context-Sensitive Action
In this subsection, we use the New Action wizard to create a Java class that will perform an action for our file type.
-
Right-click the
org.myorg.abcfiletype
package and choose New > Action.
1.
In the Action Type panel, click Conditionally Enabled. Type AbcDataObject
, which is the fully qualified name of the data object generated above by the New File Type wizard, as shown below:
Click Next.
-
In the GUI Registration panel, select the 'File' category in the Category drop-down list. The Category drop-down list controls where an action is shown in the Keyboard Shortcuts editor in the IDE.
Next, Unselect Global Menu Item and then select File Type Contect Menu Item. In the Content Type drop-down list, select the MIME type you specified above in the New File Type wizard, as shown below:
Notice that you can set the position of the menu item and that you can separate the menu item from the item before it and after it. Click Next.
-
In the Name and Location panel, type
StartAnalyzerActionListener
as the Class Name and typeStart Analyzer
as the Display Name. Optionally, provide an icon to be displayed.
Click Finish and StartAnalyzerActionListener.java
is added to the org.myorg.abcfiletype
package.
package org.myorg.abcfiletype;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionRegistration;
import org.openide.util.NbBundle.Messages;
@ActionID(
category = "File",
id = "org.myorg.abcfiletype.StartAnalyzerActionListener")
@ActionRegistration(
displayName = "#CTL_StartAnalyzerActionListener")
@ActionReference(path = "Loaders/text/x-abc/Actions", position = 0)
@Messages("CTL_StartAnalyzerActionListener=Start Analyzer")
public final class StartAnalyzerActionListener implements ActionListener {
private final AbcDataObject context;
public StartAnalyzerActionListener(AbcDataObject context) {
this.context = context;
}
@Override
public void actionPerformed(ActionEvent ev) {
// TODO use context
}
}
-
In the Source Editor, add some code to the action’s
actionPerformed
method:
@Override
public void actionPerformed(ActionEvent ev) {
FileObject f = context.getPrimaryFile();
String displayName = FileUtil.getFileDisplayName(f);
String msg = "I am " + displayName + ". Hear me roar!";
NotifyDescriptor nd = new NotifyDescriptor.Message(msg);
DialogDisplayer.getDefault().notify(nd);
}
Press Ctrl-Shift-I. The IDE automatically adds import statements to the top of the class.
-
Run the module again, as you did in the previous section.
1. Create an Abc file, using the template shown in the previous section, and right-click the file’s node in one of the explorer views, such as in the Projects window or Favorites window.
-
Choose the new menu item, the Abc file’s name and location are shown:
You now know how to create a new context-sensitive action that appears in the context menu of a file of the given type, in the Projects window, Files window or the Favorites window.
Creating Additional Multiview Windows
Let’s create a new multiview window. The first tab of a multiview window is typically used to display the source view of the file, while the second and subsequent tabs typically show various visual views. More than two tabs can also be provided, each tab providing further levels of detail about the opened file.
-
For each tab that you want to create in the multiview window, create a class that implements JPanel and MultiViewElement. For purposes of this tutorial, start by creating a class called
AbcVisualElement2
, implementing the specified classes:
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JToolBar;
import org.netbeans.core.spi.multiview.CloseOperationState;
import org.netbeans.core.spi.multiview.MultiViewElement;
import org.netbeans.core.spi.multiview.MultiViewElementCallback;
import org.openide.awt.UndoRedo;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.windows.TopComponent;
@MultiViewElement.Registration(displayName = "#LBL_Abc_VISUAL2",
iconBase = "org/myorg/abcfiletype/Datasource.gif",
mimeType = "text/x-abc",
persistenceType = TopComponent.PERSISTENCE_NEVER,
preferredID = "AbcVisual2",
position = 3000)
@NbBundle.Messages({
"LBL_Abc_VISUAL2=Visual2"
})
public class AbcVisualElement2 extends JPanel implements MultiViewElement {
private AbcDataObject obj;
private JToolBar toolbar = new JToolBar();
private transient MultiViewElementCallback callback;
public AbcVisualElement2(Lookup lkp) {
obj = lkp.lookup(AbcDataObject.class);
assert obj != null;
}
@Override
public String getName() {
return "AbcVisualElement2";
}
@Override
public JComponent getVisualRepresentation() {
return this;
}
@Override
public JComponent getToolbarRepresentation() {
return toolbar;
}
@Override
public Action[] getActions() {
return new Action[0];
}
@Override
public Lookup getLookup() {
return obj.getLookup();
}
@Override
public void componentOpened() {
}
@Override
public void componentClosed() {
}
@Override
public void componentShowing() {
}
@Override
public void componentHidden() {
}
@Override
public void componentActivated() {
}
@Override
public void componentDeactivated() {
}
@Override
public UndoRedo getUndoRedo() {
return UndoRedo.NONE;
}
@Override
public void setMultiViewCallback(MultiViewElementCallback callback) {
this.callback = callback;
}
@Override
public CloseOperationState canCloseElement() {
return CloseOperationState.STATE_OK;
}
}
-
Install and open the file again. Now you have a multiview window with an additional visual tab.
You now have two visual tabs in a multiview window. For each additional tab, create a new class just like the above.
Parsing the File
A DataObject
is like a FileObject
, except that it knows what kind of file is being shown. The "New File Type" wizard created a DataObject
for our file type, so let’s now use it to parse the underlying file and expose its content as new nodes in the explorer views, e.g., the Projects window, Files window, and the Favorites window.
For background to this section and complete details on support for nodes on the NetBeans Platform, see NetBeans Nodes API Tutorial.
-
Open the
AbcDataObject
class and add this method:
@Override
protected Node createNodeDelegate() {
return new DataNode(this, Children.LEAF, getLookup());
}
The method above provides a default Node for the underlying file. The default Node has no child nodes, which is evident by the "Children.LEAF" parameter that you see above.
Instead of passing in "Children.LEAF", let’s now use the ChildFactory
class to create new child nodes of our Node class:
@Override
protected Node createNodeDelegate() {
return new DataNode(
this,
*Children.create(new AbcChildFactory(this), true),*
getLookup());
}
-
Define the
ChildFactory
as an inner class, as follows:
private static class AbcChildFactory extends ChildFactory<String> {
private final AbcDataObject dObj;
public AbcChildFactory(AbcDataObject dObj) {
this.dObj = dObj;
}
@Override
protected boolean createKeys(List list) {
FileObject fObj = dObj.getPrimaryFile();
try {
List<String> dObjContent = fObj.asLines();
list.addAll(dObjContent);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
return true;
}
@Override
protected Node createNodeForKey(String key) {
Node childNode = new AbstractNode(Children.LEAF);
childNode.setDisplayName(key);
return childNode;
}
}
-
In your module sources, open
AbcTemplate.abc
and enter the following text, or something like it, i.e., add several lines of text to your template file:
hello
world
how are things
today
-
Run the module again, create an Abc file from the template again, and then notice that you can expand the generated file:
Extending the Properties Window
Our Node now has child Nodes. In this section, we also assign properties to our Node. The properties are displayed in the Properties window.
For background to this section and complete details on support for properties on the NetBeans Platform, see NetBeans Property Editor Tutorial.
By default, the following properties are shown in the Properties window for our new file:
We will now change the default properties to show a custom property instead.
-
Open the
AbcDataObject
class and change thecreateNodeDelegate
method so that our ownAbcNode
will be created instead of the genericDataNode
:
@Override
protected Node createNodeDelegate() {
return new *AbcNode*(
this,
Children.create(new AbcChildFactory(this), true),
getLookup());
}
The AbcNode
does not exist yet, you will create it in the next step.
-
Define the
AbcNode
as an inner class, as follows:
class AbcNode extends DataNode {
public AbcNode(AbcDataObject aThis, Children kids, Lookup lookup) {
super(aThis, kids, lookup);
}
@Override
protected Sheet createSheet() {
Sheet sheet = super.createSheet();
Sheet.Set set = Sheet.createPropertiesSet();
sheet.put(set);
set.put(new LineCountProperty(this));
return sheet;
}
private class LineCountProperty extends ReadOnly<Integer> {
private final AbcNode node;
public LineCountProperty(AbcNode node) {
super("lineCount", Integer.class, "Line Count", "Number of Lines");
this.node = node;
}
@Override
public Integer getValue() throws IllegalAccessException, InvocationTargetException {
int lineCount = 0;
DataObject abcDobj = node.getDataObject();
FileObject abcFo = abcDobj.getPrimaryFile();
try {
lineCount = abcFo.asLines().size();
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
return lineCount;
}
}
}
-
Run the module again, open the Properties window, and notice your property is displayed:
Creating Synchronized Views
Let’s now illustrate via a small example how the various views can be synchronized.
For background to this section and complete details on the Visual Library in the NetBeans Platform, see "NetBeans APIs for Visualizing Data" in the NetBeans Platform Learning Trail, especially the official Visual Library documentation.
-
Right-click the Libraries node, choose Add Module Dependency, and add a dependency on the Visual Library API.
-
Create a new Java class named "AbcVisualElementPanel".
-
Define the class as follows:
package org.myorg.abcfiletype;
import java.awt.BorderLayout;
import java.awt.Point;
import java.util.List;
import javax.swing.JPanel;
import org.netbeans.api.visual.action.ActionFactory;
import org.netbeans.api.visual.widget.LabelWidget;
import org.netbeans.api.visual.widget.LayerWidget;
import org.netbeans.api.visual.widget.Scene;
import org.openide.filesystems.FileChangeAdapter;
import org.openide.filesystems.FileEvent;
public class AbcVisualElementPanel extends JPanel {
public AbcVisualElementPanel(final AbcDataObject dobj) {
setLayout(new BorderLayout());
final Scene scene = new Scene();
final LayerWidget layer = new LayerWidget(scene);
refresh(scene, layer, dobj);
dobj.getPrimaryFile().addFileChangeListener(new FileChangeAdapter() {
@Override
public void fileChanged(FileEvent fe) {
layer.removeChildren();
refresh(scene, layer, dobj);
scene.validate();
}
});
scene.addChild(layer);
add(scene.createView(), BorderLayout.CENTER);
}
private void refresh(Scene scene, LayerWidget layer, AbcDataObject dobj) {
try {
List<String> lines = dobj.getPrimaryFile().asLines();
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i);
LabelWidget widget = new LabelWidget(scene, line);
widget.getActions().addAction(ActionFactory.createMoveAction());
widget.setPreferredLocation(new Point(20, 90 * i));
layer.addChild(widget);
}
} catch (Exception e) {
}
}
}
-
In "AbcVisualElement", return the
JPanel
created above, as follows:
@Override
public JComponent getVisualRepresentation() {
return new AbcVisualElementPanel(obj);
}
-
Run the module again and notice that the first tab is synchronized with the second tab:
Make a change in the source view, save the change, switch to this visual view, and notice that the visual view reflects the changed source view.
Next Steps
For more information about creating and developing NetBeans modules, see the following resources: