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