|
Eclipse Article |

The Eclipse Test & Performance Tools Platform (TPTP) is a powerful
tool for evaluating the overall performance and quality of Java source code. In addition to well-known features for
measuring code performance, TPTP now includes a core framework for handling
static analysis functions. The framework
for building static analysis providers was supplied in TPTP 4.1, but in more
recent versions, complete code review providers for C/C++ and Java have been made
available. In the latter case, a set of over 70 rules is included. This second article describes the process of
creating additional rules for the Java code review provider.
By
In Part 1 of this series of articles, we looked at the common user interface
for analysis available in TPTP and the process of creating an analysis configuration
by selecting providers, categories and rules.
Moreover, we discussed the results view that shows the outcome of the
performed static analysis. As noted in
Part 1, TPTP 4.2 includes over 70 rules used to review Java source code.
In Part 2 of this series you will learn how to write rules to extend the
current Java code review rule set. The
TPTP analysis framework includes a number of extension points and a complete
API for adding new forms of analysis and new rules, but more importantly for our
purposes, TPTP also supplies a fully functional analysis provider for Java
source code. This article will describe
enough of the framework to understand and write any rule you need.
We will start with a description of the components that make up a rule. Then we will cover the category and rule
extension points as well as a very quick overview of the Java Development
Tooling (JDT). Next we will review one
of the existing rules in TPTP and use it as the basis for writing a new rule. Finally
some of the more advanced capabilities of the API will be shown, including
rules template, variables, detail providers, and quick fixes.
As you may already know, the Eclipse platform supplies a complete parser for
Java source code that is used internally for common functions such as displaying
the Java source outline view, find dependencies and syntax highlighting. Fortunately, Eclipse has also exposed this
API, known as JDT, so that it can be used by platform developers. JDT parses a Java source file and produces an
in-memory tree structure representation that can be queried by other areas of
the API. The topmost portion of the Abstract
Syntax Tree (AST) tree for a Java file is roughly structured as follows:
CompilationUnit:
[ PackageDeclaration
]
{ ImportDeclaration }
{ TypeDeclaration
| EnumDeclaration | AnnotationTypeDeclaration
| ; }
The CompilationUnit is the type root, which conceptually is the
Java file itself and is a container for TypeDeclarations
(which are either classes or interfaces).
There are also MethodInvocations, MethodDeclarations and many more node types that you will
use on a regular basis to write rules. A
complete description of all nodes is available in the Eclipse Help system. There are over 100 AST node types in
documentation, but don’t be intimidated – the JavaDoc
is well written and complete.
To perform
queries on the AST parse tree, JDT uses a visitor pattern. This offers great performance, but it can be
somewhat intimidating to write a visitor class to obtain valid useful
results. The analysis API eliminates
most of the problems associated with visiting AST nodes by providing a common
visitor class and abstracting the query mechanism. This API will be described throughout the
remainder of this article. For now it is
only required that you have a basic understanding of the AST tree structure,
and the Eclipse JDT Help can provide that.
Take some time to peruse this documentation because you will eventually
need to write AST code for some of your more advanced rules.
We won’t go
into a deep description of all extension points available in the TPTP analysis
framework – there are many of them.
Instead in this article we will focus on just few that are needed to
create new categories and rules. We will
cover others such as the provider and result extension points in the next
article that is part of this series.
The “Code
Review for Java” provider is available in every TPTP install and any rule you
write to analysis Java source code should be placed within this provider. If you open the analysis configuration dialog
and expand this provider you will see that it has a number of categories. Each of these is contributed with an extension
in a plugin.xml file such as the one you will find in the “org.eclipse.tptp.platform.analysis.codereview.java.rules”
plugin in TPTP. For example, consider an
arbitrary category from the plugin.xml source:
<analysisCategory
class="org.eclipse.tptp.platform.analysis.core.category.DefaultAnalysisCategory"
id="codereview.java.category.awt"
description="%description.category.j2sebestpractices.awt"
label="%label.category.j2sebestpractices.awt"
category="codereview.java.j2sebestpractices"/>
The “class”
attribute defines a class that manages the category. Even though a custom category class can be
written, this code uses the default category supplied by the API. Custom categories must handle common
characteristics of a category such as managing a list of contained children,
label, icon name, etc. This is beyond
the scope of this article – for now accept that the default category can
provide all of the services required for any category. In almost all situations you will use the
supplied class for a category.
A category
also has a unique id, which is used by other categories and rules to determine
containment. Naturally, a category must
also have a label and a description which are shown to the user when this
category is displayed.
In this
example the extension also has a “category” attribute which indicates that this
category is nested within another category whose unique id is
“codereview.java.j2sebestpractives”. If
you want to create a top-level category, replace the category attribute with a
“provider=provider.id”. For example:
<analysisCategory
class="org.eclipse.tptp.platform.analysis.core.category.DefaultAnalysisCategory"
id="codereview.java.j2sebestpractices"
label="%label.category.j2sebestpractices"
provider="codereview.java.analysisProvider"
help=" org.eclipse.tptp.platform.analysis.codereview.java.rules.j2sebestpractices
"/>
where "codereview.java.analysisProvider"
is the unique identifier of the Java code review provider.
Rule extensions work in a similar way but include a few more details. The following is a sample extension for a
rule:
<analysisRule
category="codereview.java.category.awt"
class="org.eclipse.tptp.platform.analysis.codereview.java.rules.awt.RuleAwtPeer"
id="org.eclipse.tptp.platform.analysis.codereview.java.rules.awt.RuleAwtPeer"
label="%label.relrule.j2sebestpractices.awt.awtpeer"
severity=2
help="org.eclipse.tptp.platform.analysis.codereview.java.rules.awtpeer">
</analysisRule>
You should recognize the first part of the rule extension since it is almost
identical to the category extension previously shown. In this case, the “class” attribute contains
a fully qualified class path for the rule.
The rule also contains some additional information. The help
tag supplies an id for a help context in a related help.xml
file. This allows a rule to provide
nicely formatted example and solution information to the user. We will discuss help and other advanced rule
concepts near the end of this article so ignore these lines for now.
As we discussed in the previous section, the first half of a rule definition
is an Eclipse extension. The second half
is a Java class that uses the rule API and/or parts of JDT to make queries on
the content of a class and produce results for any issues that match the rule
criteria. The rule class is quite
simple, typically containing only a single method. Since rules usually extend a default rule
class provided by the TPTP analysis framework, most of the needed functionality
is already implemented. The following
code is a complete rule that creates results for any line of code that uses the
Java “==” operator to compare two objects:
public class RuleComparisonReferenceEquality
extends AbstractAnalysisRule
{
private static final String[] OPERATORS = { "==",
"!=" }; //$NON-NLS-1$ //$NON-NLS-2$
private static final int[]
OPERANDS = { ASTModifier.TYPE_PRIMITIVE,
ASTModifier.TYPE_NULLTYPE };
// Populate rule
filters
private static IRuleFilter[] EXPFILTERS
= {
new OperatorRuleFilter(
OPERATORS, true ),
new LeftOperandRuleFilter(
OPERANDS, false ),
new RightOperandRuleFilter(
OPERANDS, false )
};
/**
* Analyze this rule
*
* @param history A reference to the history record for this analysis
*/
public void
analyze( AnalysisHistory history)
{
// Extract the resource being analyzed
CodeReviewResource resource = (CodeReviewResource)getProvider()
.getProperty(
history.getHistoryId(), CodeReviewProvider.RESOURCE_PROPERTY );
// Obtain a list
of type declarations (classes only) that implement Cloneable
List
list = resource.getTypedNodeList( resource.getResourceCompUnit(),
ASTNode.INFIX_EXPRESSION );
ASTHelper.satisfy( list, EXPFILTERS );
for(Iterator it=list.iterator();
it.hasNext(); ) {
InfixExpression tempInf = (InfixExpression)it.next();
ITypeBinding leftBinding = tempInf.getLeftOperand().resolveTypeBinding();
ITypeBinding rightBinding = tempInf.getRightOperand().resolveTypeBinding();
if( (leftBinding != null && leftBinding.isPrimitive())
||
(rightBinding !=
null && rightBinding.isPrimitive())
) {
it.remove();
}
}
resource.generateResultsForASTNodes( this, history.getHistoryId(),
list );
}
}
The rule class extends
“org.eclipse.tptp.platform.analysis.core.rule.AbstractAnalysisRule” from the
analysis framework, and since this class is abstract, the derived rule must
implement the analyze() method. This method takes one parameter (history)
which points to the analysis session for which rules are being executed. Every time the user performs analysis, a new
history is registered to collect results.
In addition to results, the history element also keeps track of which
providers, categories and rules were selected.
The first task the analyze() method needs to
perform is obtain a reference to the code review resource. This class, which is part of the Java code
review provider, keeps track of the compilation unit for the class file being
analyzed, and manages queries into the AST tree.
// Extract the resource being analyzed
CodeReviewResource resource
= (CodeReviewResource)getProvider()
.getProperty( history.getHistoryId(), CodeReviewProvider.RESOURCE_PROPERTY );
Since this rule is concerned with infix expressions (e.g. expressions with a
left and right side, such as a comparison), it uses the code review resource to
obtain a list of such expressions in the compilation unit:
List list
= resource.getTypedNodeList( resource.getResourceCompUnit(),
ASTNode.INFIX_EXPRESSION );
Next, the rule uses the ASTHelper.satisfy() method to filter expression from the list that do not
meet a given criteria. Criteria are
defined in the static block at the top of the class:
// Rule fitlers
private static IRuleFilter[]
EXPFILTERS = {
new OperatorRuleFilter( OPERATORS,
true ),
new LeftOperandRuleFilter(
OPERANDS, false ),
new RightOperandRuleFilter(
OPERANDS, false )
};
The EXPFILTERS array contains three types of rule filters. The
first includes any expressions where the operator is either “==” or “!=”. The second
filter removes any list items where the left side of the expression is a
primitive type or a null. The final
filter repeats this for the right side of the expression. The satisfy() method
accepts the input list and the filter list and performs all filtering in one
step. However, we still need to check if
the operands in the remaining expressions are bound to null or a primitive
type. We iterate through the list and remove these.
for(Iterator it=list.iterator(); it.hasNext(); ) {
InfixExpression tempInf = (InfixExpression)it.next();
ITypeBinding leftBinding = tempInf.getLeftOperand().resolveTypeBinding();
ITypeBinding rightBinding = tempInf.getRightOperand().resolveTypeBinding();
if( (leftBinding != null
&& leftBinding.isPrimitive()) ||
(rightBinding !=
null && rightBinding.isPrimitive()) ) {
it.remove();
}
}
After this code is executed the list will contain only expressions where the
left and right side are objects and the operator is either “==” or “!=”. Since this is
the only concern of this rule, every item remaining in the list should be
reported in the result list. This is
accomplished with the final line in the rule:
resource.generateResultsForASTNodes( this, history.getHistoryId(),
list );
That was easy, wasn’t it? Hopefully
you weren’t looking for something more complicated. The API for writing a Java rule contains just
a few methods, but it is quite powerful as you will appreciate if you start
examining the source code for some of the other rules supplied in TPTP.
Now that you have been lulled into the simplicity of code review rules,
let’s make it a bit more challenging.
The rule we previously examined is about as simple as a rule can
get. It queries only for a single type
of node in the AST tree and blindly reports all results remaining after
filter. Let’s consider all the steps
needed to write a complete rule plugin with a new
category and rule.
In order to create a rule, we first need to create a plug-in. Use the Eclipse new project wizard to create
a new plug-in in your workspace named “org.eclipse.tptp.platform.analysis.codereview.java.examples”.

