Here's the example code for PMD Applied. Note that I'm keeping it up to date, so it may look slightly different than the code in the book. However, it will work with the latest PMD release (v3.5 as of this writing), which is probably the more important thing. Comments/questions are welcome!
This chapter works through an example implementation of a rule that catches empty if
statements: (download)
1 package net.sourceforge.pmd.rules; 2 3 import net.sourceforge.pmd.AbstractRule; 4 import net.sourceforge.pmd.ast.ASTBlock; 5 import net.sourceforge.pmd.ast.ASTEmptyStatement; 6 import net.sourceforge.pmd.ast.ASTIfStatement; 7 import net.sourceforge.pmd.ast.ASTStatement; 8 import net.sourceforge.pmd.ast.Node; 9 10public class EmptyIf extends AbstractRule { 11 12 public Object visit(ASTIfStatement node, Object data) { 13 ASTStatement stmt = (ASTStatement)node.jjtGetChild(1); 14 Node stmtChild = stmt.jjtGetChild(0); 15 if (stmtChild instanceof ASTEmptyStatement) { 16 addViolation(data, node); 17 } else if (stmtChild instanceof ASTBlock && stmtChild.jjtGetNumChildren() == 0) { 18 addViolation(data, node); 19 } 20 return super.visit(node, data); 21 } 22} 23
Here's the XPath expression. The ruleset definition for the XPath example is included with PMD; you can see it in the basic
ruleset here.
Section 7.7 has an example of how to use the symbol table to extract variable usages. Here's an updated version that shows how to get both the type image and the location of a variable's usages: (download)
1 package net.sourceforge.pmd.rules; 2 3 import net.sourceforge.pmd.AbstractRule; 4 import net.sourceforge.pmd.symboltable.NameOccurrence; 5 import net.sourceforge.pmd.ast.ASTVariableDeclaratorId; 6 7 import java.util.Iterator; 8 9 public class SymbolTableExample extends AbstractRule { 10 11 public Object visit(ASTVariableDeclaratorId node, Object data) { 12 if (!node.getNameDeclaration().getImage().equals("x")) { 13 return data; 14 } 15 System.out.println("Variable x is of type '" + node.getNameDeclaration().getTypeImage() + "'"); 16 for (Iterator i = node.getUsages().iterator(); i.hasNext();) { 17 NameOccurrence occurrence = (NameOccurrence)i.next(); 18 System.out.println("The variable x is used at line " + occurrence.getLocation().getBeginLine()); 19 } 20 return data; 21 } 22 23} 24
When run on the example code in section 7.7, the above code would output this:
Variable x is of type 'int' The variable x is used at line 4
This chapter shows how to create a custom Renderer
class that logs to a PostgreSQL database. Note that there's now an AbstractRenderer
class that you can extend to get an implementation of the showSuppressedWarnings
method that's been added: (download)
1 package net.sourceforge.pmd.renderers; 2 3 import net.sourceforge.pmd.Report; 4 import net.sourceforge.pmd.RuleViolation; 5 6 import java.sql.Connection; 7 import java.sql.DriverManager; 8 import java.sql.Statement; 9 import java.text.MessageFormat; 10import java.util.Iterator; 11import java.util.Properties; 12 13public class PGReport extends AbstractRenderer implements Renderer { 14 15 public String render(Report report) { 16 try { 17 Class.forName("org.postgresql.Driver"); 18 String url = "jdbc:postgresql://localhost/pmd"; 19 Properties props = new Properties(); 20 props.setProperty("user","tom"); 21 Connection conn = DriverManager.getConnection(url, props); 22 Statement st = conn.createStatement(); 23 for (Iterator i = report.iterator(); i.hasNext(); ) { 24 RuleViolation rv = (RuleViolation)i.next(); 25 String clean_desc = rv.getDescription().replace('\'', '\"'); 26 Object[] values = {rv.getFilename(), clean_desc, String.valueOf(rv.getNode().getBeginLine())}; 27 String format = "INSERT INTO problems VALUES (''{0}'', ''{1}'', {2})"; 28 String sql = MessageFormat.format(format, values); 29 st.executeUpdate(sql); 30 } 31 st.close(); 32 conn.close(); 33 34 String result = "Report creation succeeded!"; 35 if (showSuppressedViolations) { 36 result = result + " Incidentally, there are some suppressed violations; those weren't inserted into the database."; 37 } 38 return result; 39 } catch (Exception e){ 40 return "Report created failed: " + e.getMessage(); 41 } 42 } 43} 44
Note that when adding the RuleViolation
to the Report
we use RuleViolation.getNode().getBeginLine()
vs RuleViolation.getLine()
; that's because the latter version was deprecated in PMD 3.3 and duly removed in PMD 3.4.
You can download the PostgreSQL type 4 JDBC driver from here. In the book I used postgresql-8.0-311.jdbc3.jar
but this code will probably work with almost any version; it's pretty straightforward.
This chapter also shows how to adapt the copy/paste detector (CPD) to a new language. The RTF JavaCC grammar is here. You can download RTFLanguage.java
; HTMLized source is below:
1 package net.sourceforge.pmd.cpd; 2 3 import java.io.File; 4 import java.io.FilenameFilter; 5 6 public class RTFLanguage implements Language { 7 8 public static class RTFFileOrDirectoryFilter implements FilenameFilter { 9 public boolean accept(File dir, String filename) { 10 return filename.endsWith("rtf") || (new File(dir.getAbsolutePath() + System.getProperty("file.separator") + filename).isDirectory()); 11 } 12 } 13 14 public Tokenizer getTokenizer() { 15 return new RTFTokenizer(); 16 } 17 18 public FilenameFilter getFileFilter() { 19 return new RTFLanguage.RTFFileOrDirectoryFilter(); 20 } 21} 22
You can download RTFTokenizer.java
; HTMLized source is below:
1 package net.sourceforge.pmd.cpd; 2 3 import net.sourceforge.pmd.cpd.rtfast.RTFParserTokenManager; 4 import net.sourceforge.pmd.cpd.rtfast.SimpleCharStream; 5 import net.sourceforge.pmd.cpd.rtfast.Token; 6 7 import java.io.StringReader; 8 9 public class RTFTokenizer implements Tokenizer { 10 11 public void tokenize(SourceCode tokens, Tokens tokenEntries) { 12 StringBuffer sb = tokens.getCodeBuffer(); 13 new StringReader(sb.toString()))); 14 Token currToken = tokenMgr.getNextToken(); 15 while (currToken.image.length() > 0) { 16 String image = currToken.image; 17 tokenEntries.add(new TokenEntry(image, tokens.getFileName(), currToken.beginLine)); 18 currToken = tokenMgr.getNextToken(); 19 } 20 tokenEntries.add(TokenEntry.getEOF()); 21 } 22} 23 24
Thanks to the folks at Polygon Enterprises for the nifty Java2HTML utility used to make these code samples look nice!