001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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.blocks;
021
022import java.util.Locale;
023
024import com.puppycrawl.tools.checkstyle.StatelessCheck;
025import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
029
030/**
031 * <p>
032 * Checks the placement of left curly braces.
033 * The policy to verify is specified using the {@link LeftCurlyOption} class
034 * and the default one being {@link LeftCurlyOption#EOL}.
035 * </p>
036 * <p>
037 * By default the following tokens are checked:
038 *  {@link TokenTypes#LAMBDA LAMBDA},
039 *  {@link TokenTypes#LITERAL_CASE LITERAL_CASE},
040 *  {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH},
041 *  {@link TokenTypes#LITERAL_DEFAULT LITERAL_DEFAULT},
042 *  {@link TokenTypes#LITERAL_DO LITERAL_DO},
043 *  {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE},
044 *  {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY},
045 *  {@link TokenTypes#LITERAL_FOR LITERAL_FOR},
046 *  {@link TokenTypes#LITERAL_IF LITERAL_IF},
047 *  {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH},
048 *  {@link TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED},
049 *  {@link TokenTypes#LITERAL_TRY LITERAL_TRY},
050 *  {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE},
051 *  {@link TokenTypes#STATIC_INIT STATIC_INIT}.
052 * </p>
053 *
054 * <p>
055 * The policy to verify is specified using the {@link LeftCurlyOption} class and
056 * defaults to {@link LeftCurlyOption#EOL}.
057 * </p>
058 * <p>
059 * An example of how to configure the check is:
060 * </p>
061 * <pre>
062 * &lt;module name="LeftCurly"/&gt;
063 * </pre>
064 * <p>
065 * An example of how to configure the check with policy
066 * {@link LeftCurlyOption#NLOW} is:
067 * </p>
068 * <pre>
069 * &lt;module name="LeftCurly"&gt;
070 *      &lt;property name="option" value="nlow"/&gt;
071 * &lt;/module&gt;
072 * </pre>
073 * <p>
074 * An example of how to configure the check to validate enum definitions:
075 * </p>
076 * <pre>
077 * &lt;module name="LeftCurly"&gt;
078 *      &lt;property name="ignoreEnums" value="false"/&gt;
079 * &lt;/module&gt;
080 * </pre>
081 *
082 */
083@StatelessCheck
084public class LeftCurlyCheck
085    extends AbstractCheck {
086
087    /**
088     * A key is pointing to the warning message text in "messages.properties"
089     * file.
090     */
091    public static final String MSG_KEY_LINE_NEW = "line.new";
092
093    /**
094     * A key is pointing to the warning message text in "messages.properties"
095     * file.
096     */
097    public static final String MSG_KEY_LINE_PREVIOUS = "line.previous";
098
099    /**
100     * A key is pointing to the warning message text in "messages.properties"
101     * file.
102     */
103    public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after";
104
105    /** Open curly brace literal. */
106    private static final String OPEN_CURLY_BRACE = "{";
107
108    /** If true, Check will ignore enums. */
109    private boolean ignoreEnums = true;
110
111    /** The policy to enforce. */
112    private LeftCurlyOption option = LeftCurlyOption.EOL;
113
114    /**
115     * Set the option to enforce.
116     * @param optionStr string to decode option from
117     * @throws IllegalArgumentException if unable to decode
118     */
119    public void setOption(String optionStr) {
120        try {
121            option = LeftCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
122        }
123        catch (IllegalArgumentException iae) {
124            throw new IllegalArgumentException("unable to parse " + optionStr, iae);
125        }
126    }
127
128    /**
129     * Sets whether check should ignore enums when left curly brace policy is EOL.
130     * @param ignoreEnums check's option for ignoring enums.
131     */
132    public void setIgnoreEnums(boolean ignoreEnums) {
133        this.ignoreEnums = ignoreEnums;
134    }
135
136    @Override
137    public int[] getDefaultTokens() {
138        return getAcceptableTokens();
139    }
140
141    @Override
142    public int[] getAcceptableTokens() {
143        return new int[] {
144            TokenTypes.ANNOTATION_DEF,
145            TokenTypes.CLASS_DEF,
146            TokenTypes.CTOR_DEF,
147            TokenTypes.ENUM_CONSTANT_DEF,
148            TokenTypes.ENUM_DEF,
149            TokenTypes.INTERFACE_DEF,
150            TokenTypes.LAMBDA,
151            TokenTypes.LITERAL_CASE,
152            TokenTypes.LITERAL_CATCH,
153            TokenTypes.LITERAL_DEFAULT,
154            TokenTypes.LITERAL_DO,
155            TokenTypes.LITERAL_ELSE,
156            TokenTypes.LITERAL_FINALLY,
157            TokenTypes.LITERAL_FOR,
158            TokenTypes.LITERAL_IF,
159            TokenTypes.LITERAL_SWITCH,
160            TokenTypes.LITERAL_SYNCHRONIZED,
161            TokenTypes.LITERAL_TRY,
162            TokenTypes.LITERAL_WHILE,
163            TokenTypes.METHOD_DEF,
164            TokenTypes.OBJBLOCK,
165            TokenTypes.STATIC_INIT,
166        };
167    }
168
169    @Override
170    public int[] getRequiredTokens() {
171        return CommonUtil.EMPTY_INT_ARRAY;
172    }
173
174    @Override
175    public void visitToken(DetailAST ast) {
176        final DetailAST startToken;
177        DetailAST brace;
178
179        switch (ast.getType()) {
180            case TokenTypes.CTOR_DEF:
181            case TokenTypes.METHOD_DEF:
182                startToken = skipAnnotationOnlyLines(ast);
183                brace = ast.findFirstToken(TokenTypes.SLIST);
184                break;
185            case TokenTypes.INTERFACE_DEF:
186            case TokenTypes.CLASS_DEF:
187            case TokenTypes.ANNOTATION_DEF:
188            case TokenTypes.ENUM_DEF:
189            case TokenTypes.ENUM_CONSTANT_DEF:
190                startToken = skipAnnotationOnlyLines(ast);
191                final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
192                brace = objBlock;
193
194                if (objBlock != null) {
195                    brace = objBlock.getFirstChild();
196                }
197                break;
198            case TokenTypes.LITERAL_WHILE:
199            case TokenTypes.LITERAL_CATCH:
200            case TokenTypes.LITERAL_SYNCHRONIZED:
201            case TokenTypes.LITERAL_FOR:
202            case TokenTypes.LITERAL_TRY:
203            case TokenTypes.LITERAL_FINALLY:
204            case TokenTypes.LITERAL_DO:
205            case TokenTypes.LITERAL_IF:
206            case TokenTypes.STATIC_INIT:
207            case TokenTypes.LAMBDA:
208                startToken = ast;
209                brace = ast.findFirstToken(TokenTypes.SLIST);
210                break;
211            case TokenTypes.LITERAL_ELSE:
212                startToken = ast;
213                brace = getBraceAsFirstChild(ast);
214                break;
215            case TokenTypes.LITERAL_CASE:
216            case TokenTypes.LITERAL_DEFAULT:
217                startToken = ast;
218                brace = getBraceAsFirstChild(ast.getNextSibling());
219                break;
220            default:
221                // ATTENTION! We have default here, but we expect case TokenTypes.METHOD_DEF,
222                // TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO only.
223                // It has been done to improve coverage to 100%. I couldn't replace it with
224                // if-else-if block because code was ugly and didn't pass pmd check.
225
226                startToken = ast;
227                brace = ast.findFirstToken(TokenTypes.LCURLY);
228                break;
229        }
230
231        if (brace != null) {
232            verifyBrace(brace, startToken);
233        }
234    }
235
236    /**
237     * Gets a SLIST if it is the first child of the AST.
238     * @param ast {@code DetailAST}.
239     * @return {@code DetailAST} if the first child is {@code TokenTypes.SLIST},
240     *     {@code null} otherwise.
241     */
242    private static DetailAST getBraceAsFirstChild(DetailAST ast) {
243        DetailAST brace = null;
244        if (ast != null) {
245            final DetailAST candidate = ast.getFirstChild();
246            if (candidate != null && candidate.getType() == TokenTypes.SLIST) {
247                brace = candidate;
248            }
249        }
250        return brace;
251    }
252
253    /**
254     * Skip lines that only contain {@code TokenTypes.ANNOTATION}s.
255     * If the received {@code DetailAST}
256     * has annotations within its modifiers then first token on the line
257     * of the first token after all annotations is return. This might be
258     * an annotation.
259     * Otherwise, the received {@code DetailAST} is returned.
260     * @param ast {@code DetailAST}.
261     * @return {@code DetailAST}.
262     */
263    private static DetailAST skipAnnotationOnlyLines(DetailAST ast) {
264        DetailAST resultNode = ast;
265        final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
266
267        if (modifiers != null) {
268            final DetailAST lastAnnotation = findLastAnnotation(modifiers);
269
270            if (lastAnnotation != null) {
271                final DetailAST tokenAfterLast;
272
273                if (lastAnnotation.getNextSibling() == null) {
274                    tokenAfterLast = modifiers.getNextSibling();
275                }
276                else {
277                    tokenAfterLast = lastAnnotation.getNextSibling();
278                }
279
280                if (tokenAfterLast.getLineNo() > lastAnnotation.getLineNo()) {
281                    resultNode = tokenAfterLast;
282                }
283                else {
284                    resultNode = getFirstAnnotationOnSameLine(lastAnnotation);
285                }
286            }
287        }
288        return resultNode;
289    }
290
291    /**
292     * Returns first annotation on same line.
293     * @param annotation
294     *            last annotation on the line
295     * @return first annotation on same line.
296     */
297    private static DetailAST getFirstAnnotationOnSameLine(DetailAST annotation) {
298        DetailAST previousAnnotation = annotation;
299        final int lastAnnotationLineNumber = previousAnnotation.getLineNo();
300        while (previousAnnotation.getPreviousSibling() != null
301                && previousAnnotation.getPreviousSibling().getLineNo()
302                    == lastAnnotationLineNumber) {
303            previousAnnotation = previousAnnotation.getPreviousSibling();
304        }
305        return previousAnnotation;
306    }
307
308    /**
309     * Find the last token of type {@code TokenTypes.ANNOTATION}
310     * under the given set of modifiers.
311     * @param modifiers {@code DetailAST}.
312     * @return {@code DetailAST} or null if there are no annotations.
313     */
314    private static DetailAST findLastAnnotation(DetailAST modifiers) {
315        DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION);
316        while (annotation != null && annotation.getNextSibling() != null
317               && annotation.getNextSibling().getType() == TokenTypes.ANNOTATION) {
318            annotation = annotation.getNextSibling();
319        }
320        return annotation;
321    }
322
323    /**
324     * Verifies that a specified left curly brace is placed correctly
325     * according to policy.
326     * @param brace token for left curly brace
327     * @param startToken token for start of expression
328     */
329    private void verifyBrace(final DetailAST brace,
330                             final DetailAST startToken) {
331        final String braceLine = getLine(brace.getLineNo() - 1);
332
333        // Check for being told to ignore, or have '{}' which is a special case
334        if (braceLine.length() <= brace.getColumnNo() + 1
335                || braceLine.charAt(brace.getColumnNo() + 1) != '}') {
336            if (option == LeftCurlyOption.NL) {
337                if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
338                    log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
339                }
340            }
341            else if (option == LeftCurlyOption.EOL) {
342                validateEol(brace, braceLine);
343            }
344            else if (startToken.getLineNo() != brace.getLineNo()) {
345                validateNewLinePosition(brace, startToken, braceLine);
346            }
347        }
348    }
349
350    /**
351     * Validate EOL case.
352     * @param brace brace AST
353     * @param braceLine line content
354     */
355    private void validateEol(DetailAST brace, String braceLine) {
356        if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
357            log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
358        }
359        if (!hasLineBreakAfter(brace)) {
360            log(brace, MSG_KEY_LINE_BREAK_AFTER, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
361        }
362    }
363
364    /**
365     * Validate token on new Line position.
366     * @param brace brace AST
367     * @param startToken start Token
368     * @param braceLine content of line with Brace
369     */
370    private void validateNewLinePosition(DetailAST brace, DetailAST startToken, String braceLine) {
371        // not on the same line
372        if (startToken.getLineNo() + 1 == brace.getLineNo()) {
373            if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
374                log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
375            }
376            else {
377                log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
378            }
379        }
380        else if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
381            log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
382        }
383    }
384
385    /**
386     * Checks if left curly has line break after.
387     * @param leftCurly
388     *        Left curly token.
389     * @return
390     *        True, left curly has line break after.
391     */
392    private boolean hasLineBreakAfter(DetailAST leftCurly) {
393        DetailAST nextToken = null;
394        if (leftCurly.getType() == TokenTypes.SLIST) {
395            nextToken = leftCurly.getFirstChild();
396        }
397        else {
398            if (!ignoreEnums
399                    && leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF) {
400                nextToken = leftCurly.getNextSibling();
401            }
402        }
403        return nextToken == null
404                || nextToken.getType() == TokenTypes.RCURLY
405                || leftCurly.getLineNo() != nextToken.getLineNo();
406    }
407
408}