On the second screen of the wizard,
uncheck the checkbox stating “This plug-in makes
contributions to the UI”. Click
the “Finish” button to create the plug-in.

In order to successfully build rules you
need to add some dependencies to the plug-in.
Open the plug-in manifest and select the “Dependencies” tab. Add plugins as
shown in the following diagram:

The “org.eclipse.tptp.platform.analysis.core”
plugin contains the basic static analysis framework
API and extension points.
The “org.eclipse.tptp.platform.analysis.codereview.java”
plugin contains the API for writing Java code review
rules.
The “org.eclipse.jdt.core”
plug-in contains the JDT API including the AST parser and query framework.
Typically you need all three of these
plug-in dependencies when authoring new rules.
We will try to make our first rule practical. In this section we will create a rule that
detects finalize() methods in Java code that do
nothing but call super.finalize(). If a finalize()
method calls only its parent, then the method can simply be removed. For example:
public class
Example
{
private void
method() {
// Do something
}
// Don't do
this
protected void finalize() throws Throwable {
super.finalize();
}
}
So what do we need to do to detect this kind of problem? Let’s examine the steps:
1. We first need to build a list of method declarations with the source code
and specifically those named “finalize”.
Moreover, those method declarations should have no parameters.
2. Once we have a list of method declarations that meet our criteria, we
need to look at the lines of code within them to see if they have just one statement
that is a super.finalize() super method invocation.
We can build a rule using these steps.
First create a new class in the “org.eclipse.tptp.platform.analysis.codereview.java.examples”
package of the examples plug-in. Call
this class “RuleFinalizerSuper”. Make sure this rule class extends
“org.eclipse.tptp.platform.analysis.core.rule.AbstractAnalysisRule”
import
org.eclipse.tptp.platform.analysis.core.rule.AbstractAnalysisRule;
public
class RuleFinalizerSuper
extends AbstractAnalysisRule {
}
We know we will need an analyze method so add this code to the class:
/**
* Analyze this
rule
*
* @param history A reference to the history record for this analysis
*
* @throws CoreException
*/
public void analyze( AnalysisHistory history)
{
// Extract the resource being
analyzed
CodeReviewResource
resource = (CodeReviewResource)getProvider()
.getProperty( history.getHistoryId(), CodeReviewProvider.RESOURCE_PROPERTY );
}
According to step 1 of our algorithm, we need to collect a list of method declarations
from the class that is being analyzed. Add
the following line to the analyze() method to
accomplish this. This line visits the
AST tree starting at the compilation unit (the top-most node) and queries for a
list of all method declarations. Note
that the getTypedNodeList() method has several signatures – this one by default makes
recursive calls so it will also find method declarations in any inner classes.
List list = resource.getTypedNodeList( resource.getResourceCompUnit(), ASTNode.METHOD_DECLARATION );
To complete the requirements for step 1 of our algorithm, we need to filter
methods out of the method declaration list that we don’t need. As in the first rule we examined, we can
created a list of rule filters to achieve this.
Add the following lines to the top of the rule class:
// Rule filters
private
static final IRuleFilter[] MIFILTERS = {
};
First we
need to filter methods that are not declarations of finalize(). In the empty static block add:
new MethodNameRuleFilter( “finalize”, true ),
The MethodNameRuleFilter class tells the filtering mechanism
that we are interested in filtering by method names, specifically
“finalize”. The “true” boolean indicates that finalize()
methods should be retained in the list.
If this field was “false” then filtering would remove finalize()
methods and keep everything else.
We also
want to ensure that we manage only true finalize()
methods. Since it is possible for a
developer to create a finalize() method that returns a
value we want our rule ignores these. The
ReturnTypeRuleFilter class adds some additional
filtering to keep only finalize() methods with a void return type.
new ReturnTypeRuleFilter( “void”, true ),
To further
help reduce the filtered list size, we are interested only in finalize() methods that have no parameters. Methods with parameters are not class finalizers (in fact a rule should be written to report
these as invalid). Add the following
line to the static block:
new ParameterCountRuleFilter( 0, true )
In this
case we tell the filter code to pay attention to the parameter count, and it
should be 0.
The final
filter array looks like this:
// Rule filters
private
static final IRuleFilter[] MIFILTERS = {
new
MethodNameRuleFilter( “finalize”, true ),
new
ReturnTypeRuleFilter( “void”, true ),
new
ParameterCountRuleFilter( 0, true )
};
The final step
needed to complete step 1 of the planned algorithm is to perform the actual
filtering. At the end of the analyze() method add:
ASTHelper.satisfy( list,
MIFILTERS );
The easy
part of the rule is finished - now it gets a bit more complicated. We need to examine the lines of code within
the method and check if there is only one line. Remember that we have a list of
method declarations. We need to iterate
through these and check each of them – there should only be one item in the
list since Java does not support more than one method declaration with the same
signature, but let’s be safe and create a list iterator. Add these lines to the end of the analyze() method:
for( Iterator it = list.iterator(); it.hasNext(); ) {
MethodDeclaration md = (MethodDeclaration)it.next();
}
Every
method declaration contains a body (the statements inside the braces). Once we have a method declaration we can use
the getBody() method to get a Block (this is an ASTNode). The body contains a list of statements. We only care about method declarations with a
single statement. Add this code to the
end of the iterator loop:
Block block = md.getBody();
if( md.getBody().statements().size()
== 1 ) {
}
We’re getting
close. All that remains is to check the
statement to see if it is a call to super.finalize(). This isn’t as
obvious is it might sound. Within the
“if” statement, add this code:
List superList
= resource.getTypedNodeList( block, ASTNode.SUPER_METHOD_INVOCATION
);
if( superList.size() > 0 ) {
}
This needs
some explanation. The body of a method
declaration is made up of statements, which can be any kind of action (e.g. for
statement, if statement, or expression).
We need to look in the body to see if there are any super method
invocations, which are a specific type of statement. We do this by obtaining a list of ASTNodes within the block that have a type
SUPER_METHOD_INVOCATION. Since we
already know that the body has just one statement, this list of super
invocations will likely have 0 or 1 elements in it. If the list has 1 or more elements, then we
can assume that the body only makes a call to super.finalize().
The last
step is to generate a result in the analysis results view. The code review engine provides a simple API
to support this. Within the empty “if”
statement, add:
resource.generateResultsForASTNode( this, history.getHistoryId(), md.getName() );
The first
two parameters of this method call are always the same and are used to identify
the rule generating the result and the history collection where the result will
be placed. The third parameter indicates
the ASTNode that will be highlighted. In our case, for every matching method declaration
(the finalize() method) a result is created where the
name is highlighted.
The
completed rule appears as follows:
public class RuleFinalizerSuper extends AbstractAnalysisRule {
// Rule filters
private static final IRuleFilter[] MIFILTERS = {
new MethodNameRuleFilter(
"finalize", true ),
new ReturnTypeRuleFilter(
"void", true ),
new ParameterCountRuleFilter(
0, true )
};
public void
analyze( AnalysisHistory history)
{
// Extract the resource being analyzed
CodeReviewResource resource = (CodeReviewResource)getProvider().getProperty(
history.getHistoryId(), CodeReviewProvider.RESOURCE_PROPERTY );
List
list = resource.getTypedNodeList( resource.getResourceCompUnit(),
ASTNode.METHOD_DECLARATION );
ASTHelper.satisfy(list, MIFILTERS);
for( Iterator it = list.iterator();
it.hasNext(); ) {
MethodDeclaration md = (MethodDeclaration)it.next();
Block
block = md.getBody();
if( md.getBody().statements().size() == 1 )
{
List
superList = resource.getTypedNodeList( block,
ASTNode.SUPER_METHOD_INVOCATION );
if( superList.size() > 0 ) {
resource.generateResultsForASTNode( this,
history.getHistoryId(), md.getName()
);
}
}
}
}
}
Once the
rule class is completed, it needs to be described through the provided
extension points in the analysis framework so that the code review engine can
discover it. For this example we will
first create our own category. Then we will associate the rule with it.
Creating a
new category is a relatively simple task.
Open the example plug-in manifest created in the first part of this
section. Select the “Extensions” tab in
the editor and click the “Add…” button.
From the list select the org.eclipse.tptp.platform.analysis.core.analysisCategory
extension point.

