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