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:

filetype diagram dataobject2

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.

  1. 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.

  1. 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.

  1. Right-click the project node and choose New > Other > Module Development > File Type:

filetype 72 new filetype 1
  1. 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:

filetype 72 new filetype 2

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.

  1. 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:

filetype 72 new filetype 3

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: filetype Datasource

  1. Click Finish.

The Projects window should now look as follows:

filetype 72 new filetype 4

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 the package-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.

  1. In the Projects window, right-click the AbcFileType project and choose Run. A new instance of the IDE starts, installing your module into itself.

  1. Choose File | New Project and create a new Java application.

  1. Once you have a new application, go to the New File dialog and you will see your new file template:

filetype 72 trying out 1

Click Next, choose a folder to store the template, and click Finish.

  1. Open the file and you see a text editor:

filetype 72 trying out 2

Click the "Visual" tab and notice that you have a starting point for creating a visual page in a multiview editor:

filetype 72 trying out 3

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.

  1. 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:

filetype 72 new action 1

Click Next.

  1. 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:

filetype 72 new action 2

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.

  1. In the Name and Location panel, type StartAnalyzerActionListener as the Class Name and type Start Analyzer as the Display Name. Optionally, provide an icon to be displayed.

filetype 72 new action 3

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
    }

}
  1. 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.

  1. 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.

filetype 72 new action 4
  1. Choose the new menu item, the Abc file’s name and location are shown:

filetype 72 new action 5

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.

  1. 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;
    }

}
  1. 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.

  1. 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());
}
  1. 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;
    }

}
  1. 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
  1. Run the module again, create an Abc file from the template again, and then notice that you can expand the generated file:

filetype 72 new action 6

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:

filetype 72 new action 9

We will now change the default properties to show a custom property instead.

  1. Open the AbcDataObject class and change the createNodeDelegate method so that our own AbcNode will be created instead of the generic DataNode :

@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.

  1. 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;
        }

    }

}
  1. Run the module again, open the Properties window, and notice your property is displayed:

filetype 72 new action 7

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.

  1. Right-click the Libraries node, choose Add Module Dependency, and add a dependency on the Visual Library API.

  1. Create a new Java class named "AbcVisualElementPanel".

  1. 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) {
        }
    }

}
  1. In "AbcVisualElement", return the JPanel created above, as follows:

@Override
public JComponent getVisualRepresentation() {
    return new AbcVisualElementPanel(obj);
}
  1. Run the module again and notice that the first tab is synchronized with the second tab:

filetype 72 new action 8

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: