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