In the
“Extension” tab you should now see a new extension point entry. Right click on it and add a new “analysisCategory”.
Fill in the extension details as follows:

Our
category has a parent category (codereview.java.j2sebestpractices), which nests
it within the “J2SE Best Practices” category in the user interface.
The class, DefaultAnalysisCategory, can be used to supply a predefined
category that provides all the functionality required for most categories you
will ever create.
Populating
the details above results in the following text being inserted into the plugin.xml file:
<extension
point="org.eclipse.tptp.platform.analysis.core.analysisCategory">
<analysisCategory
category="codereview.java.j2sebestpractices"
class="org.eclipse.tptp.platform.analysis.core.category.DefaultAnalysisCategory"
id="analysis.codereview.java.examples"
label="Examples"/>
</extension>
Next we
need to add our rule to the new category.
Add a new extension to the plugin as we did
for the category. This time select the org.eclipse.tptp.platform.analysis.core.analysisRule
extension point.
In the
“Extensions” tab right click on the rule extension and add a new “analysisRule”.
Populate the rule details as follows:

![]()
Enter a
label and description, which will appear in the user interface when the user
sees your rule. Note that these strings
should be localized for any production rule, but we will ignore this
requirement for now. The class field
contains the qualified name of the rule class we created earlier. Finally the category field contains the
identifier string for our Examples category.
Once the details are created, the plugin.xml
file will contain the following text:
<extension
point="org.eclipse.tptp.platform.analysis.core.analysisRule">
<analysisRule
category="analysis.codereview.java.examples"
class="org.eclipse.tptp.platform.analysis.codereview.java.examples.RuleFinalizerSuper"
id="analysis.codereview.java.examples.finalizerSuper"
label="Avoid
finalize() methods that only call super"/>
</extension>
From the
Eclipse “Run” select the “Run…” option and create a new “Eclipse
Application”. In the configuration panel
click the “Run” button. This invokes a
new runtime workbench to test our rule.
When the
workbench appears, import some Java source code into it so that there is some
code to review. Try to find some code
with a finalize() method that just calls super.finalize() so the rule can be tested.
With some
source code in the workbench, select the “Analysis…” option from the Eclipse
“Run” menu. In the dialog that appears
create a new analysis configuration and go to the “Domains” tab. Expand the “Java Code Review” branch of the
tree until the “Example” subcategory is visible.

