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