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