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.whitespace;
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 *
028 * Checks for empty line separators after header, package, all import declarations,
029 * fields, constructors, methods, nested classes,
030 * static initializers and instance initializers.
031 *
032 * <p> By default the check will check the following statements:
033 *  {@link TokenTypes#PACKAGE_DEF PACKAGE_DEF},
034 *  {@link TokenTypes#IMPORT IMPORT},
035 *  {@link TokenTypes#CLASS_DEF CLASS_DEF},
036 *  {@link TokenTypes#INTERFACE_DEF INTERFACE_DEF},
037 *  {@link TokenTypes#STATIC_INIT STATIC_INIT},
038 *  {@link TokenTypes#INSTANCE_INIT INSTANCE_INIT},
039 *  {@link TokenTypes#METHOD_DEF METHOD_DEF},
040 *  {@link TokenTypes#CTOR_DEF CTOR_DEF},
041 *  {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF}.
042 * </p>
043 *
044 * <p>
045 * Example of declarations without empty line separator:
046 * </p>
047 *
048 * <pre>
049 * ///////////////////////////////////////////////////
050 * //HEADER
051 * ///////////////////////////////////////////////////
052 * package com.puppycrawl.tools.checkstyle.whitespace;
053 * import java.io.Serializable;
054 * class Foo
055 * {
056 *     public static final int FOO_CONST = 1;
057 *     public void foo() {} //should be separated from previous statement.
058 * }
059 * </pre>
060 *
061 * <p> An example of how to configure the check with default parameters is:
062 * </p>
063 *
064 * <pre>
065 * &lt;module name="EmptyLineSeparator"/&gt;
066 * </pre>
067 *
068 * <p>
069 * Example of declarations with empty line separator
070 * that is expected by the Check by default:
071 * </p>
072 *
073 * <pre>
074 * ///////////////////////////////////////////////////
075 * //HEADER
076 * ///////////////////////////////////////////////////
077 *
078 * package com.puppycrawl.tools.checkstyle.whitespace;
079 *
080 * import java.io.Serializable;
081 *
082 * class Foo
083 * {
084 *     public static final int FOO_CONST = 1;
085 *
086 *     public void foo() {}
087 * }
088 * </pre>
089 * <p> An example how to check empty line after
090 * {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF} and
091 * {@link TokenTypes#METHOD_DEF METHOD_DEF}:
092 * </p>
093 *
094 * <pre>
095 * &lt;module name="EmptyLineSeparator"&gt;
096 *    &lt;property name="tokens" value="VARIABLE_DEF, METHOD_DEF"/&gt;
097 * &lt;/module&gt;
098 * </pre>
099 *
100 * <p>
101 * An example how to allow no empty line between fields:
102 * </p>
103 * <pre>
104 * &lt;module name="EmptyLineSeparator"&gt;
105 *    &lt;property name="allowNoEmptyLineBetweenFields" value="true"/&gt;
106 * &lt;/module&gt;
107 * </pre>
108 *
109 * @author maxvetrenko
110 *
111 */
112public class EmptyLineSeparatorCheck extends Check
113{
114    /** */
115    private boolean mAllowNoEmptyLineBetweenFields;
116
117    /**
118     * Allow no empty line between fields.
119     * @param aAllow
120     *        User's value.
121     */
122    public final void setAllowNoEmptyLineBetweenFields(boolean aAllow)
123    {
124        mAllowNoEmptyLineBetweenFields = aAllow;
125    }
126
127    @Override
128    public int[] getDefaultTokens()
129    {
130        return new int[] {
131            TokenTypes.PACKAGE_DEF,
132            TokenTypes.IMPORT,
133            TokenTypes.CLASS_DEF,
134            TokenTypes.INTERFACE_DEF,
135            TokenTypes.ENUM_DEF,
136            TokenTypes.STATIC_INIT,
137            TokenTypes.INSTANCE_INIT,
138            TokenTypes.METHOD_DEF,
139            TokenTypes.CTOR_DEF,
140            TokenTypes.VARIABLE_DEF,
141        };
142    }
143
144    @Override
145    public void visitToken(DetailAST aAST)
146    {
147        final DetailAST nextToken = aAST.getNextSibling();
148
149        if (nextToken != null && nextToken.getType() != TokenTypes.RCURLY) {
150            final int astType = aAST.getType();
151            switch (astType) {
152            case TokenTypes.VARIABLE_DEF:
153                if (isTypeField(aAST) && !hasEmptyLineAfter(aAST)) {
154                    if (mAllowNoEmptyLineBetweenFields
155                            && nextToken.getType() != TokenTypes.VARIABLE_DEF)
156                    {
157                        log(nextToken.getLineNo(), "empty.line.separator", nextToken.getText());
158                    }
159                    else if (!mAllowNoEmptyLineBetweenFields) {
160                        log(nextToken.getLineNo(), "empty.line.separator", nextToken.getText());
161                    }
162                }
163                break;
164            case TokenTypes.IMPORT:
165                if (astType != nextToken.getType() && !hasEmptyLineAfter(aAST)
166                    || (aAST.getLineNo() > 1 && !hasEmptyLineBefore(aAST)
167                            && aAST.getPreviousSibling() == null))
168                {
169                    log(nextToken.getLineNo(), "empty.line.separator", nextToken.getText());
170                }
171                break;
172            case TokenTypes.PACKAGE_DEF:
173                if (aAST.getLineNo() > 1 && !hasEmptyLineBefore(aAST)) {
174                    log(aAST.getLineNo(), "empty.line.separator", aAST.getText());
175                }
176            default:
177                if (!hasEmptyLineAfter(aAST)) {
178                    log(nextToken.getLineNo(), "empty.line.separator", nextToken.getText());
179                }
180            }
181        }
182    }
183
184    /**
185     * Checks if token have empty line after.
186     * @param aToken token.
187     * @return true if token have empty line after.
188     */
189    private boolean hasEmptyLineAfter(DetailAST aToken)
190    {
191        DetailAST lastToken = aToken.getLastChild().getLastChild();
192        if (null == lastToken) {
193            lastToken = aToken.getLastChild();
194        }
195        return aToken.getNextSibling().getLineNo() - lastToken.getLineNo() > 1;
196    }
197
198    /**
199     * Checks if a token has a empty line before.
200     * @param aToken token.
201     * @return true, if token have empty line before.
202     */
203    private boolean hasEmptyLineBefore(DetailAST aToken)
204    {
205        final int lineNo = aToken.getLineNo();
206        //  [lineNo - 2] is the number of the previous line because the numbering starts from zero.
207        final String lineBefore = getLines()[lineNo - 2];
208        return lineBefore.trim().isEmpty();
209    }
210
211    /**
212     * If variable definition is a type field.
213     * @param aVariableDef variable definition.
214     * @return true variable definition is a type field.
215     */
216    private boolean isTypeField(DetailAST aVariableDef)
217    {
218        final int parentType = aVariableDef.getParent().getParent().getType();
219        return parentType == TokenTypes.CLASS_DEF;
220    }
221}