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