Playing with File Types in NetBeans IDE 4.1

I've blogged quite a lot about menus -- about creating IDE-level menus in the Options window, about creating contextual menus for free-form projects, and about hooking Ant targets to both of the above. However, I'd never considered the possibility of manipulating menus on file level. This is where it really gets interesting. I found out about it all thanks to a great tutorial by Tim Boudreau: Module Building: Basic Support for a File Type. Initially, I must admit, the whole procedure seemed quite daunting -- but when I realized that most of it was really an extension on what I'd been doing before (with Action classes and Layer XML files), things began falling into place. Yes, another NetBeans Module (NBM) is coming your way! As I worked through this process, with the help of Tim's tutorial, I learnt quite a lot of interesting things -- such as how the order in which NBMs are loaded can be set.

Related to the load order, I've learnt how to override file extensions. It's easier -- and less obscure -- than it sounds. In his tutorial, Tim creates a module that recognizes two file types that are not recognized by the IDE by default. What I'm going to do, in the steps that follow, is show how you can force the IDE to distinguish between recognized JSP Objects. By default, the IDE doesn't distinguish between certain file extensions -- several are lumped together. For example, "JSP Objects" are documents that have any of the following extensions: .jsf, .jsp, .jspf, .jspx, .tag, .tagf, and .tagx. So, the IDE treats these all in the same way -- when you right-click on any of them, for example, the same menu items are available to you for each. This may not always be desirable, especially when you consider that tag files (.tag, .tagf, and .tagx) are pretty different to JSP files (.jsf, .jsp, .jspf, .jspx). For example, maybe you'd like to add a menu item to a tag file that would register the tag file in a TLD (this is possible, even though it isn't a tag handler). This menu item could be pretty useful for tag files but very annoying if it was made available to the contextual menus for JSP files. So, let's set things straight and distinguish between these two types of JSP Objects. Observant readers will notice that the code that follows is identical to that used in Tim's tutorial -- it is only structured, presented, and applied in a slightly different way (and probably explained erroneously here and there).

