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