/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.lang.java.rule.optimizations; import java.util.ArrayList; import java.util.List; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; import net.sourceforge.pmd.lang.java.ast.ASTForInit; import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression; import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTName; import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement; import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.AbstractJavaNode; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; /** * Checks for variables in methods that are defined before they are really * needed. A reference is deemed to be premature if it is created ahead of a * block of code that doesn't use it that also has the ability to return or * throw an exception. * * @author Brian Remedios */ public class PrematureDeclarationRule extends AbstractJavaRule { /** * * @param node * ASTLocalVariableDeclaration * @param data * Object * @return Object * @see net.sourceforge.pmd.lang.java.ast.JavaParserVisitor#visit(ASTLocalVariableDeclaration, * Object) */ public Object visit(ASTLocalVariableDeclaration node, Object data) { // is it part of a for-loop declaration? if (node.jjtGetParent() instanceof ASTForInit) { // yes, those don't count return visit((AbstractJavaNode) node, data); } String varName = varNameIn(node); AbstractJavaNode grandparent = (AbstractJavaNode) node.jjtGetParent().jjtGetParent(); List<ASTBlockStatement> nextBlocks = blocksAfter(grandparent, node); for (ASTBlockStatement block : nextBlocks) { if (hasReferencesIn(block, varName) || isLambda(block)) { break; } if (hasExit(block)) { addViolation(data, node, varName); break; } } return visit((AbstractJavaNode) node, data); } private boolean isLambda(ASTBlockStatement block) { return block.getFirstParentOfType(ASTLambdaExpression.class) != null; } /** * Return whether a class of the specified type exists between the node * argument and the topParent argument. * * @param node * Node * @param intermediateParentClass * Class * @param topParent * Node * @return boolean */ public static boolean hasAsParentBetween(Node node, Class<?> intermediateParentClass, Node topParent) { Node currentParent = node.jjtGetParent(); while (!currentParent.equals(topParent)) { currentParent = currentParent.jjtGetParent(); if (currentParent.getClass().equals(intermediateParentClass)) { return true; } } return false; } /** * Returns whether the block contains a return call or throws an exception. * Exclude blocks that have these things as part of an inner class. * * @param block * ASTBlockStatement * @return boolean */ @SuppressWarnings({ "rawtypes", "unchecked" }) private boolean hasExit(ASTBlockStatement block) { List exitBlocks = block.findDescendantsOfType(ASTReturnStatement.class); exitBlocks.addAll(block.findDescendantsOfType(ASTThrowStatement.class)); if (exitBlocks.isEmpty()) { return false; } // now check to see if the ones we have are part of a method on a // declared inner class // or part of a lambda expression boolean result = false; for (int i = 0; i < exitBlocks.size(); i++) { Node exitNode = (Node) exitBlocks.get(i); if (!hasAsParentBetween(exitNode, ASTMethodDeclaration.class, block) && !hasAsParentBetween(exitNode, ASTLambdaExpression.class, block)) { result = true; break; } } return result; } /** * Returns whether the variable is mentioned within the statement block or * not. * * @param block * ASTBlockStatement * @param varName * String * @return boolean */ private static boolean hasReferencesIn(ASTBlockStatement block, String varName) { List<ASTName> names = block.findDescendantsOfType(ASTName.class); for (ASTName name : names) { if (isReference(varName, name.getImage())) { return true; } } return false; } /** * Return whether the shortName is part of the compound name by itself or as * a method call receiver. * * @param shortName * String * @param compoundName * String * @return boolean */ private static boolean isReference(String shortName, String compoundName) { int dotPos = compoundName.indexOf('.'); return dotPos < 0 ? shortName.equals(compoundName) : shortName.endsWith(compoundName.substring(0, dotPos)); } /** * Return the name of the variable we just assigned something to. * * @param node * ASTLocalVariableDeclaration * @return String */ private static String varNameIn(ASTLocalVariableDeclaration node) { ASTVariableDeclarator declarator = node.getFirstChildOfType(ASTVariableDeclarator.class); return ((ASTVariableDeclaratorId) declarator.jjtGetChild(0)).getImage(); } /** * Returns the index of the node block in relation to its siblings. * * @param block * SimpleJavaNode * @param node * Node * @return int */ private static int indexOf(AbstractJavaNode block, Node node) { int count = block.jjtGetNumChildren(); for (int i = 0; i < count; i++) { if (node == block.jjtGetChild(i)) { return i; } } return -1; } /** * Returns all the blocks found right after the node supplied within the its * current scope. * * @param block * SimpleJavaNode * @param node * SimpleNode * @return List */ private static List<ASTBlockStatement> blocksAfter(AbstractJavaNode block, AbstractJavaNode node) { int count = block.jjtGetNumChildren(); int start = indexOf(block, node.jjtGetParent()) + 1; List<ASTBlockStatement> nextBlocks = new ArrayList<>(count); for (int i = start; i < count; i++) { Node maybeBlock = block.jjtGetChild(i); if (maybeBlock instanceof ASTBlockStatement) { nextBlocks.add((ASTBlockStatement) maybeBlock); } } return nextBlocks; } }