Eclipse plugin: Consolidated Java Code analysis Report using PMD, Checkstyle, findBugs

People those working in java already know about PMD, checkstyle and findbugs. These tolls help us to analyze the code and to find bugs. The problem with the tools are, once you install the plugins you have to generate report from eclipse separately. Life would be easier if we can have a plugin which will use all these and generate a consolidated report. This tutorial will give basic understanding of eclipse plugins and also will tell you how you can create a plugin to generate consolidated report.

First we need to create a popup menu plugin project. With this you can select one or multiple files, right click on it and plugin will be available in your popup menu. The plugin.xml would look like below,

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>

<extension
point="org.eclipse.ui.popupMenus">
<objectContribution
objectClass="org.eclipse.core.resources.IFile"
id="ReportG.contribution1"
adaptable="true"
nameFilter="*.java"
>
<menu
label="New Submenu"
path="additions"
id="ReportG.menu1">
</menu>
<action
label="Report Generator"

menubarPath="ReportG.menu1"
enablesFor="+"
id="ReportG.newAction">
</action>
</objectContribution>
</extension>

</plugin>

The line enablesFor=”+” tells that we can select multiple files and run the plugin. nameFilter=”*.java” tells that the plugin will be available only if the file type is java. Build the project and test it.

In previous step we showed the plugin option in eclipse based on our filter. We have already selected few java files and clicked on it. Now we have to see what all files we have selected, so that we can analyze them. We have to do some tricks in this case. Users can select file from package explorer, navigator, or can directly right click on an opened file in the editor. For first two cases the selection type would be TreeSelection, and you will be able to get the file names directly from the nodes.

