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.blocks;
020
021import com.puppycrawl.tools.checkstyle.api.DetailAST;
022import com.puppycrawl.tools.checkstyle.api.TokenTypes;
023import com.puppycrawl.tools.checkstyle.api.Utils;
024import com.puppycrawl.tools.checkstyle.checks.AbstractOptionCheck;
025
026/**
027 * <p>
028 * Checks the placement of left curly braces on types, methods and
029 * other blocks:
030 *  {@link  TokenTypes#LITERAL_CATCH LITERAL_CATCH},  {@link
031 * TokenTypes#LITERAL_DO LITERAL_DO},  {@link TokenTypes#LITERAL_ELSE
032 * LITERAL_ELSE},  {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY},  {@link
033 * TokenTypes#LITERAL_FOR LITERAL_FOR},  {@link TokenTypes#LITERAL_IF
034 * LITERAL_IF},  {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH},  {@link
035 * TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED},  {@link
036 * TokenTypes#LITERAL_TRY LITERAL_TRY},  {@link TokenTypes#LITERAL_WHILE
037 * LITERAL_WHILE}.
038 * </p>
039 *
040 * <p>
041 * The policy to verify is specified using the {@link LeftCurlyOption} class and
042 * defaults to {@link LeftCurlyOption#EOL}. Policies {@link LeftCurlyOption#EOL}
043 * and {@link LeftCurlyOption#NLOW} take into account property maxLineLength.
044 * The default value for maxLineLength is 80.
045 * </p>
046 * <p>
047 * An example of how to configure the check is:
048 * </p>
049 * <pre>
050 * &lt;module name="LeftCurly"/&gt;
051 * </pre>
052 * <p>
053 * An example of how to configure the check with policy
054 * {@link LeftCurlyOption#NLOW} and maxLineLength 120 is:
055 * </p>
056 * <pre>
057 * &lt;module name="LeftCurly"&gt;
058 *      &lt;property name="option"
059 * value="nlow"/&gt;     &lt;property name="maxLineLength" value="120"/&gt; &lt;
060 * /module&gt;
061 * </pre>
062 * <p>
063 * An example of how to configure the check to validate enum definitions:
064 * </p>
065 * <pre>
066 * &lt;module name="LeftCurly"&gt;
067 *      &lt;property name="ignoreEnums" value="false"/&gt;
068 * &lt;/module&gt;
069 * </pre>
070 *
071 * @author Oliver Burn
072 * @author lkuehne
073 * @author maxvetrenko
074 * @version 1.0
075 */
076public class LeftCurlyCheck
077    extends AbstractOptionCheck<LeftCurlyOption>
078{
079    /** default maximum line length */
080    private static final int DEFAULT_MAX_LINE_LENGTH = 80;
081
082    /** TODO: replace this ugly hack **/
083    private int mMaxLineLength = DEFAULT_MAX_LINE_LENGTH;
084
085    /** If true, Check will ignore enums*/
086    private boolean mIgnoreEnums = true;
087
088    /**
089     * Creates a default instance and sets the policy to EOL.
090     */
091    public LeftCurlyCheck()
092    {
093        super(LeftCurlyOption.EOL, LeftCurlyOption.class);
094    }
095
096    /**
097     * Sets the maximum line length used in calculating the placement of the
098     * left curly brace.
099     * @param aMaxLineLength the max allowed line length
100     */
101    public void setMaxLineLength(int aMaxLineLength)
102    {
103        mMaxLineLength = aMaxLineLength;
104    }
105
106    @Override
107    public int[] getDefaultTokens()
108    {
109        return new int[] {
110            TokenTypes.INTERFACE_DEF,
111            TokenTypes.CLASS_DEF,
112            TokenTypes.ANNOTATION_DEF,
113            TokenTypes.ENUM_DEF,
114            TokenTypes.CTOR_DEF,
115            TokenTypes.METHOD_DEF,
116            TokenTypes.ENUM_CONSTANT_DEF,
117            TokenTypes.LITERAL_WHILE,
118            TokenTypes.LITERAL_TRY,
119            TokenTypes.LITERAL_CATCH,
120            TokenTypes.LITERAL_FINALLY,
121            TokenTypes.LITERAL_SYNCHRONIZED,
122            TokenTypes.LITERAL_SWITCH,
123            TokenTypes.LITERAL_DO,
124            TokenTypes.LITERAL_IF,
125            TokenTypes.LITERAL_ELSE,
126            TokenTypes.LITERAL_FOR,
127            // TODO: need to handle....
128            //TokenTypes.STATIC_INIT,
129        };
130    }
131
132    @Override
133    public void visitToken(DetailAST aAST)
134    {
135        final DetailAST startToken;
136        final DetailAST brace;
137
138        switch (aAST.getType()) {
139        case TokenTypes.CTOR_DEF :
140        case TokenTypes.METHOD_DEF :
141            startToken = skipAnnotationOnlyLines(aAST);
142            brace = aAST.findFirstToken(TokenTypes.SLIST);
143            break;
144
145        case TokenTypes.INTERFACE_DEF :
146        case TokenTypes.CLASS_DEF :
147        case TokenTypes.ANNOTATION_DEF :
148        case TokenTypes.ENUM_DEF :
149        case TokenTypes.ENUM_CONSTANT_DEF :
150            startToken = skipAnnotationOnlyLines(aAST);
151            final DetailAST objBlock = aAST.findFirstToken(TokenTypes.OBJBLOCK);
152            brace = (objBlock == null)
153                ? null
154                : (DetailAST) objBlock.getFirstChild();
155            break;
156
157        case TokenTypes.LITERAL_WHILE:
158        case TokenTypes.LITERAL_CATCH:
159        case TokenTypes.LITERAL_SYNCHRONIZED:
160        case TokenTypes.LITERAL_FOR:
161        case TokenTypes.LITERAL_TRY:
162        case TokenTypes.LITERAL_FINALLY:
163        case TokenTypes.LITERAL_DO:
164        case TokenTypes.LITERAL_IF :
165            startToken = aAST;
166            brace = aAST.findFirstToken(TokenTypes.SLIST);
167            break;
168
169        case TokenTypes.LITERAL_ELSE :
170            startToken = aAST;
171            final DetailAST candidate = aAST.getFirstChild();
172            brace =
173                (candidate.getType() == TokenTypes.SLIST)
174                ? candidate
175                : null; // silently ignore
176            break;
177
178        case TokenTypes.LITERAL_SWITCH :
179            startToken = aAST;
180            brace = aAST.findFirstToken(TokenTypes.LCURLY);
181            break;
182
183        default :
184            startToken = null;
185            brace = null;
186        }
187
188        if ((brace != null) && (startToken != null)) {
189            verifyBrace(brace, startToken);
190        }
191    }
192
193    /**
194     * Skip lines that only contain <code>TokenTypes.ANNOTATION</code>s.
195     * If the received <code>DetailAST</code>
196     * has annotations within its modifiers then first token on the line
197     * of the first token afer all annotations is return. This might be
198     * an annotation.
199     * Otherwise, the received <code>DetailAST</code> is returned.
200     * @param aAST <code>DetailAST</code>.
201     * @return <code>DetailAST</code>.
202     */
203    private DetailAST skipAnnotationOnlyLines(DetailAST aAST)
204    {
205        final DetailAST modifiers = aAST.findFirstToken(TokenTypes.MODIFIERS);
206        if (modifiers == null) {
207            return aAST;
208        }
209        DetailAST lastAnnot = findLastAnnotation(modifiers);
210        if (lastAnnot == null) {
211            // There are no annotations.
212            return aAST;
213        }
214        final DetailAST tokenAfterLast = lastAnnot.getNextSibling() != null
215                                       ? lastAnnot.getNextSibling()
216                                       : modifiers.getNextSibling();
217        if (tokenAfterLast.getLineNo() > lastAnnot.getLineNo()) {
218            return tokenAfterLast;
219        }
220        final int lastAnnotLineNumber = lastAnnot.getLineNo();
221        while (lastAnnot.getPreviousSibling() != null
222               && (lastAnnot.getPreviousSibling().getLineNo()
223                    == lastAnnotLineNumber))
224        {
225            lastAnnot = lastAnnot.getPreviousSibling();
226        }
227        return lastAnnot;
228    }
229
230    /**
231     * Find the last token of type <code>TokenTypes.ANNOTATION</code>
232     * under the given set of modifiers.
233     * @param aModifiers <code>DetailAST</code>.
234     * @return <code>DetailAST</code> or null if there are no annotations.
235     */
236    private DetailAST findLastAnnotation(DetailAST aModifiers)
237    {
238        DetailAST aAnnot = aModifiers.findFirstToken(TokenTypes.ANNOTATION);
239        while (aAnnot != null && aAnnot.getNextSibling() != null
240               && aAnnot.getNextSibling().getType() == TokenTypes.ANNOTATION)
241        {
242            aAnnot = aAnnot.getNextSibling();
243        }
244        return aAnnot;
245    }
246
247    /**
248     * Verifies that a specified left curly brace is placed correctly
249     * according to policy.
250     * @param aBrace token for left curly brace
251     * @param aStartToken token for start of expression
252     */
253    private void verifyBrace(final DetailAST aBrace,
254                             final DetailAST aStartToken)
255    {
256        final String braceLine = getLines()[aBrace.getLineNo() - 1];
257
258        // calculate the previous line length without trailing whitespace. Need
259        // to handle the case where there is no previous line, cause the line
260        // being check is the first line in the file.
261        final int prevLineLen = (aBrace.getLineNo() == 1)
262            ? mMaxLineLength
263            : Utils.lengthMinusTrailingWhitespace(
264                getLines()[aBrace.getLineNo() - 2]);
265
266        // Check for being told to ignore, or have '{}' which is a special case
267        if ((braceLine.length() > (aBrace.getColumnNo() + 1))
268            && (braceLine.charAt(aBrace.getColumnNo() + 1) == '}'))
269        {
270            ; // ignore
271        }
272        else if (getAbstractOption() == LeftCurlyOption.NL) {
273            if (!Utils.whitespaceBefore(aBrace.getColumnNo(), braceLine)) {
274                log(aBrace.getLineNo(), aBrace.getColumnNo(),
275                    "line.new", "{");
276            }
277        }
278        else if (getAbstractOption() == LeftCurlyOption.EOL) {
279            if (Utils.whitespaceBefore(aBrace.getColumnNo(), braceLine)
280                && ((prevLineLen + 2) <= mMaxLineLength))
281            {
282                log(aBrace.getLineNo(), aBrace.getColumnNo(),
283                    "line.previous", "{");
284            }
285            if (!hasLineBreakAfter(aBrace)) {
286                log(aBrace.getLineNo(), aBrace.getColumnNo(), "line.break.after");
287            }
288        }
289        else if (getAbstractOption() == LeftCurlyOption.NLOW) {
290            if (aStartToken.getLineNo() == aBrace.getLineNo()) {
291                ; // all ok as on the same line
292            }
293            else if ((aStartToken.getLineNo() + 1) == aBrace.getLineNo()) {
294                if (!Utils.whitespaceBefore(aBrace.getColumnNo(), braceLine)) {
295                    log(aBrace.getLineNo(), aBrace.getColumnNo(),
296                        "line.new", "{");
297                }
298                else if ((prevLineLen + 2) <= mMaxLineLength) {
299                    log(aBrace.getLineNo(), aBrace.getColumnNo(),
300                        "line.previous", "{");
301                }
302            }
303            else if (!Utils.whitespaceBefore(aBrace.getColumnNo(), braceLine)) {
304                log(aBrace.getLineNo(), aBrace.getColumnNo(),
305                    "line.new", "{");
306            }
307        }
308    }
309
310    /**
311     * Checks if left curly has line break after.
312     * @param aLeftCurly
313     *        Left curly token.
314     * @return
315     *        True, left curly has line break after.
316     */
317    private boolean hasLineBreakAfter(DetailAST aLeftCurly)
318    {
319        DetailAST nextToken = null;
320        if (aLeftCurly.getType() == TokenTypes.SLIST) {
321            nextToken = aLeftCurly.getFirstChild();
322        }
323        else {
324            if (aLeftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF)
325            {
326                if (!mIgnoreEnums) {
327                    nextToken = aLeftCurly.getNextSibling();
328                }
329            }
330        }
331        if (nextToken != null && nextToken.getType() != TokenTypes.RCURLY) {
332            if (aLeftCurly.getLineNo() == nextToken.getLineNo()) {
333                return false;
334            }
335        }
336        return true;
337    }
338}