001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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.ArrayDeque; 023import java.util.Deque; 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 * <div> 033 * Abstract class for checking that an overriding method with no parameters 034 * invokes the super method. 035 * </div> 036 */ 037@FileStatefulCheck 038public abstract class AbstractSuperCheck 039 extends AbstractCheck { 040 041 /** 042 * A key pointing to the warning message text in "messages.properties" file. 043 */ 044 private final String messageKey; 045 046 /** Stack of methods. */ 047 private final Deque<MethodNode> methodStack = new ArrayDeque<>(); 048 049 /** 050 * Creates a new {@code AbstractSuperCheck} instance. 051 * 052 * @param messageKey the message key to log violations with 053 */ 054 protected AbstractSuperCheck(String messageKey) { 055 this.messageKey = messageKey; 056 } 057 058 /** 059 * Returns the name of the overriding method. 060 * 061 * @return the name of the overriding method. 062 */ 063 protected abstract String getMethodName(); 064 065 @Override 066 public int[] getAcceptableTokens() { 067 return getRequiredTokens(); 068 } 069 070 @Override 071 public int[] getDefaultTokens() { 072 return getRequiredTokens(); 073 } 074 075 @Override 076 public int[] getRequiredTokens() { 077 return new int[] { 078 TokenTypes.METHOD_DEF, 079 TokenTypes.LITERAL_SUPER, 080 }; 081 } 082 083 @Override 084 public void beginTree(DetailAST rootAST) { 085 methodStack.clear(); 086 } 087 088 @Override 089 public void visitToken(DetailAST ast) { 090 if (isOverridingMethod(ast)) { 091 methodStack.add(new MethodNode(ast)); 092 } 093 else if (isSuperCall(ast)) { 094 final MethodNode methodNode = methodStack.getLast(); 095 methodNode.setCallingSuper(); 096 } 097 } 098 099 /** 100 * Determines whether a 'super' literal is a call to the super method 101 * for this check. 102 * 103 * @param literalSuperAst the AST node of a 'super' literal. 104 * @return true if ast is a call to the super method for this check. 105 */ 106 private boolean isSuperCall(DetailAST literalSuperAst) { 107 boolean superCall = false; 108 109 if (!isSameNameMethod(literalSuperAst)) { 110 final DetailAST parent = literalSuperAst.getParent(); 111 if (parent.getType() == TokenTypes.METHOD_REF 112 || !hasArguments(parent)) { 113 superCall = isSuperCallInOverridingMethod(parent); 114 } 115 } 116 return superCall; 117 } 118 119 /** 120 * Determines whether a super call in overriding method. 121 * 122 * @param ast The AST node of a 'dot operator' in 'super' call. 123 * @return true if super call in overriding method. 124 */ 125 private boolean isSuperCallInOverridingMethod(DetailAST ast) { 126 boolean inOverridingMethod = false; 127 DetailAST dotAst = ast; 128 129 while (dotAst.getType() != TokenTypes.CTOR_DEF 130 && dotAst.getType() != TokenTypes.INSTANCE_INIT) { 131 if (dotAst.getType() == TokenTypes.METHOD_DEF) { 132 inOverridingMethod = isOverridingMethod(dotAst); 133 break; 134 } 135 dotAst = dotAst.getParent(); 136 } 137 return inOverridingMethod; 138 } 139 140 /** 141 * Does method have any arguments. 142 * 143 * @param methodCallDotAst DOT DetailAST 144 * @return true if any parameters found 145 */ 146 private static boolean hasArguments(DetailAST methodCallDotAst) { 147 final DetailAST argumentsList = methodCallDotAst.getNextSibling(); 148 return argumentsList.hasChildren(); 149 } 150 151 /** 152 * Is same name of method. 153 * 154 * @param ast method AST 155 * @return true if method name is the same 156 */ 157 private boolean isSameNameMethod(DetailAST ast) { 158 DetailAST sibling = ast.getNextSibling(); 159 // ignore type parameters 160 if (sibling != null 161 && sibling.getType() == TokenTypes.TYPE_ARGUMENTS) { 162 sibling = sibling.getNextSibling(); 163 } 164 return sibling == null || !getMethodName().equals(sibling.getText()); 165 } 166 167 @Override 168 public void leaveToken(DetailAST ast) { 169 if (isOverridingMethod(ast)) { 170 final MethodNode methodNode = 171 methodStack.removeLast(); 172 if (!methodNode.isCallingSuper()) { 173 final DetailAST methodAST = methodNode.getMethod(); 174 final DetailAST nameAST = 175 methodAST.findFirstToken(TokenTypes.IDENT); 176 log(nameAST, messageKey, nameAST.getText()); 177 } 178 } 179 } 180 181 /** 182 * Determines whether an AST is a method definition for this check, 183 * without any parameters. 184 * 185 * @param ast the method definition AST. 186 * @return true if the method of ast is a method for this check. 187 */ 188 private boolean isOverridingMethod(DetailAST ast) { 189 boolean overridingMethod = false; 190 191 if (ast.getType() == TokenTypes.METHOD_DEF 192 && !ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) { 193 final DetailAST nameAST = ast.findFirstToken(TokenTypes.IDENT); 194 final String name = nameAST.getText(); 195 final DetailAST modifiersAST = ast.findFirstToken(TokenTypes.MODIFIERS); 196 197 if (getMethodName().equals(name) 198 && modifiersAST.findFirstToken(TokenTypes.LITERAL_NATIVE) == null) { 199 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 200 overridingMethod = !params.hasChildren(); 201 } 202 } 203 return overridingMethod; 204 } 205 206 /** 207 * Stack node for a method definition and a record of 208 * whether the method has a call to the super method. 209 */ 210 private static final class MethodNode { 211 212 /** Method definition. */ 213 private final DetailAST method; 214 215 /** True if the overriding method calls the super method. */ 216 private boolean callingSuper; 217 218 /** 219 * Constructs a stack node for a method definition. 220 * 221 * @param ast AST for the method definition. 222 */ 223 private MethodNode(DetailAST ast) { 224 method = ast; 225 } 226 227 /** 228 * Records that the overriding method has a call to the super method. 229 */ 230 /* package */ void setCallingSuper() { 231 callingSuper = true; 232 } 233 234 /** 235 * Determines whether the overriding method has a call to the super 236 * method. 237 * 238 * @return true if the overriding method has a call to the super method. 239 */ 240 /* package */ boolean isCallingSuper() { 241 return callingSuper; 242 } 243 244 /** 245 * Returns the overriding method definition AST. 246 * 247 * @return the overriding method definition AST. 248 */ 249 /* package */ DetailAST getMethod() { 250 return method; 251 } 252 253 } 254 255}