The general procedure to follow can be summed up in two sentences: "Create a DataLoader subclass that will recognize the file, a DataObject subclass to represent the file's contents and a Node subclass to represent those files in the UI. Register the DataLoader in the module's manifest." (From the NetBeans Developer's Guide.) The step-by-step process is as follows:

  1. Let the IDE create the NBM structure. Click Ctrl-Shift-N. In the New Project wizard, select the "General" category and then the "Java Application" project. Name the project as follows:

    tagextension

    Before clicking Finish, specify that the following main class should be created:

    org.netbeans.modules.tagextension.TagExtensionDataLoader

    Click Finish.

  2. Select the file types you want to manipulate. In a similar way to how you used the Layer XML file in previous blog entries to specify which menus (and/or toolbars and/or actions) you wanted to manipulate, you use the DataLoader class to specify which files you'd like to manipulate. Here, we select all files with extensions that denote a tag file. So, create the TagExtensionDataLoader class in org.netbeans.modules.tagextension, with the following content:

    package org.netbeans.modules.tagextension;
    
    public class TagExtensionDataLoader extends UniFileLoader {
        
     public TagExtensionDataLoader() {
       super ("org.netbeans.modules.tagextension.TagExtensionDataObject"); 
       ExtensionList list = new ExtensionList();
       list.addExtension("tag");
       list.addExtension("tagf");
       list.addExtension("tagx");
       setExtensions(list);
       setDisplayName("Tag Files"); 
     }
          
     protected MultiDataObject createMultiObject(FileObject primaryFile) 
       throws DataObjectExistsException, java.io.IOException {
       return new TagExtensionDataObject(primaryFile, this);
     }
    }

    Understanding the code. In the code above, the methods do the following:

    • TagExtensionDataLoader(). Recognizes tag files whenever the system encounters one. This could be in situations such as when you click on the tag file's node or when you right-click it to access its contextual menu. The DataLoader class is a factory for the DataObject, which is defined in step 4 below. Notice that we also set a display name in the code above. After you install the module, you can go to the Options window, then IDE Configuration > System > Object Types and there you will see the new File Type listed as "Tag Files".
    • createMultiObject(). Creates a single DataObject out of the multiple file extensions assigned to it. So, in this case, the tag file extensions are wrapped into a MultiDataObject.

    Fixing the Import statements. Click Alt-Shift-F in the Source Editor to generate import statements. After that, you'll notice that some packages are still needed. Right-click the project's Libraries node and add the following JARs:

    • netbeans-installation-directory/platform5/core/openide.jar
    • netbeans-installation-directory/platform5/core/openide-loader.jar

    Now click Alt-Shift-F again. The following import statements should be automatically added to your file:

    import org.openide.filesystems.FileObject;
    import org.openide.loaders.DataObjectExistsException;
    import org.openide.loaders.ExtensionList;
    import org.openide.loaders.MultiDataObject;
    import org.openide.loaders.UniFileLoader;
  3. Manipulate the file types. In a similar way to how the Action class can insert new actions in the IDE's menubar, toolbar, and action list, so the Node class can insert new actions in a specified file's contextual menu. The Node class extends DataNode and therefore has certain requirements. So, create the TagExtensionNode class in org.netbeans.modules.jspextension, with the following content:

    package org.netbeans.modules.tagextension;
    
    public class TagExtensionDataNode extends DataNode {
        
     public TagExtensionDataNode(TagExtensionDataObject obj) {
        super(obj, Children.LEAF);     
     }
           
     public Image getIcon(int type) {
        return Utilities.loadImage("org/netbeans/modules/tagextension/resources/TagFileIcon.jpg");
     }
         
     public Action[] getActions(boolean context) {
        Action[] result = new Action[] {
        SystemAction.get(EditAction.class),
        SystemAction.get(CutAction.class),
        SystemAction.get(CopyAction.class),
        SystemAction.get(RenameAction.class),
        SystemAction.get(DeleteAction.class),
        };
        return result;
     }
            
     public Action getPreferredAction() {
       return SystemAction.get(EditAction.class);
     }
    }

    Understanding the code. In the code above, the methods do the following:

    • TagExtensionNode(). Passes Children.LEAF to the Constructor because the file types that we are interested in are leaves and not branches, unlike directories or JAR files which have files within them.
    • getIcon(). Defines the icon that will replace the JSP Objects icon for the file types we want to manipulate. Make sure you get one and put it in the specified place!!!
    • getActions(). Defines the contextual menu items that you want to make available for the file type.
    • getPreferredAction(). Defines the default action for when a file of the specified type is selected.

    Fixing the Import statements. Click Alt-Shift-F in the Source Editor to generate import statements. It's quite hard to know which import statements to select, so here they are to make it easier for you:

    import java.awt.Image;
    import javax.swing.Action;
    import org.openide.actions.CopyAction;
    import org.openide.actions.CutAction;
    import org.openide.actions.DeleteAction;
    import org.openide.actions.EditAction;
    import org.openide.actions.RenameAction;
    import org.openide.loaders.DataNode;
    import org.openide.nodes.Children;
    import org.openide.util.Utilities;
    import org.openide.util.actions.SystemAction;

  4. Create the object. And now we need to create an object each time that the IDE encounters a tag file. So, create the TagExtensionDataObject class in org.netbeans.modules.jspextension, with the following content:

    public class TagExtensionDataObject extends MultiDataObject {
        public TagExtensionDataObject(FileObject file, MultiFileLoader ldr) 
            throws DataObjectExistsException {
            super(file, ldr);
        }
        
        public Node createNodeDelegate() {
            return new TagExtensionDataNode(this);
        }
    }

    Fixing the Import statements. Click Alt-Shift-F in the Source Editor to generate import statements. Select org.openide.nodes.Node so that the following import statements are created for you:

    import org.openide.filesystems.FileObject;
    import org.openide.loaders.DataObjectExistsException;
    import org.openide.loaders.MultiDataObject;
    import org.openide.loaders.MultiFileLoader;
    import org.openide.nodes.Node;

  5. Register the module and the loader and specify the load order. Replace the content of manifest.mf with the following content, making sure that there is a line before and after the second section:

    Manifest-Version: 1.0
    OpenIDE-Module: org.netbeans.modules/1
    OpenIDE-Module-IDE-Dependencies: IDE/1 > 4.0 
    OpenIDE-Module-Specification-Version: 1.0
    
    Name: org/netbeans/modules/tagextension/TagExtensionDataLoader.class
    OpenIDE-Module-Class: Loader
    Install-Before: org.netbeans.modules.web.core.jsploader.JspDataObject
    
    

    Now, the Install-Before line is really interesting -- and its not from Tim's tutorial! When you go to Tools > Options, and then select IDE Configuration, System, Object Types, you see all the IDE's file types listed. If you click on the JSP Objects node, you see all the extensions that are associated with the node by default. However, you also see the Representation Class. To override a file type's associations, create a new module to manipulate the file extension -- as we are doing -- and specify in the manifest.mf that you want to load the new module after the representation class associated with the file type in the IDE (as shown above). In this way you override the default association for the selected file type.

Now you are ready to build and install the module. Your Files window should display the following structure, right at the end, after you have created the module:

There's no point in repeating something that's as close to hand as my previous blog entry -- just follow steps 4 and 5 in the final part of the NetBeans Modules for Dummies series (when you add properties to the project.properties file, replace the value of module.dir and cluster.dir with tagextension) and everything should be fine. In addition, before building the module, you will need to add nbantext.jar separately. If you don't have a copy in the NetBeans IDE's nb4.1/ant/extra directory, go to the NetBeans Update Center by using Tools > Update Center. Make sure to tell NetBeans to check the NetBeans Update Center, the first option. In the Available Updates and New Modules, select the NetBeans Update Center > Libraries > Ant Extensions for Open APIs Support and add it to the Include to Install. Proceed through the rest of the Update Center wizard to install the additional module. Unless you selected the "global" option as the location to install the Ant Extensions for Open APIs Support module, the nbantext.jar will be placed in your $HOME/.netbeans/nb4.1/ant/extra directory. If you select "global", the nbantext.jar will in the NetBeans IDE's nb4.1/ant/ext directory. Traverse to the directory where nbantext.jar resides and add that jar to the project as you did above for the openide.jar and openide-loaders.jar. It can be difficult to add the nbantext.jar if it is located in $HOME/.netbeans/nb4.1/ant/extra since the Add Jar/Folder does not see ".netbeans" as a directory on Linux.

After you build and install the module, create a web application (Ctrl-Shift-N) and then create a tag file (Ctrl-N and then go to the Web category). The first sign of success is that you'll see your new icon alongside the tag file! When you see that new icon, your heart will pitter-patter slightly faster... and then you will know that it was all worth it. For example, this is what my list of File Types for the Web category (in the New File wizard) looks like:

Do you see how the tag file has a different icon to the JSP? That's my doing. Now, after I've created the tag file, I'm able to right-click the node in the Projects, Files, or Favorites window and see that I have less menu items available than I normally would. And that's because I successfully manipulated the IDE and made it do my bidding. Yet again.


Comments:

Post a Comment:
  • HTML Syntax: NOT allowed
About

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
12
13
14
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today