View Javadoc

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