Eclipse Article

TPTP Static Analysis Tutorial Part 2

Enhancing Java Code Review

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 Steve Gutz and Orlando Marquez, IBM
April 18, 2006


Introduction

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.

 

JDT Overview

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.

 

Java Analysis Extension Points

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.

 

Anatomy of a Code Review Rule

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.

 

Writing a Rule from scratch

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. 

Create a Plug-in

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.coreplugin contains the basic static analysis framework API and extension points.

The “org.eclipse.tptp.platform.analysis.codereview.javaplugin 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.

 

Create a Rule Class

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() );

                                                                }

                                                }

                                }

 

                }

}

Extension Point Creation

 

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>

 

Run the Rule

 

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. 

 

 

 

Rule Filters Revisited

 

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

.

Enhancing the Rule

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.

 

Rule Help

 

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.

 

 

Supporting Rule Severity

 

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.

 

Generic Rule Parameters

 

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();

 

Rule Templates

 

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.

 

Custom Rules

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.

Quickfix Support

 

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;

                }             

 

}

 

Rule Set Support

 

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.

 

Conclusion

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.