001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2015 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.coding;
021
022import java.util.ArrayDeque;
023import java.util.Collections;
024import java.util.Deque;
025import java.util.Set;
026
027import com.google.common.collect.Sets;
028import com.puppycrawl.tools.checkstyle.api.Check;
029import com.puppycrawl.tools.checkstyle.api.DetailAST;
030import com.puppycrawl.tools.checkstyle.api.TokenTypes;
031
032/**
033 * <p>
034 * Disallow assignment of parameters.
035 * </p>
036 * <p>
037 * Rationale:
038 * Parameter assignment is often considered poor
039 * programming practice. Forcing developers to declare
040 * parameters as final is often onerous. Having a check
041 * ensure that parameters are never assigned would give
042 * the best of both worlds.
043 * </p>
044 * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
045 */
046public final class ParameterAssignmentCheck extends Check {
047
048    /**
049     * A key is pointing to the warning message text in "messages.properties"
050     * file.
051     */
052    public static final String MSG_KEY = "parameter.assignment";
053
054    /** Stack of methods' parameters. */
055    private final Deque<Set<String>> parameterNamesStack = new ArrayDeque<>();
056    /** Current set of parameters. */
057    private Set<String> parameterNames;
058
059    @Override
060    public int[] getDefaultTokens() {
061        return new int[] {
062            TokenTypes.CTOR_DEF,
063            TokenTypes.METHOD_DEF,
064            TokenTypes.ASSIGN,
065            TokenTypes.PLUS_ASSIGN,
066            TokenTypes.MINUS_ASSIGN,
067            TokenTypes.STAR_ASSIGN,
068            TokenTypes.DIV_ASSIGN,
069            TokenTypes.MOD_ASSIGN,
070            TokenTypes.SR_ASSIGN,
071            TokenTypes.BSR_ASSIGN,
072            TokenTypes.SL_ASSIGN,
073            TokenTypes.BAND_ASSIGN,
074            TokenTypes.BXOR_ASSIGN,
075            TokenTypes.BOR_ASSIGN,
076            TokenTypes.INC,
077            TokenTypes.POST_INC,
078            TokenTypes.DEC,
079            TokenTypes.POST_DEC,
080        };
081    }
082
083    @Override
084    public int[] getRequiredTokens() {
085        return getDefaultTokens();
086    }
087
088    @Override
089    public int[] getAcceptableTokens() {
090        return new int[] {
091            TokenTypes.CTOR_DEF,
092            TokenTypes.METHOD_DEF,
093            TokenTypes.ASSIGN,
094            TokenTypes.PLUS_ASSIGN,
095            TokenTypes.MINUS_ASSIGN,
096            TokenTypes.STAR_ASSIGN,
097            TokenTypes.DIV_ASSIGN,
098            TokenTypes.MOD_ASSIGN,
099            TokenTypes.SR_ASSIGN,
100            TokenTypes.BSR_ASSIGN,
101            TokenTypes.SL_ASSIGN,
102            TokenTypes.BAND_ASSIGN,
103            TokenTypes.BXOR_ASSIGN,
104            TokenTypes.BOR_ASSIGN,
105            TokenTypes.INC,
106            TokenTypes.POST_INC,
107            TokenTypes.DEC,
108            TokenTypes.POST_DEC,
109        };
110    }
111
112    @Override
113    public void beginTree(DetailAST rootAST) {
114        // clear data
115        parameterNamesStack.clear();
116        parameterNames = Collections.emptySet();
117    }
118
119    @Override
120    public void visitToken(DetailAST ast) {
121        switch (ast.getType()) {
122            case TokenTypes.CTOR_DEF:
123            case TokenTypes.METHOD_DEF:
124                visitMethodDef(ast);
125                break;
126            case TokenTypes.ASSIGN:
127            case TokenTypes.PLUS_ASSIGN:
128            case TokenTypes.MINUS_ASSIGN:
129            case TokenTypes.STAR_ASSIGN:
130            case TokenTypes.DIV_ASSIGN:
131            case TokenTypes.MOD_ASSIGN:
132            case TokenTypes.SR_ASSIGN:
133            case TokenTypes.BSR_ASSIGN:
134            case TokenTypes.SL_ASSIGN:
135            case TokenTypes.BAND_ASSIGN:
136            case TokenTypes.BXOR_ASSIGN:
137            case TokenTypes.BOR_ASSIGN:
138                visitAssign(ast);
139                break;
140            case TokenTypes.INC:
141            case TokenTypes.POST_INC:
142            case TokenTypes.DEC:
143            case TokenTypes.POST_DEC:
144                visitIncDec(ast);
145                break;
146            default:
147                throw new IllegalStateException(ast.toString());
148        }
149    }
150
151    @Override
152    public void leaveToken(DetailAST ast) {
153        switch (ast.getType()) {
154            case TokenTypes.CTOR_DEF:
155            case TokenTypes.METHOD_DEF:
156                leaveMethodDef();
157                break;
158            case TokenTypes.ASSIGN:
159            case TokenTypes.PLUS_ASSIGN:
160            case TokenTypes.MINUS_ASSIGN:
161            case TokenTypes.STAR_ASSIGN:
162            case TokenTypes.DIV_ASSIGN:
163            case TokenTypes.MOD_ASSIGN:
164            case TokenTypes.SR_ASSIGN:
165            case TokenTypes.BSR_ASSIGN:
166            case TokenTypes.SL_ASSIGN:
167            case TokenTypes.BAND_ASSIGN:
168            case TokenTypes.BXOR_ASSIGN:
169            case TokenTypes.BOR_ASSIGN:
170            case TokenTypes.INC:
171            case TokenTypes.POST_INC:
172            case TokenTypes.DEC:
173            case TokenTypes.POST_DEC:
174                // Do nothing
175                break;
176            default:
177                throw new IllegalStateException(ast.toString());
178        }
179    }
180
181    /**
182     * Checks if this is assignments of parameter.
183     * @param ast assignment to check.
184     */
185    private void visitAssign(DetailAST ast) {
186        checkIdent(ast);
187    }
188
189    /**
190     * Checks if this is increment/decrement of parameter.
191     * @param ast dec/inc to check.
192     */
193    private void visitIncDec(DetailAST ast) {
194        checkIdent(ast);
195    }
196
197    /**
198     * Check if ident is parameter.
199     * @param ast ident to check.
200     */
201    private void checkIdent(DetailAST ast) {
202        if (!parameterNames.isEmpty()) {
203            final DetailAST identAST = ast.getFirstChild();
204
205            if (identAST != null
206                && identAST.getType() == TokenTypes.IDENT
207                && parameterNames.contains(identAST.getText())) {
208                log(ast.getLineNo(), ast.getColumnNo(),
209                    MSG_KEY, identAST.getText());
210            }
211        }
212    }
213
214    /**
215     * Creates new set of parameters and store old one in stack.
216     * @param ast a method to process.
217     */
218    private void visitMethodDef(DetailAST ast) {
219        parameterNamesStack.push(parameterNames);
220        parameterNames = Sets.newHashSet();
221
222        visitMethodParameters(ast.findFirstToken(TokenTypes.PARAMETERS));
223    }
224
225    /** Restores old set of parameters. */
226    private void leaveMethodDef() {
227        parameterNames = parameterNamesStack.pop();
228    }
229
230    /**
231     * Creates new parameter set for given method.
232     * @param ast a method for process.
233     */
234    private void visitMethodParameters(DetailAST ast) {
235        DetailAST parameterDefAST =
236            ast.findFirstToken(TokenTypes.PARAMETER_DEF);
237
238        while (parameterDefAST != null) {
239            if (parameterDefAST.getType() == TokenTypes.PARAMETER_DEF) {
240                final DetailAST param =
241                    parameterDefAST.findFirstToken(TokenTypes.IDENT);
242                parameterNames.add(param.getText());
243            }
244            parameterDefAST = parameterDefAST.getNextSibling();
245        }
246    }
247}