/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.rules; import net.sourceforge.pmd.AbstractRule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.ast.ASTArgumentList; import net.sourceforge.pmd.ast.ASTCompilationUnit; import net.sourceforge.pmd.ast.ASTLiteral; import net.sourceforge.pmd.ast.ASTVariableInitializer; import net.sourceforge.pmd.ast.Node; import net.sourceforge.pmd.ast.SimpleNode; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.LineNumberReader; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; public class AvoidDuplicateLiteralsRule extends AbstractRule { public static class ExceptionParser { private static final char ESCAPE_CHAR = '\\'; private char delimiter; public ExceptionParser(char delimiter) { this.delimiter = delimiter; } public Set parse(String in) { Set result = new HashSet(); StringBuffer currentToken = new StringBuffer(); boolean inEscapeMode = false; for (int i=0; i<in.length(); i++) { if (inEscapeMode) { inEscapeMode = false; currentToken.append(in.charAt(i)); continue; } if (!inEscapeMode && in.charAt(i) == ESCAPE_CHAR) { inEscapeMode = true; continue; } if (in.charAt(i) == delimiter) { result.add(currentToken.toString()); currentToken = new StringBuffer(); } else { currentToken.append(in.charAt(i)); } } if (currentToken.length()>0) { result.add(currentToken.toString()); currentToken = new StringBuffer(); } return result; } } private static final char DEFAULT_SEPARATOR = ','; private static final String EXCEPTION_LIST_PROPERTY = "exceptionlist"; private static final String SEPARATOR_PROPERTY = "separator"; private static final String EXCEPTION_FILE_NAME_PROPERTY = "exceptionfile"; private Map literals = new HashMap(); private Set exceptions = new HashSet(); public Object visit(ASTCompilationUnit node, Object data) { literals.clear(); if (hasProperty(EXCEPTION_LIST_PROPERTY)) { ExceptionParser p; if (hasProperty(SEPARATOR_PROPERTY)) { p = new ExceptionParser(getStringProperty(SEPARATOR_PROPERTY).charAt(0)); } else { p = new ExceptionParser(DEFAULT_SEPARATOR); } exceptions = p.parse(getStringProperty(EXCEPTION_LIST_PROPERTY)); } else if (hasProperty(EXCEPTION_FILE_NAME_PROPERTY)) { exceptions = new HashSet(); try { LineNumberReader reader = new LineNumberReader(new BufferedReader(new FileReader(new File(getStringProperty(EXCEPTION_FILE_NAME_PROPERTY))))); String line = null; while ((line = reader.readLine()) != null) { exceptions.add(line); } reader.close(); } catch (IOException ioe) { ioe.printStackTrace(); } } super.visit(node, data); int threshold = getIntProperty("threshold"); for (Iterator i = literals.keySet().iterator(); i.hasNext();) { String key = (String) i.next(); List occurrences = (List) literals.get(key); if (occurrences.size() >= threshold) { Object[] args = new Object[]{new Integer(occurrences.size()), new Integer(((SimpleNode) occurrences.get(0)).getBeginLine())}; String msg = MessageFormat.format(getMessage(), args); RuleContext ctx = (RuleContext) data; ctx.getReport().addRuleViolation(createRuleViolation(ctx, ((SimpleNode) occurrences.get(0)).getBeginLine(), msg)); } } return data; } public Object visit(ASTLiteral node, Object data) { if (!hasAtLeast4Parents(node) || (!fourthParentIsAnArgList(node) && !fourthParentIsAVariableInitializer(node))) { return data; } // just catching strings of 3 chars or more for now - no numbers if (node.getImage() == null || node.getImage().indexOf('\"') == -1 || node.getImage().length() < 3) { return data; } // skip any exceptions if (exceptions.contains(node.getImage().substring(1, node.getImage().length()-1))) { return data; } if (literals.containsKey(node.getImage())) { List occurrences = (List) literals.get(node.getImage()); occurrences.add(node); } else { List occurrences = new ArrayList(); occurrences.add(node); literals.put(node.getImage(), occurrences); } return data; } private boolean fourthParentIsAVariableInitializer(ASTLiteral node) { return node.jjtGetParent().jjtGetParent().jjtGetParent().jjtGetParent() instanceof ASTVariableInitializer; } private boolean fourthParentIsAnArgList(ASTLiteral node) { return node.jjtGetParent().jjtGetParent().jjtGetParent().jjtGetParent() instanceof ASTArgumentList; } private boolean hasAtLeast4Parents(Node node) { Node currentNode = node; for (int i = 0; i < 4; i++) { if (currentNode instanceof ASTCompilationUnit) { return false; } currentNode = currentNode.jjtGetParent(); } return true; } }