With the new rule
selected click the “Analyze” button to start the code review process. If the source code in the workbench has code
with a finalize() method that fits our rule, the
Analysis Results view will contain a result.

In this section we have
examined some rules and more specifically how they use Rule Filters. The API supplies rule filters to support most
of the rules you would ever care to write.
The following is a list of the rule filters provided by TPTP. We won’t go into details here, but the JavaDoc for the Analysis API has more information on
how to use each of these rule filters.
|
ArgumentTypeRuleFilter |
ConstructorRuleFilter |
|
DeclaringClassRuleFilter |
EnclosingNodeRuleFilter |
|
ExceptionCountRuleFilter |
ExpressionRuleFilter |
|
ForInitializerCountRuleFilter |
ForUpdateCountRuleFilter |
|
FragmentCountRuleFilter |
IfElseStatementCountRuleFilter |
|
IfElseStatementRuleFilter |
IfThenStatementCountRuleFilter |
|
IfThenStatementRuleFilter |
ImplementedInterfaceRuleFilter |
|
LeftOperandRuleFilter |
MethodNameRuleFilter |
|
ModifierRuleFilter |
OperatorRuleFilter |
|
ParameterCountRuleFilter |
ParameterTypeRuleFilter |
|
ReturnTypeRuleFilter |
RightOperandRuleFilter |
|
SuperClassRuleFilter |
TypeRuleFilter |
There are also two rule
filters for logical operations (LogicalOrFilter and LogicalAndFilter).
You can use these to avoid duplication in rules that must compare to
similar array of filters that differ in a particular way. For example:
private
static final IRuleFilter[] MIFILTERS = {
new MethodNameRuleFilter( “testMethod”, true ),
new ReturnTypeRuleFilter( “void”,
true ),
new LogicalOrFilter(
new ParameterCountRuleFilter(
0, true ),
new ParameterCountRuleFilter(
1, true )
)
};
This code filters
methods name “testMethod()” that return void and contain 0 or 1 parameters.
It is possible that the
rule filter you need is not available.
The good news is that you can write your own rule filters without much
effort. The API provides an interface (IRuleFilter) that you can implement to plug into the filter
system. You can also extend the AbstractRuleFilter class which implements IRuleFilter. Below
is an example of one of the rule filters supplied with the Java code review
provider in TPTP:
public
class SuperClassRuleFilter
extends AbstractRuleFilter
{
private static final String SATISFIES_SUPER_CLASS = "satisfiesSuperClass"; //$NON-NLS-1$
private String superclassName;
/**
* Constructor
*
* @param superclassName
* The super class name by which to filter
* @param inclusive
* True if filtering will include only nodes that match the
* filter criteria, false to exclude matching nodes
*/
public SuperClassRuleFilter(
String superclassName, boolean inclusive ) {
super( inclusive );
this.superclassName = superclassName;
}
/**
* Determine if the node is satisfied by the specified filter rule
*
* @param node The ASTNode to test
* @return true if the node satisifes
the filtering rule
*/
public boolean satisfies( ASTNode
node ) {
try {
if (node.getNodeType()
== ASTNode.TYPE_DECLARATION)
{
//get the super class type. It is null if
this type declaration does not extend any type
Type
superClassType = ((TypeDeclaration)node).getSuperclassType();
if( superClassType != null ) {
return superClassType.resolveBinding().getQualifiedName().equals( superclassName );
}
}
else {
Log.severe (Messages.bind(Messages.RULE_FILTER_ERROR_,
new Object[]{ SATISFIES_SUPER_CLASS, node.getClass().getName()}));
}
} catch (NullPointerException e) {
// Do nothing
}
return false;
}
}
As you can see, the
code is straight forward. You need to
create a constructor to accept the values you need to make the node
evaluation. In the case of the example
shown here, the constructor accepts a string representing the superclass name.
Note that you should always accept the Boolean parameter in order to
support inclusionary/exclusionary filtering.
You must also implement
the satisfy() method, which is all cases will accept
the ASTNode under test.
Finally since rule
filters must be reliable, you should ensure that all exceptions are caught
within satisfy().
In the example here, the code catches NullPointerException
in case binding resolution in the code fails.
When your rule filter
is created you can use it like any of the predefined filters by placing it in
an IRuleFilter array and calling ASTHelper.satisfy() within your
rules
.
The rule we created in
the last section is fairly uncomplicated but it ignores many capabilities due
to its simplicity. This section
describes some of the more advanced features of rule creation. It describes how to add a severity indicator
to a rule and generic rule variables that let the user add configurable
parameters. It also describes detail
providers that allow rule authors to provide more detailed information about a
rule such as examples and solutions.
Once you have finished
building your rule you need to tell the user why it exists and how to fix the
problems it reports. You do this by
adding a help entry to the Eclipse help system to show code examples that cause
the rule to react, and some solutions.
Adding this kind of rule help is actually a relatively simple task if
you follow these basic steps:
-
Create a help document using an
HTML editor
-
Add a help=”my_help_id”
tag to your rule extension
-
Create or update your plug-in’s help.xml file to add a help
context for your rule
When creating a help
document, take the easy route. For
consistency you should make the help for your rule look the same as the help
for other rules, and the best way to achieve this is to clone the HTML file
from one of the rules included with TPTP.
By convention create a folder in your plug-in named “ruleinfo”
and place your html file inside it.
If you examine the ruleinfo folder included with the TPTP rules you may notice
that they all reference a cascading style sheet. To simplify the HTML in your rule help, you
will probably want one of these CSS files in your ruleinfo
folder as well. It looks like this:
A:link { color: #000099; text-decoration: underline; font-size: 11px; font-family: tahoma }
A:active { color: #000099; text-decoration: underline; font-size: 11px; font-family: tahoma }
A:visited { color: #000099; text-decoration: underline; font-size: 11px; font-family: tahoma }
A:hover { color: #000099; text-decoration: underline; font-size: 11px; font-family: tahoma }
.Text {font-family: tahoma; font-size: 11px; color: black}
.Italic {font-family: tahoma; font-size: 11px; font-style: italic}
.Strong {font-family: tahoma; font-size: 11px; font-weight: bold}
.Header {font-family: tahoma; font-size: 16px; color: black; font-weight: bold;
}
.SubHeader {font-family: tahoma; font-size: 11px; color: black; font-weight: bold;
}
.Code {font-family: tahoma; font-size: 11px; color: black;
font-weight: bold; background: #F7F8F9; }
.JavaType {font-family: tahoma; font-size: 11px; color: black; font-weight: bold;
}
.JavaKeyword {font-family: tahoma; font-size: 11px; color: #7F0055; font-weight: bold;
}
.JavaConst {font-family: tahoma; font-size: 11px; color: #2A00FF; font-weight: bold;
}
.JavaComment {font-family: tahoma; font-size: 11px; color: #3F7F5F
}
.XmlDefinition {font-family: tahoma; font-size: 11px; color: black;
text-decoration: italic }
.indent {padding-left: 10px;}
The next step required
to add help your rules is to create unique help id’s
for them. Once you decide on your id,
you can add a help attribute to your rule.
For example:
<analysisRule
category="codereview.java.category.awt"
class="org.eclipse.tptp.platform.analysis.codereview.java.rules.awt.RuleAwtPeer"
id="codereview.java.rules.awt.RuleAwtPeer"
label="%label.relrule.j2sebestpractices.awt.awtpeer"
severity="2"
help="org.eclipse.tptp.platform.analysis.codereview.java.rules.awtpeer">
</analysisRule>
Finally you need to
associate this id with a help context.
Do this by creating or updating the help.xml
file for your plug-in. Here is an
example of a simple help.xml:
<?xml version="1.0"
encoding="UTF-8"?>
<?eclipse version="3.0"?>
<contexts>
<context id="awt"
title="AWT">
<description>
This
category contains rules that identify problems
related to the Java Abstract Windowing Toolkit.
</description>
</context>
<context id="awtpeer"
title="Avoid using java.awt.peer interfaces">
<description>
java.awt.peer interfaces were designed to
handle platform-
specific graphics issues in Java. Since Java is meant to be
platform-independent, using java.awt.peer
directly is forbidden.
</description>
<topic href="\ruleinfo\j2sebestpractices\awt\AwtPeer.html"
label="Example"/>
</context>
</contexts>
There a couple of
notably features in this file. First,
notice that the first context describes a category. Yes, categories can have help
too and it is implemented the same way using a help attribute in the
extension. Second notice that all help
context id’s are named relative to the plug-in. This is why the id in the help.xml
is “awtpeer” while in the rule definition the fully
qualified name is used. Finally, ensure that the “topic” includes a relative
path to the HTML file that will be displayed when the user clicks the link on
the top-level help page (“Example” in this case).
When your help is
completed and correctly integrated, the user will be able to use the <F1>
key to display it in either the Analysis configuration dialog or in the
Analysis Results view.

The TPTP’s
static analysis user interface provides a “Details...” button that displays a
dialog allowing the user to view and change rule properties. In addition to any custom details provided by
the rule author, the details dialog also provides a pull-down list allowing the
user to change the default severity for the rule. The value of this parameter
controls the icon associated with the rule and its results in order to indicate
importance. There are three basic modes
of severity: recommendation, warning
and severe. They can be used to indicate to the user how
important it is to address the results produced. It is not mandatory, however you should
specify a default severity for any rules you create. This will help guide the user when results
are reported for your rules.

Use the wizard facilities in Eclipse to edit the analysisRule and set the severity field using the drop-down
list. A value of 0 set the default
severity as a recommendation, while 1 and 2 set warning and severe values
respectively. The resulting XML text is:
<analysisRule
category="analysis.codereview.java.examples"
class="org.eclipse.tptp.platform.analysis.codereview.java.examples.RuleFinalizerSuper"
description="finalize()
methods that call only super.finalize() are
inefficient and should be removed"
id="analysis.codereview.java.examples.finalizerSuper"
label="Avoid
finalize() methods that only call super"
severity=0>
</analysisRule>
The next time you
execute the runtime workbench you will notice that the example rule now has a
Properties tab and the current severity will be set to “Recommendation”. You can change the default value by adjusting
the “severity” field in the rule.
There will be
situations when you will need to allow the user to store custom data for a
rule. The analysis rule API allows you
to create custom variables and present them on the “Properties” tab when the
rule is selected.
To add a
new rule parameter, use the Eclipse extension editor to add a new “ruleParameter”.

Parameters are
name/value pairs with some additional information. The label field contains the text that the
user will see when viewing the parameters field in the user interface.
The type field
determines the data type for the value and is used to provide the correct field
editor and also field validation. Valid
values for this field are “string” or “integer”.
The style field
controls the type of UI control used to display the parameter. For example data can be represented as
standard text, a combo box or a checkbox.

If the style is a combo
box, then its values must also be added.
Add a new comboValue extension to the RuleParameter for each value you want to have in the list. ComboValue
extensions have only a value field that contains the text that the user will
see when the combo box is expanded.

Now that you know how
to add custom data parameters to a rule extension, you need to learn how to use
those values in your code. To access a
rule parameter in your rule’s analyze() method you can
use the following code fragment:
//
Extract required parameters
RuleParameter rv = this.getParameter( “parameterName” );
String
value = rv.getValue();
As you
begin writing rules, you will quickly realize that many rules are the very similar. For example, you may want rules that generate
results when code extends various Java classes.
Since the only fundamental difference between them is the name of the
class, it would be nice if there was a simple way to template a single rule
that handled all cases. Fortunately, the analysis API supports this.
By exploiting
rule parameters, we can create simple, flexible templates for rules. If you examine the Java core review rules you
will see several templates. For example,
the template that generates result for class extending is as follows:
public class Template_Avoid_Extending_Class
extends AbstractAnalysisRule
{
private static final String CLASS = "CLASS";
//$NON-NLS-1$
private String className;
/**
* Analyze this rule
*
* @param history A reference to the history record for this analysis
*/
public void
analyze( AnalysisHistory history) {
// Extract the resource being analyzed
CodeReviewResource resource = (CodeReviewResource)getProvider().getProperty(
history.getHistoryId(), CodeReviewProvider.RESOURCE_PROPERTY );
// Extract required variables
RuleParameter rv = this.getParameter( CLASS );
className = rv.getValue();
List
tdFilters = new ArrayList(1);
tdFilters.add(
new SuperClassRuleFilter(
className, true ) );
// Obtain a list
of string literals
List
list = resource.getTypedNodeList( resource.getResourceCompUnit(),
ASTNode.TYPE_DECLARATION );
ASTHelper.satisfy( list, tdFilters );
for( Iterator it = list.iterator();
it.hasNext(); ) {
TypeDeclaration td = (TypeDeclaration)it.next();
resource.generateResultsForASTNode( this, history.getHistoryId(),
td.getSuperclassType() );
}
}
}
Notice that the code makes use of a rule parameter called “CLASS” and uses
its value to check the superclass. The extension of an analysisRule
using this template looks like this:
<analysisRule
category="codereview.java.category.exceptions"
class="org.eclipse.tptp.platform.analysis.codereview.java.templates.Template_Avoid_Extending_Class"
description="%description.relrule.j2sebestpractices.exceptions.extenderror"
id="org.eclipse.tptp.platform.analysis.codereview.java.rules.exceptions.RuleExceptionsExtendError"
label="%label.template.avoid.extending.class"
severity="0">
<ruleParameter name="CLASS" value="java.lang.Error" />
</analysisRule>
Notice the ruleParameter line, which defines the
“CLASS” parameter and assigns a value, java.lang.Error,
to it. Since this parameter does not
have a label, it will not appear in the rule’s “Properties” tab. This is how invisible parameters are created.
The other notable line in this test is the class definition, which points to
the template class instead of a one you might create.
Templates can also be exploited by the end user to define new rules without
writing any code. If you have previously
defined a rule class that can be used as a template you can use the analysisRuleTemplate extension point to expose it to the
end user. For example consider the
following extension example:
<extension
point="org.eclipse.tptp.platform.analysis.core.analysisRuleTemplate">
<ruleTemplate
provider="codereview.java.analysisProvider"
class="org.eclipse.tptp.platform.analysis.templates.Template_Avoid_Extending_Class"
id=" codereview.java.templates.Template_Avoid_Extending_Class"
label="Avoid
extending <CLASS>">
<ruleParameter
label="Class
Name:"
description="Enter a
qualified Class Name"
name="CLASS"
style="text"
type="string"/>
</ruleTemplate>
</extension>
This example defines a new rule template that allows the end user to define
new rules to report and code that extends a given class. This example uses a rule parameter to declare
and describe a field where the user can type a qualified class name.
As described in Part 1 of this series, any rule templates you define will
appear in the “New Rule…” wizard in the Eclipse preferences. You can find these by selecting the
“Window->Preferences option from the main Eclipse menu. Then in the preference tree select the
“Analysis->Custom Rules and Categories” page.
Sometimes
the results of a rule are so easy to fix that you can provide a simple repair
procedure for the user. This article
will not cover the intricacies of using JDT to manipulate a source file – you
can find other documents that discuss JDT; however the basic interface
requirements will be discussed here. The
analysisRule extension offers a “quickfix”
sub-extension point in which you can place the unique identifier for your quickfix. A sample
follows:
<analysisRule
category="codereview.java.category.exceptions"
class="org.eclipse.tptp.platform.analysis.codereview.java.rules.examples.MyExampleRule"
description="Example
rule"
id="org.eclipse.tptp.platform.analysis.codereview.java.rules.examples.MyExampleRule"
label="Example
rule"
severity="0">
<quickfix id="
org.eclipse.tptp.platform.analysis.codereview.java.rules.examples.MyExampleRule.quickfix"
/>
</analysisRule>
Note that
if violations of your rule can be resolved in more than one way you can place
more than one quickfix extension in the rule
definition. In the results view each quickfix you specify will appear in the result context
menu.
A QuickFix is of course an extension point available from the
analysis API. To define a new one, use
the analysisRuleQuickFix extension point. This extension requires that you define a
unique id (the one used in the rule definition), a class to implement the quickfix, and a label.
Once it is defined, it will resemble the follow plugin.xml
fragment:
<extension
point="org.eclipse.tptp.platform.analysis.core.analysisRuleQuickFix">
<analysisRuleQuickFix
class="com.ibm.xtools.analysis.codereview.java.j2se.bestpractices.declaration.quickfix.AvoidExplicitStringLiteralsSolution"
label="%label.stringLiteral.quickfix"
id="analysis.codereview.java.j2sebestpractices.RuleDeclarationDeclareCostants.quickFix"/>
The label
field in this extension is optional. If
you omit this value the analysis framework will automatically set the quickfix label to “QuickFix”. The only time you really need to specify a
label is in cases where you have more than one quickfix
for the rule.
The quickfix class you implement must extend the AbstractAnalysisQuickFix class supplied by the API and
strictly speaking, is only required to implement a quickfix() method that
receives the analysis result being fixed.
Within this quickfix method, you can use JDT
to perform any required modifications. To assist with the implementation of Java
code review quick fixes, the analysis API provides the JavaCodeReviewQuickFix
base class. This class does all of the
heavy lifting needed to fix a problem, and you should extend it.
The
following code implements a simple quick fix for one of the rules supplied with
TPTP:
public class RuleComparisonReferenceEqualityQuickFix extends
JavaCodeReviewQuickFix {
private static final String NOT = "!"; //$NON-NLS-1$
private static final String CLOSE_PAREN = " )";
//$NON-NLS-1$
private static final String EQUALS = ".equals( "; //$NON-NLS-1$
private InfixExpression infixExpr = null;
public TextEdit fixCodeReviewResult ( ASTNode theNode, IDocument docToChange ) {
// reset
infixExpr = null;
if(theNode instanceof InfixExpression)
{
infixExpr = (InfixExpression)theNode;
}
if(infixExpr != null) {
ASTNode enclosingClass = this.getEnclosingClass(infixExpr);
AST
ast = enclosingClass.getAST();
ASTRewrite rewriter = ASTRewrite.create( ast );
Expression
left = infixExpr.getLeftOperand();
Expression
right = infixExpr.getRightOperand();
Operator
op = infixExpr.getOperator();
StringBuffer replaceString = new StringBuffer(left.toString());
replaceString.append(EQUALS);
replaceString.append(right.toString());
replaceString.append(CLOSE_PAREN);
ASTNode methodInv = (Operator.NOT_EQUALS.equals( op )) ?
rewriter.createStringPlaceholder(NOT + replaceString.toString(), ASTNode.METHOD_INVOCATION)
:
rewriter.createStringPlaceholder(replaceString.toString(), ASTNode.METHOD_INVOCATION);
rewriter.replace(infixExpr, methodInv, null);
TextEdit edits = new MultiTextEdit();
edits.addChild(rewriter.rewriteAST(docToChange, null));
return edits;
}
return null;
}
/*
* Returns the ASTNode
of type TYPE_DECLARATION or ANONYMOUS_CLASS_DECLARATION
* that encloses the
given node.
* It returns null if there is not enclosing
node of those types
*/
private ASTNode getEnclosingClass( ASTNode node )
{
ASTNode currentNode = node;
while ( currentNode !=
null &&
currentNode.getNodeType() != ASTNode.TYPE_DECLARATION
&&
currentNode.getNodeType() != ASTNode.ANONYMOUS_CLASS_DECLARATION
) {
currentNode = currentNode.getParent();
}
return currentNode;
}
}
Suppose you
have created many rules of your own and have scattered them over several
different categories. If
your rules fit into a particular theme (i.e. Security), it may be desirable to
allow the user to quickly select them without sifting through the rule tree.
Fortunately
you can achieve this simply and without writing any code at all using the
extension points provided by the static analysis framework. This notion is known as a Rule Set and can be implemented by
adding analysisRuleSet extensions to your plugin.xml. Here is
an example:
<extension
point="org.eclipse.tptp.platform.analysis.core.analysisRuleSet">
<analysisRuleSet
id="analysis.codereview.java.ruleset.quick"
label="My Rule
Set">
<analysisRuleSetRule id="codereview.java.rules.awt.RuleAwtPeer"/>
<analysisRuleSetRule id="codereview.java.threads.whilesleep"/>
<analysisRuleSetCategory id="codereview.java.j2sebestpractices.loops"/>
</analysisRuleSet>
</extension>
In this
example the defined rule set includes two separate rules and one entire
category. Note that including a category
will automatically include any of its contained rules and categories. The id used in the analysisRuleSetRule
and analysisRuleSetCategory entries is the unique
identifier that was defined for the rules and categories respectively.
Also note
that rule sets are additive – if you define two rule set extensions with the
same id then the rule set will include all defined elements of both
extensions. In this case however only
one of the extensions should define a value for the label.
Rule sets
will appear in the “Rules” tab of the analysis input dialog. The user can browse the known rule sets using
the pull-down list and select the desired item.
When the “Set” button is clicked all rules and categories in the rule
set will be selected in the rule tree.

In part 2 of this tutorial, we looked at the inner workings of the analysis
API as it applies to writing new code review rules for Java. We discussed the core concepts of creating
categories and rule specification in the plugin.xml
file, then wrote a class to create a basic rule. Finally, we covered the concepts of rule
severity, custom rule parameters, and rule templating. You should now have enough knowledge to write
your own rules.
In the third installment in this series we will dive much deeper into the analysis API and use it to create new analysis providers, custom categories, and result viewers.