Creating Custom Rules

While AppPerfect Java Code Test provides a rich set of pre-defined coding rules, at times you may find it necessary to define your own custom rules. This chapter describes the steps needed to create a custom rule.

Let us assume that you want to add a rule for checking if a local integer variable is starting with an underscore (_). If not, then throw an exception.

Process Overview

Write rule for DOM Tree, not code

The first thing to note before coding the rule is that the code to be checked is not passed as-is to the rule, but as its DOM tree. AppPerfect Code Test Engine parses the code to be checked to create its DOM tree which is then passed to the rule class for analysis.

This information is required for correctly coding rules.

Now before we implement the rule, let us first consider a piece of code and its DOM tree.

Code:

public class Sample
{
      public void foo()
      {
        int i = 0;
        int _j = 0;
        String _k = "";
        i = i + 1;
        _j--;
        _k = "AppPerfect";

        System.out.println(_k + i + " " + _j);
      }
}

DOM Tree:

   NODE TYPE=COMPILATION_UNIT    STRINGREPRESENTATION=public class Sample {   public void foo(){     int i=0;     int _j=0;     String _k="";     i=i + 1;     _j--;     _k="AppPerfect";     System.out.println(_k + i + " "+ _j);   } }
      NODE TYPE=TYPE_DECLARATION    STRINGREPRESENTATION=public class Sample {   public void foo(){     int i=0;     int _j=0;     String _k="";     i=i + 1;     _j--;     _k="AppPerfect";     System.out.println(_k + i + " "+ _j);   } }
         NODE TYPE=MODIFIER    STRINGREPRESENTATION=public
         NODE TYPE=SIMPLE_NAME    STRINGREPRESENTATION=Sample
         NODE TYPE=METHOD_DECLARATION    STRINGREPRESENTATION=public void foo(){   int i=0;   int _j=0;   String _k="";   i=i + 1;   _j--;   _k="AppPerfect";   System.out.println(_k + i + " "+ _j); }
            NODE TYPE=MODIFIER    STRINGREPRESENTATION=public
            NODE TYPE=PRIMITIVE_TYPE    STRINGREPRESENTATION=void
            NODE TYPE=SIMPLE_NAME    STRINGREPRESENTATION=foo
            NODE TYPE=BLOCK    STRINGREPRESENTATION={   int i=0;   int _j=0;   String _k="";   i=i + 1;   _j--;   _k="AppPerfect";   System.out.println(_k + i + " "+ _j); }
               NODE TYPE=VARIABLE_DECLARATION_STATEMENT    STRINGREPRESENTATION=int i=0;
                  NODE TYPE=PRIMITIVE_TYPE    STRINGREPRESENTATION=int
                  NODE TYPE=VARIABLE_DECLARATION_FRAGMENT    STRINGREPRESENTATION=i=0
                     NODE TYPE=SIMPLE_NAME    STRINGREPRESENTATION=i
                     NODE TYPE=NUMBER_LITERAL    STRINGREPRESENTATION=0
               NODE TYPE=VARIABLE_DECLARATION_STATEMENT    STRINGREPRESENTATION=int _j=0;
                  NODE TYPE=PRIMITIVE_TYPE    STRINGREPRESENTATION=int
                  NODE TYPE=VARIABLE_DECLARATION_FRAGMENT    STRINGREPRESENTATION=_j=0
                     NODE TYPE=SIMPLE_NAME    STRINGREPRESENTATION=_j
                     NODE TYPE=NUMBER_LITERAL    STRINGREPRESENTATION=0
               NODE TYPE=VARIABLE_DECLARATION_STATEMENT    STRINGREPRESENTATION=String _k="";
                  NODE TYPE=SIMPLE_TYPE    STRINGREPRESENTATION=String
                     NODE TYPE=SIMPLE_NAME    STRINGREPRESENTATION=String
                  NODE TYPE=VARIABLE_DECLARATION_FRAGMENT    STRINGREPRESENTATION=_k=""
                     NODE TYPE=SIMPLE_NAME    STRINGREPRESENTATION=_k
                     NODE TYPE=STRING_LITERAL    STRINGREPRESENTATION=""
               NODE TYPE=EXPRESSION_STATEMENT    STRINGREPRESENTATION=i=i + 1;
                  NODE TYPE=ASSIGNMENT    STRINGREPRESENTATION=i=i + 1
                     NODE TYPE=SIMPLE_NAME    STRINGREPRESENTATION=i
                     NODE TYPE=INFIX_EXPRESSION    STRINGREPRESENTATION=i + 1
                        NODE TYPE=SIMPLE_NAME    STRINGREPRESENTATION=i
                        NODE TYPE=NUMBER_LITERAL    STRINGREPRESENTATION=1
               NODE TYPE=EXPRESSION_STATEMENT    STRINGREPRESENTATION=_j--;
                  NODE TYPE=POSTFIX_EXPRESSION    STRINGREPRESENTATION=_j--
                     NODE TYPE=SIMPLE_NAME    STRINGREPRESENTATION=_j
               NODE TYPE=EXPRESSION_STATEMENT    STRINGREPRESENTATION=_k="AppPerfect";
                  NODE TYPE=ASSIGNMENT    STRINGREPRESENTATION=_k="AppPerfect"
                     NODE TYPE=SIMPLE_NAME    STRINGREPRESENTATION=_k
                     NODE TYPE=STRING_LITERAL    STRINGREPRESENTATION="AppPerfect"
               NODE TYPE=EXPRESSION_STATEMENT    STRINGREPRESENTATION=System.out.println(_k + i + " "+ _j);
                  NODE TYPE=METHOD_INVOCATION    STRINGREPRESENTATION=System.out.println(_k + i + " "+ _j)
                     NODE TYPE=QUALIFIED_NAME    STRINGREPRESENTATION=System.out
                        NODE TYPE=SIMPLE_NAME    STRINGREPRESENTATION=System
                        NODE TYPE=SIMPLE_NAME    STRINGREPRESENTATION=out
                     NODE TYPE=SIMPLE_NAME    STRINGREPRESENTATION=println
                     NODE TYPE=INFIX_EXPRESSION    STRINGREPRESENTATION=_k + i + " "+ _j
                        NODE TYPE=SIMPLE_NAME    STRINGREPRESENTATION=_k
                        NODE TYPE=SIMPLE_NAME    STRINGREPRESENTATION=i
                        NODE TYPE=STRING_LITERAL    STRINGREPRESENTATION=" "
                        NODE TYPE=SIMPLE_NAME    STRINGREPRESENTATION=_j

