001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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 *
046 * @since 3.0
047 */
048@StatelessCheck
049public class FinalParametersCheck extends AbstractCheck {
050
051    /**
052     * A key is pointing to the warning message text in "messages.properties" file.
053     */
054    public static final String MSG_KEY = "final.parameter";
055
056    /**
057     * Contains
058     * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html">
059     * primitive datatypes</a>.
060     */
061    private final BitSet primitiveDataTypes = TokenUtil.asBitSet(
062        TokenTypes.LITERAL_BYTE,
063        TokenTypes.LITERAL_SHORT,
064        TokenTypes.LITERAL_INT,
065        TokenTypes.LITERAL_LONG,
066        TokenTypes.LITERAL_FLOAT,
067        TokenTypes.LITERAL_DOUBLE,
068        TokenTypes.LITERAL_BOOLEAN,
069        TokenTypes.LITERAL_CHAR
070    );
071
072    /**
073     * Ignore primitive types as parameters.
074     */
075    private boolean ignorePrimitiveTypes;
076
077    /**
078     * Ignore <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html">
079     * unnamed parameters</a>.
080     */
081    private boolean ignoreUnnamedParameters = true;
082
083    /**
084     * Creates a new {@code FinalParametersCheck} instance.
085     */
086    public FinalParametersCheck() {
087        // no code by default
088    }
089
090    /**
091     * Setter to ignore primitive types as parameters.
092     *
093     * @param ignorePrimitiveTypes true or false.
094     * @since 6.2
095     */
096    public void setIgnorePrimitiveTypes(boolean ignorePrimitiveTypes) {
097        this.ignorePrimitiveTypes = ignorePrimitiveTypes;
098    }
099
100    /**
101     * Setter to ignore
102     * <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html">
103     * unnamed parameters</a>.
104     *
105     * @param ignoreUnnamedParameters true or false.
106     * @since 10.18.0
107     */
108    public void setIgnoreUnnamedParameters(boolean ignoreUnnamedParameters) {
109        this.ignoreUnnamedParameters = ignoreUnnamedParameters;
110    }
111
112    @Override
113    public int[] getDefaultTokens() {
114        return new int[] {
115            TokenTypes.METHOD_DEF,
116            TokenTypes.CTOR_DEF,
117        };
118    }
119
120    @Override
121    public int[] getAcceptableTokens() {
122        return new int[] {
123            TokenTypes.METHOD_DEF,
124            TokenTypes.CTOR_DEF,
125            TokenTypes.LITERAL_CATCH,
126            TokenTypes.FOR_EACH_CLAUSE,
127            TokenTypes.PATTERN_VARIABLE_DEF,
128        };
129    }
130
131    @Override
132    public int[] getRequiredTokens() {
133        return CommonUtil.EMPTY_INT_ARRAY;
134    }
135
136    @Override
137    public void visitToken(DetailAST ast) {
138        if (ast.getType() == TokenTypes.LITERAL_CATCH) {
139            visitCatch(ast);
140        }
141        else if (ast.getType() == TokenTypes.FOR_EACH_CLAUSE) {
142            visitForEachClause(ast);
143        }
144        else if (ast.getType() == TokenTypes.PATTERN_VARIABLE_DEF) {
145            visitPatternVariableDef(ast);
146        }
147        else {
148            visitMethod(ast);
149        }
150    }
151
152    /**
153     * Checks parameter of the pattern variable definition.
154     *
155     * @param patternVariableDef pattern variable definition to check
156     */
157    private void visitPatternVariableDef(final DetailAST patternVariableDef) {
158        checkParam(patternVariableDef);
159    }
160
161    /**
162     * Checks parameters of the method or ctor.
163     *
164     * @param method method or ctor to check.
165     */
166    private void visitMethod(final DetailAST method) {
167        // skip if there is no method body
168        // - abstract method
169        // - interface method (not implemented)
170        // - native method
171        if (method.findFirstToken(TokenTypes.SLIST) != null) {
172            final DetailAST parameters =
173                method.findFirstToken(TokenTypes.PARAMETERS);
174            TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, this::checkParam);
175        }
176    }
177
178    /**
179     * Checks parameter of the catch block.
180     *
181     * @param catchClause catch block to check.
182     */
183    private void visitCatch(final DetailAST catchClause) {
184        checkParam(catchClause.findFirstToken(TokenTypes.PARAMETER_DEF));
185    }
186
187    /**
188     * Checks parameter of the for each clause.
189     *
190     * @param forEachClause for each clause to check.
191     */
192    private void visitForEachClause(final DetailAST forEachClause) {
193        final DetailAST variableDef = forEachClause.findFirstToken(TokenTypes.VARIABLE_DEF);
194        if (variableDef != null) {
195            // can be missing for record pattern def
196            // (only available as a preview feature in Java 20, never released)
197            checkParam(variableDef);
198        }
199    }
200
201    /**
202     * Checks if the given parameter is final.
203     *
204     * @param param parameter to check.
205     */
206    private void checkParam(final DetailAST param) {
207        if (param.findFirstToken(TokenTypes.MODIFIERS).findFirstToken(TokenTypes.FINAL) == null
208                && !isIgnoredPrimitiveParam(param)
209                && !isIgnoredUnnamedParam(param)
210                && !CheckUtil.isReceiverParameter(param)) {
211            final DetailAST paramName = TokenUtil.getIdent(param);
212            final DetailAST firstNode = CheckUtil.getFirstNode(param);
213            log(firstNode,
214                MSG_KEY, paramName.getText());
215        }
216    }
217
218    /**
219     * Checks for skip current param due to <b>ignorePrimitiveTypes</b> option.
220     *
221     * @param paramDef {@link TokenTypes#PARAMETER_DEF PARAMETER_DEF}
222     * @return true if param has to be skipped.
223     */
224    private boolean isIgnoredPrimitiveParam(DetailAST paramDef) {
225        boolean result = false;
226        if (ignorePrimitiveTypes) {
227            final DetailAST type = paramDef.findFirstToken(TokenTypes.TYPE);
228            final DetailAST parameterType = type.getFirstChild();
229            final DetailAST arrayDeclarator = type
230                    .findFirstToken(TokenTypes.ARRAY_DECLARATOR);
231            if (arrayDeclarator == null
232                    && primitiveDataTypes.get(parameterType.getType())) {
233                result = true;
234            }
235        }
236        return result;
237    }
238
239    /**
240     *  Checks for skip current param due to <b>ignoreUnnamedParameters</b> option.
241     *
242     * @param paramDef parameter to check
243     * @return true if the parameter should be skipped due to the ignoreUnnamedParameters option.
244     */
245    private boolean isIgnoredUnnamedParam(final DetailAST paramDef) {
246        final DetailAST paramName = paramDef.findFirstToken(TokenTypes.IDENT);
247        return ignoreUnnamedParameters && paramName != null && "_".equals(paramName.getText());
248    }
249
250}