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