The rule we are writing requires that we check all places where there are new variable declarations. From the above, we can observe that

int i = 0;

is represented by the DOM sub-tree:

NODE TYPE=VARIABLE_DECLARATION_STATEMENT    STRINGREPRESENTATION=int i=0;
   NODE TYPE=PRIMITIVE_TYPE    STRINGREPRESENTATION=int
   NODE TYPE=VARIABLE_DECLARATION_FRAGMENT    STRINGREPRESENTATION=i=0
      NODE TYPE=SIMPLE_NAME    STRINGREPRESENTATION=i
      NODE TYPE=NUMBER_LITERAL    STRINGREPRESENTATION=0

This means the rule should get triggered for every VARIABLE_DECLARATION_FRAGMENT. The rule will then check for the child-element: SIMPLE_NAME and throw a violation if this name does not start with an underscore.

Sample Rule

Lets follow these steps to create our sample custom rule after understanding that we have to write the rule for the DOM tree of the code:

Step 1: Interfaces and Methods to be implemented

Every CodeTest rule needs to implement the interface: com.appperfect.codeanalyzer.engine.rules.IRule. (view JavaDoc)

In case the rule is configurable, it should implement the sub interface: com.appperfect.codeanalyzer.engine.rules.IConfigurableRule.

Our sample custom rule is not configurable, so we will implement the IRule interface only.

This implies that we have to implement the following three methods:

Step 2: Code for getASTNodeTypes()

