001: package net.sourceforge.pmd.rules;
002:
003: import net.sourceforge.pmd.AbstractRule;
004: import net.sourceforge.pmd.ast.ASTAdditiveExpression;
005: import net.sourceforge.pmd.ast.ASTLiteral;
006: import net.sourceforge.pmd.ast.ASTPrimaryExpression;
007: import net.sourceforge.pmd.ast.ASTVariableDeclaratorId;
008: import net.sourceforge.pmd.ast.SimpleNode;
009: import net.sourceforge.pmd.symboltable.NameOccurrence;
010:
011: import java.util.List;
012:
013: /**
014: * Detects and flags the occurrences of specific method calls against an instance of
015: * a designated class. I.e. String.indexOf. The goal is to be able to suggest more
016: * efficient/modern ways of implementing the same function.
017: *
018: * Concrete subclasses are expected to provide the name of the target class and an
019: * array of method names that we are looking for. We then pass judgement on any literal
020: * arguments we find in the subclass as well.
021: *
022: * @author Brian Remedios
023: * @version $Revision: 5017 $
024: */
025: public abstract class AbstractPoorMethodCall extends AbstractRule {
026:
027: /**
028: * The name of the type the method will be invoked against.
029: * @return String
030: */
031: protected abstract String targetTypename();
032:
033: /**
034: * Return the names of all the methods we are scanning for, no brackets or
035: * argument types.
036: *
037: * @return String[]
038: */
039: protected abstract String[] methodNames();
040:
041: /**
042: * Returns whether the string argument at the stated position being sent to
043: * the method is ok or not. Return true if you want to record the method call
044: * as a violation, false otherwise.
045: *
046: * @param argIndex int
047: * @param arg String
048: * @return boolean
049: */
050: protected abstract boolean isViolationArgument(int argIndex,
051: String arg);
052:
053: /**
054: * Returns whether the name occurrence is one of the method calls
055: * we are interested in.
056: *
057: * @param occurrence NameOccurrence
058: * @return boolean
059: */
060: private boolean isNotedMethod(NameOccurrence occurrence) {
061:
062: if (occurrence == null)
063: return false;
064:
065: String methodCall = occurrence.getImage();
066: String[] methodNames = methodNames();
067:
068: for (int i = 0; i < methodNames.length; i++) {
069: if (methodCall.indexOf(methodNames[i]) != -1)
070: return true;
071: }
072: return false;
073: }
074:
075: /**
076: * Returns whether the value argument is a single character string.
077: *
078: * @param value String
079: * @return boolean
080: */
081: public static boolean isSingleCharAsString(String value) {
082: return value.length() == 3 && value.charAt(0) == '\"';
083: }
084:
085: /**
086: * Method visit.
087: * @param node ASTVariableDeclaratorId
088: * @param data Object
089: * @return Object
090: * @see net.sourceforge.pmd.ast.JavaParserVisitor#visit(ASTVariableDeclaratorId, Object)
091: */
092: public Object visit(ASTVariableDeclaratorId node, Object data) {
093:
094: if (!node.getNameDeclaration().getTypeImage().equals(
095: targetTypename())) {
096: return data;
097: }
098:
099: for (NameOccurrence occ : node.getUsages()) {
100: if (isNotedMethod(occ.getNameForWhichThisIsAQualifier())) {
101: SimpleNode parent = (SimpleNode) occ.getLocation()
102: .jjtGetParent().jjtGetParent();
103: if (parent instanceof ASTPrimaryExpression) {
104: // bail out if it's something like indexOf("a" + "b")
105: List additives = parent
106: .findChildrenOfType(ASTAdditiveExpression.class);
107: if (!additives.isEmpty()) {
108: return data;
109: }
110: List literals = parent
111: .findChildrenOfType(ASTLiteral.class);
112: for (int l = 0; l < literals.size(); l++) {
113: ASTLiteral literal = (ASTLiteral) literals
114: .get(l);
115: if (isViolationArgument(l, literal.getImage())) {
116: addViolation(data, occ.getLocation());
117: }
118: }
119: }
120: }
121: }
122: return data;
123: }
124: }
|