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