View Javadoc

    1   /**
    2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
    3    */
    4   package net.sourceforge.pmd.rules;
    5
    6   import java.util.ArrayList;
    7   import java.util.List;
    8   import java.util.Map;
    9
    10  import net.sourceforge.pmd.AbstractRule;
    11  import net.sourceforge.pmd.PropertyDescriptor;
    12  import net.sourceforge.pmd.ast.ASTAnnotation;
    13  import net.sourceforge.pmd.ast.ASTArgumentList;
    14  import net.sourceforge.pmd.ast.ASTArguments;
    15  import net.sourceforge.pmd.ast.ASTBlock;
    16  import net.sourceforge.pmd.ast.ASTClassOrInterfaceBodyDeclaration;
    17  import net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration;
    18  import net.sourceforge.pmd.ast.ASTFormalParameter;
    19  import net.sourceforge.pmd.ast.ASTFormalParameters;
    20  import net.sourceforge.pmd.ast.ASTImplementsList;
    21  import net.sourceforge.pmd.ast.ASTMarkerAnnotation;
    22  import net.sourceforge.pmd.ast.ASTMethodDeclaration;
    23  import net.sourceforge.pmd.ast.ASTMethodDeclarator;
    24  import net.sourceforge.pmd.ast.ASTName;
    25  import net.sourceforge.pmd.ast.ASTNameList;
    26  import net.sourceforge.pmd.ast.ASTPrimaryExpression;
    27  import net.sourceforge.pmd.ast.ASTPrimaryPrefix;
    28  import net.sourceforge.pmd.ast.ASTPrimarySuffix;
    29  import net.sourceforge.pmd.ast.ASTResultType;
    30  import net.sourceforge.pmd.ast.ASTStatement;
    31  import net.sourceforge.pmd.ast.ASTVariableDeclaratorId;
    32  import net.sourceforge.pmd.ast.Node;
    33  import net.sourceforge.pmd.ast.SimpleNode;
    34  import net.sourceforge.pmd.properties.BooleanProperty;
    35
    36  import org.jaxen.JaxenException;
    37
    38  /**
    39   * @author Romain Pelisse, bugfix for [ 1522517 ] False +: UselessOverridingMethod
    40   */
    41  public class UselessOverridingMethod extends AbstractRule {
    42
    43      private final List<String> exceptions;
    44      private boolean ignoreAnnotations;
    45      private static final String CLONE = "clone";
    46      private static final String OBJECT = "Object";
    47
    48      private static final PropertyDescriptor ignoreAnnotationsDescriptor = new BooleanProperty(
    49              "ignoreAnnotations", "Ignore annotations", false, 1.0f);
    50
    51      private static final Map<String, PropertyDescriptor> propertyDescriptorsByName = asFixedMap(new PropertyDescriptor[] { ignoreAnnotationsDescriptor });
    52
    53      @Override
    54      protected Map<String, PropertyDescriptor> propertiesByName() {
    55          return propertyDescriptorsByName;
    56      }
    57
    58      public UselessOverridingMethod() {
    59          exceptions = new ArrayList<String>(1);
    60          exceptions.add("CloneNotSupportedException");
    61      }
    62
    63  	@Override
    64      public Object visit(ASTImplementsList clz, Object data)
    65  	{
    66  		return super.visit(clz,data);
    67  	}
    68
    69      @Override
    70      public Object visit(ASTClassOrInterfaceDeclaration clz, Object data) {
    71          if (clz.isInterface()) {
    72              return data;
    73          }
    74          ignoreAnnotations = getBooleanProperty(ignoreAnnotationsDescriptor);
    75          return super.visit(clz, data);
    76      }
    77
    78      //TODO: this method should be externalize into an utility class, shouldn't it ?
    79      private boolean isMethodType(ASTMethodDeclaration node,String methodType)
    80      {
    81      	boolean result = false;
    82      	ASTResultType type = node.getResultType();
    83      	if ( type != null ) {
    84      		List results = null;
    85              try {
    86  	            results = type.findChildNodesWithXPath("./Type/ReferenceType/ClassOrInterfaceType[@Image = '" + methodType + "']");
    87              }
    88              catch (JaxenException e) {
    89  	            e.printStackTrace();
    90              }
    91      		if ( results != null && results.size() > 0 ) {
    92      			result = true;
    93      		}
    94      	}
    95      	return result;
    96      }
    97
    98      //TODO: this method should be externalize into an utility class, shouldn't it ?
    99      private boolean isMethodThrowingType(ASTMethodDeclaration node, List<String> exceptedExceptions) {
    100     	boolean result = false;
    101 	    ASTNameList thrownsExceptions = node.getFirstChildOfType(ASTNameList.class);
    102 	    if ( thrownsExceptions != null ) {
    103 	    	List<ASTName> names = thrownsExceptions.findChildrenOfType(ASTName.class);
    104 	    	for ( ASTName name : names ) {
    105 	    		for ( String exceptedException : exceptedExceptions) {
    106 		    		if ( exceptedException.equals(name.getImage()) )
    107 		    			result = true;
    108 	    		}
    109 	    	}
    110 	    }
    111 	    return result;
    112     }
    113
    114 	private boolean hasArguments(ASTMethodDeclaration node) {
    115 		boolean result = false;
    116 		try
    117 		{
    118 			List parameters = node.findChildNodesWithXPath("./MethodDeclarator/FormalParameters/*");
    119 			if ( parameters != null && parameters.size() > 0 ) {
    120 				result = true;
    121 			}
    122 		} catch (JaxenException e) {
    123 			e.printStackTrace();
    124 		}
    125 		return result;
    126 	}
    127
    128     @Override
    129     public Object visit(ASTMethodDeclaration node, Object data) {
    130         // Can skip abstract methods and methods whose only purpose is to
    131         // guarantee that the inherited method is not changed by finalizing
    132         // them.
    133         if (node.isAbstract() || node.isFinal() || node.isNative() || node.isSynchronized()) {
    134             return super.visit(node, data);
    135         }
    136         // We can also skip the 'clone' method as they are generally
    137         // 'useless' but as it is considered a 'good practice' to
    138         // implement them anyway ( see bug 1522517)
    139         if ( CLONE.equals(node.getMethodName()) && node.isPublic() &&
    140         	 ! this.hasArguments(node) &&
    141         	 this.isMethodType(node, OBJECT) &&
    142         	 this.isMethodThrowingType(node,exceptions) )
    143         {
    144         	return super.visit(node,data);
    145         }
    146
    147         ASTBlock block = node.getBlock();
    148         if (block == null) {
    149             return super.visit(node, data);
    150         }
    151         //Only process functions with one BlockStatement
    152         if (block.jjtGetNumChildren() != 1 || block.findChildrenOfType(ASTStatement.class).size() != 1)
    153             return super.visit(node, data);
    154
    155         ASTStatement statement = (ASTStatement) block.jjtGetChild(0).jjtGetChild(0);
    156         if (statement.jjtGetChild(0).jjtGetNumChildren() == 0) {
    157             return data;     // skips empty return statements
    158         }
    159         SimpleNode statementGrandChild = (SimpleNode) statement.jjtGetChild(0).jjtGetChild(0);
    160         ASTPrimaryExpression primaryExpression;
    161
    162         if (statementGrandChild instanceof ASTPrimaryExpression)
    163             primaryExpression = (ASTPrimaryExpression) statementGrandChild;
    164         else {
    165             List<ASTPrimaryExpression> primaryExpressions = findFirstDegreeChildrenOfType(statementGrandChild, ASTPrimaryExpression.class);
    166             if (primaryExpressions.size() != 1)
    167                 return super.visit(node, data);
    168             primaryExpression = primaryExpressions.get(0);
    169         }
    170
    171         ASTPrimaryPrefix primaryPrefix = findFirstDegreeChildrenOfType(primaryExpression, ASTPrimaryPrefix.class).get(0);
    172         if (!primaryPrefix.usesSuperModifier())
    173             return super.visit(node, data);
    174
    175         ASTMethodDeclarator methodDeclarator = findFirstDegreeChildrenOfType(node, ASTMethodDeclarator.class).get(0);
    176         if (!primaryPrefix.hasImageEqualTo(methodDeclarator.getImage()))
    177             return super.visit(node, data);
    178
    179         List<ASTPrimarySuffix> primarySuffixList = findFirstDegreeChildrenOfType(primaryExpression, ASTPrimarySuffix.class);
    180         if (primarySuffixList.size() != 1) {
    181             // extra method call on result of super method
    182             return super.visit(node, data);
    183         }
    184         //Process arguments
    185         ASTPrimarySuffix primarySuffix = primarySuffixList.get(0);
    186         ASTArguments arguments = (ASTArguments) primarySuffix.jjtGetChild(0);
    187         ASTFormalParameters formalParameters = (ASTFormalParameters) methodDeclarator.jjtGetChild(0);
    188         if (formalParameters.jjtGetNumChildren() != arguments.jjtGetNumChildren())
    189             return super.visit(node, data);
    190
    191         if (!ignoreAnnotations) {
    192             ASTClassOrInterfaceBodyDeclaration parent = (ASTClassOrInterfaceBodyDeclaration) node.jjtGetParent();
    193             for (int i = 0; i < parent.jjtGetNumChildren(); i++) {
    194                 Node n = parent.jjtGetChild(i);
    195                 if (n instanceof ASTAnnotation) {
    196                     if (n.jjtGetChild(0) instanceof ASTMarkerAnnotation) {
    197                         // @Override is ignored
    198                         if ("Override".equals(((ASTName) n.jjtGetChild(0).jjtGetChild(0)).getImage())) {
    199                             continue;
    200                         }
    201                     }
    202                     return super.visit(node, data);
    203                 }
    204             }
    205         }
    206
    207         if (arguments.jjtGetNumChildren() == 0) //No arguments to check
    208             addViolation(data, node, getMessage());
    209         else {
    210             ASTArgumentList argumentList = (ASTArgumentList) arguments.jjtGetChild(0);
    211             for (int i = 0; i < argumentList.jjtGetNumChildren(); i++) {
    212                 Node ExpressionChild = argumentList.jjtGetChild(i).jjtGetChild(0);
    213                 if (!(ExpressionChild instanceof ASTPrimaryExpression) || ExpressionChild.jjtGetNumChildren() != 1)
    214                     return super.visit(node, data); //The arguments are not simply passed through
    215
    216                 ASTPrimaryExpression argumentPrimaryExpression = (ASTPrimaryExpression) ExpressionChild;
    217                 ASTPrimaryPrefix argumentPrimaryPrefix = (ASTPrimaryPrefix) argumentPrimaryExpression.jjtGetChild(0);
    218                 if (argumentPrimaryPrefix.jjtGetNumChildren() == 0) {
    219                     return super.visit(node, data); //The arguments are not simply passed through (using "this" for instance)
    220                 }
    221                 Node argumentPrimaryPrefixChild = argumentPrimaryPrefix.jjtGetChild(0);
    222                 if (!(argumentPrimaryPrefixChild instanceof ASTName))
    223                     return super.visit(node, data); //The arguments are not simply passed through
    224
    225                 if (formalParameters.jjtGetNumChildren() < i + 1) {
    226                     return super.visit(node, data); // different number of args
    227                 }
    228
    229                 ASTName argumentName = (ASTName) argumentPrimaryPrefixChild;
    230                 ASTFormalParameter formalParameter = (ASTFormalParameter) formalParameters.jjtGetChild(i);
    231                 ASTVariableDeclaratorId variableId = findFirstDegreeChildrenOfType(formalParameter, ASTVariableDeclaratorId.class).get(0);
    232                 if (!argumentName.hasImageEqualTo(variableId.getImage())) {
    233                     return super.visit(node, data); //The arguments are not simply passed through
    234                 }
    235
    236             }
    237             addViolation(data, node, getMessage()); //All arguments are passed through directly
    238         }
    239         return super.visit(node, data);
    240     }
    241
    242
    243
    244 	public <T> List<T> findFirstDegreeChildrenOfType(SimpleNode n, Class<T> targetType) {
    245         List<T> l = new ArrayList<T>();
    246         lclFindChildrenOfType(n, targetType, l);
    247         return l;
    248     }
    249
    250     private <T> void lclFindChildrenOfType(Node node, Class<T> targetType, List<T> results) {
    251         if (node.getClass().equals(targetType)) {
    252             results.add((T) node);
    253         }
    254
    255         if (node instanceof ASTClassOrInterfaceDeclaration && ((ASTClassOrInterfaceDeclaration) node).isNested()) {
    256             return;
    257         }
    258
    259         if (node instanceof ASTClassOrInterfaceBodyDeclaration && ((ASTClassOrInterfaceBodyDeclaration) node).isAnonymousInnerClass()) {
    260             return;
    261         }
    262
    263         for (int i = 0; i < node.jjtGetNumChildren(); i++) {
    264             Node child = node.jjtGetChild(i);
    265             if (child.getClass().equals(targetType)) {
    266                 results.add((T) child);
    267             }
    268         }
    269     }
    270 }