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 com.puppycrawl.tools.checkstyle.api.Check;
023import com.puppycrawl.tools.checkstyle.api.DetailAST;
024import com.puppycrawl.tools.checkstyle.api.TokenTypes;
025
026/**
027 * Restricts the number of statements per line to one.
028 * <p>
029 *     Rationale: It's very difficult to read multiple statements on one line.
030 * </p>
031 * <p>
032 *     In the Java programming language, statements are the fundamental unit of
033 *     execution. All statements except blocks are terminated by a semicolon.
034 *     Blocks are denoted by open and close curly braces.
035 * </p>
036 * <p>
037 *     OneStatementPerLineCheck checks the following types of statements:
038 *     variable declaration statements, empty statements, assignment statements,
039 *     expression statements, increment statements, object creation statements,
040 *     'for loop' statements, 'break' statements, 'continue' statements,
041 *     'return' statements, import statements.
042 * </p>
043 * <p>
044 *     The following examples will be flagged as a violation:
045 * </p>
046 * <pre>
047 *     //Each line causes violation:
048 *     int var1; int var2;
049 *     var1 = 1; var2 = 2;
050 *     int var1 = 1; int var2 = 2;
051 *     var1++; var2++;
052 *     Object obj1 = new Object(); Object obj2 = new Object();
053 *     import java.io.EOFException; import java.io.BufferedReader;
054 *     ;; //two empty statements on the same line.
055 *
056 *     //Multi-line statements:
057 *     int var1 = 1
058 *     ; var2 = 2; //violation here
059 *     int o = 1, p = 2,
060 *     r = 5; int t; //violation here
061 * </pre>
062 *
063 * @author Alexander Jesse
064 * @author Oliver Burn
065 * @author Andrei Selkin
066 */
067public final class OneStatementPerLineCheck extends Check {
068
069    /**
070     * A key is pointing to the warning message text in "messages.properties"
071     * file.
072     */
073    public static final String MSG_KEY = "multiple.statements.line";
074
075    /**
076     * Hold the line-number where the last statement ended.
077     */
078    private int lastStatementEnd = -1;
079
080    /**
081     * Hold the line-number where the last 'for-loop' statement ended.
082     */
083    private int forStatementEnd = -1;
084
085    /**
086     * The for-header usually has 3 statements on one line, but THIS IS OK.
087     */
088    private boolean inForHeader;
089
090    @Override
091    public int[] getDefaultTokens() {
092        return getAcceptableTokens();
093    }
094
095    @Override
096    public int[] getAcceptableTokens() {
097        return new int[]{
098            TokenTypes.SEMI, TokenTypes.FOR_INIT,
099            TokenTypes.FOR_ITERATOR,
100        };
101    }
102
103    @Override
104    public int[] getRequiredTokens() {
105        return getAcceptableTokens();
106    }
107
108    @Override
109    public void beginTree(DetailAST rootAST) {
110        inForHeader = false;
111        lastStatementEnd = -1;
112        forStatementEnd = -1;
113    }
114
115    @Override
116    public void visitToken(DetailAST ast) {
117        switch (ast.getType()) {
118            case TokenTypes.SEMI:
119                DetailAST currentStatement = ast;
120                if (isMultilineStatement(currentStatement)) {
121                    currentStatement = ast.getPreviousSibling();
122                }
123                if (isOnTheSameLine(currentStatement, lastStatementEnd,
124                        forStatementEnd) && !inForHeader) {
125                    log(ast, MSG_KEY);
126                }
127                break;
128            case TokenTypes.FOR_ITERATOR:
129                forStatementEnd = ast.getLineNo();
130                break;
131            default:
132                inForHeader = true;
133                break;
134        }
135    }
136
137    @Override
138    public void leaveToken(DetailAST ast) {
139        switch (ast.getType()) {
140            case TokenTypes.SEMI:
141                lastStatementEnd = ast.getLineNo();
142                forStatementEnd = -1;
143                break;
144            case TokenTypes.FOR_ITERATOR:
145                inForHeader = false;
146                break;
147            default:
148                break;
149        }
150    }
151
152    /**
153     * Checks whether two statements are on the same line.
154     * @param ast token for the current statement.
155     * @param lastStatementEnd the line-number where the last statement ended.
156     * @param forStatementEnd the line-number where the last 'for-loop'
157     *                        statement ended.
158     * @return true if two statements are on the same line.
159     */
160    private static boolean isOnTheSameLine(DetailAST ast, int lastStatementEnd,
161                                           int forStatementEnd) {
162        return lastStatementEnd == ast.getLineNo() && forStatementEnd != ast.getLineNo();
163    }
164
165    /**
166     * Checks whether statement is multiline.
167     * @param ast token for the current statement.
168     * @return true if one statement is distributed over two or more lines.
169     */
170    private static boolean isMultilineStatement(DetailAST ast) {
171        final boolean multiline;
172        if (ast.getPreviousSibling() == null) {
173            multiline = false;
174        }
175        else {
176            final DetailAST prevSibling = ast.getPreviousSibling();
177            multiline = prevSibling.getLineNo() != ast.getLineNo()
178                    && ast.getParent() != null;
179        }
180        return multiline;
181    }
182}