001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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.CheckUtils; 032import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 033 034/** 035 * Check that method/constructor/catch/foreach parameters are final. 036 * The user can set the token set to METHOD_DEF, CONSTRUCTOR_DEF, 037 * LITERAL_CATCH, FOR_EACH_CLAUSE or any combination of these token 038 * types, to control the scope of this check. 039 * Default scope is both METHOD_DEF and CONSTRUCTOR_DEF. 040 * <p> 041 * Check has an option <b>ignorePrimitiveTypes</b> which allows ignoring lack of 042 * final modifier at 043 * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html"> 044 * primitive data type</a> parameter. Default value <b>false</b>. 045 * </p> 046 * E.g.: 047 * <p> 048 * {@code 049 * private void foo(int x) { ... } //parameter is of primitive type 050 * } 051 * </p> 052 * 053 * @author lkuehne 054 * @author o_sukhodolsky 055 * @author Michael Studman 056 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> 057 */ 058@StatelessCheck 059public class FinalParametersCheck extends AbstractCheck { 060 061 /** 062 * A key is pointing to the warning message text in "messages.properties" 063 * file. 064 */ 065 public static final String MSG_KEY = "final.parameter"; 066 067 /** 068 * Contains 069 * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html"> 070 * primitive datatypes</a>. 071 */ 072 private final Set<Integer> primitiveDataTypes = Collections.unmodifiableSet( 073 Arrays.stream(new Integer[] { 074 TokenTypes.LITERAL_BYTE, 075 TokenTypes.LITERAL_SHORT, 076 TokenTypes.LITERAL_INT, 077 TokenTypes.LITERAL_LONG, 078 TokenTypes.LITERAL_FLOAT, 079 TokenTypes.LITERAL_DOUBLE, 080 TokenTypes.LITERAL_BOOLEAN, 081 TokenTypes.LITERAL_CHAR, }) 082 .collect(Collectors.toSet())); 083 084 /** 085 * Option to ignore primitive types as params. 086 */ 087 private boolean ignorePrimitiveTypes; 088 089 /** 090 * Sets ignoring primitive types as params. 091 * @param ignorePrimitiveTypes true or false. 092 */ 093 public void setIgnorePrimitiveTypes(boolean ignorePrimitiveTypes) { 094 this.ignorePrimitiveTypes = ignorePrimitiveTypes; 095 } 096 097 @Override 098 public int[] getDefaultTokens() { 099 return new int[] { 100 TokenTypes.METHOD_DEF, 101 TokenTypes.CTOR_DEF, 102 }; 103 } 104 105 @Override 106 public int[] getAcceptableTokens() { 107 return new int[] { 108 TokenTypes.METHOD_DEF, 109 TokenTypes.CTOR_DEF, 110 TokenTypes.LITERAL_CATCH, 111 TokenTypes.FOR_EACH_CLAUSE, 112 }; 113 } 114 115 @Override 116 public int[] getRequiredTokens() { 117 return CommonUtils.EMPTY_INT_ARRAY; 118 } 119 120 @Override 121 public void visitToken(DetailAST ast) { 122 // don't flag interfaces 123 final DetailAST container = ast.getParent().getParent(); 124 if (container.getType() != TokenTypes.INTERFACE_DEF) { 125 if (ast.getType() == TokenTypes.LITERAL_CATCH) { 126 visitCatch(ast); 127 } 128 else if (ast.getType() == TokenTypes.FOR_EACH_CLAUSE) { 129 visitForEachClause(ast); 130 } 131 else { 132 visitMethod(ast); 133 } 134 } 135 } 136 137 /** 138 * Checks parameters of the method or ctor. 139 * @param method method or ctor to check. 140 */ 141 private void visitMethod(final DetailAST method) { 142 final DetailAST modifiers = 143 method.findFirstToken(TokenTypes.MODIFIERS); 144 // exit on fast lane if there is nothing to check here 145 146 if (method.findFirstToken(TokenTypes.PARAMETERS) 147 .findFirstToken(TokenTypes.PARAMETER_DEF) != null 148 // ignore abstract and native methods 149 && modifiers.findFirstToken(TokenTypes.ABSTRACT) == null 150 && modifiers.findFirstToken(TokenTypes.LITERAL_NATIVE) == null) { 151 // we can now be sure that there is at least one parameter 152 final DetailAST parameters = 153 method.findFirstToken(TokenTypes.PARAMETERS); 154 DetailAST child = parameters.getFirstChild(); 155 while (child != null) { 156 // children are PARAMETER_DEF and COMMA 157 if (child.getType() == TokenTypes.PARAMETER_DEF) { 158 checkParam(child); 159 } 160 child = child.getNextSibling(); 161 } 162 } 163 } 164 165 /** 166 * Checks parameter of the catch block. 167 * @param catchClause catch block to check. 168 */ 169 private void visitCatch(final DetailAST catchClause) { 170 checkParam(catchClause.findFirstToken(TokenTypes.PARAMETER_DEF)); 171 } 172 173 /** 174 * Checks parameter of the for each clause. 175 * @param forEachClause for each clause to check. 176 */ 177 private void visitForEachClause(final DetailAST forEachClause) { 178 checkParam(forEachClause.findFirstToken(TokenTypes.VARIABLE_DEF)); 179 } 180 181 /** 182 * Checks if the given parameter is final. 183 * @param param parameter to check. 184 */ 185 private void checkParam(final DetailAST param) { 186 if (param.findFirstToken(TokenTypes.MODIFIERS).findFirstToken(TokenTypes.FINAL) == null 187 && !isIgnoredParam(param) 188 && !CheckUtils.isReceiverParameter(param)) { 189 final DetailAST paramName = param.findFirstToken(TokenTypes.IDENT); 190 final DetailAST firstNode = CheckUtils.getFirstNode(param); 191 log(firstNode.getLineNo(), firstNode.getColumnNo(), 192 MSG_KEY, paramName.getText()); 193 } 194 } 195 196 /** 197 * Checks for skip current param due to <b>ignorePrimitiveTypes</b> option. 198 * @param paramDef {@link TokenTypes#PARAMETER_DEF PARAMETER_DEF} 199 * @return true if param has to be skipped. 200 */ 201 private boolean isIgnoredParam(DetailAST paramDef) { 202 boolean result = false; 203 if (ignorePrimitiveTypes) { 204 final DetailAST parameterType = paramDef 205 .findFirstToken(TokenTypes.TYPE).getFirstChild(); 206 if (primitiveDataTypes.contains(parameterType.getType())) { 207 result = true; 208 } 209 } 210 return result; 211 } 212 213}