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