Pages

Friday, August 28, 2009

javacvs – Netbeans lib to access CVS in java, Tutorial

Well, I din expect i will have a topic to write so soon after join for MS course. But, thanks to Dr. Li I had a chance to work with java again!!! I love java! So, here goes a tutorial which explains how to use javacvs lib.

I will explain important snippets here, will provide the complete source at the end of the tutorial.

Step 1: Create the CVS Root object defining the meta data such as username, host etc To do this, we have to read the CVS/Root file.

File root = new File(cvsPath + PATH_DELIM + cvsRoot);        
if(true == root.exists()){
BufferedReader bufferedReader = new BufferedReader(new FileReader(root));
if(null != bufferedReader){
System.out.println(MSG_CAPTURE_CVS_DETAIL);
String cvsRootData = bufferedReader.readLine();
CVSRoot rootData = CVSRoot.parse(cvsRootData);//Create the CVS Root object defining the meta data such as username, host etc


Step 2: Set the global options, needed for executing commands.
GlobalOptions globalOptions = new GlobalOptions();
globalOptions.setCVSRoot(cvsRootData);

Step 3: Obtain the connection with the credentials and the details provided and then open the connection.
PServerConnection connection = new PServerConnection(rootData);
connection.open();

Step 4: Create a client that can execute the cvs log command, and register the listener to be invoked for CVS outputs. The loglistener is explained at the end.
Client client = new Client(connection, new StandardAdminHandler());     
client.setLocalPath(cvsPath);
client.getEventManager().addCVSListener(new LogListener());

Step 5: The log command that does the job of getting the log for every file in the directory. The log builder is responsible to invoke fileinfo events on the loglistener.
LogCommand command = new LogCommand();
command.setRecursive(true);
Builder builder = new LogBuilder(client.getEventManager(), command);
command.setBuilder(builder);

Step 6: Initialize the writers, and execute the command, close the writers..
xl = new File(F_OUTPUT_CSV);
xlWriter = new BufferedWriter(new FileWriter(xl));
xlWriter.write(HEADER);
client.executeCommand(command, globalOptions);
xlWriter.close();
bufferedReader.close();

LogListener
It extends BasicListener, and overrides the messageSent with an empty definition. It also overrides the fileInfoGenerated method, where the actual processing of each file is done. That is log each of  file. The snippet is below.
//Handle control to Super class.
super.fileInfoGenerated(fileinfoevent);

//Get the log information for the current file event.
LogInformation infoContainer = (LogInformation) fileinfoevent.getInfoContainer();
try {
//Log to Excel in csv format.
logToExcel(infoContainer);
} catch (IOException e) {
//Just print trace, and try logging the next file event.
e.printStackTrace();
}

logToExcel is a simple method that writes the log to the file. The datastructure LogInformation has data related to the file being processed, all the revisions of the file, head revision, the message associated with each revision and lot of more details. I use only the ones I mentioned.
The complete code including the LogListener is below. Ignore the logic that is being done at logToExcel method, that some thing related to my work. But, the goal is to know that fileInfoEvent has LogInformation, that can be used to see data. You can write different listeners, builders in the similar way.
/**
*
*/
package com.ssb.nb.cvs;
/*
* Java SE Packages
*/
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;

/*
* Java CVS Packages
*/
import org.netbeans.lib.cvsclient.CVSRoot;
import org.netbeans.lib.cvsclient.Client;
import org.netbeans.lib.cvsclient.admin.StandardAdminHandler;
import org.netbeans.lib.cvsclient.command.Builder;
import org.netbeans.lib.cvsclient.command.CommandException;
import org.netbeans.lib.cvsclient.command.GlobalOptions;
import org.netbeans.lib.cvsclient.command.log.LogBuilder;
import org.netbeans.lib.cvsclient.command.log.LogCommand;
import org.netbeans.lib.cvsclient.command.log.LogInformation;
import org.netbeans.lib.cvsclient.command.log.LogInformation.Revision;
import org.netbeans.lib.cvsclient.commandLine.BasicListener;
import org.netbeans.lib.cvsclient.connection.AuthenticationException;
import org.netbeans.lib.cvsclient.connection.PServerConnection;
import org.netbeans.lib.cvsclient.event.FileInfoEvent;
import org.netbeans.lib.cvsclient.event.MessageEvent;

/**
* @author Shreyas Purohit
*
*/
public class NBCvs {
/*
* Private Static variables
*/
private static File xl;
private static BufferedWriter xlWriter;
private static String cvsPath = "F:/EWorkspace/org.eclipse.core.commands";
private static String F_OUTPUT_CSV = "F:/output.csv";

/*
* Private Constants
*/
private static final String APP_NAME = "CVS2l : ";
private static final String MSG_METHOD = " method: ";
private static final String MSG_USERNAME = " username: ";
private static final String MSG_REPOSITORY = " repository: ";
private static final String MSG_PORT = " port: ";
private static final String MSG_HOST = "Using host: ";
private static final String cvsRoot = "CVS/Root";
private static final String PATH_DELIM = "/";
private static final String MSG_EXIT_SUCCESS = APP_NAME + "Completed sucessfully, terminating process.";
private static final String MSG_EXEC_LOG = APP_NAME + "Executing log command";
private static final String MSG_INIT_OUTPUT = APP_NAME + "Initializing output file";
private static final String MSG_CONNECTING_CVS = APP_NAME + "Connecting to CVS";
private static final String MSG_CAPTURE_CVS_DETAIL = APP_NAME + "Capturing CVS metadata";
private static final String MSG_LOADED_PROP = APP_NAME + "Loaded required properties";
private static final String MSG_PROP_NOT_READABLE = APP_NAME + "Could not read tool.properties, make sure it is not being used by some other application";
private static final String MSG_EXITING = APP_NAME + "Exiting..";
private static final String MSG_PROP_NOT_FOUND = APP_NAME + "Could not find tool.properties, make sure it exists in same directory as the cvs2l.jar";
private static final String TOOL_PROPERTIES = "tool.properties";
private static final String MSG_INIT = APP_NAME + "Initializing";
private static final String HEADER = "File Name,Package,Revision,Bug no.,Description\n";

/*
* Static block to load properties.
*/
static{
System.out.println(MSG_INIT);
Properties prop = new Properties();
try {
prop.load(new FileInputStream(TOOL_PROPERTIES));
} catch (FileNotFoundException e) {
System.out.println(MSG_PROP_NOT_FOUND);
splashExitMsg();
systemExit();
} catch (IOException e) {
System.out.println(MSG_PROP_NOT_READABLE);
splashExitMsg();
systemExit();
}
cvsPath = prop.getProperty("CVS_PATH");
F_OUTPUT_CSV = prop.getProperty("OUTPUT");
System.out.println(MSG_LOADED_PROP);
}

/**
* Displays exit message
*/
private static void splashExitMsg() {
System.out.println(MSG_EXITING);
}

/**
* Exits system
*/
private static void systemExit() {
System.exit(1);
}

/**
* @param args
* @throws IOException
* @throws FileNotFoundException
* @throws AuthenticationException
* @throws CommandException
*/
public static void main(String[] args) throws FileNotFoundException, IOException, AuthenticationException, CommandException {
/*
* Get the path to the CVS Root folder, connect to CVS and execute the log command to retrieve the
* file revision, package, bug number and description.
*/
File root = new File(cvsPath + PATH_DELIM + cvsRoot);
if(true == root.exists()){
BufferedReader bufferedReader = new BufferedReader(new FileReader(root));
if(null != bufferedReader){
System.out.println(MSG_CAPTURE_CVS_DETAIL);
String cvsRootData = bufferedReader.readLine();
CVSRoot rootData = CVSRoot.parse(cvsRootData);//Create the CVS Root object defining the meta data such as username, host etc

GlobalOptions globalOptions = new GlobalOptions();
globalOptions.setCVSRoot(cvsRootData); //Set the global options, needed for executing commands.

System.out.println(APP_NAME + MSG_HOST + rootData.getHostName() + MSG_PORT + rootData.getPort() + MSG_REPOSITORY + rootData.getRepository() + MSG_USERNAME + rootData.getUserName() + MSG_METHOD + rootData.getMethod());
System.out.println(MSG_CONNECTING_CVS);
PServerConnection connection = new PServerConnection(rootData);// Obtain the connection with the credentials and the details provided.
connection.open(); //Open the connection.

/*
* Create a client that can execute the cvs log command, and register the listener to be invoked for CVS outputs.
*/
Client client = new Client(connection, new StandardAdminHandler());
client.setLocalPath(cvsPath);
client.getEventManager().addCVSListener(new LogListener());

/*
* The log command that does the job of getting the log for every file in the
* directory.
* The log builder is responsible to invoke fileinfo events on the loglistner.
*/
LogCommand command = new LogCommand();
command.setRecursive(true);
Builder builder = new LogBuilder(client.getEventManager(), command);
command.setBuilder(builder);

/*
* Initialize the writers, and execute the command, close the writers..
*/
System.out.println(MSG_INIT_OUTPUT);
xl = new File(F_OUTPUT_CSV);
xlWriter = new BufferedWriter(new FileWriter(xl));
xlWriter.write(HEADER);
System.out.println(MSG_EXEC_LOG);
client.executeCommand(command, globalOptions);
xlWriter.close();
bufferedReader.close();
System.out.println(MSG_EXIT_SUCCESS);
}
}
}
/**
* The Listener class for the log command to be executed.
*
* @author Shreyas Purohit
*
*/
public static class LogListener extends BasicListener{
private static final String DOT_STR = ".";
/*
* Private static constants.
*/
private static final String BUG_FOLLOWED_SPACE_STR = "Bug ";
private static final String BUG_STR = "Bug";
private static final String SPACE = " ";
private static final String NEW_LINE = "\n";
private static final String DOUBLE_COMMA = ",,";
private static final String COMMA_STR = ",";
private static final String EMPTY_STR = "";
private static final String JAVA_STR = "java";
private static final String SRC_STR = "src";
private static final String BACKSLASH = "\\";
private static final String SRC = "\\src\\";

@Override
public void messageSent(MessageEvent e) {
/*
* Override the super class, so as to prevent from logging to console.
*/
}

@Override
public void fileInfoGenerated(FileInfoEvent fileinfoevent) {
//Handle control to Super class.
super.fileInfoGenerated(fileinfoevent);

//Get the log information for the current file event.
LogInformation infoContainer = (LogInformation) fileinfoevent.getInfoContainer();
try {
//Log to Excel in csv format.
logToExcel(infoContainer);
} catch (IOException e) {
//Just print trace, and try logging the next file event.
e.printStackTrace();
}
}

public void logToExcel(LogInformation info) throws IOException{
System.out.print(NEW_LINE);
System.out.println("------------------Processing---------------------------");
System.out.println("File : " + info.getRepositoryFilename());
System.out.println("Available Revisions...");

String path = info.getFile().getPath();
String strPackage = EMPTY_STR;
/*
* Extract package information from the path information.
*/
if(path.indexOf(SRC) >= 0 ){
String packagePath = path.substring(path.indexOf(SRC));
StringTokenizer tokenizer = new StringTokenizer(packagePath,BACKSLASH);
while(tokenizer.hasMoreTokens()){
String nextToken = tokenizer.nextToken();
if(!nextToken.equalsIgnoreCase(SRC_STR) && !(nextToken.indexOf(JAVA_STR)>=0)){
strPackage += nextToken + DOT_STR;
}
}
if(strPackage != EMPTY_STR){
strPackage = strPackage.substring(0, strPackage.length()-1);
}
}
boolean fileNameWritten = false;

/*
* Process all the revisions for the current file.
*/
List<Revision> revisionList = info.getRevisionList();
for(Revision revision : revisionList){
System.out.print(revision.getNumber() + SPACE);
//Remove new line character from the message else it hinders rendering in the Excel sheet.
String message = (revision.getMessage() != null && revision.getMessage().indexOf(NEW_LINE) >= 0 ? revision.getMessage().replaceAll(NEW_LINE, SPACE) : revision.getMessage());
String bugNumber = null;

/*
* Extract the bug number from the message string if any.
*/
if(message.indexOf(BUG_FOLLOWED_SPACE_STR) >= 0){
StringTokenizer tokenizer = new StringTokenizer(message,SPACE);
while(tokenizer.hasMoreTokens()){
if(BUG_STR.equalsIgnoreCase(tokenizer.nextToken())){
if(bugNumber != null){
bugNumber += SPACE + tokenizer.nextToken();
}else{
bugNumber = tokenizer.nextToken();
}
if(null != bugNumber){
bugNumber = bugNumber.replaceAll(COMMA_STR, SPACE);
}
}
}

}
/*
* Write to CSV, if there exist a bug in the message for this revision.
*/
if(null != bugNumber){
/*
* Write the filename only once, and repeat the revisions for the same file.
*/
if(false == fileNameWritten){
xlWriter.write(info.getFile().getName() + COMMA_STR + strPackage + DOUBLE_COMMA+ NEW_LINE);
fileNameWritten = true;
}
xlWriter.write(EMPTY_STR+ DOUBLE_COMMA + revision.getNumber() + COMMA_STR + (bugNumber != null ? bugNumber : SPACE) + COMMA_STR + message + NEW_LINE);
}
}
}
}
}