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////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.coding;
021
022import java.util.Arrays;
023
024import antlr.collections.AST;
025
026import com.puppycrawl.tools.checkstyle.api.Check;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029
030/**
031 * <p>
032 * Checks for assignments in subexpressions, such as in
033 * <code>String s = Integer.toString(i = 2);</code>.
034 * </p>
035 * <p>
036 * Rationale: With the exception of <code>for</code> iterators, all assignments
037 * should occur in their own toplevel statement to increase readability.
038 * With inner assignments like the above it is difficult to see all places
039 * where a variable is set.
040 * </p>
041 *
042 * @author lkuehne
043 */
044public class InnerAssignmentCheck
045        extends Check
046{
047    /**
048     * list of allowed AST types from an assignement AST node
049     * towards the root.
050     */
051    private static final int[][] ALLOWED_ASSIGMENT_CONTEXT = {
052        {TokenTypes.EXPR, TokenTypes.SLIST},
053        {TokenTypes.VARIABLE_DEF},
054        {TokenTypes.EXPR, TokenTypes.ELIST, TokenTypes.FOR_INIT},
055        {TokenTypes.EXPR, TokenTypes.ELIST, TokenTypes.FOR_ITERATOR},
056        {TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR},
057        {
058            TokenTypes.RESOURCE,
059            TokenTypes.RESOURCES,
060            TokenTypes.RESOURCE_SPECIFICATION,
061        },
062    };
063
064    /**
065     * list of allowed AST types from an assignement AST node
066     * towards the root.
067     */
068    private static final int[][] CONTROL_CONTEXT = {
069        {TokenTypes.EXPR, TokenTypes.LITERAL_DO},
070        {TokenTypes.EXPR, TokenTypes.LITERAL_FOR},
071        {TokenTypes.EXPR, TokenTypes.LITERAL_WHILE},
072        {TokenTypes.EXPR, TokenTypes.LITERAL_IF},
073        {TokenTypes.EXPR, TokenTypes.LITERAL_ELSE},
074    };
075
076    /**
077     * list of allowed AST types from a comparison node (above an assignement)
078     * towards the root.
079     */
080    private static final int[][] ALLOWED_ASSIGMENT_IN_COMPARISON_CONTEXT = {
081        {TokenTypes.EXPR, TokenTypes.LITERAL_WHILE, },
082    };
083
084    /**
085     * The token types that identify comparison operators.
086     */
087    private static final int[] COMPARISON_TYPES = {
088        TokenTypes.EQUAL,
089        TokenTypes.GE,
090        TokenTypes.GT,
091        TokenTypes.LE,
092        TokenTypes.LT,
093        TokenTypes.NOT_EQUAL,
094    };
095
096    static {
097        Arrays.sort(COMPARISON_TYPES);
098    }
099
100    @Override
101    public int[] getDefaultTokens()
102    {
103        return new int[] {
104            TokenTypes.ASSIGN,            // '='
105            TokenTypes.DIV_ASSIGN,        // "/="
106            TokenTypes.PLUS_ASSIGN,       // "+="
107            TokenTypes.MINUS_ASSIGN,      //"-="
108            TokenTypes.STAR_ASSIGN,       // "*="
109            TokenTypes.MOD_ASSIGN,        // "%="
110            TokenTypes.SR_ASSIGN,         // ">>="
111            TokenTypes.BSR_ASSIGN,        // ">>>="
112            TokenTypes.SL_ASSIGN,         // "<<="
113            TokenTypes.BXOR_ASSIGN,       // "^="
114            TokenTypes.BOR_ASSIGN,        // "|="
115            TokenTypes.BAND_ASSIGN,       // "&="
116        };
117    }
118
119    @Override
120    public void visitToken(DetailAST aAST)
121    {
122        if (isInContext(aAST, ALLOWED_ASSIGMENT_CONTEXT)) {
123            return;
124        }
125
126        if (isInNoBraceControlStatement(aAST)) {
127            return;
128        }
129
130        if (isInWhileIdiom(aAST)) {
131            return;
132        }
133
134        log(aAST.getLineNo(), aAST.getColumnNo(), "assignment.inner.avoid");
135    }
136
137    /**
138     * Determines if aAST is in the body of a flow control statement without
139     * braces. An example of such a statement would be
140     * <p>
141     * <pre>
142     * if (y < 0)
143     *     x = y;
144     * </pre>
145     * </p>
146     * <p>
147     * This leads to the following AST structure:
148     * </p>
149     * <p>
150     * <pre>
151     * LITERAL_IF
152     *     LPAREN
153     *     EXPR // test
154     *     RPAREN
155     *     EXPR // body
156     *     SEMI
157     * </pre>
158     * </p>
159     * <p>
160     * We need to ensure that aAST is in the body and not in the test.
161     * </p>
162     *
163     * @param aAST an assignment operator AST
164     * @return whether aAST is in the body of a flow control statement
165     */
166    private static boolean isInNoBraceControlStatement(DetailAST aAST)
167    {
168        if (!isInContext(aAST, CONTROL_CONTEXT)) {
169            return false;
170        }
171        final DetailAST expr = aAST.getParent();
172        final AST exprNext = expr.getNextSibling();
173        return (exprNext != null) && (exprNext.getType() == TokenTypes.SEMI);
174    }
175
176    /**
177     * Tests whether the given AST is used in the "assignment in while test"
178     * idiom.
179     * <p>
180     * <pre>
181     * while ((b = is.read()) != -1) {
182     *   // work with b
183     * }
184     * </pre>
185     * </p>
186     *
187     * @param aAST assignment AST
188     * @return whether the context of the assignemt AST indicates the idiom
189     */
190    private boolean isInWhileIdiom(DetailAST aAST)
191    {
192        if (!isComparison(aAST.getParent())) {
193            return false;
194        }
195        return isInContext(
196                aAST.getParent(), ALLOWED_ASSIGMENT_IN_COMPARISON_CONTEXT);
197    }
198
199    /**
200     * Checks if an AST is a comparison operator.
201     * @param aAST the AST to check
202     * @return true iff aAST is a comparison operator.
203     */
204    private static boolean isComparison(DetailAST aAST)
205    {
206        final int astType = aAST.getType();
207        return (Arrays.binarySearch(COMPARISON_TYPES, astType) >= 0);
208    }
209
210    /**
211     * Tests whether the provided AST is in
212     * one of the given contexts.
213     *
214     * @param aAST the AST from which to start walking towards root
215     * @param aContextSet the contexts to test against.
216     *
217     * @return whether the parents nodes of aAST match
218     * one of the allowed type paths
219     */
220    private static boolean isInContext(DetailAST aAST, int[][] aContextSet)
221    {
222        for (int[] element : aContextSet) {
223            DetailAST current = aAST;
224            final int len = element.length;
225            for (int j = 0; j < len; j++) {
226                current = current.getParent();
227                final int expectedType = element[j];
228                if ((current == null) || (current.getType() != expectedType)) {
229                    break;
230                }
231                if (j == len - 1) {
232                    return true;
233                }
234            }
235        }
236        return false;
237    }
238}