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