September 7, 2009

NetBeans Platform: Implement Perforce client - part I

NetBeans and NetBeans Platform were always the tools for the heart. Unfortunately, my usual job duties did not included enough time spent with them. So, I tool my chanses to spent some nights on these. Since I have to use Perforce versioning system and because NetBeans has not (yet!) a client for this system, I decided to implement such a client. Hope I can give something back to the community in this way, a humble little piece compared to the things I received. You will see project details here http://kenai.com/projects/perforcenb.

So, a versioning control system. If you want to know more about Perforce, you can go to their site www.perforce.com. It should be enought to say that I know at least two very big companies which use it as a main VCS.

NetBeans Platform gives us an API which can be used to integrate a client for a versioning system into the IDE. A nice side effect of this is the fact that all the versioning control system clients will behave in a similar way. We will implement our Perforce client as a NetBeans module. In this way we can use it on our daily basis usual tasks.

First thing to create is a NetBeans module project. We can do that by using New -> Project from NetBeans IDE. (I use 6.7.1, thought you should use a recent version too). We chose then NetBeans Modules -> Module for the project type and hit Next button.


In the next dialog box we insert information about the project. Like project name, location and so. You can see the data I enetered from the image. Afer that, hit the Next button again.


In the next dialog box we insert some information useful for code generation. Base package can be configured here, title of the plugin and layer. The will be important in the next parts of this story. For the moment just be sure you select it. To generate the code for the project you should use action Finish button.

So, we have now a module project which does nothing for the moment. We want to implement a versioning system client in it, so the starting point should be extending the VersioningSystem class. But, to be able to use that class, we must do another preparation step. Importing the NetBeans modules which we will base our module on. For the beginning we will add two dependencies at the project. For that, action the contextual menu of the project. From there, action Properties -> Libraries. In the dialog box fire Add button which will gives us a search screen.


The interesting thing in this screen is that you can use it in two ways. First you know already which module you should import, so you can roll over the Module list and select modules you like to use it. The second way is to use search Filter. The nice thing here is that you can search for an exposed class. So, if you don't know which module to import in order to use a specific class or interface, you filter module by that class or interface and you will have listed all the modules which offer that name. Nice trick when you are lost somehow.

So, we need to add dependencies to the following modules: Versioning and Utiities API. The last because we use an utility class for getting the resources (look for NbBundle to see what I mean).

The next stept is to let the NetBeans IDE "know" that I just implemented a new versioning client for him.We do this by extending org.netbeans.modules.versioning.spi.VersioningSystem in a service provider interface way. So we create a derived class, PerforceVS which extends VersioningSystem. We create META-INF folder into the source root. In META-INF we create a folder called services. In META-INF/services we create an empty text file. That file will be called org.netbeans.modules.versioning.spi.VersioningSystem and will have one line only: org.padreati.perforcenb.PerforceVS. This is the name of the class which extends VersioningSystem. For a broader view of Service Provider Interface you can take a look here.

package org.padreati.perforcenb;

import java.io.File;
import org.netbeans.modules.versioning.spi.VCSAnnotator;
import org.netbeans.modules.versioning.spi.VersioningSystem;
import org.openide.util.NbBundle;
import org.padreati.perforcenb.wrapper.PerforceSystem;

/**
 *
 * @author padreati
 */
public class PerforceVS extends VersioningSystem {

    public PerforceVS() {
        putProperty(PROP_DISPLAY_NAME, NbBundle.getMessage(
            PerforceVS.class, "CTL_Perforce_DisplayName")); // NOI18N
        putProperty(PROP_MENU_LABEL, NbBundle.getMessage(
            PerforceVS.class, "CTL_Perforce_MainMenu")); // NOI18N
    }

    @Override
    public File getTopmostManagedAncestor(File file) {
        // TODO for the moment all will be considered under
        // Perforce just for testing
        return new File("/");
    }

    @Override
    public VCSAnnotator getVCSAnnotator() {
        return PerforceSystem.getSystem().getAnnotator();
    }
}

This class is in the very basic form. We do the following:

  • In constructor we set values for menu labels. In order to work I have inserted entries into resource (properties) files for CTL_Perforce_DisplayName and CTL_Perforce_MainMenu.
  • I implemented getVCSAnnotator just to return the instance of annotator we will use (details a little bit latter).
  • implement getTopmostManagedAncestor. If this method returns a value, it means that the file/folder receicved as parameter is managed by our versioning system. If not, we return null.
One important aspect is the design of versioning system in NetBeans. It is considered that one resource from disk (either file or folder) can be in two states only. Either is managed by one versioning system (only one versioning system can own the resource in the same time) or is not managed at all. The versioning API consider also that if a folder is managed by a versioning system, all of the folders and files should be managed by that version control. So, to find for a folder if it is managed by a versioning control, we should tell to the versioning API that the specified files resides under a managed folder. We do that by implementing getTopmostManagedAncestor. This method should return the topmost managed folder where resides the file received as parameter. 
In my sample I simply return root folder. Why, would you ask. Just for the testing purposes. Returning the root folder of the file system (this is similar to C:\ folder on Windows) I specify that all files are managed by Perforce. Obviously this is not true. We do it for now just to see where we go.
Also, I created a singleton class to hold all the references of the Perforce versioning system. For the moment we hold only annotator instance. But what is an annotator?
package org.padreati.perforcenb.impl;
import java.awt.Image;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Action;
import org.netbeans.modules.versioning.spi.VCSAnnotator;
import org.netbeans.modules.versioning.spi.VCSContext;
/**
 *
 * @author padreati
 */
public class PerforceAnnotator extends VCSAnnotator {
    @Override
    public Image annotateIcon(Image arg0, VCSContext arg1) {
        return super.annotateIcon(arg0, arg1);
    }
    @Override
    public String annotateName(String name, VCSContext ctx) {
        return name + " [PERFORCE]";
    }
    @Override
    public Action[] getActions(VCSContext ctx, ActionDestination destination) {
        // TODO complete actions, for the moment only an empty
        // menu as a sample
        List actions = new ArrayList();
        if (destination == VCSAnnotator.ActionDestination.MainMenu) {
            actions.add(null);
        }
        return actions.toArray(new Action[actions.size()]);
    }
}
An VCS Annotator is the class which implements the decoration in IDE of the code elements controlled by a versioning system. In our case, all the files managed by Perforce should be specified in a way that is visible into interface. Just because we signaled that all files are managed by Perforce (we know that is not true), all the files will be decorated by this annotator.

annotateName will decorate the name of the resource, and annotateIcon will decorate the icon. These methods receives VSContext. This class encapsulates a selection of objects, so we can multiple annotate the resources. In our sample implementation we only append the [PERFORCE] string at the name of resource.
The method getActions deserves also a lot of attention. Bu on that in another next chapter. For the moment just remember that this is a way to provide actions to main or contextual menus. For the moment we give an empty menu.
Until the next episode, just give it a try. Run the module. It will open the IDE with our module enabled. Open   a project in IDE and watch for names, You will the the appended text for all the resources. Like it would be managed by Perforce.
On the next things in the following episode. Follow it! NetBeans Platform deserves it!

No comments:

Post a Comment