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 * Example:
072 * </p>
073 * <pre>
074 * public class Point {
075 *   public Point() { } // ok
076 *   public Point(final int m) { } // ok
077 *   public Point(final int m,int n) { } // violation, n should be final
078 *   public void methodOne(final int x) { } // ok
079 *   public void methodTwo(int x) { } // violation, x should be final
080 *   public static void main(String[] args) { } // violation, args should be final
081 * }
082 * </pre>
083 * <p>
084 * To configure the check to enforce final parameters only for constructors:
085 * </p>
086 * <pre>
087 * &lt;module name=&quot;FinalParameters&quot;&gt;
088 *   &lt;property name=&quot;tokens&quot; value=&quot;CTOR_DEF&quot;/&gt;
089 * &lt;/module&gt;
090 * </pre>
091 * <p>
092 * Example:
093 * </p>
094 * <pre>
095 * public class Point {
096 *   public Point() { } // ok
097 *   public Point(final int m) { } // ok
098 *   public Point(final int m,int n) { } // violation, n should be final
099 *   public void methodOne(final int x) { } // ok
100 *   public void methodTwo(int x) { } // ok
101 *   public static void main(String[] args) { } // ok
102 * }
103 * </pre>
104 * <p>
105 * To configure the check to allow ignoring
106 * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html">
107 * primitive datatypes</a> as parameters:
108 * </p>
109 * <pre>
110 * &lt;module name=&quot;FinalParameters&quot;&gt;
111 *   &lt;property name=&quot;ignorePrimitiveTypes&quot; value=&quot;true&quot;/&gt;
112 * &lt;/module&gt;
113 * </pre>
114 * <p>
115 * Example:
116 * </p>
117 * <pre>
118 * public class Point {
119 *   public Point() { } // ok
120 *   public Point(final int m) { } // ok
121 *   public Point(final int m,int n) { } // ok
122 *   public void methodOne(final int x) { } // ok
123 *   public void methodTwo(int x) { } // ok
124 *   public static void main(String[] args) { } // violation, args should be final
125 * }
126 * </pre>
127 * <p>
128 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
129 * </p>
130 * <p>
131 * Violation Message Keys:
132 * </p>
133 * <ul>
134 * <li>
135 * {@code final.parameter}
136 * </li>
137 * </ul>
138 *
139 * @since 3.0
140 */
141@StatelessCheck
142public class FinalParametersCheck extends AbstractCheck {
143
144    /**
145     * A key is pointing to the warning message text in "messages.properties"
146     * file.
147     */
148    public static final String MSG_KEY = "final.parameter";
149
150    /**
151     * Contains
152     * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html">
153     * primitive datatypes</a>.
154     */
155    private final Set<Integer> primitiveDataTypes = Collections.unmodifiableSet(
156        Arrays.stream(new Integer[] {
157            TokenTypes.LITERAL_BYTE,
158            TokenTypes.LITERAL_SHORT,
159            TokenTypes.LITERAL_INT,
160            TokenTypes.LITERAL_LONG,
161            TokenTypes.LITERAL_FLOAT,
162            TokenTypes.LITERAL_DOUBLE,
163            TokenTypes.LITERAL_BOOLEAN,
164            TokenTypes.LITERAL_CHAR, })
165        .collect(Collectors.toSet()));
166
167    /**
168     * Ignore primitive types as parameters.
169     */
170    private boolean ignorePrimitiveTypes;
171
172    /**
173     * Setter to ignore primitive types as parameters.
174     *
175     * @param ignorePrimitiveTypes true or false.
176     */
177    public void setIgnorePrimitiveTypes(boolean ignorePrimitiveTypes) {
178        this.ignorePrimitiveTypes = ignorePrimitiveTypes;
179    }
180
181    @Override
182    public int[] getDefaultTokens() {
183        return new int[] {
184            TokenTypes.METHOD_DEF,
185            TokenTypes.CTOR_DEF,
186        };
187    }
188
189    @Override
190    public int[] getAcceptableTokens() {
191        return new int[] {
192            TokenTypes.METHOD_DEF,
193            TokenTypes.CTOR_DEF,
194            TokenTypes.LITERAL_CATCH,
195            TokenTypes.FOR_EACH_CLAUSE,
196        };
197    }
198
199    @Override
200    public int[] getRequiredTokens() {
201        return CommonUtil.EMPTY_INT_ARRAY;
202    }
203
204    @Override
205    public void visitToken(DetailAST ast) {
206        // don't flag interfaces
207        final DetailAST container = ast.getParent().getParent();
208        if (container.getType() != TokenTypes.INTERFACE_DEF) {
209            if (ast.getType() == TokenTypes.LITERAL_CATCH) {
210                visitCatch(ast);
211            }
212            else if (ast.getType() == TokenTypes.FOR_EACH_CLAUSE) {
213                visitForEachClause(ast);
214            }
215            else {
216                visitMethod(ast);
217            }
218        }
219    }
220
221    /**
222     * Checks parameters of the method or ctor.
223     *
224     * @param method method or ctor to check.
225     */
226    private void visitMethod(final DetailAST method) {
227        final DetailAST modifiers =
228            method.findFirstToken(TokenTypes.MODIFIERS);
229
230        // ignore abstract and native methods
231        if (modifiers.findFirstToken(TokenTypes.ABSTRACT) == null
232                && modifiers.findFirstToken(TokenTypes.LITERAL_NATIVE) == null) {
233            final DetailAST parameters =
234                method.findFirstToken(TokenTypes.PARAMETERS);
235            TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, this::checkParam);
236        }
237    }
238
239    /**
240     * Checks parameter of the catch block.
241     *
242     * @param catchClause catch block to check.
243     */
244    private void visitCatch(final DetailAST catchClause) {
245        checkParam(catchClause.findFirstToken(TokenTypes.PARAMETER_DEF));
246    }
247
248    /**
249     * Checks parameter of the for each clause.
250     *
251     * @param forEachClause for each clause to check.
252     */
253    private void visitForEachClause(final DetailAST forEachClause) {
254        checkParam(forEachClause.findFirstToken(TokenTypes.VARIABLE_DEF));
255    }
256
257    /**
258     * Checks if the given parameter is final.
259     *
260     * @param param parameter to check.
261     */
262    private void checkParam(final DetailAST param) {
263        if (param.findFirstToken(TokenTypes.MODIFIERS).findFirstToken(TokenTypes.FINAL) == null
264                && !isIgnoredParam(param)
265                && !CheckUtil.isReceiverParameter(param)) {
266            final DetailAST paramName = param.findFirstToken(TokenTypes.IDENT);
267            final DetailAST firstNode = CheckUtil.getFirstNode(param);
268            log(firstNode,
269                MSG_KEY, paramName.getText());
270        }
271    }
272
273    /**
274     * Checks for skip current param due to <b>ignorePrimitiveTypes</b> option.
275     *
276     * @param paramDef {@link TokenTypes#PARAMETER_DEF PARAMETER_DEF}
277     * @return true if param has to be skipped.
278     */
279    private boolean isIgnoredParam(DetailAST paramDef) {
280        boolean result = false;
281        if (ignorePrimitiveTypes) {
282            final DetailAST type = paramDef.findFirstToken(TokenTypes.TYPE);
283            final DetailAST parameterType = type.getFirstChild();
284            final DetailAST arrayDeclarator = type
285                    .findFirstToken(TokenTypes.ARRAY_DECLARATOR);
286            if (arrayDeclarator == null
287                    && primitiveDataTypes.contains(parameterType.getType())) {
288                result = true;
289            }
290        }
291        return result;
292    }
293
294}