001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.coding; 021 022import java.util.Deque; 023import java.util.LinkedList; 024 025import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 030 031/** 032 * <p> 033 * Abstract class for checking that an overriding method with no parameters 034 * invokes the super method. 035 * </p> 036 */ 037@FileStatefulCheck 038public abstract class AbstractSuperCheck 039 extends AbstractCheck { 040 041 /** 042 * A key is pointing to the warning message text in "messages.properties" 043 * file. 044 */ 045 public static final String MSG_KEY = "missing.super.call"; 046 047 /** Stack of methods. */ 048 private final Deque<MethodNode> methodStack = new LinkedList<>(); 049 050 /** 051 * Returns the name of the overriding method. 052 * 053 * @return the name of the overriding method. 054 */ 055 protected abstract String getMethodName(); 056 057 @Override 058 public int[] getAcceptableTokens() { 059 return getRequiredTokens(); 060 } 061 062 @Override 063 public int[] getDefaultTokens() { 064 return getRequiredTokens(); 065 } 066 067 @Override 068 public int[] getRequiredTokens() { 069 return new int[] { 070 TokenTypes.METHOD_DEF, 071 TokenTypes.LITERAL_SUPER, 072 }; 073 } 074 075 @Override 076 public void beginTree(DetailAST rootAST) { 077 methodStack.clear(); 078 } 079 080 @Override 081 public void visitToken(DetailAST ast) { 082 if (isOverridingMethod(ast)) { 083 methodStack.add(new MethodNode(ast)); 084 } 085 else if (isSuperCall(ast)) { 086 final MethodNode methodNode = methodStack.getLast(); 087 methodNode.setCallingSuper(); 088 } 089 } 090 091 /** 092 * Determines whether a 'super' literal is a call to the super method 093 * for this check. 094 * 095 * @param literalSuperAst the AST node of a 'super' literal. 096 * @return true if ast is a call to the super method for this check. 097 */ 098 private boolean isSuperCall(DetailAST literalSuperAst) { 099 boolean superCall = false; 100 101 if (literalSuperAst.getType() == TokenTypes.LITERAL_SUPER 102 && !isSameNameMethod(literalSuperAst)) { 103 final DetailAST parent = literalSuperAst.getParent(); 104 if (parent.getType() == TokenTypes.METHOD_REF 105 || !hasArguments(parent)) { 106 superCall = isSuperCallInOverridingMethod(parent); 107 } 108 } 109 return superCall; 110 } 111 112 /** 113 * Determines whether a super call in overriding method. 114 * 115 * @param ast The AST node of a 'dot operator' in 'super' call. 116 * @return true if super call in overriding method. 117 */ 118 private boolean isSuperCallInOverridingMethod(DetailAST ast) { 119 boolean inOverridingMethod = false; 120 DetailAST dotAst = ast; 121 122 while (dotAst.getType() != TokenTypes.CTOR_DEF 123 && dotAst.getType() != TokenTypes.INSTANCE_INIT) { 124 if (dotAst.getType() == TokenTypes.METHOD_DEF) { 125 inOverridingMethod = isOverridingMethod(dotAst); 126 break; 127 } 128 dotAst = dotAst.getParent(); 129 } 130 return inOverridingMethod; 131 } 132 133 /** 134 * Does method have any arguments. 135 * 136 * @param methodCallDotAst DOT DetailAST 137 * @return true if any parameters found 138 */ 139 private static boolean hasArguments(DetailAST methodCallDotAst) { 140 final DetailAST argumentsList = methodCallDotAst.getNextSibling(); 141 return argumentsList.hasChildren(); 142 } 143 144 /** 145 * Is same name of method. 146 * 147 * @param ast method AST 148 * @return true if method name is the same 149 */ 150 private boolean isSameNameMethod(DetailAST ast) { 151 DetailAST sibling = ast.getNextSibling(); 152 // ignore type parameters 153 if (sibling != null 154 && sibling.getType() == TokenTypes.TYPE_ARGUMENTS) { 155 sibling = sibling.getNextSibling(); 156 } 157 return sibling == null || !getMethodName().equals(sibling.getText()); 158 } 159 160 @Override 161 public void leaveToken(DetailAST ast) { 162 if (isOverridingMethod(ast)) { 163 final MethodNode methodNode = 164 methodStack.removeLast(); 165 if (!methodNode.isCallingSuper()) { 166 final DetailAST methodAST = methodNode.getMethod(); 167 final DetailAST nameAST = 168 methodAST.findFirstToken(TokenTypes.IDENT); 169 log(nameAST, MSG_KEY, nameAST.getText()); 170 } 171 } 172 } 173 174 /** 175 * Determines whether an AST is a method definition for this check, 176 * without any parameters. 177 * 178 * @param ast the method definition AST. 179 * @return true if the method of ast is a method for this check. 180 */ 181 private boolean isOverridingMethod(DetailAST ast) { 182 boolean overridingMethod = false; 183 184 if (ast.getType() == TokenTypes.METHOD_DEF 185 && !ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) { 186 final DetailAST nameAST = ast.findFirstToken(TokenTypes.IDENT); 187 final String name = nameAST.getText(); 188 final DetailAST modifiersAST = ast.findFirstToken(TokenTypes.MODIFIERS); 189 190 if (getMethodName().equals(name) 191 && modifiersAST.findFirstToken(TokenTypes.LITERAL_NATIVE) == null) { 192 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 193 overridingMethod = !params.hasChildren(); 194 } 195 } 196 return overridingMethod; 197 } 198 199 /** 200 * Stack node for a method definition and a record of 201 * whether the method has a call to the super method. 202 */ 203 private static class MethodNode { 204 205 /** Method definition. */ 206 private final DetailAST method; 207 208 /** True if the overriding method calls the super method. */ 209 private boolean callingSuper; 210 211 /** 212 * Constructs a stack node for a method definition. 213 * 214 * @param ast AST for the method definition. 215 */ 216 /* package */ MethodNode(DetailAST ast) { 217 method = ast; 218 callingSuper = false; 219 } 220 221 /** 222 * Records that the overriding method has a call to the super method. 223 */ 224 public void setCallingSuper() { 225 callingSuper = true; 226 } 227 228 /** 229 * Determines whether the overriding method has a call to the super 230 * method. 231 * 232 * @return true if the overriding method has a call to the super method. 233 */ 234 public boolean isCallingSuper() { 235 return callingSuper; 236 } 237 238 /** 239 * Returns the overriding method definition AST. 240 * 241 * @return the overriding method definition AST. 242 */ 243 public DetailAST getMethod() { 244 return method; 245 } 246 247 } 248 249}