final ISelection sel = targetPart.getSite().getSelectionProvider()
.getSelection();
if (sel instanceof TreeSelection) { // selected from packageExplorer
TreeSelection istS = (TreeSelection) sel;
TreePath paths[] = istS.getPaths();
//some more codes here

Now we have to get the all the last nodes from the paths we have created in last snippet.


for (int i = 0; i < paths.length; i++) {
Object lastSegment = paths[i].getLastSegment();

The object lastSegment will be type of org.eclipse.jdt.internal.core.CompilationUnit, but as there are some bugs with eclipse, instanceof check on lastSegment will fail, even if you try to type cast it to org.eclipse.jdt.internal.core.CompilationUnit, it will fail. So we have to do something like below,


if (lastSegment.getClass().getCanonicalName().equals(
"org.eclipse.jdt.internal.core.CompilationUnit")) {
// this is to prevent an eclipse bug which can not cast
// CompilationUnit to CompilationUnit and also it
// instanceof
// fails

Same way we can get the file path using below hack,


/**
* This is a workaround to resolve eclipse issue to cast CompilationUnit to
* CompilationUnit
*
* @param compilationUnit
*/
private String compilationUnitHackFName(Object compilationUnit) {
try {
Method getUnderlyingResource = compilationUnit.getClass()
.getMethod("getUnderlyingResource", null);
IResource resource = (IResource) getUnderlyingResource.invoke(
compilationUnit, null);

if (resource.getType() == IResource.FILE) {
IFile ifile = (IFile) resource;
// String path = ifile.getRawLocation().toString();
System.out
.println("NewAction.compilationUnitHack() project path: "
+ ifile.getProject().getFullPath());
String fileName = ifile.getName();
return fileName;
}

} catch (Throwable e) {
MessageDialog.openError(shell, TITLE, "issue: " + e.getMessage());
e.printStackTrace();
}
return "";
}

In case user is calling the plugin from text editor, the selection type will be TextSelection. In this case, we need to just get the opened file in eclipse, and there will be only one, which is active.


IWorkbenchWindow window = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow();
IWorkbenchPage page = window.getActivePage();
IEditorPart editor = page.getActiveEditor();
IEditorInput input = editor.getEditorInput();
IFile file = (IFile) input.getAdapter(IFile.class);

Using above codes you can get all the files’ name and location. You can use these to generate report. For findbugs you also need to get the .class files locations. You can come up with some idea how you are going to achieve it. Once all the paths and class paths are ready, we can proceed with report generation.

Before that there is one note. If you get selection type of IAdaptable, you can do the following,


if (lastSegment instanceof IAdaptable) { //Selected from Navigator
IAdaptable adaptable = (IAdaptable) lastSegment;
IFile ifile = (IFile) adaptable.getAdapter(IFile.class);
if(ifile != null){
fileNames[i] = ifile.getName();

}
}

To generate a report either you can use an ant build script or can directly call ant apis from java. The below code snippet is an example of how you can run findbugs from java directly,


Project fBugsP = new Project();
fBugsP.setDefaultInputStream(System.in);
fBugsP.addBuildListener(getConsoleLogger());
((Java) fBugsP.createTask("java")).setSpawn(true);
fBugsP.init();
Taskdef taskDef = new Taskdef();
taskDef.setClassname("edu.umd.cs.findbugs.anttask.FindBugsTask");
taskDef.setClasspath(new Path(fBugsP, System.getProperty("findbugs.class.path")));
taskDef.setName("findbugs");
taskDef.setProject(fBugsP);

FindBugsTask fbTask = new FindBugsTask();
fbTask.setHome(new File(System.getProperty("findbugs.home")));
fbTask.setOutput("html");
fbTask.setOutputFile(System.getProperty("destination.dir")+"/findBugs_report.html");
fbTask.setDebug(true);
fbTask.setWorkHard(true);
fbTask.setAdjustExperimental(true);
fbTask.setFailOnError(true);
fbTask.setProject(fBugsP);

Target fbTarget = new Target();
fbTarget.setName("findbugs");
fbTarget.addTask(fbTask);
fbTarget.setProject(fBugsP);

//some codes here

fBugsP.fireBuildStarted();
try {
fbTask.execute();
fBugsP.fireBuildFinished(null);
result.append("Findbugs report generated.");
} catch (Throwable th) {
th.printStackTrace();
fBugsP.fireBuildFinished(th);
result.append(th.getMessage());
error = true;
}

To use ant script, you can refer below build script,


<project name="BUILD" default="all">

<!-- Don't change the keys, else plugin will not work properly -->

<!-- set base prperties, if required -->
<!--
<property name="base.src.dir" location="D:\data\Official\project\sources" />
<property name="cqi.dir" location="" />
<property name="report.dir" location="" />
<property name="binary.location" location="" />
-->

<!--PMD properties-->
<property name="pmd.rule.file" location="${cqi.dir}/rules/pmd.xml" />
<property name="pmd.class.path" location="${cqi.dir}/lib/pmd-4.2.5.jar" />
<property name="pmd.xsl.path" location="${cqi.dir}/rules/pmd.xsl" />

<!--Checkstyle properties-->
<property name="checkstyle.rule.file" location="${cqi.dir}/rules/checkstyle.xml" />
<property name="checkstyle.class.path" location="${cqi.dir}/lib/checkstyle-all-4.4.jar" />
<property name="checkstyle.xsl.path" location="${cqi.dir}/rules/checkstyle.xsl" />

<!--FindBugs properties-->
<property name="findbugs.home" value="${cqi.dir}/findbugs-2.0.2" />
<property name="findbugs.class.path" location="${cqi.dir}/lib/findbugs-ant.jar" />

<!-- result file properties -->

<property name="pmd.r.file" value="${report.dir}/pmd_report.xml" />
<property name="cstyle.r.file" location="${report.dir}/checkstyle_report.xml" />
<property name="fbugs.r.file" location="${report.dir}/findBugs_report.xml" />

<!-- TARGETS -->

<target name="pmd">
<echo message="${java.files}"/>
<echo message="${base.src.dir}"/>
<taskdef name="pmd" classname="net.sourceforge.pmd.ant.PMDTask" classpath="${pmd.class.path}" />
<pmd rulesetfiles="${pmd.rule.file}" shortFilenames="true" >
<formatter type="xml" toFile="${pmd.r.file}" toConsole="true"/>
<fileset dir="${base.src.dir}" includes="${java.files}" />
</pmd>
<!-- xslt in="${report.dir}/pmd_report.xml" out="${report.dir}/pmd_report.html" style="${cqi.dir}/xslt/pmd_per_class.xslt" / -->
</target>

<!-- target name="cpd">
<taskdef name="cpd" classname="net.sourceforge.pmd.cpd.CPDTask" classpath="${pmd.class.path}"/>
<cpd minimumTokenCount="100" outputFile="${report.dir}/cpd_report.html" format="html">
<fileset dir="${base.src.dir}" includes="${java.files}" />
</cpd>
</target -->

<target name="checkstyle" description="Generates a report of code convention violations.">
<echo message="${java.files}"/>
<taskdef resource="checkstyletask.properties" classpath="${checkstyle.class.path}" />

<checkstyle config="${checkstyle.rule.file}" failureProperty="checkstyle.failure" failOnViolation="false">
<formatter type="xml" tofile="${cstyle.r.file}" />
<fileset dir="${base.src.dir}" includes="${java.files}" />
</checkstyle>
<!-- xslt in="${report.dir}/checkstyle_report.xml" out="${report.dir}/checkstyle_report.html" style="${checkstyle.xsl.path}" / -->
</target>

<target name="findbugs">
<echo message="${findbugs.class.path}"/>
<echo message="${fbugs.r.file}"/>
<taskdef name="findbugs" classname="edu.umd.cs.findbugs.anttask.FindBugsTask" classpath="${findbugs.class.path}" />
<findbugs home="${findbugs.home}" workHard="true" output="xml:withMessages" outputFile="${fbugs.r.file}" debug="true" effort="max" adjustExperimental="false" failonerror="true">
<fileset dir="${binary.location}" includes="${classes}"/>
</findbugs>
</target>

<target name="all" depends="checkstyle, pmd, findbugs">
<!-- <delete file="${report.dir}/pmd_report.xml" />
<delete file="${report.dir}/checkstyle_report.xml" />
-->
</target>

</project>

Below code shows how to run ant script from java code,


private void runAntScript() {
Project p = new Project();
p.addBuildListener(getConsoleLogger());
try {
p.fireBuildStarted();
p.setUserProperty("ant.file", buildFile);
Set keys = prop.keySet();
for (Iterator iterator = keys.iterator(); iterator.hasNext();) {
String key = iterator.next().toString();
p.setUserProperty(key, prop.getProperty(key));

}
p.setDefaultInputStream(new BufferedInputStream(new FileInputStream(buildFile)));
p.init();
ProjectHelper helper = new ProjectHelperImpl();
p.addReference("ant.projectHelper", helper);
helper.parse(p, new File(buildFile));
p.executeTarget(p.getDefaultTarget());
p.fireBuildFinished(null);
result.append("SCA report successfully generated");
prop.putAll(p.getProperties());
} catch (Throwable th) {
p.fireBuildFinished(th);
th.printStackTrace();
result.append(th.getMessage());
error = true;
}
}

imp: This line prop.putAll(p.getProperties());, will add all the property changes done buy build.xml to the java system properties.

Now we have generated three different reports in one shot. Our next step would be to consolidate these reports in one file. I used this tutorial to consolidate the report. You can use the same. Follow the tutorials, download the xslts and try using it.

You can follow me on Twitter, add me to your circle on Google+ or like our Facebook page to keep yourself updated on all the latest from Photography, Technology, Microsoft, Google, Apple and the web.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s