001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.checks;
020
021import java.util.Set;
022
023import com.google.common.collect.ImmutableSet;
024import com.puppycrawl.tools.checkstyle.api.Check;
025import com.puppycrawl.tools.checkstyle.api.DetailAST;
026import com.puppycrawl.tools.checkstyle.api.TokenTypes;
027
028/**
029 * Check that method/constructor/catch/foreach parameters are final.
030 * The user can set the token set to METHOD_DEF, CONSTRUCTOR_DEF,
031 * LITERAL_CATCH, FOR_EACH_CLAUSE or any combination of these token
032 * types, to control the scope of this check.
033 * Default scope is both METHOD_DEF and CONSTRUCTOR_DEF.
034 * <p>
035 * Check has an option <b>ignorePrimitiveTypes</b> which allows ignoring lack of
036 * final modifier at
037 * <a href="http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html">
038 *  primitive datatype</a> parameter. Default value <b>false</b>.
039 * </p>
040 * E.g.:
041 * <p>
042 * <code>
043 * private void foo(int x) { ... } //parameter is of primitive type
044 * </code>
045 * </p>
046 *
047 * @author lkuehne
048 * @author o_sukhodolsky
049 * @author Michael Studman
050 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
051 */
052public class FinalParametersCheck extends Check
053{
054    /**
055     * Option to ignore primitive types as params.
056     */
057    private boolean ignorePrimitiveTypes;
058
059    /**
060     * Sets ignoring primitive types as params.
061     * @param ignorePrimitiveTypes true or false.
062     */
063    public void setIgnorePrimitiveTypes(boolean ignorePrimitiveTypes)
064    {
065        this.ignorePrimitiveTypes = ignorePrimitiveTypes;
066    }
067
068    /**
069     * Contains
070     * <a href="http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html">
071     * primitive datatypes</a>.
072     */
073    private final Set<Integer> primitiveDataTypes = ImmutableSet.of(
074            TokenTypes.LITERAL_BYTE,
075            TokenTypes.LITERAL_SHORT,
076            TokenTypes.LITERAL_INT,
077            TokenTypes.LITERAL_LONG,
078            TokenTypes.LITERAL_FLOAT,
079            TokenTypes.LITERAL_DOUBLE,
080            TokenTypes.LITERAL_BOOLEAN,
081            TokenTypes.LITERAL_CHAR);
082
083    @Override
084    public int[] getDefaultTokens()
085    {
086        return new int[] {
087            TokenTypes.METHOD_DEF,
088            TokenTypes.CTOR_DEF,
089        };
090    }
091
092    @Override
093    public int[] getAcceptableTokens()
094    {
095        return new int[] {
096            TokenTypes.METHOD_DEF,
097            TokenTypes.CTOR_DEF,
098            TokenTypes.LITERAL_CATCH,
099            TokenTypes.FOR_EACH_CLAUSE,
100        };
101    }
102
103    @Override
104    public void visitToken(DetailAST ast)
105    {
106        // don't flag interfaces
107        final DetailAST container = ast.getParent().getParent();
108        if (container.getType() == TokenTypes.INTERFACE_DEF) {
109            return;
110        }
111
112        if (ast.getType() == TokenTypes.LITERAL_CATCH) {
113            visitCatch(ast);
114        }
115        else if (ast.getType() == TokenTypes.FOR_EACH_CLAUSE) {
116            visitForEachClause(ast);
117        }
118        else {
119            visitMethod(ast);
120        }
121    }
122
123    /**
124     * Checks parameters of the method or ctor.
125     * @param method method or ctor to check.
126     */
127    private void visitMethod(final DetailAST method)
128    {
129        // exit on fast lane if there is nothing to check here
130        if (!method.branchContains(TokenTypes.PARAMETER_DEF)) {
131            return;
132        }
133
134        // ignore abstract method
135        final DetailAST modifiers =
136            method.findFirstToken(TokenTypes.MODIFIERS);
137        if (modifiers.branchContains(TokenTypes.ABSTRACT)) {
138            return;
139        }
140
141        // we can now be sure that there is at least one parameter
142        final DetailAST parameters =
143            method.findFirstToken(TokenTypes.PARAMETERS);
144        DetailAST child = parameters.getFirstChild();
145        while (child != null) {
146            // childs are PARAMETER_DEF and COMMA
147            if (child.getType() == TokenTypes.PARAMETER_DEF) {
148                checkParam(child);
149            }
150            child = child.getNextSibling();
151        }
152    }
153
154    /**
155     * Checks parameter of the catch block.
156     * @param catchClause catch block to check.
157     */
158    private void visitCatch(final DetailAST catchClause)
159    {
160        checkParam(catchClause.findFirstToken(TokenTypes.PARAMETER_DEF));
161    }
162
163    /**
164     * Checks parameter of the for each clause.
165     * @param forEachClause for each clause to check.
166     */
167    private void visitForEachClause(final DetailAST forEachClause)
168    {
169        checkParam(forEachClause.findFirstToken(TokenTypes.VARIABLE_DEF));
170    }
171
172    /**
173     * Checks if the given parameter is final.
174     * @param param parameter to check.
175     */
176    private void checkParam(final DetailAST param)
177    {
178        if (!param.branchContains(TokenTypes.FINAL) && !isIgnoredParam(param)) {
179            final DetailAST paramName = param.findFirstToken(TokenTypes.IDENT);
180            final DetailAST firstNode = CheckUtils.getFirstNode(param);
181            log(firstNode.getLineNo(), firstNode.getColumnNo(),
182                "final.parameter", paramName.getText());
183        }
184    }
185
186    /**
187     * Checks for skip current param due to <b>ignorePrimitiveTypes</b> option.
188     * @param paramDef {@link TokenTypes#PARAMETER_DEF PARAMETER_DEF}
189     * @return true if param has to be skipped.
190     */
191    private boolean isIgnoredParam(DetailAST paramDef)
192    {
193        boolean result = false;
194        if (ignorePrimitiveTypes) {
195            final DetailAST parameterType = paramDef.
196                    findFirstToken(TokenTypes.TYPE).getFirstChild();
197            if (primitiveDataTypes.contains(parameterType.getType())) {
198                result = true;
199            }
200        }
201        return result;
202    }
203}