001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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; 021 022import java.util.BitSet; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 029import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 030import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 031 032/** 033 * <p> 034 * Checks that parameters for methods, constructors, catch and for-each blocks are final. 035 * Interface, abstract, and native methods are not checked: the final keyword 036 * does not make sense for interface, abstract, and native method parameters as 037 * there is no code that could modify the parameter. 038 * </p> 039 * <p> 040 * Rationale: Changing the value of parameters during the execution of the method's 041 * algorithm can be confusing and should be avoided. A great way to let the Java compiler 042 * prevent this coding style is to declare parameters final. 043 * </p> 044 * <ul> 045 * <li> 046 * Property {@code ignorePrimitiveTypes} - Ignore primitive types as parameters. 047 * Type is {@code boolean}. 048 * Default value is {@code false}. 049 * </li> 050 * <li> 051 * Property {@code tokens} - tokens to check 052 * Type is {@code java.lang.String[]}. 053 * Validation type is {@code tokenSet}. 054 * Default value is: 055 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 056 * METHOD_DEF</a>, 057 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 058 * CTOR_DEF</a>. 059 * </li> 060 * </ul> 061 * <p> 062 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 063 * </p> 064 * <p> 065 * Violation Message Keys: 066 * </p> 067 * <ul> 068 * <li> 069 * {@code final.parameter} 070 * </li> 071 * </ul> 072 * 073 * @since 3.0 074 */ 075@StatelessCheck 076public class FinalParametersCheck extends AbstractCheck { 077 078 /** 079 * A key is pointing to the warning message text in "messages.properties" 080 * file. 081 */ 082 public static final String MSG_KEY = "final.parameter"; 083 084 /** 085 * Contains 086 * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html"> 087 * primitive datatypes</a>. 088 */ 089 private final BitSet primitiveDataTypes = TokenUtil.asBitSet( 090 TokenTypes.LITERAL_BYTE, 091 TokenTypes.LITERAL_SHORT, 092 TokenTypes.LITERAL_INT, 093 TokenTypes.LITERAL_LONG, 094 TokenTypes.LITERAL_FLOAT, 095 TokenTypes.LITERAL_DOUBLE, 096 TokenTypes.LITERAL_BOOLEAN, 097 TokenTypes.LITERAL_CHAR 098 ); 099 100 /** 101 * Ignore primitive types as parameters. 102 */ 103 private boolean ignorePrimitiveTypes; 104 105 /** 106 * Setter to ignore primitive types as parameters. 107 * 108 * @param ignorePrimitiveTypes true or false. 109 * @since 6.2 110 */ 111 public void setIgnorePrimitiveTypes(boolean ignorePrimitiveTypes) { 112 this.ignorePrimitiveTypes = ignorePrimitiveTypes; 113 } 114 115 @Override 116 public int[] getDefaultTokens() { 117 return new int[] { 118 TokenTypes.METHOD_DEF, 119 TokenTypes.CTOR_DEF, 120 }; 121 } 122 123 @Override 124 public int[] getAcceptableTokens() { 125 return new int[] { 126 TokenTypes.METHOD_DEF, 127 TokenTypes.CTOR_DEF, 128 TokenTypes.LITERAL_CATCH, 129 TokenTypes.FOR_EACH_CLAUSE, 130 }; 131 } 132 133 @Override 134 public int[] getRequiredTokens() { 135 return CommonUtil.EMPTY_INT_ARRAY; 136 } 137 138 @Override 139 public void visitToken(DetailAST ast) { 140 // don't flag interfaces 141 final DetailAST container = ast.getParent().getParent(); 142 if (container.getType() != TokenTypes.INTERFACE_DEF) { 143 if (ast.getType() == TokenTypes.LITERAL_CATCH) { 144 visitCatch(ast); 145 } 146 else if (ast.getType() == TokenTypes.FOR_EACH_CLAUSE) { 147 visitForEachClause(ast); 148 } 149 else { 150 visitMethod(ast); 151 } 152 } 153 } 154 155 /** 156 * Checks parameters of the method or ctor. 157 * 158 * @param method method or ctor to check. 159 */ 160 private void visitMethod(final DetailAST method) { 161 final DetailAST modifiers = 162 method.findFirstToken(TokenTypes.MODIFIERS); 163 164 // ignore abstract and native methods 165 if (modifiers.findFirstToken(TokenTypes.ABSTRACT) == null 166 && modifiers.findFirstToken(TokenTypes.LITERAL_NATIVE) == null) { 167 final DetailAST parameters = 168 method.findFirstToken(TokenTypes.PARAMETERS); 169 TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, this::checkParam); 170 } 171 } 172 173 /** 174 * Checks parameter of the catch block. 175 * 176 * @param catchClause catch block to check. 177 */ 178 private void visitCatch(final DetailAST catchClause) { 179 checkParam(catchClause.findFirstToken(TokenTypes.PARAMETER_DEF)); 180 } 181 182 /** 183 * Checks parameter of the for each clause. 184 * 185 * @param forEachClause for each clause to check. 186 */ 187 private void visitForEachClause(final DetailAST forEachClause) { 188 checkParam(forEachClause.findFirstToken(TokenTypes.VARIABLE_DEF)); 189 } 190 191 /** 192 * Checks if the given parameter is final. 193 * 194 * @param param parameter to check. 195 */ 196 private void checkParam(final DetailAST param) { 197 if (param.findFirstToken(TokenTypes.MODIFIERS).findFirstToken(TokenTypes.FINAL) == null 198 && !isIgnoredParam(param) 199 && !CheckUtil.isReceiverParameter(param)) { 200 final DetailAST paramName = param.findFirstToken(TokenTypes.IDENT); 201 final DetailAST firstNode = CheckUtil.getFirstNode(param); 202 log(firstNode, 203 MSG_KEY, paramName.getText()); 204 } 205 } 206 207 /** 208 * Checks for skip current param due to <b>ignorePrimitiveTypes</b> option. 209 * 210 * @param paramDef {@link TokenTypes#PARAMETER_DEF PARAMETER_DEF} 211 * @return true if param has to be skipped. 212 */ 213 private boolean isIgnoredParam(DetailAST paramDef) { 214 boolean result = false; 215 if (ignorePrimitiveTypes) { 216 final DetailAST type = paramDef.findFirstToken(TokenTypes.TYPE); 217 final DetailAST parameterType = type.getFirstChild(); 218 final DetailAST arrayDeclarator = type 219 .findFirstToken(TokenTypes.ARRAY_DECLARATOR); 220 if (arrayDeclarator == null 221 && primitiveDataTypes.get(parameterType.getType())) { 222 result = true; 223 } 224 } 225 return result; 226 } 227 228}