001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 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.Scope;
028import com.puppycrawl.tools.checkstyle.api.TokenTypes;
029import com.puppycrawl.tools.checkstyle.utils.CheckUtils;
030import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
031import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
032
033/**
034 * <p>
035 * Checks the placement of right curly braces.
036 * The policy to verify is specified using the {@link RightCurlyOption} class
037 * and defaults to {@link RightCurlyOption#SAME}.
038 * </p>
039 * <p> By default the check will check the following tokens:
040 *  {@link TokenTypes#LITERAL_TRY LITERAL_TRY},
041 *  {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH},
042 *  {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY},
043 *  {@link TokenTypes#LITERAL_IF LITERAL_IF},
044 *  {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE}.
045 * Other acceptable tokens are:
046 *  {@link TokenTypes#CLASS_DEF CLASS_DEF},
047 *  {@link TokenTypes#METHOD_DEF METHOD_DEF},
048 *  {@link TokenTypes#CTOR_DEF CTOR_DEF}.
049 *  {@link TokenTypes#LITERAL_FOR LITERAL_FOR}.
050 *  {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE}.
051 *  {@link TokenTypes#LITERAL_DO LITERAL_DO}.
052 *  {@link TokenTypes#STATIC_INIT STATIC_INIT}.
053 *  {@link TokenTypes#INSTANCE_INIT INSTANCE_INIT}.
054 *  {@link TokenTypes#LAMBDA LAMBDA}.
055 * </p>
056 * <p>
057 * <b>shouldStartLine</b> - does the check need to check
058 * if right curly starts line. Default value is <b>true</b>
059 * </p>
060 * <p>
061 * An example of how to configure the check is:
062 * </p>
063 * <pre>
064 * &lt;module name="RightCurly"/&gt;
065 * </pre>
066 * <p>
067 * An example of how to configure the check with policy
068 * {@link RightCurlyOption#ALONE} for {@code else} and
069 * {@code {@link TokenTypes#METHOD_DEF METHOD_DEF}}tokens is:
070 * </p>
071 * <pre>
072 * &lt;module name="RightCurly"&gt;
073 *     &lt;property name="tokens" value="LITERAL_ELSE"/&gt;
074 *     &lt;property name="option" value="alone"/&gt;
075 * &lt;/module&gt;
076 * </pre>
077 *
078 * @author Oliver Burn
079 * @author lkuehne
080 * @author o_sukhodolsky
081 * @author maxvetrenko
082 * @author Andrei Selkin
083 * @author <a href="mailto:piotr.listkiewicz@gmail.com">liscju</a>
084 */
085@StatelessCheck
086public class RightCurlyCheck extends AbstractCheck {
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_BREAK_BEFORE = "line.break.before";
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_ALONE = "line.alone";
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_SAME = "line.same";
104
105    /**
106     * A key is pointing to the warning message text in "messages.properties"
107     * file.
108     */
109    public static final String MSG_KEY_LINE_NEW = "line.new";
110
111    /** Do we need to check if right curly starts line. */
112    private boolean shouldStartLine = true;
113
114    /** The policy to enforce. */
115    private RightCurlyOption option = RightCurlyOption.SAME;
116
117    /**
118     * Sets the option to enforce.
119     * @param optionStr string to decode option from
120     * @throws IllegalArgumentException if unable to decode
121     */
122    public void setOption(String optionStr) {
123        try {
124            option = RightCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
125        }
126        catch (IllegalArgumentException iae) {
127            throw new IllegalArgumentException("unable to parse " + optionStr, iae);
128        }
129    }
130
131    /**
132     * Does the check need to check if right curly starts line.
133     * @param flag new value of this property.
134     */
135    public void setShouldStartLine(boolean flag) {
136        shouldStartLine = flag;
137    }
138
139    @Override
140    public int[] getDefaultTokens() {
141        return new int[] {
142            TokenTypes.LITERAL_TRY,
143            TokenTypes.LITERAL_CATCH,
144            TokenTypes.LITERAL_FINALLY,
145            TokenTypes.LITERAL_IF,
146            TokenTypes.LITERAL_ELSE,
147        };
148    }
149
150    @Override
151    public int[] getAcceptableTokens() {
152        return new int[] {
153            TokenTypes.LITERAL_TRY,
154            TokenTypes.LITERAL_CATCH,
155            TokenTypes.LITERAL_FINALLY,
156            TokenTypes.LITERAL_IF,
157            TokenTypes.LITERAL_ELSE,
158            TokenTypes.CLASS_DEF,
159            TokenTypes.METHOD_DEF,
160            TokenTypes.CTOR_DEF,
161            TokenTypes.LITERAL_FOR,
162            TokenTypes.LITERAL_WHILE,
163            TokenTypes.LITERAL_DO,
164            TokenTypes.STATIC_INIT,
165            TokenTypes.INSTANCE_INIT,
166            TokenTypes.LAMBDA,
167        };
168    }
169
170    @Override
171    public int[] getRequiredTokens() {
172        return CommonUtils.EMPTY_INT_ARRAY;
173    }
174
175    @Override
176    public void visitToken(DetailAST ast) {
177        final Details details = Details.getDetails(ast);
178        final DetailAST rcurly = details.rcurly;
179
180        if (rcurly != null) {
181            final String violation = validate(details);
182            if (!violation.isEmpty()) {
183                log(rcurly, violation, "}", rcurly.getColumnNo() + 1);
184            }
185        }
186    }
187
188    /**
189     * Does general validation.
190     * @param details for validation.
191     * @return violation message or empty string
192     *     if there was not violation during validation.
193     */
194    private String validate(Details details) {
195        String violation = "";
196        if (shouldHaveLineBreakBefore(option, details)) {
197            violation = MSG_KEY_LINE_BREAK_BEFORE;
198        }
199        else if (shouldBeOnSameLine(option, details)) {
200            violation = MSG_KEY_LINE_SAME;
201        }
202        else if (shouldBeAloneOnLine(option, details)) {
203            violation = MSG_KEY_LINE_ALONE;
204        }
205        else if (shouldStartLine) {
206            final String targetSourceLine = getLines()[details.rcurly.getLineNo() - 1];
207            if (!isOnStartOfLine(details, targetSourceLine)) {
208                violation = MSG_KEY_LINE_NEW;
209            }
210        }
211        return violation;
212    }
213
214    /**
215     * Checks whether a right curly should have a line break before.
216     * @param bracePolicy option for placing the right curly brace.
217     * @param details details for validation.
218     * @return true if a right curly should have a line break before.
219     */
220    private static boolean shouldHaveLineBreakBefore(RightCurlyOption bracePolicy,
221                                                     Details details) {
222        return bracePolicy == RightCurlyOption.SAME
223                && !hasLineBreakBefore(details.rcurly)
224                && details.lcurly.getLineNo() != details.rcurly.getLineNo();
225    }
226
227    /**
228     * Checks that a right curly should be on the same line as the next statement.
229     * @param bracePolicy option for placing the right curly brace
230     * @param details Details for validation
231     * @return true if a right curly should be alone on a line.
232     */
233    private static boolean shouldBeOnSameLine(RightCurlyOption bracePolicy, Details details) {
234        return bracePolicy == RightCurlyOption.SAME
235                && !details.shouldCheckLastRcurly
236                && details.rcurly.getLineNo() != details.nextToken.getLineNo();
237    }
238
239    /**
240     * Checks that a right curly should be alone on a line.
241     * @param bracePolicy option for placing the right curly brace
242     * @param details Details for validation
243     * @return true if a right curly should be alone on a line.
244     */
245    private static boolean shouldBeAloneOnLine(RightCurlyOption bracePolicy, Details details) {
246        return bracePolicy == RightCurlyOption.ALONE
247                    && shouldBeAloneOnLineWithAloneOption(details)
248                || bracePolicy == RightCurlyOption.ALONE_OR_SINGLELINE
249                    && shouldBeAloneOnLineWithAloneOrSinglelineOption(details)
250                || details.shouldCheckLastRcurly
251                    && details.rcurly.getLineNo() == details.nextToken.getLineNo();
252    }
253
254    /**
255     * Whether right curly should be alone on line when ALONE option is used.
256     * @param details details for validation.
257     * @return true, if right curly should be alone on line when ALONE option is used.
258     */
259    private static boolean shouldBeAloneOnLineWithAloneOption(Details details) {
260        return !isAloneOnLine(details)
261                && !isEmptyBody(details.lcurly);
262    }
263
264    /**
265     * Whether right curly should be alone on line when ALONE_OR_SINGLELINE option is used.
266     * @param details details for validation.
267     * @return true, if right curly should be alone on line
268     *         when ALONE_OR_SINGLELINE option is used.
269     */
270    private static boolean shouldBeAloneOnLineWithAloneOrSinglelineOption(Details details) {
271        return !isAloneOnLine(details)
272                && !isSingleLineBlock(details)
273                && !isAnonInnerClassInit(details.lcurly)
274                && !isEmptyBody(details.lcurly);
275    }
276
277    /**
278     * Whether right curly brace starts target source line.
279     * @param details Details of right curly brace for validation
280     * @param targetSourceLine source line to check
281     * @return true if right curly brace starts target source line.
282     */
283    private static boolean isOnStartOfLine(Details details, String targetSourceLine) {
284        return CommonUtils.hasWhitespaceBefore(details.rcurly.getColumnNo(), targetSourceLine)
285                || details.lcurly.getLineNo() == details.rcurly.getLineNo();
286    }
287
288    /**
289     * Checks whether right curly is alone on a line.
290     * @param details for validation.
291     * @return true if right curly is alone on a line.
292     */
293    private static boolean isAloneOnLine(Details details) {
294        final DetailAST rcurly = details.rcurly;
295        final DetailAST lcurly = details.lcurly;
296        final DetailAST nextToken = details.nextToken;
297        return rcurly.getLineNo() != lcurly.getLineNo()
298            && rcurly.getLineNo() != nextToken.getLineNo();
299    }
300
301    /**
302     * Checks whether block has a single-line format.
303     * @param details for validation.
304     * @return true if block has single-line format.
305     */
306    private static boolean isSingleLineBlock(Details details) {
307        final DetailAST rcurly = details.rcurly;
308        final DetailAST lcurly = details.lcurly;
309        final DetailAST nextToken = details.nextToken;
310        return rcurly.getLineNo() == lcurly.getLineNo()
311            && rcurly.getLineNo() != nextToken.getLineNo();
312    }
313
314    /**
315     * Checks whether lcurly is in anonymous inner class initialization.
316     * @param lcurly left curly token.
317     * @return true if lcurly begins anonymous inner class initialization.
318     */
319    private static boolean isAnonInnerClassInit(DetailAST lcurly) {
320        final Scope surroundingScope = ScopeUtils.getSurroundingScope(lcurly);
321        return surroundingScope.ordinal() == Scope.ANONINNER.ordinal();
322    }
323
324    /**
325     * Checks if definition body is empty.
326     * @param lcurly left curly.
327     * @return true if definition body is empty.
328     */
329    private static boolean isEmptyBody(DetailAST lcurly) {
330        boolean result = false;
331        if (lcurly.getParent().getType() == TokenTypes.OBJBLOCK) {
332            if (lcurly.getNextSibling().getType() == TokenTypes.RCURLY) {
333                result = true;
334            }
335        }
336        else if (lcurly.getFirstChild().getType() == TokenTypes.RCURLY) {
337            result = true;
338        }
339        return result;
340    }
341
342    /**
343     * Checks if right curly has line break before.
344     * @param rightCurly right curly token.
345     * @return true, if right curly has line break before.
346     */
347    private static boolean hasLineBreakBefore(DetailAST rightCurly) {
348        final DetailAST previousToken = rightCurly.getPreviousSibling();
349        return previousToken == null
350                || rightCurly.getLineNo() != previousToken.getLineNo();
351    }
352
353    /**
354     * Structure that contains all details for validation.
355     */
356    private static final class Details {
357
358        /** Right curly. */
359        private final DetailAST rcurly;
360        /** Left curly. */
361        private final DetailAST lcurly;
362        /** Next token. */
363        private final DetailAST nextToken;
364        /** Should check last right curly. */
365        private final boolean shouldCheckLastRcurly;
366
367        /**
368         * Constructor.
369         * @param lcurly the lcurly of the token whose details are being collected
370         * @param rcurly the rcurly of the token whose details are being collected
371         * @param nextToken the token after the token whose details are being collected
372         * @param shouldCheckLastRcurly boolean value to determine if to check last rcurly
373         */
374        private Details(DetailAST lcurly, DetailAST rcurly,
375                        DetailAST nextToken, boolean shouldCheckLastRcurly) {
376            this.lcurly = lcurly;
377            this.rcurly = rcurly;
378            this.nextToken = nextToken;
379            this.shouldCheckLastRcurly = shouldCheckLastRcurly;
380        }
381
382        /**
383         * Collects validation Details.
384         * @param ast a {@code DetailAST} value
385         * @return object containing all details to make a validation
386         */
387        private static Details getDetails(DetailAST ast) {
388            final Details details;
389            switch (ast.getType()) {
390                case TokenTypes.LITERAL_TRY:
391                case TokenTypes.LITERAL_CATCH:
392                case TokenTypes.LITERAL_FINALLY:
393                    details = getDetailsForTryCatchFinally(ast);
394                    break;
395                case TokenTypes.LITERAL_IF:
396                case TokenTypes.LITERAL_ELSE:
397                    details = getDetailsForIfElse(ast);
398                    break;
399                case TokenTypes.LITERAL_DO:
400                case TokenTypes.LITERAL_WHILE:
401                case TokenTypes.LITERAL_FOR:
402                    details = getDetailsForLoops(ast);
403                    break;
404                case TokenTypes.LAMBDA:
405                    details = getDetailsForLambda(ast);
406                    break;
407                default:
408                    details = getDetailsForOthers(ast);
409                    break;
410            }
411            return details;
412        }
413
414        /**
415         * Collects validation details for LITERAL_TRY, LITERAL_CATCH, and LITERAL_FINALLY.
416         * @param ast a {@code DetailAST} value
417         * @return object containing all details to make a validation
418         */
419        private static Details getDetailsForTryCatchFinally(DetailAST ast) {
420            boolean shouldCheckLastRcurly = false;
421            final DetailAST rcurly;
422            final DetailAST lcurly;
423            DetailAST nextToken;
424            final int tokenType = ast.getType();
425            if (tokenType == TokenTypes.LITERAL_TRY) {
426                if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) {
427                    lcurly = ast.getFirstChild().getNextSibling();
428                }
429                else {
430                    lcurly = ast.getFirstChild();
431                }
432                nextToken = lcurly.getNextSibling();
433                rcurly = lcurly.getLastChild();
434
435                if (nextToken == null) {
436                    shouldCheckLastRcurly = true;
437                    nextToken = getNextToken(ast);
438                }
439            }
440            else if (tokenType == TokenTypes.LITERAL_CATCH) {
441                nextToken = ast.getNextSibling();
442                lcurly = ast.getLastChild();
443                rcurly = lcurly.getLastChild();
444                if (nextToken == null) {
445                    shouldCheckLastRcurly = true;
446                    nextToken = getNextToken(ast);
447                }
448
449            }
450            else {
451                shouldCheckLastRcurly = true;
452                nextToken = getNextToken(ast);
453                lcurly = ast.getFirstChild();
454                rcurly = lcurly.getLastChild();
455            }
456            return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
457        }
458
459        /**
460         * Collects validation details for LITERAL_IF and LITERAL_ELSE.
461         * @param ast a {@code DetailAST} value
462         * @return object containing all details to make a validation
463         */
464        private static Details getDetailsForIfElse(DetailAST ast) {
465            boolean shouldCheckLastRcurly = false;
466            DetailAST rcurly = null;
467            final DetailAST lcurly;
468            DetailAST nextToken;
469            final int tokenType = ast.getType();
470            if (tokenType == TokenTypes.LITERAL_IF) {
471                nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE);
472                if (nextToken == null) {
473                    shouldCheckLastRcurly = true;
474                    nextToken = getNextToken(ast);
475                    lcurly = ast.getLastChild();
476                }
477                else {
478                    lcurly = nextToken.getPreviousSibling();
479                }
480                if (lcurly.getType() == TokenTypes.SLIST) {
481                    rcurly = lcurly.getLastChild();
482                }
483
484            }
485            else {
486                shouldCheckLastRcurly = true;
487                nextToken = getNextToken(ast);
488                lcurly = ast.getFirstChild();
489                if (lcurly.getType() == TokenTypes.SLIST) {
490                    rcurly = lcurly.getLastChild();
491                }
492            }
493            return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
494        }
495
496        /**
497         * Collects validation details for CLASS_DEF, METHOD DEF, CTOR_DEF, STATIC_INIT, and
498         * INSTANCE_INIT.
499         * @param ast a {@code DetailAST} value
500         * @return an object containing all details to make a validation
501         */
502        private static Details getDetailsForOthers(DetailAST ast) {
503            DetailAST rcurly = null;
504            final DetailAST lcurly;
505            final DetailAST nextToken;
506            final int tokenType = ast.getType();
507            if (tokenType == TokenTypes.CLASS_DEF) {
508                final DetailAST child = ast.getLastChild();
509                lcurly = child.getFirstChild();
510                rcurly = child.getLastChild();
511                nextToken = ast;
512            }
513            else if (tokenType == TokenTypes.METHOD_DEF) {
514                lcurly = ast.findFirstToken(TokenTypes.SLIST);
515                if (lcurly != null) {
516                    // SLIST could be absent if method is abstract
517                    rcurly = lcurly.getLastChild();
518                }
519                nextToken = getNextToken(ast);
520            }
521            else {
522                lcurly = ast.findFirstToken(TokenTypes.SLIST);
523                rcurly = lcurly.getLastChild();
524                nextToken = getNextToken(ast);
525            }
526            return new Details(lcurly, rcurly, nextToken, false);
527        }
528
529        /**
530         * Collects validation details for loops' tokens.
531         * @param ast a {@code DetailAST} value
532         * @return an object containing all details to make a validation
533         */
534        private static Details getDetailsForLoops(DetailAST ast) {
535            DetailAST rcurly = null;
536            final DetailAST lcurly;
537            final DetailAST nextToken;
538            final int tokenType = ast.getType();
539            if (tokenType == TokenTypes.LITERAL_DO) {
540                nextToken = ast.findFirstToken(TokenTypes.DO_WHILE);
541                lcurly = ast.findFirstToken(TokenTypes.SLIST);
542                if (lcurly != null) {
543                    rcurly = lcurly.getLastChild();
544                }
545            }
546            else {
547                lcurly = ast.findFirstToken(TokenTypes.SLIST);
548                if (lcurly != null) {
549                    // SLIST could be absent in code like "while(true);"
550                    rcurly = lcurly.getLastChild();
551                }
552                nextToken = getNextToken(ast);
553            }
554            return new Details(lcurly, rcurly, nextToken, false);
555        }
556
557        /**
558         * Collects validation details for Lambdas.
559         * @param ast a {@code DetailAST} value
560         * @return an object containing all details to make a validation
561         */
562        private static Details getDetailsForLambda(DetailAST ast) {
563            final DetailAST lcurly = ast.findFirstToken(TokenTypes.SLIST);
564            boolean shouldCheckLastRcurly = false;
565            DetailAST nextToken = getNextToken(ast);
566            if (nextToken.getType() != TokenTypes.RPAREN
567                    && nextToken.getType() != TokenTypes.COMMA) {
568                shouldCheckLastRcurly = true;
569                nextToken = getNextToken(nextToken);
570            }
571            DetailAST rcurly = null;
572            if (lcurly != null) {
573                rcurly = lcurly.getLastChild();
574            }
575            return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
576        }
577
578        /**
579         * Finds next token after the given one.
580         * @param ast the given node.
581         * @return the token which represents next lexical item.
582         */
583        private static DetailAST getNextToken(DetailAST ast) {
584            DetailAST next = null;
585            DetailAST parent = ast;
586            while (next == null) {
587                next = parent.getNextSibling();
588                parent = parent.getParent();
589            }
590            return CheckUtils.getFirstNode(next);
591        }
592    }
593}