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