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