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