AppPerfect Java Code Test creates an Abstract Syntax Tree with help of the eclipse plugin - org.eclipse.jdt.core.dom for each source file (java or jsp) it analyzes. This way a rule specifies the node type/s for which that rule needs to be evaluated. As the AppPerfect Code Test Engine traverses the AST, it notifies the rule clases when any of the specified nodes are found.

In our example, we know that the rule should be triggered only for VARIABLE_DECLARATION_FRAGMENT nodes and nothing else, so we implement the method getASTNodeTypes() as follows:

public int[] getASTNodeTypes()
{
  return new int[]{ASTNode.VARIABLE_DECLARATION_FRAGMENT};
}

This way we specify to the rule engine that the rule needs to be evaluated for every node of kind VARIABLE_DECLARATION_FRAGMENT. Therefore, if there is a large user class, not containing any variable declarations, then the evaluate() method will not be called or we can say that the rule will not be invoked.

Step 3: Code for evaluate()

When a specified node is found, the engine calls the evaluate() method of the rule class. The rule examines the node to see if it meets certain criteria, if not it reports a violation by calling vl.violationFound(node);

If a rule cannot be evaluated just by checking a single node but needs to further traverse the tree starting at any node then the rule should:

  1. Implement the interface com.appperfect.codeanalyzer.engine.ITokenListener.
  2. Call EventRuleEngine.traverse(node, nodeTypes, this), where node is the node at which the traversing should begin, nodeTypes is an int[] listing the node types (ASTNode constants) for which the rule should be notified. The last argument is the ITokenListener, which is the rule class itself in most cases.
  3. Implement the method reportTokenFound(ASTNode node), through which the engine notifies this listener when any of the given node type/s are found.

Now we will write the code for the evaluate() method for our example.

The evaluate() method will do all the work. It will extract the variables name, check if it starts with an underscore, if not, will throw a violation.

public void evaluate(ASTNode node, IViolationListener vl)
{
    VariableDeclarationFragment vdf = (VariableDeclarationFragment) node;
    SimpleName sn = vdf.getName();
    String variableName = sn.getIdentifier();

    if (!variableName.startsWith("_"))
    {
       vl.violationFound(node);
    }
}

Step 4: Code for reset()

The reset() method is called by the AppPerfect Java Code Test engine before calling evaluate(). This is because we create only one instance of the rule class and repeatedly call evaluate(). So this method should clean up the class variables etc. if used by the rule. In our case we do not have any class variables etc. so may provide an empty implementation of it.

Step 5: Complete Code

So here we have the complete code for our sample custom rule:

import com.appperfect.codeanalyzer.engine.rules.*;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;

public class LocalIntVariableShouldStartWithUnderScore implements IRule
{

      public int[] getASTNodeTypes()
      {
           return new int[]{ASTNode.VARIABLE_DECLARATION_FRAGMENT};
      }

      public void evaluate(ASTNode node, IViolationListener vl)
      {
           VariableDeclarationFragment vdf = (VariableDeclarationFragment) node;
           SimpleName sn = vdf.getName();
           String variableName = sn.getIdentifier();

           if (!variableName.startsWith("_"))
           {
                 vl.violationFound(node);
           }
      }

      public void reset()
      {
            //this methos is used to reset class variables between two calls to evaluate, since we have no
            //class variables there is no need to reset anything.
      }
}

JARs requried for compilation

To compile your custom rule, you need to add the following JARs to your classpath:

  1. %CodeTest_HOME%\apcodetools\eclipse\plugins\com.appperfect.codeanalyzer_<version>\apcodeanalyzer.jar
  2. %CodeTest_HOME%\eclipse\plugins\org.eclipse.jdt.core_<version>.jar
  3. %CodeTest_HOME%\apcodetools\eclipse\plugins\com.appperfect.teststudio_<version>\apcommon.jar

Updating Rules Manager

Once the rule class is ready, it may be added to AppPerfect Java Code Test by following these steps:

  1. Invoke the Rules Manager by clicking Project -> Java Code Test -> Rules Manager... in the menubar.
  2. Select File -> New Rule...
  3. Specify the rule class in the 'Validators' field. Specify the classpath for the rule.
  4. Specify the ID and Name for the rule.