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.
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.
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:
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:
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.
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:
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); } }
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.
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. } }
To compile your custom rule, you need to add the following JARs to your classpath:
Once the rule class is ready, it may be added to AppPerfect Java Code Test by following these steps: