001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 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            TokenTypes.PATTERN_VARIABLE_DEF,
159        };
160    }
161
162    @Override
163    public int[] getRequiredTokens() {
164        return CommonUtil.EMPTY_INT_ARRAY;
165    }
166
167    @Override
168    public void visitToken(DetailAST ast) {
169        if (ast.getType() == TokenTypes.LITERAL_CATCH) {
170            visitCatch(ast);
171        }
172        else if (ast.getType() == TokenTypes.FOR_EACH_CLAUSE) {
173            visitForEachClause(ast);
174        }
175        else if (ast.getType() == TokenTypes.PATTERN_VARIABLE_DEF) {
176            visitPatternVariableDef(ast);
177        }
178        else {
179            visitMethod(ast);
180        }
181    }
182
183    /**
184     * Checks parameter of the pattern variable definition.
185     *
186     * @param patternVariableDef pattern variable definition to check
187     */
188    private void visitPatternVariableDef(final DetailAST patternVariableDef) {
189        checkParam(patternVariableDef);
190    }
191
192    /**
193     * Checks parameters of the method or ctor.
194     *
195     * @param method method or ctor to check.
196     */
197    private void visitMethod(final DetailAST method) {
198        // skip if there is no method body
199        // - abstract method
200        // - interface method (not implemented)
201        // - native method
202        if (method.findFirstToken(TokenTypes.SLIST) != null) {
203            final DetailAST parameters =
204                method.findFirstToken(TokenTypes.PARAMETERS);
205            TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, this::checkParam);
206        }
207    }
208
209    /**
210     * Checks parameter of the catch block.
211     *
212     * @param catchClause catch block to check.
213     */
214    private void visitCatch(final DetailAST catchClause) {
215        checkParam(catchClause.findFirstToken(TokenTypes.PARAMETER_DEF));
216    }
217
218    /**
219     * Checks parameter of the for each clause.
220     *
221     * @param forEachClause for each clause to check.
222     */
223    private void visitForEachClause(final DetailAST forEachClause) {
224        final DetailAST variableDef = forEachClause.findFirstToken(TokenTypes.VARIABLE_DEF);
225        if (variableDef != null) {
226            // can be missing for record pattern def
227            // (only available as a preview feature in Java 20, never released)
228            checkParam(variableDef);
229        }
230    }
231
232    /**
233     * Checks if the given parameter is final.
234     *
235     * @param param parameter to check.
236     */
237    private void checkParam(final DetailAST param) {
238        if (param.findFirstToken(TokenTypes.MODIFIERS).findFirstToken(TokenTypes.FINAL) == null
239                && !isIgnoredPrimitiveParam(param)
240                && !isIgnoredUnnamedParam(param)
241                && !CheckUtil.isReceiverParameter(param)) {
242            final DetailAST paramName = param.findFirstToken(TokenTypes.IDENT);
243            final DetailAST firstNode = CheckUtil.getFirstNode(param);
244            log(firstNode,
245                MSG_KEY, paramName.getText());
246        }
247    }
248
249    /**
250     * Checks for skip current param due to <b>ignorePrimitiveTypes</b> option.
251     *
252     * @param paramDef {@link TokenTypes#PARAMETER_DEF PARAMETER_DEF}
253     * @return true if param has to be skipped.
254     */
255    private boolean isIgnoredPrimitiveParam(DetailAST paramDef) {
256        boolean result = false;
257        if (ignorePrimitiveTypes) {
258            final DetailAST type = paramDef.findFirstToken(TokenTypes.TYPE);
259            final DetailAST parameterType = type.getFirstChild();
260            final DetailAST arrayDeclarator = type
261                    .findFirstToken(TokenTypes.ARRAY_DECLARATOR);
262            if (arrayDeclarator == null
263                    && primitiveDataTypes.get(parameterType.getType())) {
264                result = true;
265            }
266        }
267        return result;
268    }
269
270    /**
271     *  Checks for skip current param due to <b>ignoreUnnamedParameters</b> option.
272     *
273     * @param paramDef parameter to check
274     * @return true if the parameter should be skipped due to the ignoreUnnamedParameters option.
275     */
276    private boolean isIgnoredUnnamedParam(final DetailAST paramDef) {
277        final DetailAST paramName = paramDef.findFirstToken(TokenTypes.IDENT);
278        return ignoreUnnamedParameters && paramName != null && "_".equals(paramName.getText());
279    }
280
281}